在日常爬蟲中我們會涉及到同步與異步問題,一般異步編程可以大幅度的提高系統的吞吐量,提高單位時間內發出的請求數目。之前的文章分享了些同步的知識,就是對aurl發起請求,等待響應。然後再訪問burl,等待響應。。。
大量的時間消耗在等待上,如果能近似的同時對多個網址發起請求,等待響應,速度回快很多倍。其實所謂的同時也是有先後順序的,所以叫異步。
異步爬蟲的方式有以下2種
1、多線程,多進程(不建議):
好處:可以爲相關阻塞的操作單獨開啓線程,阻塞操作就可以異步執行。弊端:無法無限制的開啓多線程或者多進程。
2、線程池、進程池(適當的使用):好處:可以降低系統對進程或者線程創建和銷燬的一個頻率,從而很好的降低系統的開銷。弊端:池中線程或進程的數量是有上限。
接下來我們通過aiohttp異步爬蟲來爬取一個書籍網站的數據, https://spa5.scrape.center/,通過簡單的網站分析,反爬機制不是很嚴,爲了爬取順利這裏添加了代理IP,由於這個網站的數據量多一些,所以選擇用異步方式來爬取,代碼實例如下:
# 導入相關庫
import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector
from bs4 import BeautifulSoup
# 定義目標網站和代理服務器的參數
url = "https://spa5.scrape.center/"
proxy = "socks5://16yun:[email protected]:11111"
# 定義異步函數來發送GET請求,並使用代理服務器來連接目標網站
async def fetch(session, url):
try:
async with session.get(url) as response:
# 檢查響應狀態碼是否爲200,否則拋出異常
if response.status != 200:
raise Exception(f"Bad status code: {response.status}")
# 返回響應內容的文本格式
return await response.text()
except Exception as e:
# 打印異常信息,並返回None
print(e)
return None
# 定義異步函數來處理響應結果,並解析HTML內容
async def parse(html):
# 如果響應結果不爲空,則進行解析操作
if html is not None:
# 使用bs4庫來創建BeautifulSoup對象,並指定解析器爲html.parser
soup = BeautifulSoup(html, "html.parser")
# 提取網頁中的標題標籤,並打印其文本內容
title = soup.find("title")
print(title.text)
else:
# 否則打印None表示無效結果
print(None)
# 定義異步函數來統計成功次數,並打印結果
async def count(results):
# 初始化成功次數爲0
success = 0
# 遍歷所有的結果,如果不爲空,則增加成功次數,否則跳過
for result in results:
if result is not None:
success += 1
# 打印總共的請求數和成功次數
print(f"Total requests: {len(results)}")
print(f"Success requests: {success}")
# 定義異步主函數來創建並運行多個協程任務,並控制併發數量和超時時間等參數
async def main():
# 創建一個aiohttp_socks.ProxyConnector對象,用來設置代理服務器的參數
connector = ProxyConnector.from_url(proxy)
# 創建一個aiohttp.ClientSession對象,用來發送HTTP請求,並傳入connector參數
async with aiohttp.ClientSession(connector=connector) as session:
# 創建一個空列表,用來存儲所有的協程任務
tasks = []
# 循環10000次,每次創建一個fetch函數的協程任務,並添加到列表中
for i in range(10000):
task = asyncio.create_task(fetch(session, url))
tasks.append(task)
# 使用asyncio.gather函數來收集並執行所有的協程任務,並返回一個包含所有結果的列表
results = await asyncio.gather(*tasks)
# 創建一個空列表,用來存儲所有的解析任務
parse_tasks = []
for result in results:
parse_task = asyncio.create_task(parse(result))
parse_tasks.append(parse_task)
await asyncio.gather(*parse_tasks)
await count(results)
# 在程序入口處調用異步主函數,並啓動事件循環
if __name__ == "__main__":
asyncio.run(main())