1. 程式人生 > >小白學 Python 爬蟲(32):非同步請求庫 AIOHTTP 基礎入門

小白學 Python 爬蟲(32):非同步請求庫 AIOHTTP 基礎入門

人生苦短,我用 Python

前文傳送門:

小白學 Python 爬蟲(1):開篇

小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝

小白學 Python 爬蟲(3):前置準備(二)Linux基礎入門

小白學 Python 爬蟲(4):前置準備(三)Docker基礎入門

小白學 Python 爬蟲(5):前置準備(四)資料庫基礎

小白學 Python 爬蟲(6):前置準備(五)爬蟲框架的安裝

小白學 Python 爬蟲(7):HTTP 基礎

小白學 Python 爬蟲(8):網頁基礎

小白學 Python 爬蟲(9):爬蟲基礎

小白學 Python 爬蟲(10):Session 和 Cookies

小白學 Python 爬蟲(11):urllib 基礎使用(一)

小白學 Python 爬蟲(12):urllib 基礎使用(二)

小白學 Python 爬蟲(13):urllib 基礎使用(三)

小白學 Python 爬蟲(14):urllib 基礎使用(四)

小白學 Python 爬蟲(15):urllib 基礎使用(五)

小白學 Python 爬蟲(16):urllib 實戰之爬取妹子圖

小白學 Python 爬蟲(17):Requests 基礎使用

小白學 Python 爬蟲(18):Requests 進階操作

小白學 Python 爬蟲(19):Xpath 基操

小白學 Python 爬蟲(20):Xpath 進階

小白學 Python 爬蟲(21):解析庫 Beautiful Soup(上)

小白學 Python 爬蟲(22):解析庫 Beautiful Soup(下)

小白學 Python 爬蟲(23):解析庫 pyquery 入門

小白學 Python 爬蟲(24):2019 豆瓣電影排行

小白學 Python 爬蟲(25):爬取股票資訊

小白學 Python 爬蟲(26):為啥買不起上海二手房你都買不起

小白學 Python 爬蟲(27):自動化測試框架 Selenium 從入門到放棄(上)

小白學 Python 爬蟲(28):自動化測試框架 Selenium 從入門到放棄(下)

小白學 Python 爬蟲(29):Selenium 獲取某大型電商網站商品資訊

小白學 Python 爬蟲(30):代理基礎

小白學 Python 爬蟲(31):自己構建一個簡單的代理池

PS:原諒小編一件事兒,昨天公眾號推送的前文傳送門連結沒搞對,導致所有連線都失效了,微信又對已經推送的文章有修改限制,只支援刪改,不支援加連結,小編誠懇的給大家道個歉。

為什麼需要非同步請求庫

按照慣例,先放官方連結:

官方文件:https://docs.aiohttp.org/en/stable/

可惜這個沒有中文版的,瀏覽器自帶的翻譯軟體湊合看吧,有看不懂的再看原文。

原因當然很簡單,快啊~~~

啊呸,不對,是效率高。

這個效率高怎麼定義呢?如果是爬取的介面或者頁面沒有前後的邏輯關係,舉個栗子:必須要先從 a 頁面獲取某個資料才能拼出來 b 頁面訪問連結,這個就叫有前後邏輯關係。

我們很多情況下頁面的爬取是沒有前後邏輯關係的,使用同步請求庫如: Requests 就只能等一個請求先出去,再回來才會傳送下一個請求。

如果是換成非同步請求庫就不會有這個等待了,一個請求發出去,才不會管這個請求什麼時間響應,直接下一個請求就接著發出去了,然後再是下下個請求。

當然,非同步請求庫也為我們提供了回撥方法,不然我們都不知道什麼時候請求有響應,什麼時候會有我們想要的資料回來。

先看個簡單的例子,我們先直觀的感受下非同步請求庫到底能比同步請求庫快多少。

這裡使用的網站是度娘(其實本來想使用 Github 的,實在是小編使用的移動的寬頻網路太xxx,迴圈開啟十次 5 分鐘都跑不完),無奈轉換度娘,訪問 100 次,因為 10 次太少了,看不出來差距。

Requests 版示例

示例程式碼如下:

import requests
from datetime import datetime

start = datetime.now()

for i in range(100):
    print(requests.get('https://www.baidu.com/').text)

end = datetime.now()

print("request花費時間為:", end - start)

結果如下:

request花費時間為: 0:00:13.410708

其他的列印小編這裡就不貼了,單純的貼一下最後時間差的列印。

AioHttp 版示例

示例程式碼如下:

import aiohttp
import asyncio
from datetime import datetime

async def main():
    async with aiohttp.ClientSession() as client:
        html = await client.get('https://www.baidu.com/')
        print(html)

loop = asyncio.get_event_loop()

tasks = []
for i in range(100):
    task = loop.create_task(main())
    tasks.append(task)

start = datetime.now()

loop.run_until_complete(main())

end = datetime.now()

print("aiohttp花費時間為:", end - start)

結果如下:

aiohttp花費時間為: 0:00:00.249995

各位同學,看到了沒,這個訪問速度天差地別啊,一個用了 13s 多,一個連 1s 都沒到,這中間的差距小編已經不想算了,太大了。

不過訪問速度這麼快,訪問有 ip 防禦的網站,封的速度也挺快的,可能爬蟲剛開始執行,茶杯子都沒端起來就已經把 ip 封掉了。

基操

接下來我們簡單的瞭解一下 AIOHTTP 的一些基本操作。

發請求

示例程式碼:

import aiohttp
import asyncio

async def aio_1():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://www.baidu.com/') as resp:
            print(resp.status)
            print(await resp.text())

loop = asyncio.get_event_loop()
loop.run_until_complete(aio_1())

結果就不貼了,這裡主要是給各位同學演示如何使用 AIOHTTP 傳送請求。

這裡,我們使用一個 ClientSession 作為被呼叫的 session 和一個 ClientResponse 物件作為響應結果。

一下內容為來自官方文件的提示:

注意:

不要為每個請求建立會話。每個應用程式很可能需要一個會話來執行所有請求。

更復雜的情況可能需要在每個站點上進行一次會話,例如,一個會話用於Github,另一個會話用於Facebook API。無論如何,為每個請求建立會話是一個非常糟糕的主意。

會話內部包含一個連線池。連線重用和保持活動狀態(預設情況下均處於啟用狀態)可能會提高整體效能。

響應

先看個示例:

async def aio_2():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://www.geekdigging.com/') as resp:
            print(resp.status)
            print(await resp.text())

loop = asyncio.get_event_loop()
loop.run_until_complete(aio_2())

AIOHTTP 為我們提供了自動解碼的功能,

這裡的示例訪問小編的部落格站,其中首頁有大量的中文內容,如果解碼不正確中文是不能正常顯示的。結果小編就不貼了,解碼正確。

當然,如果我們發現自動解碼不正確的時候可以認為的設定解碼型別,程式碼如下:

await resp.text(encoding='gb2312')

響應我們同樣可以通過二進位制位元組流的方式來進行訪問,程式碼如下:

print(await resp.read())

AIOHTTP 還為我們內建了一個 JSON 解碼器,可以供我們直接處理 JSON 格式的響應資料,示例程式碼如下:

print(await resp.json())

超時

在前面我們介紹其他請求庫的時候,都有遇到過超時的問題,一般而言,我們會為請求新增一個超時時間,那麼在 AIOHTTP 中,超時時間的新增如下示例程式碼:

async def aio_3():
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout = timeout) as session:
        async with session.get('https://www.geekdigging.com/', timeout = timeout) as resp:
            print(resp.status)

loop = asyncio.get_event_loop()
loop.run_until_complete(aio_3())

如果我們不設定超時時間 AIOHTTP 為我們預設設定的超時時間是 5 分鐘,如果我們設定了超時時間,則以我們設定的為準,超時時間的設定可以在兩個地方設定,小編已經在示例中都舉例出來了。

我們可以直接在建立 ClientSession 的時候直接設定超時時間,這時,整個超時時間是在當前的會話中都有效的,如果在後面的呼叫中如 ClientSession.get(): 中重新設定超時時間,則會覆蓋我們在建立 ClientSession 設定的超時時間。

而 ClientTimeout 則還有很多種屬性可以進行設定,列表如下:

  • total:整個操作時間包括連線建立,請求傳送和響應讀取。
  • connect:該時間包括建立新連線或在超過池連線限制時等待池中的空閒連線的連線。
  • sock_connect:連線到對等點以進行新連線的超時,不是從池中給出的。
  • sock_read:從對等體讀取新資料部分之間的時間段內允許的最大超時。

預設超時如下:

aiohttp.ClientTimeout(total=5*60, connect=None,
                      sock_connect=None, sock_read=None)

示例程式碼

本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。

示例程式碼-Github

示例程式碼-Gi