實戰 | 用aiohttp和uvloop實現一個高性能爬蟲

asyncio於Python3.4引入標準庫,增長了對異步I/O的支持,asyncio基於事件循環,能夠輕鬆實現異步I/O操做。接下來,咱們用基於asyncio的庫實現一個高性能爬蟲。python

準備工做

Earth View from Google Earth是一款Chrome插件,會在打開新標籤頁時自動加載一張來自Google Earth的背景圖片。web

Earth View from Google Earth

使用Chrome開發者工具觀察插件的網絡請求,咱們發現插件會請求一個地址如www.gstatic.com/prettyearth…的JSON文件,文件中包含了通過Base64的圖片內容,觀察發現,圖片的ID範圍大體在1000-8000之間,咱們的爬蟲就要來爬取這些精美的背景圖片。chrome

實現主要邏輯

因爲爬取目標是JSON文件,爬蟲的主要邏輯就變成了爬取JSON-->提取圖片-->保存圖片json

requests是一個經常使用的http請求庫,可是因爲requests的請求都是同步的,咱們使用aiohttp這個異步http請求庫來代替。網絡

async def fetch_image_by_id(item_id):
	url = f'https://www.gstatic.com/prettyearth/assets/data/v2/{item_id}.json'
        # 因爲URL是https的,因此選擇不驗證SSL
	async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
		async with session.get(url) as response:
            # 獲取後須要將JSON字符串轉爲對象
			try:
				json_obj = json.loads(await response.text())
			except json.decoder.JSONDecodeError as e:
				print(f'Download failed - {item_id}.jpg')
				return
            # 獲取JSON中的圖片內容字段,通過Base64解碼成二進制內容
			image_str = json_obj['dataUri'].replace('data:image/jpeg;base64,', '')
			image_data = base64.b64decode(image_str)
			save_folder = dir_path = os.path.dirname(
				os.path.realpath(__file__)) + '/google_earth/'
			with open(f'{save_folder}{item_id}.jpg', 'wb') as f:
				f.write(image_data)
			print(f'Download complete - {item_id}.jpg')
複製代碼

aiohttp基於asyncio,因此在調用時須要使用async/await語法糖,能夠看到,因爲aiohttp中提供了一個ClientSession上下文,代碼中使用了async with的語法糖。session

加入並行邏輯

上面的代碼是抓取單張圖片的邏輯,批量抓取圖片,須要再嵌套一層方法:併發

async def fetch_all_images():
    # 使用Semaphore限制最大併發數
	sem = asyncio.Semaphore(10)
	ids = [id for id in range(1000, 8000)]
	for current_id in ids:
		async with sem:
			await fetch_image_by_id(current_id)
複製代碼

接下來,將這個方法加入到asyncio的事件循環中。異步

event_loop = asyncio.get_event_loop()
future = asyncio.ensure_future(fetch_all_images())
results = event_loop.run_until_complete(future)
複製代碼

使用uvloop加速

uvloop基於libuv,libuv是一個使用C語言實現的高性能異步I/O庫,uvloop用來代替asyncio默認事件循環,能夠進一步加快異步I/O操做的速度。async

uvloop的使用很是簡單,只要在獲取事件循環前,調用以下方法,將asyncio的事件循環策略設置爲uvloop的事件循環策略。工具

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
複製代碼

使用上面的代碼,咱們能夠快速將大約1500張的圖片爬取下來。

爬取下來的Google Earth圖片

性能對比

爲了驗證aiohttp和uvloop的性能,筆者使用requests+concurrent庫實現了一個多進程版的爬蟲,分別爬取20個id,消耗的時間如圖。

能夠看到,耗時相差了大概7倍,aiohttp+uvloop的組合在爬蟲這種I/O密集型的場景下,能夠說具備壓倒性優點。相信在不遠的未來,基於asyncio的庫會將無數爬蟲工程師從加班中拯救出來。

掃碼關注Python私房菜

拯救加班的你
相關文章
相關標籤/搜索