我們剛開始學習爬蟲的時候,往往學習的都是單線程的同步類型的爬蟲。這樣的爬蟲在遇到阻塞的時候會將進程掛起,十分耗費時間。使用異步爬蟲就可以在單個進程阻塞的時候其他進程也繼續(xù)工作。那么python異步爬蟲的怎么實現(xiàn)呢?今天小編就帶來了一篇python異步爬蟲的實現(xiàn)原理和知識總結(jié)。
多線程爬蟲對服務器的請求是比較頻繁的,這樣會給目標服務器造成很大的負擔,很容易被對方警告。另外,非法爬取別人網(wǎng)站的信息是會被法律制裁的,在學習爬蟲的時候請注意爬蟲的度(不要使用次數(shù)很多的循環(huán),不使用多線程,一次只爬取少量數(shù)據(jù))。
一、背景
默認情況下,用get請求時,會出現(xiàn)阻塞,需要很多時間來等待,對于有很多請求url時,速度就很慢。因為需要一個url請求的完成,才能讓下一個url繼續(xù)訪問。一種很自然的想法就是用異步機制來提高爬蟲速度。通過構(gòu)建線程池或者進程池完成異步爬蟲,即使用多線程或者多進程來處理多個請求(在別的進程或者線程阻塞時)。
import time
#串形
def getPage(url):
print("開始爬取網(wǎng)站",url)
time.sleep(2)#阻塞
print("爬取完成?。?!",url)
urls = ['url1','url2','url3','url4','url5']
beginTime = time.time()#開始計時
for url in urls:
getPage(url)
endTime= time.time()#結(jié)束計時
print("完成時間%d"%(endTime - beginTime))
下面通過模擬爬取網(wǎng)站來完成對多線程,多進程,協(xié)程的理解。
二、多線程實現(xiàn)
import time
#使用線程池對象
from multiprocessing.dummy import Pool
def getPage(url):
print("開始爬取網(wǎng)站",url)
time.sleep(2)#阻塞
print("爬取完成?。?!",url)
urls = ['url1','url2','url3','url4','url5']
beginTime = time.time()#開始計時
#準備開啟5個線程,并示例化對象
pool = Pool(5)
pool.map(getPage, urls)#urls是可迭代對象,里面每個參數(shù)都會給getPage方法處理
endTime= time.time()#結(jié)束計時
print("完成時間%d"%(endTime - beginTime))
完成時間只需要2s!!!!!!!!
線程池使用原則:適合處理耗時并且阻塞的操作
三、協(xié)程實現(xiàn)
單線程+異步協(xié)程!?。。。。。。。?!強烈推薦,目前流行的方式。
相關(guān)概念:
#%%
import time
#使用協(xié)程
import asyncio
async def getPage(url): #定義了一個協(xié)程對象,python中函數(shù)也是對象
print("開始爬取網(wǎng)站",url)
time.sleep(2)#阻塞
print("爬取完成?。?!",url)
#async修飾的函數(shù)返回的對象
c = getPage(11)
#創(chuàng)建事件對象
loop_event = asyncio.get_event_loop()
#注冊并啟動looP
loop_event.run_until_complete(c)
#task對象使用,封裝協(xié)程對象c
'''
loop_event = asyncio.get_event_loop()
task = loop_event.create_task(c)
loop_event.run_until_complete(task)
'''
#Future對象使用,封裝協(xié)程對象c 用法和task差不多
'''
loop_event = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
loop_event.run_until_complete(task)
'''
#綁定回調(diào)使用
async def getPage2(url): #定義了一個協(xié)程對象,python中函數(shù)也是對象
print("開始爬取網(wǎng)站",url)
time.sleep(2)#阻塞
print("爬取完成!??!",url)
return url
#async修飾的函數(shù)返回的對象
c2 = getPage2(2)
def callback_func(task):
print(task.result()) #task.result()返回任務對象中封裝的協(xié)程對象對應函數(shù)的返回值
#綁定回調(diào)
loop_event = asyncio.get_event_loop()
task = asyncio.ensure_future(c2)
task.add_done_callback(callback_func) #真正綁定,
loop_event.run_until_complete(task)
輸出:
四、多任務協(xié)程實現(xiàn)
import time
#使用多任務協(xié)程
import asyncio
urls = ['url1','url2','url3','url4','url5']
async def getPage(url): #定義了一個協(xié)程對象,python中函數(shù)也是對象
print("開始爬取網(wǎng)站",url)
#在異步協(xié)程中如果出現(xiàn)同步模塊相關(guān)的代碼,那么無法實現(xiàn)異步
#time.sleep(2)#阻塞
await asyncio.sleep(2)#遇到阻塞操作必須手動掛起
print("爬取完成!?。?,url)
return url
beginTime = time.time()
#任務列表,有多個任務
tasks = []
for url in urls:
c = getPage(url)
task = asyncio.ensure_future(c)#創(chuàng)建任務對象
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))#不能直接放task,需要封裝進入asyncio,wait()方法中
endTime = time.time()
print("完成時間%d"%(endTime - beginTime))
此時不能用time.sleep(2),用了還是10秒
對于真正爬取過程中,如在getPage()方法中真正爬取數(shù)據(jù)時,即requests.get(url) ,它是基于同步方式實現(xiàn)。應該使用異步網(wǎng)絡請求模塊aiohttp
參考下面代碼:
async def getPage(url): #定義了一個協(xié)程對象,python中函數(shù)也是對象
print("開始爬取網(wǎng)站",url)
#在異步協(xié)程中如果出現(xiàn)同步模塊相關(guān)的代碼,那么無法實現(xiàn)異步
#requests.get(url)#阻塞
async with aiohttp.ClintSession() as session:
async with await session.get(url) as response: #手動掛起
page_text = await response.text() #.text()返回字符串,read()返回二進制數(shù)據(jù),注意不是content
print("爬取完成?。。?,url)
return page_text
到此這篇Python異步爬蟲的實現(xiàn)原理與知識總結(jié)就介紹到這里了,希望能給小伙伴帶來幫助,也希望大家以后多多支持W3Cschool!