概念
爬蟲避免進(jìn)局子的風(fēng)險(xiǎn)
- 時(shí)常的優(yōu)化自己的程序,避免干擾被訪問網(wǎng)站的正常運(yùn)行
- 在使用,傳播獲取到的數(shù)據(jù)時(shí),審查抓取到的內(nèi)容,如果涉及到用戶隱私,商業(yè)機(jī)密等敏感內(nèi)容需要及時(shí)停止或傳播
爬蟲的分類:
- 通用爬蟲:抓取系統(tǒng)重要組成部分,抓取的是一整張頁面數(shù)據(jù)
- 聚焦爬蟲:是建立在通用爬蟲基礎(chǔ)上的,爬取的是需求頁面中特定的局部?jī)?nèi)容
- 增量式爬蟲:檢測(cè)網(wǎng)站中的數(shù)據(jù)更新情況,只會(huì)爬取網(wǎng)站中最新更新出來的數(shù)據(jù);
爬蟲的矛盾
反爬機(jī)制
- 門戶網(wǎng)站,可以通過相應(yīng)的策略或者技術(shù)手段,防止爬蟲程序進(jìn)行網(wǎng)頁數(shù)據(jù)的爬取
反反爬策略 - 爬蟲程序可以通過制定相關(guān)的策略或技術(shù)手段,破解門戶網(wǎng)站中具備的反爬機(jī)制,從而可以獲取門戶網(wǎng)站的數(shù)據(jù);
robots.txt協(xié)議: - 君子協(xié)議.規(guī)定了網(wǎng)站中哪些數(shù)據(jù)可以被爬蟲程序抓取哪些不可以;
http協(xié)議
- 概念: 就是服務(wù)器和客戶端的進(jìn)行數(shù)據(jù)交互的一種形式,超文本傳輸協(xié)議
常用的請(qǐng)求頭:
- User-Agent:請(qǐng)求載體的身份標(biāo)識(shí)符
- Connection:請(qǐng)求完畢后,是斷開連接還是保持連接
常見的響應(yīng)頭
- Content-Type:服務(wù)器響應(yīng)回復(fù)客戶端的數(shù)據(jù)類型
https協(xié)議
- 安全的超文本傳輸協(xié)議
加密方式
- 對(duì)稱秘鑰加密
- 非對(duì)稱秘鑰加密
- 證書加密
requests模塊
- urllib 模塊
- requests 模塊
requests模塊:python中原生的一款基于網(wǎng)絡(luò)請(qǐng)求的模塊,功能非常強(qiáng)大,簡(jiǎn)單便捷,效率極高.
作用:模擬瀏覽器發(fā)送請(qǐng)求
如何使用:
- 指定url
- 發(fā)起請(qǐng)求
- 獲取響應(yīng)數(shù)據(jù)
- 持久化存儲(chǔ)
環(huán)境安裝: pip install requests
實(shí)戰(zhàn)編碼:
import requests
heads = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}
def get_url():
kw = input('請(qǐng)輸入檢索關(guān)鍵字')
return "https://cn.bing.com/search?q=" + kw
def get_data(url):
res = requests.get(url, headers=heads)
save_file("檢索結(jié)果數(shù)據(jù)", res.text)
def save_file(name, data):
with open(name + ".html", "w", encoding="utf-8") as wf:
wf.write(data)
if __name__ == "__main__":
url = get_url()
get_data(url)
數(shù)據(jù)解析分類
- 正則
- bs4
- xpath (重點(diǎn))
正則解析
ex=‘<div class="thumb">.*?<img src="(.*?)" alt.*?</div>’
數(shù)據(jù)解析概況:
解析的局部的文本內(nèi)容都會(huì)在標(biāo)簽之間或者標(biāo)簽對(duì)應(yīng)的屬性中進(jìn)行存儲(chǔ)
- 進(jìn)行指定標(biāo)簽定位
- 標(biāo)簽或者標(biāo)簽對(duì)應(yīng)的屬性中存儲(chǔ)的數(shù)據(jù)進(jìn)行提?。ń馕觯?/li>
bs4進(jìn)行數(shù)據(jù)解析
- 數(shù)據(jù)解析的原理
- 標(biāo)簽定位
- 提取標(biāo)簽、標(biāo)簽屬性中存儲(chǔ)的數(shù)據(jù)值
- bs4數(shù)據(jù)解析原理
1. 通過實(shí)例化一個(gè)BeautifulSoup對(duì)象,并且將頁面數(shù)據(jù)加載到該對(duì)象中
2. 通過調(diào)用BeautifulSoup對(duì)象中的屬性或方法進(jìn)行標(biāo)簽定位和數(shù)據(jù)提取
環(huán)境安裝
- pip install bs4
- pip install lxml
如何實(shí)例化BeautifulSoup對(duì)象:
from bs4 import BeautifulSoup
對(duì)象的實(shí)例化
- 將本地的html文檔中的數(shù)據(jù)加載到該對(duì)象中
fp=open('./test.html','r',encoding='utf-8')
soup=BeautifulSoup(fp,'lxml') - 將互聯(lián)網(wǎng)上獲取的頁面源碼加載到該對(duì)象中
page_text = response.text
soup=BeautifulSoup(page_text,'lxml')
提供的用于數(shù)據(jù)解析的方法和屬性
soup.tagName:返回的是文檔中第一次出現(xiàn)的tagName對(duì)應(yīng)的標(biāo)簽,如soup.title
soup.find():find('tagname')等同于soup.div-屬性定位soup.find('div',class_/id/attr='song)
soup.find_all('tagName',)返回符合要求的所有標(biāo)簽(列表)
select:
select('某種選擇器(id,class,標(biāo)簽...選擇器'))返回的是一個(gè)列表.
層級(jí)選擇器:
--soup.select('.tang > ul > li > a')>標(biāo)識(shí)是一個(gè)層級(jí)
--soup.select('.tang > ul a')>空格標(biāo)識(shí)多個(gè)層級(jí)
獲取標(biāo)簽之間的數(shù)據(jù)
soup.a.text/string.get_text()text/get_text()可以過去摸一個(gè)標(biāo)簽中所有的文本內(nèi)容
string 值可以獲取該標(biāo)簽下的文本內(nèi)容
獲取標(biāo)簽中的屬性值
soup.a['href']
Xpath解析
xpath解析:最常用最便捷的高效的一種解析方式,通用性.
xpath 解析原理:
- 實(shí)例化一個(gè)etree的對(duì)象,且需要將其解析源碼數(shù)據(jù)加載到該對(duì)象中
- 調(diào)用etree對(duì)象中的xpath方法結(jié)合著xpath表達(dá)式實(shí)現(xiàn)標(biāo)簽的定位和內(nèi)容的捕獲.
環(huán)境的安裝:
- pip install lxml
使用:from lxml import etree
如何實(shí)例化一個(gè)etree對(duì)象,且需要將解析的頁面源碼數(shù)據(jù)加載到該對(duì)象中. - 將本地的html文檔的數(shù)據(jù)源碼加載到etree對(duì)象中:etree.parse(filePath, etree.HTMLParser())
- 可以將互聯(lián)網(wǎng)上獲取的源碼數(shù)據(jù)加載到etree對(duì)象中:etree.HTML(str)
使用:result = html.xpath('//li')
print(result)
xpath表達(dá)式:
- /表示的是從根節(jié)點(diǎn)開始,表示的是一個(gè)層級(jí)
- //表示的是多個(gè)層級(jí),可以表示從任意位置開始
- 屬性定位://div[@class="song"] tag[@attrName="attrValue"]
- 索引定位://div[@class="song"]/p[3] 索引是從1開始的
- 取文本:
- /text()獲取標(biāo)簽中直系的文本內(nèi)容如://p[1]/span/a/text()
- //text()獲取標(biāo)簽中非直系的文本內(nèi)容(該標(biāo)簽所有文本內(nèi)容)
- 取屬性:
- /@attrName //img[@id="imgCode"]/@src'
驗(yàn)證碼識(shí)別
驗(yàn)證碼和爬蟲之間的愛恨情仇?
反爬蟲機(jī)制:驗(yàn)證碼。識(shí)別驗(yàn)證碼圖片中的數(shù)據(jù),用于模擬登錄操作
識(shí)別驗(yàn)證碼的操作:
- 人工肉眼識(shí)別
- 第三方 自動(dòng)識(shí)別驗(yàn)證碼識(shí)別技術(shù)開發(fā)文檔-云碼 (jfbym.com)
ddddocr的庫使用
云打碼使用
- 進(jìn)入打碼平臺(tái)注冊(cè)賬號(hào)登陸后使用接口調(diào)用即可
import requests
import base64
def get_ocr_img(filePath):
url='https://www.jfbym.com/api/YmServer/verifyapi'
base=encode_base64(filePath)
data={
"image":base,
"token":"lvfJhEHuS+2Kk5HWWFGz2pYZOOiaDvLeIauN35puIc=",
"type":"10101"
}
headers={
"Content-Type":"application/x-www-form-urlencoded"
}
res=requests.post(url,data=data,headers=headers)
print(res.text)
def encode_base64(file):
with open(file, 'rb') as f:
img_data = f.read()
base64_data = base64.b64encode(img_data)
print(type(base64_data))
# print(base64_data)
# 如果想要在瀏覽器上訪問base64格式圖片,需要在前面加上:data:image/jpeg;base64,
base64_str = str(base64_data, 'utf-8')
print(base64_str)
return base64_data
def decode_base64(base64_data):
with open('./images/base64.jpg', 'wb') as file:
img = base64.b64decode(base64_data)
file.write(img)
if __name__ == '__main__':
get_ocr_img('./yzm.jpg')
http/https協(xié)議特征:無狀態(tài)。
沒有請(qǐng)求到對(duì)應(yīng)頁面數(shù)據(jù)的原因
- 發(fā)起的第二次基于個(gè)人主頁面請(qǐng)求的時(shí)候。服務(wù)器端并不知道該此請(qǐng)求是基于登錄狀態(tài)下的請(qǐng)求。
cookie:用來讓服務(wù)器端記錄客戶端的相應(yīng)狀態(tài):
- 手動(dòng)處理:通過抓包工具獲取cookie值。將該值封裝到headers中(不建議)
- 自動(dòng)處理:
- 創(chuàng)建一個(gè)session對(duì)象:session=requests.Session()
- 使用session對(duì)象進(jìn)行模擬登錄post請(qǐng)求的發(fā)送(cookie就會(huì)被存儲(chǔ)在session中)
- session對(duì)象對(duì)個(gè)人主頁對(duì)應(yīng)的get請(qǐng)求發(fā)送(攜帶了cookie)
代理:破解封IP這種反爬機(jī)制。
什么是代理?-代理服務(wù)器
代理的作用:突破自身IP訪問的限制,隱藏自身的真實(shí)IP
代理相關(guān)網(wǎng)站:快代理,西祠代理,www.goubanjia.com
代理IP的類型
- http:應(yīng)用到http協(xié)議對(duì)應(yīng)的url中
- https:應(yīng)用到https協(xié)議對(duì)應(yīng)的url中
使用方法:res=requests.get(url,headers=headers,proxies={'https':'103.103.3.6:8080'}).text
代理ip的匿名度
- 透明:服務(wù)器知道該次請(qǐng)求使用了代理,也知道對(duì)應(yīng)真是的IP地址
- 匿名:知道了使用代理,不是到真實(shí)IP
- 高匿:不知道使用了代理,不知道真是IP
import requests
url ='https://ip.hao86.com/'
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37'}
res=requests.get(url,headers=headers,proxies={'https':'10.103.3.6:8080'}).text
with open('./dd.html','w',encoding='utf-8') as wf:
wf.write(res)
高性能異步爬蟲
目的:在爬蟲中使用異步實(shí)現(xiàn)高性能的數(shù)據(jù)爬取操作
異步爬蟲的方式:
- 多線程,多進(jìn)程:(不建議)
好處:可以為相關(guān)阻塞的操作單獨(dú)開啟線程或進(jìn)程,阻塞操作就可以異步執(zhí)行.
弊端:無法限制的開啟多線程或多進(jìn)程. - 線程池,進(jìn)程池(適當(dāng)使用)
好處:我們可以降低系統(tǒng)對(duì)進(jìn)程或線程創(chuàng)建和銷毀的一個(gè)頻率,從而很好的降低系統(tǒng)的開銷
弊端:池中線程或進(jìn)程的數(shù)量是有上限. - 單線程+異步協(xié)程(推薦):
event_loop:事件循環(huán),相當(dāng)于一個(gè)無限循環(huán),我們可以把一些函數(shù)注冊(cè)到這個(gè)事件循環(huán)上,當(dāng)滿足某些條件的時(shí)候,函數(shù)就會(huì)被循環(huán)執(zhí)行
coroutine 協(xié)程對(duì)象,我們可以將協(xié)程對(duì)象注冊(cè)到事件循環(huán)中,它會(huì)被事件循環(huán)調(diào)用.我們可以使用async關(guān)鍵字來頂一個(gè)方法,這個(gè)方法在調(diào)用時(shí)不會(huì)被執(zhí)行,而是返回一個(gè)協(xié)程對(duì)象.
task 任務(wù),他是對(duì)協(xié)程對(duì)象的進(jìn)一步封裝,包含了任務(wù)的各個(gè)狀態(tài)
future:代表將來執(zhí)行或者還沒有執(zhí)行的任務(wù),實(shí)際上和task沒有本質(zhì)區(qū)別
async定義一個(gè)協(xié)程
await 用來掛起阻塞方法的執(zhí)行.
不使用線程池方式
import time
def get_page(str):
print('start-',str)
time.sleep(2)
print('end-',str)
name_list=['xiaozi','aaa','bbb','ccc']
start_time=time.time()
for i in range(len(name_list)):
get_page(name_list[i])
end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 8 sdde
使用線程池方式
import time
from multiprocessing.dummy import Pool
def get_page(str):
print('start-',str)
time.sleep(2)
print('end-',str)
name_list=['xiaozi','aaa','bbb','ccc']
start_time=time.time()
# for i in range(len(name_list)):
# get_page(name_list[i])
# 實(shí)例化線程池對(duì)象
pool=Pool(4)# 開啟4個(gè)線程池
# 將列表中每一個(gè)列表元素傳遞給get_page進(jìn)行處理,返回的結(jié)果就是get_page的return
pool.map(get_page,name_list)
end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 2 sdde
使用單線程+異步協(xié)程
import asyncio
async def request_url(url):
print('正在請(qǐng)求的url時(shí)',url)
print('請(qǐng)求成功',url)
return url
c=request_url('www.baidu.com')
# 創(chuàng)建一個(gè)事件循環(huán)對(duì)象
# loop=asyncio.get_event_loop()
# # 將協(xié)程對(duì)象注冊(cè)到loop中然后啟動(dòng)loop
# loop.run_until_complete(c)
# asyncio.run(c) # 上述的封裝方法
# task的使用
# loop=asyncio.get_event_loop()
# task=loop.create_task(c,name='task1')
# print(task)
# loop.run_until_complete(task)
# # future
# loop=asyncio.get_event_loop()
# task=asyncio.ensure_future(c)
# print(task)
# loop.run_until_complete(task)
# 綁定回調(diào)
def callback_func(task):
print(task.result())
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
# 將回調(diào)函數(shù)綁定到任務(wù)對(duì)象中,task執(zhí)行完成后就會(huì)調(diào)用該函數(shù)
task.add_done_callback(callback_func)
loop.run_until_complete(task)
import asyncio
import time
async def request_url(url):
print('正在下載',url)
await asyncio.sleep(2)
print('下載完成',url)
start=time.time()
urls=[
'baidu',
'google',
'sougou',
'bing'
]
# stasks=[]
# for ul in urls:
# c=request_url(ul)
# task=asyncio.ensure_future(c)
# stasks.append(task)
tasks=[request_url(url) for url in urls]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
ends=time.time()
print(ends-start)
案例
import asyncio
import time
import requests
import aiohttp
urls=[
'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
print('正在下載',url)
# requests.get是基于同步,必須使用基于異步的網(wǎng)絡(luò)請(qǐng)求模塊進(jìn)行指定url的請(qǐng)求發(fā)送,或者給他弄成線程再轉(zhuǎn)換為協(xié)程
# aiohttp基于異步網(wǎng)絡(luò)請(qǐng)求的模塊
async with aiohttp.ClientSession() as session:
# get(),post()
# headers,params/data,proxy='http://ip:port'
async with session.get(url) as res:
pageText= await res.text()
print('下載完畢',pageText)
tasks=[get_page(url) for url in urls]
start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('總耗時(shí)',ends-start) # 總耗時(shí) 2.0147018432617188
錯(cuò)誤演示//request沒有協(xié)程
import asyncio
import time
import requests
urls=[
'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
print('正在下載',url)
res=requests.get(url)
print('下載完畢',res.text)
tasks=[get_page(url) for url in urls]
start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('總耗時(shí)',ends-start)
后端代碼 類似下方方式
from flask import Flask
import time
app=Flask(__name__)
@app.route('/b1')
def index_b1():
time.sleep(2)
return 'Hello b1'
@app.route('/b2')
def index_b2():
time.sleep(2)
return 'Hello b2'
@app.route('/b3')
def index_b3():
time.sleep(2)
return 'Hello b3'
if __name__=='__main__':
app.run(threaded=True)
擴(kuò)展Python自帶的網(wǎng)絡(luò)請(qǐng)求庫
示例
發(fā)送get請(qǐng)求
import urllib.request
url='https://funletu.com/dong-tai/page/2'
req=urllib.request.Request(url)
with urllib.request.urlopen(req) as response:
data=response.read()
ss=data.decode()
print(ss)
發(fā)送post請(qǐng)求
import urllib.request
import urllib.parse
url='https://funletu.com/dong-tai/page/2'
params_dict={'id':123,'query':'all'}
params_str=urllib.parse.urlencode(params_dict)
params_bytes=params_str.encode()
req=urllib.request.Request(url,data=params_bytes)
with urllib.request.urlopen(req) as response:
data=response.read()
ss=data.decode()
print(ss)