今天比較忙,水一下
下面的程式碼來源於這個視訊裡面提到的,github 的連結為:github.com/mikeckenned…
第一個程式碼如下,就是一個普通的 for 迴圈爬蟲。原文地址。
import requests
import bs4
from colorama import Fore
def main():
get_title_range()
print("Done.")
def get_html(episode_number: int) -> str:
print(Fore.YELLOW + f"Getting HTML for episode {episode_number}", flush=True)
url = f'https://talkpython.fm/{episode_number}'
resp = requests.get(url)
resp.raise_for_status()
return resp.text
def get_title(html: str, episode_number: int) -> str:
print(Fore.CYAN + f"Getting TITLE for episode {episode_number}", flush=True)
soup = bs4.BeautifulSoup(html, 'html.parser')
header = soup.select_one('h1')
if not header:
return "MISSING"
return header.text.strip()
def get_title_range():
# Please keep this range pretty small to not DDoS my site. ;)
for n in range(185, 200):
html = get_html(n)
title = get_title(html, n)
print(Fore.WHITE + f"Title found: {title}", flush=True)
if __name__ == '__main__':
main()
複製程式碼
這段程式碼跑完花了37s,然後我們用 pycharm 的 profiler 工具來具體看看哪些地方比較耗時間。
點選Profile (檔名稱)
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/37a286259d8abe7eb958deacc4e17ea37d76e9a072333e2e6af4d7ab2449714b.jpg)
之後獲取到得到一個詳細的函式呼叫關係、耗時圖:
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/a173a8ed430ae06cbed4043984b820e0d055a8d4a8b9f8d64d116845ba6d55e5.jpg)
可以看到 get_html
這個方法佔了96.7%的時間。這個程式的 IO 耗時達到了97%,獲取 html 的時候,這段時間內程式就在那死等著。如果我們能夠讓他不要在那兒傻傻地等待 IO 完成,而是開始幹些其他有意義的事,就能節省大量的時間。
稍微做一個計算,試用asyncio
非同步抓取,能將時間降低多少?
get_html
這個方法耗時36.8s
,一共呼叫了15
次,說明實際上獲取一個連結的 html 的時間為36.8s / 15
= 2.4s
。**要是全非同步的話,獲取15個連結的時間還是2.4s。**然後加上get_title
這個函式的耗時0.6s
,所以我們估算,改進後的程式將可以用 3s
左右的時間完成,也就是效能能夠提升13倍。
再看下改進後的程式碼。原文地址。
import asyncio
from asyncio import AbstractEventLoop
import aiohttp
import requests
import bs4
from colorama import Fore
def main():
# Create loop
loop = asyncio.get_event_loop()
loop.run_until_complete(get_title_range(loop))
print("Done.")
async def get_html(episode_number: int) -> str:
print(Fore.YELLOW + f"Getting HTML for episode {episode_number}", flush=True)
# Make this async with aiohttp's ClientSession
url = f'https://talkpython.fm/{episode_number}'
# resp = await requests.get(url)
# resp.raise_for_status()
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
resp.raise_for_status()
html = await resp.text()
return html
def get_title(html: str, episode_number: int) -> str:
print(Fore.CYAN + f"Getting TITLE for episode {episode_number}", flush=True)
soup = bs4.BeautifulSoup(html, 'html.parser')
header = soup.select_one('h1')
if not header:
return "MISSING"
return header.text.strip()
async def get_title_range(loop: AbstractEventLoop):
# Please keep this range pretty small to not DDoS my site. ;)
tasks = []
for n in range(190, 200):
tasks.append((loop.create_task(get_html(n)), n))
for task, n in tasks:
html = await task
title = get_title(html, n)
print(Fore.WHITE + f"Title found: {title}", flush=True)
if __name__ == '__main__':
main()
複製程式碼
同樣的步驟生成profile 圖:
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/df9396eba554096dcd196af0394843be78b41bf722694d64d5789a8dc05acbec.jpg)
可見現在耗時為大約3.8s,基本符合我們的預期了。
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/718c7fe5243d0a8eb561fa15d2adccfd9a85590921a1e97238d687ee57915e6a.jpg)
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/99e0e63b2e683ed4e375f0a851a375727bec6ae76681d1ae0060f9c3f8fd7f0c.jpg)
我的公眾號:全棧不存在的
![用PyCharm Profile分析非同步爬蟲效率](https://i.iter01.com/images/07f2442f8bbc91597dc2788309f6045c9b3fde60fffc0ede8bddce6029feda50.jpg)