Python非同步Request操作: aiohttp
目錄
1. Tutorial
2. 其他庫推薦
2.1. aiohttp-requests
這個庫時對aiohttp庫的網路請求模組的封裝,用了這個庫,在非同步網路請求的時候,可以在寫法上更簡潔易懂。本質上還是aiohttp庫的使用。推薦使用這個庫來做網路請求。
2.2. aiofiles
aiofiles是一個用Python編寫,用於處理asyncio應用程式中的本地磁碟檔案。爬蟲過程中用它來進行檔案的非同步操作。
2.3. grequests
grequests模組相當於是封裝了gevent的requests模組。
3. 問題記錄
3.1. Multipart.FormData 示例
下面示例展示上傳圖片至SM.MS。
with open(abspath_file, 'rb') as fp: multipart_form_data = aiohttp.FormData(quote_fields=False) # quote_fields: 將對中文進行轉碼 multipart_form_data.add_field('smfile', fp, content_type="image/jpeg", filename=os.path.basename(relpath_file), content_transfer_encoding="base64") headers = {'Authorization': self.api_token} if self.api_token else None # headers = {"Content-Type": "multipart/form-data"} async with aiohttp.ClientSession() as session: async with session.post(self.endpoint, data=multipart_form_data, headers=headers) as resp: await resp.text() str_response = await resp.text() json_content = json.loads(str_response) if not json_content['success']: logger.error(json_content) raise UploadError() print(f"[+] 完成上傳: {relpath_file}")
3.2. with open("xxx") 會被自動關閉
程式是這樣的:
with open("xxx", "rb") as fp: ... async with aiohttp.ClientSession() as session: async with session.post(self.endpoint, data=fp) as resp: await resp.text() ... file_hash = hashlib.md5() while chunk := fp.read(8192): # 這裡報錯:ValueError: read of closed file file_hash.update(chunk) return file_hash.hexdigest()
報錯:ValueError: read of closed file
找到一篇相似的文章,解釋不保證準確:
問題是open(...)返回一個檔案物件,並且您要將同一檔案物件傳遞給要start()在頂層建立的所有協程。恰好先排程的協程例項將檔案物件session.post()作為的一部分傳輸data,session.post()並將讀取檔案到最後並關閉檔案物件。下一個start()協程將嘗試從現在關閉的物件中讀取,這將引發異常。
要解決此問題而不多次開啟檔案,您需要確保實際將資料作為位元組物件讀取:
data = {'file': open('test_img.jpg', 'rb').read()}
這會將相同的位元組物件傳遞給所有協程,它們應按預期工作。
3.3. filename中文錯誤
使用post方式,上傳multipart到SM.MS時,影象儲存沒問題,但檔名從中文變成了諸如 %E9%B2%8D%E9%B.jpg
的樣子……應該是編碼問題。怎麼避免呢?
multipart_form_data = aiohttp.FormData(quote_fields=False) # quote_fields: 將對中文進行轉碼
使用引數 quote_fields
將避免該問題。
3.4. aiohttp(yarl)對url部分字元自動urldecode
最新碰到一個用 aiohttp 訪問不出內容,但是用 requests 能訪問的情況,url 是事先進行了 urlencode 的, 下面的 url 隨便找了個站點代替,但是把重點的引數提了出來
%40 對應的是 `@`
%3a 對應的是 `:`
解決方案:
str_url = "https://www.xxx.com?xxx%40yyy%3azzz"
proxy_url = "http://localhost:8080"
async with session.get(URL(str_url), proxy=proxy_url) as resp:
print(await resp.text())
async with session.get(URL(str_url, encoded=True), proxy=proxy_url) as resp:
print(await resp.text())