今天比較忙,水一下

下面的代碼來源於這個視頻裡面提到的,github 的鏈接為:github.com/mikeckennedy

第一個代碼如下,就是一個普通的 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 = fhttps://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(文件名稱)

之後獲取到得到一個詳細的函數調用關係、耗時圖:

可以看到 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 aiohttps ClientSession
url = fhttps://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 圖:

可見現在耗時為大約3.8s,基本符合我們的預期了。

我的公眾號:全棧不存在的


推薦閱讀:
相关文章