人生苦短,我用 Python
前文傳送門:
小白學 Python 爬蟲(2):前置准備(一)基本類庫的安裝
小白學 Python 爬蟲(3):前置准備(二)Linux基礎入門
小白學 Python 爬蟲(4):前置准備(三)Docker基礎入門
小白學 Python 爬蟲(6):前置准備(五)爬蟲框架的安裝
小白學 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 爬蟲(21):解析庫 Beautiful Soup(上)
小白學 Python 爬蟲(22):解析庫 Beautiful Soup(下)
小白學 Python 爬蟲(23):解析庫 pyquery 入門
小白學 Python 爬蟲(26):為啥買不起上海二手房你都買不起
小白學 Python 爬蟲(27):自動化測試框架 Selenium 從入門到放棄(上)
小白學 Python 爬蟲(28):自動化測試框架 Selenium 從入門到放棄(下)
小白學 Python 爬蟲(29):Selenium 獲取某大型電商網站商品信息
小白學 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 上,方便大家取用。