異步爬蟲之協程
建議從代碼部分開始閱讀!!!不清楚的地方向上檢視基本概念
異步程式設計大緻流程:
1.事件循環:了解為一個死循環(去檢測并執行某些代碼)
#僞代碼
任務清單={任務1,任務2,任務3.。。。}
while True:
可執行的任務清單,已完成的任務清單=去任務清單中檢查所有的任務,将’可執行‘和’已完 成‘的任務傳回
for 就緒任務 in 可執行的任務清單:
執行已就緒的任務
for 已完成的任務 in 已完成的任務清單;
在任務清單中移除,已完成的任務
如果 任務清單 中的任務都已完成,則終止循環
文章目錄
- 異步爬蟲之協程
-
- 基本概念介紹
-
- 1. 特殊的函數
- 2. 協程對象
- 3. 任務對象
- 4. 事件循環對象
- 5. 注意事項!!!!!
- 代碼部分
-
- 1. 基本協程的使用
- 2. 基于loop的任務對象的使用
- 3. 任務對象future的使用
- 4. 回調函數的綁定和使用
- 5. 多任務協程
- 6. aiohttp的使用---實戰
基本概念介紹
1. 特殊的函數
- 如果一個函數的定義被async關鍵字修飾後,則該函數就變成了一個特殊的函數
-
特殊之處:
1. 該特殊的函數被調用後,内部的語句不會被立即執行
2. 該函數被調用後傳回一個協程對象
2. 協程對象
-
一個對象,通過特殊的函數調用放回的一個協程對象。
是以,協程 = 特殊的函數 =一組指定的操作 => 協程= 一組特殊的函數
3. 任務對象
- 任務對象就是一個進階的協程對象(即對協程對象的進一步封裝)
- 有task和future兩種。本質上無差別。但是基于loop建立的任務對象的前提是得有loop。
如何建立一個任務對象:
· asyncio.ensure_future(協程對象)—(future)
第二種方法為基于事件循環對象的建立task,在後面介紹
- 任務對象的進階之處:
可以給任務對象綁定回調
task.add_done_callback(task_callback)
回調函數的調用時機:
任務被執行結束後,才可以調用回調函數
-回調函數的參數隻可以有一個;表示的就是該回調函數的調用者(任務對象)
-使用回調函數的參數調用result()傳回的就是任務對象表示的特殊函數的傳回值
4. 事件循環對象
作用:
-可以将多個任務對象注冊/裝載到任務循環對象中
-如果開啟了事件循環,則其内部注冊/裝載的任務對象表示的就是指定操作就是任務對象
-建立方式:
-loop=asyncio.get_event_loop()
- 建立任務對象
- task=loop.creat_task(協程對象)
-注冊且啟動方式:
-loop.run_until_complete(task)
啟動時不不能直接将task清單放入,要加asyncio.wait(tasks)修飾
wait()作用:可以将任務清單中的任務對象賦予可挂起權限。(每一個任務對象都可以被挂起)
任務對象的挂起:将目前挂起的任務對象交出CPU的使用權。隻有當任務對象的CPU的使用權,
loop才可以使用CPU去執行下一個任務對象
-當loop在執行某一個任務對象時,遇到了阻塞操作,則loop會跳過阻塞操作執行下一個任務對象
-當loop在執行某一個任務對象時,前面一個任務對象的阻塞操作結束了,則loop會回頭将該阻塞
結束的任務對象阻塞之後的操作進行執行
5. 注意事項!!!!!
-在特殊函數内部不可以出現不支援異步子產品對應的代碼,否則會中斷整個異步效果,requests不支援異步
-await關鍵字
-在特殊函數内部,凡是阻塞操作前,都必須使用await進行修飾,await就可以保證阻塞
操作在異步執行過程中不會被跳過!!!
-aiohttp
-是一個支援異步的網絡請求子產品。
-使用代碼:
1.寫一個大緻的架構
async def get_request(url):
#執行個體化好了一個請求對象
with aiohttp.ClientSession() as sess:
#調用get發起請求,傳回一個響應對象
#get/post(url,headers,params/data,proxy=''http://ip:port)
with sess.get(url=url) as response:
#擷取了字元串形式的響應資料
page_text=response.text()
return page_text
2.補充細節:
-在阻塞操作前加await關鍵字,在每個with前加上async關鍵字(爬蟲中隻有發送請求和擷取響應資料是阻塞操作)
3。完整代碼:
async def get_request(url):
#執行個體化好了一個請求對象
async with aiohttp.ClientSession() as sess:
#調用get發起請求,傳回一個響應對象
#get/post(url,headers,params/data,proxy=''http://ip:port)
async with await sess.get(url=url) as response:
#text()擷取了字元串形式的響應資料
#read()擷取byte類型的響應資料
page_text=await response.text()
return page_text
代碼部分
安裝子產品 : pip install asyncio
1. 基本協程的使用
import asyncio
# 定義協程函數
async def request(url):
print('正在請求:',url)
print('請求結束',url)
# async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')
# 建立一個事件循環對象
loop=asyncio.get_event_loop()
# 将協程對象注冊到loop中,然後啟動loop
loop.run_until_complete(c)
2. 基于loop的任務對象的使用
import asyncio
async def request(url):
print('正在請求:',url)
print('請求結束',url)
# async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')
# task的使用
#建立事件循環對象
loop=asyncio.get_event_loop()
# 基于loop建立的task對象
# 通過creat_task建立任務對象,将協程對象封裝進去
task=loop.create_task(c)
print(task)
# 注冊并執行任務
loop.run_until_complete(task)
print(task)

3. 任務對象future的使用
import asyncio
async def request(url):
print('正在請求:',url)
print('請求結束',url)
# async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')
# future的使用
# 建立事件循環對象
loop=asyncio.get_event_loop()
# 建立future
future=asyncio.ensure_future(c)
print(future)
#注冊并啟動
loop.run_until_complete(future)
print(future)
狀态與task相同
4. 回調函數的綁定和使用
import asyncio
async def request(url):
print('正在請求:',url)
print('請求結束',url)
# 傳回,回調函數可以通過.result擷取傳回值
return url
# async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')
#綁定回調
# 回調函數的建立無需關鍵字async,參數隻能有一個,即回調函數的調用者,task對象
def callback_func(task):
print(task.result())
#建立事件循環對象
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
#将回調函數綁定到任務對象中
task.add_done_callback(callback_func)
#注冊并啟動任務
loop.run_until_complete(task)
5. 多任務協程
import asyncio
import time
#模拟請求發送
async def request(url):
print('正在請求',url)
time.sleep(2)
print("下載下傳完畢",url)
# 請求清單
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']
# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
c=request(url)
task=asyncio.ensure_future(c)
# 将建立的任務對象加入到任務清單中
tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
原因:time是同步子產品,在特殊函數内部不可以出現不支援異步子產品對應的代碼,否則會中斷整個異步效果。 詳情看注意事項
解決辦法: 改用異步子產品,await asyncio.sleep(2)
import asyncio
import time
#模拟請求發送
async def request(url):
print('正在請求',url)
# time.sleep(2)
# 使用異步子產品,
await asyncio.sleep(2)
print("下載下傳完畢",url)
# 請求清單
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']
# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
c=request(url)
task=asyncio.ensure_future(c)
# 将建立的任務對象加入到任務清單中
tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
6. aiohttp的使用—實戰
詳情可看注意事項
import asyncio
import time
import aiohttp #使用該子產品中的ClientSession進行網絡請求發送
import requests
from lxml import etree
# 請求清單
urls=['https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
,'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD',
'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
]
headers={
"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.67',
}
#請求發送
async def get_request(url):
#執行個體化一個session對象發送請求
async with aiohttp.ClientSession() as sess:
# 請求發送,手動挂起
async with await sess.get(url=url,headers=headers) as response:
#text()方法可以傳回字元串形式的響應資料
#read()方法可以傳回二進制形式的資料
#json()方法傳回的就是json對象
# 注意在擷取響應資料操作之前要使用await進行手動挂起
page_text=await response.text()
return page_text
# 解析響應資料
def parse(task):
page_text=task.result()
tree=etree.HTML(page_text)
title=tree.xpath('/html/head/title/text()')[0]
print(title)
# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
c=get_request(url)
task=asyncio.ensure_future(c)
#綁定回調
task.add_done_callback(parse)
# 将建立的任務對象加入到任務清單中
tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)