攝影:產品經理
與產品經理在蘇州的小生活
據說過異步爬蟲的同窗,應該或多或少據說過aiohttp這個庫。它經過 Python 自帶的async/await實現了異步爬蟲。api
使用 aiohttp,咱們能夠經過 requests 的api寫出併發量匹敵 Scrapy 的爬蟲。網絡
咱們在 aiohttp 的官方文檔上面,能夠看到它給出了一個代碼示例,以下圖所示:session
咱們如今稍稍修改一下,來看看這樣寫爬蟲,運行效率如何。併發
修改之後的代碼以下:app
import asyncio import aiohttp template = 'http://exercise.kingname.info/exercise_middleware_ip/{page}' async def get(session, page): url = template.format(page=page) resp = await session.get(url) print(await resp.text(encoding='utf-8')) async def main(): async with aiohttp.ClientSession() as session: for page in range(100): await get(session, page) loop = asyncio.get_event_loop() loop.run_until_complete(main())
這段代碼訪問個人爬蟲練習站100次,獲取100頁的內容。異步
你們能夠經過下面這個視頻看看它的運行效率:async
能夠說,目前這個運行速度,跟 requests 寫的單線程爬蟲幾乎沒有區別,代碼還多了那麼多。ide
那麼,應該如何正確釋放 aiohttp 的超能力呢?oop
咱們如今把代碼作一下修改:url
import asyncio import aiohttp template = 'http://exercise.kingname.info/exercise_middleware_ip/{page}' async def get(session, queue): while True: try: page = queue.get_nowait() except asyncio.QueueEmpty: return url = template.format(page=page) resp = await session.get(url) print(await resp.text(encoding='utf-8')) async def main(): async with aiohttp.ClientSession() as session: queue = asyncio.Queue() for page in range(1000): queue.put_nowait(page) tasks = [] for _ in range(100): task = get(session, queue) tasks.append(task) await asyncio.wait(tasks) loop = asyncio.get_event_loop() loop.run_until_complete(main())
在修改之後的代碼裏面,我讓這個爬蟲爬1000頁的內容,咱們來看看下面這個視頻。
能夠看到,目前這個速度已經能夠跟 Scrapy 比一比了。而且你們須要知道,這個爬蟲只有1個進程1個線程,它是經過異步的方式達到這個速度的。
那麼,修改之後的代碼,爲何速度能快那麼多呢?
關鍵的代碼,就在:
tasks = [] for _ in range(100): task = get(session, queue) tasks.append(task) await asyncio.wait(tasks)
在慢速版本里面,咱們只有1個協程在運行。而在如今這個快速版本里面,咱們建立了100個協程,並把它提交給asyncio.wait來統一調度。asyncio.wait會在全部協程所有結束的時候才返回。
咱們把1000個 URL 放在asyncio.Queue生成的一個異步隊列裏面,每個協程都經過 while True 不停從這個異步隊列裏面取 URL 並進行訪問,直到異步隊列爲空,退出。
當程序運行時,Python 會自動調度這100個協程,當一個協程在等待網絡 IO 返回時,切換到第二個協程併發起請求,在這個協程等待返回時,繼續切換到第三個協程併發起請求……。程序充分利用了網絡 IO 的等待時間,從而大大提升了運行速度。
最後,感謝實習生小河給出的這種加速方案。