Python使用aiohttp實(shí)現(xiàn)每秒千次的網(wǎng)頁(yè)抓取
引言
在當(dāng)今大數(shù)據(jù)時(shí)代,高效的網(wǎng)絡(luò)爬蟲(chóng)是數(shù)據(jù)采集的關(guān)鍵工具。傳統(tǒng)的同步爬蟲(chóng)(如**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">requests</font>**庫(kù))由于受限于I/O阻塞,難以實(shí)現(xiàn)高并發(fā)請(qǐng)求。而Python的**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">aiohttp</font>**庫(kù)結(jié)合**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">asyncio</font>**,可以輕松實(shí)現(xiàn)異步高并發(fā)爬蟲(chóng),達(dá)到每秒千次甚至更高的請(qǐng)求速率。
本文將詳細(xì)介紹如何使用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">aiohttp</font>**構(gòu)建一個(gè)高性能爬蟲(chóng),涵蓋以下內(nèi)容:
- aiohttp的基本原理與優(yōu)勢(shì)
- 搭建異步爬蟲(chóng)框架
- 優(yōu)化并發(fā)請(qǐng)求(連接池、超時(shí)控制)
- 代理IP與User-Agent輪換(應(yīng)對(duì)反爬)
- 性能測(cè)試與優(yōu)化(實(shí)現(xiàn)1000+ QPS)
最后,我們將提供一個(gè)完整的代碼示例,并進(jìn)行基準(zhǔn)測(cè)試,展示如何真正實(shí)現(xiàn)每秒千次的網(wǎng)頁(yè)抓取。
1. aiohttp的基本原理與優(yōu)勢(shì)
1.1 同步 vs. 異步爬蟲(chóng)
- 同步爬蟲(chóng)(如requests):每個(gè)請(qǐng)求必須等待服務(wù)器響應(yīng)后才能繼續(xù)下一個(gè)請(qǐng)求,I/O阻塞導(dǎo)致性能低下。
- 異步爬蟲(chóng)(aiohttp + asyncio):利用事件循環(huán)(Event Loop)實(shí)現(xiàn)非阻塞I/O,多個(gè)請(qǐng)求可同時(shí)進(jìn)行,極大提高并發(fā)能力。
1.2 aiohttp的核心組件
**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ClientSession</font>**:管理HTTP連接池,復(fù)用TCP連接,減少握手開(kāi)銷(xiāo)。**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">async/await</font>**語(yǔ)法:Python 3.5+的異步編程方式,使代碼更簡(jiǎn)潔。**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">asyncio.gather()</font>**:并發(fā)執(zhí)行多個(gè)協(xié)程任務(wù)。
2. 搭建異步爬蟲(chóng)框架
2.1 安裝依賴(lài)
2.2 基礎(chǔ)爬蟲(chóng)示例
import aiohttp
import asyncio
from bs4 import BeautifulSoup
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def parse(url):
async with aiohttp.ClientSession() as session:
html = await fetch(session, url)
soup = BeautifulSoup(html, 'html.parser')
title = soup.title.string
print(f"URL: {url} | Title: {title}")
async def main(urls):
tasks = [parse(url) for url in urls]
await asyncio.gather(*tasks)
if __name__ == "__main__":
urls = [
"https://example.com",
"https://python.org",
"https://aiohttp.readthedocs.io",
]
asyncio.run(main(urls))
代碼解析:
**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">fetch()</font>**發(fā)起HTTP請(qǐng)求并返回HTML。**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">parse()</font>**解析HTML并提取標(biāo)題。**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">main()</font>**使用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">asyncio.gather()</font>**并發(fā)執(zhí)行多個(gè)任務(wù)。
3. 優(yōu)化并發(fā)請(qǐng)求(實(shí)現(xiàn)1000+ QPS)
3.1 使用連接池(TCP Keep-Alive)
默認(rèn)情況下,**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">aiohttp</font>**會(huì)自動(dòng)復(fù)用TCP連接,但我們可以手動(dòng)優(yōu)化:
conn = aiohttp.TCPConnector(limit=100, force_close=False) # 最大100個(gè)連接
async with aiohttp.ClientSession(connector=conn) as session:
# 發(fā)起請(qǐng)求...
3.2 控制并發(fā)量(Semaphore)
避免因請(qǐng)求過(guò)多被目標(biāo)網(wǎng)站封禁:
semaphore = asyncio.Semaphore(100) # 限制并發(fā)數(shù)為100
async def fetch(session, url):
async with semaphore:
async with session.get(url) as response:
return await response.text()
3.3 超時(shí)設(shè)置
防止某些請(qǐng)求卡住整個(gè)爬蟲(chóng):
timeout = aiohttp.ClientTimeout(total=10) # 10秒超時(shí)
async with session.get(url, timeout=timeout) as response:
# 處理響應(yīng)...
4. 代理IP與User-Agent輪換(應(yīng)對(duì)反爬)
4.1 隨機(jī)User-Agent
from fake_useragent import UserAgent
ua = UserAgent()
headers = {"User-Agent": ua.random}
async def fetch(session, url):
async with session.get(url, headers=headers) as response:
return await response.text()
4.2 代理IP池
import aiohttp
import asyncio
from fake_useragent import UserAgent
# 代理配置
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"
# 構(gòu)建帶認(rèn)證的代理URL
proxy_auth = aiohttp.BasicAuth(proxyUser, proxyPass)
proxy_url = f"http://{proxyHost}:{proxyPort}"
ua = UserAgent()
semaphore = asyncio.Semaphore(100) # 限制并發(fā)數(shù)
async def fetch(session, url):
headers = {"User-Agent": ua.random}
timeout = aiohttp.ClientTimeout(total=10)
async with semaphore:
async with session.get(
url,
headers=headers,
timeout=timeout,
proxy=proxy_url,
proxy_auth=proxy_auth
) as response:
return await response.text()
async def main(urls):
conn = aiohttp.TCPConnector(limit=100, force_close=False)
async with aiohttp.ClientSession(connector=conn) as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
if __name__ == "__main__":
urls = ["https://example.com"] * 1000
asyncio.run(main(urls))
5. 性能測(cè)試(實(shí)現(xiàn)1000+ QPS)
5.1 基準(zhǔn)測(cè)試代碼
import time
async def benchmark():
urls = ["https://example.com"] * 1000 # 測(cè)試1000次請(qǐng)求
start = time.time()
await main(urls)
end = time.time()
qps = len(urls) / (end - start)
print(f"QPS: {qps:.2f}")
asyncio.run(benchmark())
5.2 優(yōu)化后的完整代碼
import aiohttp
import asyncio
from fake_useragent import UserAgent
ua = UserAgent()
semaphore = asyncio.Semaphore(100) # 限制并發(fā)數(shù)
async def fetch(session, url):
headers = {"User-Agent": ua.random}
timeout = aiohttp.ClientTimeout(total=10)
async with semaphore:
async with session.get(url, headers=headers, timeout=timeout) as response:
return await response.text()
async def main(urls):
conn = aiohttp.TCPConnector(limit=100, force_close=False)
async with aiohttp.ClientSession(connector=conn) as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
if __name__ == "__main__":
urls = ["https://example.com"] * 1000
asyncio.run(main(urls))
5.3 測(cè)試結(jié)果
- 未優(yōu)化(單線程requests):~10 QPS
- 優(yōu)化后(aiohttp + 100并發(fā)):~1200 QPS
結(jié)論
通過(guò)**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">aiohttp</font>**和**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">asyncio</font>**,我們可以輕松構(gòu)建一個(gè)高并發(fā)的異步爬蟲(chóng),實(shí)現(xiàn)每秒千次以上的網(wǎng)頁(yè)抓取。
關(guān)鍵優(yōu)化點(diǎn)包括:
- 使用
**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ClientSession</font>**管理連接池 - 控制并發(fā)量(Semaphore)
- 代理IP和隨機(jī)User-Agent防止封禁
- 超時(shí)設(shè)置避免卡死
以上就是Python使用aiohttp實(shí)現(xiàn)每秒千次的網(wǎng)頁(yè)抓取的詳細(xì)內(nèi)容,更多關(guān)于Python aiohttp網(wǎng)頁(yè)抓取的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
用python 批量操作redis數(shù)據(jù)庫(kù)
這篇文章主要介紹了如何用python 批量操作redis數(shù)據(jù)庫(kù),幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03
Python實(shí)現(xiàn)批量獲取文件夾內(nèi)文件名并重命名
這篇文章主要為大家詳細(xì)介紹了Python如何批量獲取文件夾內(nèi)文件名及重命名文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02
Python中raise用法簡(jiǎn)單實(shí)例(超級(jí)詳細(xì),看了無(wú)師自通)
python中raise語(yǔ)句用于手動(dòng)觸發(fā)異常,通過(guò)raise語(yǔ)句可以在代碼中顯式地引發(fā)異常,從而使程序進(jìn)入異常處理流程,下面這篇文章主要給大家介紹了關(guān)于Python中raise用法的相關(guān)資料,需要的朋友可以參考下2024-03-03
python交互模式基礎(chǔ)知識(shí)點(diǎn)學(xué)習(xí)
在本篇內(nèi)容里小編給大家整理的是關(guān)于python交互模式是什么的相關(guān)基礎(chǔ)知識(shí)點(diǎn),需要的朋友們可以參考下。2020-06-06
Django 限制用戶訪問(wèn)頻率的中間件的實(shí)現(xiàn)
這篇文章主要介紹了Django 限制用戶訪問(wèn)頻率的中間件的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Python腳本實(shí)現(xiàn)datax全量同步mysql到hive
這篇文章主要和大家分享一下mysql全量同步到hive自動(dòng)生成json文件的python腳本,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參加一下2024-10-10
Python中SyntaxError的常見(jiàn)場(chǎng)景和修正指南
在Python編程中,語(yǔ)法錯(cuò)誤(SyntaxError)是最基礎(chǔ)的異常類(lèi)型之一,它發(fā)生在代碼違反Python語(yǔ)法規(guī)則時(shí),本文將系統(tǒng)梳理Python中常見(jiàn)的SyntaxError場(chǎng)景,結(jié)合典型案例與修正策略,幫助開(kāi)發(fā)者快速定位并修復(fù)語(yǔ)法問(wèn)題,需要的朋友可以參考下2025-12-12

