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())