1. 程式人生 > >python爬蟲aiohttp非同步請求,高效率

python爬蟲aiohttp非同步請求,高效率

0. 前言

本文翻譯自aiohttp的官方文件,如有紕漏,歡迎指出。

aiohttp分為伺服器端和客戶端,本文只介紹客戶端。

另外我已經對 aiohttp 和 asyncio進行了封裝,可以參考我的 github 地址:

由於上下文的緣故,請求程式碼必須在一個非同步的函式中進行:

async def fn():

pass

1. aiohttp安裝

pip3 install aiohttp

1.1. 基本請求用法

  1. async with aiohttp.get('https://github.com') as r:

  2. await r.text()

其中r.text(), 可以在括號中指定解碼方式,編碼方式,例如

await resp.text(encoding='windows-1251')

或者也可以選擇不編碼,適合讀取影象等,是無法編碼的

await resp.read()

2.發起一個session請求

首先是匯入aiohttp模組:

import aiohttp

然後我們試著獲取一個web原始碼,這裡以GitHub的公共Time-line頁面為例:

async with aiohttp.ClientSession() as session:
    async with session.get('https://api.github.com/events') as resp:
        print(resp.status)
        print(await resp.text())

上面的程式碼中,我們建立了一個 ClientSession 物件命名為session,然後通過session的get方法得到一個 ClientResponse 物件,命名為resp,get方法中傳入了一個必須的引數url,就是要獲得原始碼的http url。至此便通過協程完成了一個非同步IO的get請求。

有get請求當然有post請求,並且post請求也是一個協程:

session.post('http://httpbin.org/post', data=b'data')

用法和get是一樣的,區別是post需要一個額外的引數data,即是需要post的資料。

除了get和post請求外,其他http的操作方法也是一樣的:

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

小記:

不要為每次的連線都建立一次session,一般情況下只需要建立一個session,然後使用這個session執行所有的請求。

每個session物件,內部包含了一個連線池,並且將會保持連線和連線複用(預設開啟)可以加快整體的效能。

3.在URL中傳遞引數

我們經常需要通過 get 在url中傳遞一些引數,引數將會作為url問號後面的一部分發給伺服器。在aiohttp的請求中,允許以dict的形式來表示問號後的引數。舉個例子,如果你想傳遞 key1=value1   key2=value2 到 httpbin.org/get 你可以使用下面的程式碼:

params = {'key1': 'value1', 'key2': 'value2'}
async with session.get('http://httpbin.org/get',
                       params=params) as resp:
                       assert resp.url == 'http://httpbin.org/get?key2=value2&key1=value1'

可以看到,程式碼正確的執行了,說明引數被正確的傳遞了進去。不管是一個引數兩個引數,還是更多的引數,都可以通過這種方式來傳遞。除了這種方式之外,還有另外一個,使用一個 list 來傳遞(這種方式可以傳遞一些特殊的引數,例如下面兩個key是相等的也可以正確傳遞):

params = [('key', 'value1'), ('key', 'value2')]
async with session.get('http://httpbin.org/get',
                       params=params) as r:
    assert r.url == 'http://httpbin.org/get?key=value2&key=value1'

除了上面兩種,我們也可以直接通過傳遞字串作為引數來傳遞,但是需要注意,通過字串傳遞的特殊字元不會被編碼:

async with session.get('http://httpbin.org/get',
                       params='key=value+1') as r:
        assert r.url == 'http://httpbin.org/get?key=value+1'

4.響應的內容

還是以GitHub的公共Time-line頁面為例,我們可以獲得頁面響應的內容:

async with session.get('https://api.github.com/events') as resp:
    print(await resp.text())

執行之後,會打印出類似於如下的內容:

'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

resp的text方法,會自動將伺服器端返回的內容進行解碼--decode,當然我們也可以自定義編碼方式:

await resp.text(encoding='gb2312')

除了text方法可以返回解碼後的內容外,我們也可以得到型別是位元組的內容:

print(await resp.read())

執行的結果是:

b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

gzip和deflate轉換編碼已經為你自動解碼。

小記:

text(),read()方法是把整個響應體讀入記憶體,如果你是獲取大量的資料,請考慮使用”位元組流“(streaming response)

5.特殊響應內容:json

如果我們獲取的頁面的響應內容是json,aiohttp內建了更好的方法來處理json:

async with session.get('https://api.github.com/events') as resp:
    print(await resp.json())

如果因為某種原因而導致resp.json()解析json失敗,例如返回不是json字串等等,那麼resp.json()將丟擲一個錯誤,也可以給json()方法指定一個解碼方式:

print(await resp.json(
encoding='gb2312'

)) 或者傳遞一個函式進去:

print(await resp.json( lambda(x:x.replace('a','b')) ))

6.以位元組流的方式讀取響應內容

雖然json(),text(),read()很方便的能把響應的資料讀入到記憶體,但是我們仍然應該謹慎的使用它們,因為它們是把整個的響應體全部讀入了記憶體。即使你只是想下載幾個位元組大小的檔案,但這些方法卻將在記憶體中載入所有的資料。所以我們可以通過控制位元組數來控制讀入記憶體的響應內容:

async with session.get('https://api.github.com/events') as resp:
    await resp.content.read(10) #讀取前10個位元組

一般地,我們應該使用以下的模式來把讀取的位元組流儲存到檔案中:

with open(filename, 'wb') as fd:
    while True:
        chunk = await resp.content.read(chunk_size)
        if not chunk:
            break
        fd.write(chunk)

7.自定義請求頭

如果你想新增請求頭,可以像get新增引數那樣以dict的形式,作為get或者post的引數進行請求:

import json
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
headers = {'content-type': 'application/json'}

await session.post(url,
                   data=json.dumps(payload),
                   headers=headers)

8.自定義Cookie

給伺服器傳送cookie,可以通過給 ClientSession 傳遞一個cookie引數:

url = 'http://httpbin.org/cookies'
cookies = {'cookies_are': 'working'}
async with ClientSession(cookies=cookies) as session:
    async with session.get(url) as resp:
        assert await resp.json() == {
           "cookies": {"cookies_are": "working"}}

可直接訪問連結 “httpbin.org/cookies”檢視當前cookie,訪問session中的cookie請見第10節。

9.post資料的幾種方式

(1)模擬表單post資料

payload = {'key1': 'value1', 'key2': 'value2'}
async with session.post('http://httpbin.org/post',
                        data=payload) as resp:
    print(await resp.text())

注意:data=dict的方式post的資料將被轉碼,和form提交資料是一樣的作用,如果你不想被轉碼,可以直接以字串的形式 data=str 提交,這樣就不會被轉碼。

(2)post json

import json
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}

async with session.post(url, data=json.dumps(payload)) as resp:
    ...

其實json.dumps(payload)返回的也是一個字串,只不過這個字串可以被識別為json格式

(3)post 小檔案

url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}

await session.post(url, data=files)

可以設定好檔名和content-type:

url = 'http://httpbin.org/post'
data = FormData()
data.add_field('file',
               open('report.xls', 'rb'),
               filename='report.xls',
               content_type='application/vnd.ms-excel')

await session.post(url, data=data)

如果將檔案物件設定為資料引數,aiohttp將自動以位元組流的形式傳送給伺服器。

(4)post 大檔案

aiohttp支援多種型別的檔案以流媒體的形式上傳,所以我們可以在檔案未讀入記憶體的情況下發送大檔案。

@aiohttp.streamer
def file_sender(writer, file_name=None):
    with open(file_name, 'rb') as f:
        chunk = f.read(2**16)
        while chunk:
            yield from writer.write(chunk)
            chunk = f.read(2**16)

# Then you can use `file_sender` as a data provider:

async with session.post('http://httpbin.org/post',
                        data=file_sender(file_name='huge_file')) as resp:
    print(await resp.text())

同時我們可以從一個url獲取檔案後,直接post給另一個url,並計算hash值:

async def feed_stream(resp, stream):
    h = hashlib.sha256()

    while True:
        chunk = await resp.content.readany()
        if not chunk:
            break
        h.update(chunk)
        stream.feed_data(chunk)

    return h.hexdigest()

resp = session.get('http://httpbin.org/post')
stream = StreamReader()
loop.create_task(session.post('http://httpbin.org/post', data=stream))

file_hash = await feed_stream(resp, stream)

因為響應內容型別是StreamReader,所以可以把get和post連線起來,同時進行post和get:

r = await session.get('http://python.org')
await session.post('http://httpbin.org/post',
                   data=r.content)

(5)post預壓縮資料

在通過aiohttp傳送前就已經壓縮的資料, 呼叫壓縮函式的函式名(通常是deflate 或 zlib)作為content-encoding的值:

async def my_coroutine(session, headers, my_data):
    data = zlib.compress(my_data)
    headers = {'Content-Encoding': 'deflate'}
    async with session.post('http://httpbin.org/post',
                            data=data,
                            headers=headers)
        pass

10.keep-alive, 連線池,共享cookie

ClientSession 用於在多個連線之間共享cookie:

async with aiohttp.ClientSession() as session:
    await session.get(
        'http://httpbin.org/cookies/set?my_cookie=my_value')
    filtered = session.cookie_jar.filter_cookies('http://httpbin.org')
    assert filtered['my_cookie'].value == 'my_value'
    async with session.get('http://httpbin.org/cookies') as r:
        json_body = await r.json()
        assert json_body['cookies']['my_cookie'] == 'my_value'

也可以為所有的連線設定共同的請求頭:

async with aiohttp.ClientSession(
    headers={"Authorization": "Basic bG9naW46cGFzcw=="}) as session:
    async with session.get("http://httpbin.org/headers") as r:
        json_body = await r.json()
        assert json_body['headers']['Authorization'] == \
            'Basic bG9naW46cGFzcw=='

ClientSession 還支援 keep-alive連線和連線池(connection pooling)

11.cookie安全性

預設ClientSession使用的是嚴格模式的 aiohttp.CookieJar. RFC 2109,明確的禁止接受url和ip地址產生的cookie,只能接受 DNS 解析IP產生的cookie。可以通過設定aiohttp.CookieJar 的 unsafe=True 來配置:

jar = aiohttp.CookieJar(unsafe=True)
session = aiohttp.ClientSession(cookie_jar=jar)

12.控制同時連線的數量(連線池)

也可以理解為同時請求的數量,為了限制同時開啟的連線數量,我們可以將限制引數傳遞給聯結器:

conn = aiohttp.TCPConnector(limit=30)#同時最大進行連線的連線數為30,預設是100,limit=0的時候是無限制

限制同時開啟限制同時開啟連線到同一端點的數量((host, port, is_ssl) 三的倍數),可以通過設定 limit_per_host 引數:

conn = aiohttp.TCPConnector(limit_per_host=30)#預設是0

13.自定義域名解析

我們可以指定域名伺服器的 IP 對我們提供的get或post的url進行解析:

from aiohttp.resolver import AsyncResolver

resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"])
conn = aiohttp.TCPConnector(resolver=resolver)

14.設定代理

aiohttp支援使用代理來訪問網頁:

async with aiohttp.ClientSession() as session:
    async with session.get("http://python.org",
                           proxy="http://some.proxy.com") as resp:
        print(resp.status)

當然也支援需要授權的頁面:

async with aiohttp.ClientSession() as session:
    proxy_auth = aiohttp.BasicAuth('user', 'pass')
    async with session.get("http://python.org",
                           proxy="http://some.proxy.com",
                           proxy_auth=proxy_auth) as resp:
        print(resp.status)

或者通過這種方式來驗證授權:

session.get("http://python.org",
            proxy="http://user:[email protected]")

15.響應狀態碼 response status code

可以通過 resp.status來檢查狀態碼是不是200:

async with session.get('http://httpbin.org/get') as resp:
    assert resp.status == 200

16.響應頭

我們可以直接使用 resp.headers 來檢視響應頭,得到的值型別是一個dict:

>>> resp.headers
{'ACCESS-CONTROL-ALLOW-ORIGIN': '*',
 'CONTENT-TYPE': 'application/json',
 'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT',
 'SERVER': 'gunicorn/18.0',
 'CONTENT-LENGTH': '331',
 'CONNECTION': 'keep-alive'}

或者我們可以檢視原生的響應頭:

>>> resp.raw_headers
((b'SERVER', b'nginx'),
 (b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'),
 (b'CONTENT-TYPE', b'text/html; charset=utf-8'),
 (b'CONTENT-LENGTH', b'12150'),
 (b'CONNECTION', b'keep-alive'))

17.檢視cookie

url = 'http://example.com/some/cookie/setting/url'
async with session.get(url) as resp:
    print(resp.cookies)

18.重定向的響應頭

如果一個請求被重定向了,我們依然可以檢視被重定向之前的響應頭資訊:

>>> resp = await session.get('http://example.com/some/redirect/')
>>> resp
<ClientResponse(http://example.com/some/other/url/) [200]>
>>> resp.history
(<ClientResponse(http://example.com/some/redirect/) [301]>,)

19.超時處理

預設的IO操作都有5分鐘的響應時間 我們可以通過 timeout 進行重寫:

async with session.get('https://github.com', timeout=60) as r:
    ...

如果 timeout=None 或者 timeout=0 將不進行超時檢查,也就是不限時長

相關推薦

python爬蟲aiohttp非同步請求高效率

0. 前言 本文翻譯自aiohttp的官方文件,如有紕漏,歡迎指出。 aiohttp分為伺服器端和客戶端,本文只介紹客戶端。 另外我已經對 aiohttp 和 asyncio進行了封裝,可以參考我的 github 地址: 由於上下文的緣故,請求程式碼必須在一個非同

python flask裏 post請求JSON數據獲取方式總結

out ren response 獲取 post task world! json數據 appid #!flask/bin/python #encodig=utf-8 # _*_ coding:utf-8 _*_ # Writer : byz # dateTim

Python爬蟲之post請求

對象 parse ... src pytho clas open 網址 源代碼 暑假放假在家沒什麽事情做,所以在學習了爬蟲,在這個博客園裏整理記錄一些學習的筆記。 構建表單數據(以http://www.iqianyue.com/mypost 這個簡單的網頁為例) 查看源代碼

python爬取ajax請求返回的json資料格式化報錯json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

python爬取ajax請求,返回Json資料中帶有<html><head></head><body><prestyle="word-wrap: break-word; white-space: pre-wrap;"></pre>

python:爬蟲之Post請求以及動態Ajax資料的爬取(3)

#爬蟲的post方式 作用:對引數進行打包反饋給伺服器 import urllib.request import urllib.parse #對引數打包 url = "http://www.sunck.wang:8085/form" data = { "use

python爬蟲自學第一天全新的開始!

一直在學習,跟著雪峰老師的講解,跟著《Python爬蟲開發與專案實戰》,跟著笨辦法學Python(不得不說確實是新手上路的一本好書),跟著CSDN裡的大神,跟著慕課網的老師,跟著一切能跟著資源......只是今天才開始記錄自己的學習歷程,之前的會慢慢補齊,今天就從“分散式程序”開始記錄吧 話不多少

非同步請求不要讓shiro重定向

轉自:https://blog.csdn.net/iTommyChi/article/details/81474638 shiro 重要的幾個類: DefaultFilter 列舉類 AuthorizationFilter  FormAuthenticationFilter

非同步請求跨域訪問報錯問題 POST提交方式變成OPTIONS

跨域訪問報錯:無訪問許可權、POST請求過來變成OPTIONS 在返回servletResponse物件裡增加如下設定: servletResponse.setHeader("Access-Contro

[python]socket傳送http請求非阻塞io的一個例子

#通過非阻塞io實現http請求 import socket from urllib.parse import urlparse #使用非阻塞io完成http請求 def get_url(url): #通過socket請求html url

Python爬蟲有什麼用網友紛紛給出自己的答案以前是小看了爬蟲

  前言 爬蟲可以從網站某一個頁面(通常是首頁)開始,讀取網頁的內容,找到在網頁中的其它連結地址,然後通過這些連結地址尋找下一個網頁,這樣一直迴圈下去,直到把這個網站所有的網頁都抓取完為止。 難道爬蟲真的只是這樣麼? 小編特地詢問很多網友Python有什麼用,大家給出

Python爬蟲有什麼用網友紛紛給出自己的答案爬蟲能做的還是很多的

爬蟲可以從網站某一個頁面(通常是首頁)開始,讀取網頁的內容,找到在網頁中的其它連結地址,然後通過這些連結地址尋找下一個網頁,這樣一直迴圈下去,直到把這個網站所有的網頁都抓取完為止。 難道爬蟲真的只是這樣麼? 小編特地詢問很多網友Python有什麼用,大家給出答案也是五花八門。 @冰藍

python爬蟲建立代理池爬取5000個代理IP並進行驗證!

前面已經介紹了urllib+正則表示式和BeautifulSoup進行爬取資料的方法,今天再解決一個實際問題——構建自己的代理池。     通過爬蟲在網上進行資料的獲取,由於效率很快,換言之,訪問的速度過快,導致一段時間內的流量過大,會使得對方的伺服器壓力過

python爬蟲:http請求頭部(header)詳解

通常HTTP訊息包括客戶機向伺服器的請求訊息和伺服器向客戶機的響應訊息。這兩種型別的訊息由一個起始行,一個或者多個頭域,一個只是頭域結束的空行和可 選的訊息體組成。HTTP的頭域包括通用頭,請求頭,響應頭和實體頭四個部分。每個頭域由一個域名,冒號

Django專案實戰總結一----非同步請求echarts

function ajax_submit() { $.ajax({ //url: "{% url 'GetIndex' %}", url:"/index/", type: "GET", data: {},

Android--使用原生技術實現ListView(原生技術實現網路非同步請求解析json資料)

涉及到的原生技術: 1.原生技術實現網路非同步請求 1.原生技術解析json資料 實現步驟: 實現程式碼: **第一二步比較簡單,直接跳過 import android.content.Context; import

Python爬蟲】Requests 請求並讀寫、儲存到excel檔案中

爬取前程無憂職位資訊 此次我們用簡單的爬蟲來展示如何把爬到提取出的資訊儲存的excel檔案中.(ps:首先你要安裝好模組openpyxl否則就點選右上角離開,百度搜素安裝.) 選前程無憂的網頁作為案例是因為主編最近在看看工作的訊息,想想就順手寫了一個為方便尋找滿足自己要

Ajax非同步請求頁面不跳轉問題的解決

         背景:在進行ssm整和shiro時有一個許可權不足時跳轉到許可權不足頁面的需求。前端是easyUI的dataGrid表格傳送了一個Ajax請求,到達後端之後這個請求被perms攔截器攔截,許可權校驗未通過,於是要向/webApp/u

解決微信小程式 app onLaunch非同步請求在沒有請求執行完就載入首頁了的問題

 今天在除錯小程式的過程中,進了一個坑,程式載入需要先獲取使用者資訊,然後儲存到Storage中,然後首頁去Storage取資訊,根據使用者資訊去查本地伺服器資料列表,可是發現第一次進入的時候,資料總是載入不出來,只有再次進入才有資料。除錯之後發現app.js onLaunc

從零開始學 Web 之 Ajax(五)同步非同步請求資料格式

一、同步請求與非同步請求 同步請求:在使用者進行請求傳送之後,瀏覽器會一直等待伺服器的資料返回,如果網路延遲比較高,瀏覽器就一直卡在當前介面,直到伺服器返回資料才可進行其他操作。 非同步請求:在使用者進行請求傳送之後,瀏覽器可以自由操作頁面中其他的元素,當伺服器放回資料的時候,才觸發相應事件,對返回的資料

python 爬蟲抓豆瓣電影並存入資料庫

import urllib.request import json import codecs class info(object): #@classmethod def moviedown(url): #網址 url = "https://m