我們平時使用 Requests 的時候,一般是這樣寫代碼的:
import requests
def parse(html):
print('對 html 進行處理')
html = requests.get('url')
parse(html)
複制
這是一種非常常見的直線性思維,我先請求網站拿到 html,然後我再把 html 傳給負責處理的函數。在整個過程中,“我“擔任着排程的角色。
在這種思維方式的影響下,有些同學即使在使用 aiohttp 寫異步爬蟲,也是這樣寫的:
import aiohttp
import asyncio
async def request(url):
async with aiohttp.ClientSession() as session:
resp = await session.get(url)
html = await resp.text(encoding='utf-8')
def parse(html):
print('處理 html')
async def main():
url_list = [url1, url2, url3, url4]
tasks = []
for url in url_list:
tasks.append(request(url))
html_list = await asyncio.gather(*tasks)
for html in html_list:
parse(html)
if __name__ == '__main__':
asyncio.run(main())
複制
确實,這些 URL 的網絡請求是異步了,但是卻必須等到所有 URL 全部請求完成以後,才能開始處理這些 HTML。假如其中一個 URL 通路隻需要1秒鐘,其他的 URL 請求需要3秒鐘。那麼這個1秒鐘的請求結束以後,還需要等待2秒,才能開始進行處理。
于是,有些同學會修改代碼,多包裝一層函數:
import aiohttp
import asyncio
async def request(url):
async with aiohttp.ClientSession() as session:
resp = await session.get(url)
html = await resp.text(encoding='utf-8')
def parse(html):
print('處理 html')
async def get(url):
html = await request(url)
parse(html)
async def main():
url_list = [url1, url2, url3, url4]
tasks = []
for url in url_list:
tasks.append(get(url))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
複制
get()
函數整體負責擷取一個 URL 的源代碼并對它進行解析。然後讓
get()
函數異步。
這樣做确實能夠解決問題,但是大家如果仔細體會就會發現,在
get()
函數裡面的代碼寫法,還是用的同步處理的思想。
既然要寫異步代碼,那麼我們腦子裡就要一直記住——很多個請求會同時發出,但是我們并不知道他們什麼時候完成。與其讓我們去等待它完成,然後再把完成結果傳給另外一個函數。不如讓這些請求在結束的時候,自行主動把結果傳給處理函數。
有了這種思想以後,我們再來修改一下上面的代碼:
import aiohttp
import asyncio
async def request(url, callback):
async with aiohttp.ClientSession() as session:
resp = await session.get(url)
html = await resp.text(encoding='utf-8')
callback(html)
def parse(html):
print('處理 html: ', html)
async def main():
url_list = [
'http://exercise.kingname.info/exercise_middleware_ip/1',
'http://exercise.kingname.info/exercise_middleware_ip/2',
'http://exercise.kingname.info/exercise_middleware_ip/3',
'http://exercise.kingname.info/exercise_middleware_ip/4',
'http://exercise.kingname.info/exercise_middleware_ip/5',
'http://exercise.kingname.info/exercise_middleware_ip/6',
]
tasks = []
for url in url_list:
tasks.append(request(url, parse))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
複制
運作效果如下圖所示:
這種寫法,初看起來與用get()函數包裝沒什麼差別,但是他們在思維方式上卻完全不一樣。
via:https://mp.weixin.qq.com/s/2EhxWqZRBibr3Mb1mRZR8Q