參考:https://www.jianshu.com/p/20ca9daba85f
參考官網:https://docs.aiohttp.org/en/stable/
什么是aiohttp
用於asyncio和Python的異步HTTP客戶端/服務器
安裝與使用
使用pip安裝
pip install aiohttp
簡單實例使用
aiohttp的自我介紹中就包含了客戶端和服務器端,所以我們分部來看下客戶端和服務器端的簡單代碼示例。
客戶端
use_aiohttp.py
import aiohttp import asyncio # 創建獲取網頁的函數,傳遞參數為session和一個url async def fetch(session,url): async with session.get(url) as response: return await response.text() async def main(): # 創建session並把session和需要獲取網頁的url作為參數傳遞給協程函數fetch # 協程函數把網頁文本下載下來 async with aiohttp.ClientSession() as session: html = await fetch(session,"http://httpbin.org/headers") print(html) asyncio.run(main())
運行結果如下
# { # "headers": { # "Accept": "*/*", # "Accept-Encoding": "gzip, deflate", # "Host": "httpbin.org", # "User-Agent": "Python/3.7 aiohttp/3.8.0", # "X-Amzn-Trace-Id": "Root=1-6180979e-602b0f9d2a6eece96d9dae2f" # } # }
這個代碼是不是很簡單,一個函數用來發起請求,另外一個函數用來下載網頁。
服務器端
aiohttp_server.py
from aiohttp import web async def handle(request): # 從請求獲取name的屬性信息,如果沒有則取'Anonymous' name = request.match_info.get('name', "Anonymous") print(request.match_info) # print(request) print(name) text = "Hello, " + name # 把text作為相應返回,如果訪問的為/則返回 "Hello, Anonymous" # 如果訪問為/{name}則返回對應的 "Hello,{name}" return web.Response(text=text) app = web.Application() # 添加兩個路由訪問/和訪問/{name}都去調用函數handle # 如果使用/訪問則默認用戶為Anonymous如果使用/{name}訪問則用戶為對應的{name} app.add_routes([web.get('/', handle),web.get('/{name}', handle)]) if __name__ == '__main__': web.run_app(app)
運行這個代碼,然后訪問http://127.0.0.1:8080就可以看到你的網站了,很 基礎的一個網頁,你可以在后面跟上你的名字。
訪問根目錄/
帶一個name訪問 /liuym
運行這個代碼,然后訪問http://127.0.0.1:8080就可以看到你的網站了,很 基礎的一個網頁,你可以在后面跟上你的名字。
入門
簡單示范
首先是學習客戶端,也就是用來發送http請求的用法。首先看一段代碼,會在代碼中講述需要注意的地方:
aiohttp_client.py
# 客戶端示范 start import aiohttp import asyncio async def main(): # 使用關鍵字async with 以及ClientSession方法創建一個session對象 async with aiohttp.ClientSession() as session: # 所以該session對象訪問一個站點,創建一個response對象 async with session.get('http://httpbin.org/get') as resp: # 打印響應狀態碼 print(resp.status) # 200 # 打印響應文本 print(await resp.text()) asyncio.run(main()) # 客戶端示范 end
代碼解釋:
在網絡請求中,一個請求就是一個會話,然后aiohttp使用的是ClientSession來關聯會話,所以第一個重點,看一下ClientSession: 看源碼
class ClientSession: """First-class interface for making HTTP requests.""" ...
在源碼中,這個類的注釋是使用HTTP請求接口的一個類。然后上面的代碼就是實例化一個ClientSession類,然后命名為session,然后用session去發送輕松。這里有一個坑,那就是ClientSession.get()
協程的必需參數只能是str
類和yarl.URL
的實例。
當然這只是get請求,其他的post、put都是支持的:
session.put('http://httpbin.org/put', data=b'data') session.delete('http://httpbin.org/delete') session.head('http://httpbin.org/get') session.options('http://httpbin.org/get') session.patch('http://httpbin.org/patch', data=b'data')
在URL中傳遞參數
有時候在發起網絡請求的時候需要附加一些參數到url中,這一點也是支持的
import aiohttp import asyncio async def main(): # 使用關鍵字async with 以及ClientSession方法創建一個session對象 async with aiohttp.ClientSession() as session: # 所以該session對象訪問一個站點,創建一個response對象 # async with session.get('http://httpbin.org/get') as resp: params = {'key1': 'value1', 'key2': 'value2'} async with session.get('http://httpbin.org/get',params=params) as resp: expect = 'http://httpbin.org/get?key1=value1&key2=value2' print(resp.url) assert str(resp.url) == expect # 打印響應狀態碼 print(resp.status) # 200 # 打印響應文本 print(await resp.text()) asyncio.run(main())
運行輸出
我們可以通過params
參數來指定要傳遞的參數,
同時如果需要指定一個鍵對應多個值的參數,那么MultiDict
就在這個時候起作用了。你可以傳遞兩個元祖列表來作為參數:
# 一個建對應多個值參數 start import aiohttp import asyncio async def main(): # 使用關鍵字async with 以及ClientSession方法創建一個session對象 async with aiohttp.ClientSession() as session: # 所以該session對象訪問一個站點,創建一個response對象 # async with session.get('http://httpbin.org/get') as resp: params = [('key','value1'), ('key','value2')] async with session.get('http://httpbin.org/get',params=params) as resp: expect = 'http://httpbin.org/get?key=value1&key=value2' print(resp.url) assert str(resp.url) == expect # 打印響應狀態碼 print(resp.status) # 200 # 打印響應文本 print(await resp.text()) asyncio.run(main()) # 一個建對應多個值參數 end
輸出如下
當然也可以之間傳遞一個str類型的數據到url中,但是這個時候需要注意的就是傳遞的字符串類型中最好是都可以被編碼的。
async with session.get('http://www.yznx.xyz', params='/index.php/2019/09/02/使用flask-mail和實現郵箱激活賬戶/') as resp:
編譯后的路徑大概是這樣:
http://www.yznx.xyz/?/index.php/2019/09/02/%E4%BD%BF%E7%94%A8flask-mail%E5%92%8C%E5%AE%9E%E7%8E%B0%E9%82%AE%E7%AE%B1%E6%BF%80%E6%B4%BB%E8%B4%A6%E6%88%B7/=
讀取響應內容
我們可以讀取到服務器的響應狀態和響應內容,這也是使用請求的一個很重要的部分。通過status
來獲取響應狀態碼,text()
來獲取到響應內容,當然也可以之計指明編碼格式為你想要的編碼格式:
async def main(): async with aiohttp.ClientSession() as session: async with session.get('http://httpbin.org/get') as resp: print(resp.status) print(await resp.text(encoding=utf-8)) """輸出結果: 200 <!doctype html> <html lang="zh-CN"> <head> ...... """
非文本內容格式
對於網絡請求,有時候是去訪問一張圖片,這種返回值是二進制的也是可以讀取到的:
await resp.read()
將text()
方法換成read()
方法就好。
獲取請求信息
ClientResponse(客戶端響應)對象含有request_info(請求信息),主要是url和headers信息。 raise_for_status結構體上的信息會被復制給ClientResponseError實例。
請求的自定義
有時候做請求的時候需要自定義header,主要是為了讓服務器認為我們是一個瀏覽器。然后就需要我們自己來定義一個headers:
示例:
aiohttp_client.py
# 自定義Header start import aiohttp import asyncio async def main(): # 自定義header headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko)" " Chrome/78.0.3904.108 Safari/537.36" } async with aiohttp.ClientSession() as session: # 傳遞參數headers值為自定義的header async with session.get('http://192.168.1.100',headers=headers) as resp: print(await resp.text()) asyncio.run(main()) # 自定義Header end
訪問自己搭建的nginx服務器查看nginx日志可以看到,加headers和沒有加headers的日志顯示原始瀏覽器信息是不同的
自定義cookie
發送你自己的cookies給服務器,你可以為ClientSession對象指定cookies參數:
# 自定義cookie start import aiohttp import asyncio async def main(): url = 'http://httpbin.org/cookies' cookies = {'cookies_are': 'working'} async with aiohttp.ClientSession(cookies=cookies) as session: # 傳遞參數headers值為自定義的header async with session.get(url) as resp: print(await resp.json()) asyncio.run(main()) # 自定義cookie end
輸出cookies如下
{'cookies': {'cookies_are': 'working'}}
使用代理
有時候在寫爬蟲的時候需要使用到代理,所以aiohttp也是支持使用代理的,我們可以在發起請求的時候使用代理,只需要使用關鍵字proxy
來指明就好,但是有一個很難受的地方就是它只支持http
代理,不支持HTTPS代理。使用起來大概是這樣:
proxy = “http://127.0.0.1:10809” async with aiohttp.ClientSession(headers=headers) as session: async with session.get(url=login_url, proxy=proxy) as response: resu = await response.text()
使用起來大概是這樣,然后代理記得改成自己的。
和asyncio結合使用
其實aiohttp最適合的伴侶就是asyncio,這兩個結合起來使用是最好不過的了。然后這里我就寫一個簡單的實例代碼來對比一下。同步和異步的差別。
示例代碼
示例就簡單的用豆瓣電影吧,這是我從開始學習爬蟲就一直練習的網站。然后寫一些基本需要使用到的庫:
- lxml
- requests
- datetime
- asyncio
- aiohttp
同步
from datetime import datetime import requests from lxml import etree headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit" "/537.36 (KHTML, like Gecko) " "Chrome/72.0.3626.121 Safari/537.36"} def get_movie_url(): req_url = "https://movie.douban.com/chart" response = requests.get(url=req_url, headers=headers) html = etree.HTML(response.text) movies_url = html.xpath( "//*[@id='content']/div/div[1]/div/div/table/tr/td/a/@href") return movies_url def get_movie_content(movie_url): response = requests.get(movie_url, headers=headers) result = etree.HTML(response.text) movie = dict() name = result.xpath('//*[@id="content"]/h1/span[1]//text()') author = result.xpath('//*[@id="info"]/span[1]/span[2]//text()') movie["name"] = name movie["author"] = author return movie if __name__ == '__main__': start = datetime.now() movie_url_list = get_movie_url() movies = dict() for url in movie_url_list: movies[url] = get_movie_content(url) print(movies) print("同步用時為:{}".format(datetime.now() - start))
異步
import asyncio from datetime import datetime import aiohttp from lxml import etree headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit" "/537.36 (KHTML, like Gecko) " "Chrome/72.0.3626.121 Safari/537.36"} async def get_movie_url(): req_url = "https://movie.douban.com/chart" async with aiohttp.ClientSession(headers=headers) as session: async with session.get(url=req_url, headers=headers) as response: result = await response.text() result = etree.HTML(result) return result.xpath("//*[@id='content']/div/div[1]/div/div/table/tr/td/a/@href") async def get_movie_content(movie_url): async with aiohttp.ClientSession(headers=headers) as session: async with session.get(url=movie_url, headers=headers) as response: result = await response.text() result = etree.HTML(result) movie = dict() name = result.xpath('//*[@id="content"]/h1/span[1]//text()') author = result.xpath('//*[@id="info"]/span[1]/span[2]//text()') movie["name"] = name movie["author"] = author return movie if __name__ == '__main__': start = datetime.now() loop = asyncio.get_event_loop() movie_url_list = loop.run_until_complete(get_movie_url()) tasks = [get_movie_content(url) for url in movie_url_list] movies = loop.run_until_complete(asyncio.gather(*tasks)) print(movies) print("異步用時為:{}".format(datetime.now() - start))