httpx
httpx
HTTPX 是 Python3下的全功能 HTTP 客户端,它提供同步和异步 API,并支持 HTTP/1.1 和 HTTP/2,并且高度兼容requests库。

安装
httpx需要python3.6+的支持

pip3 install httpx

同时httpx还提供了客户端,如果需要使用的话,请使用下面的命令进行安装

pip3 install httpx[cli]

另外http2支持是可选的,不是默认就支持的,如果需要支持http2,需要使用下面的命令进行安装

pip3 install httpx[http2]

额外的其它安装选项包括对socks代理的支持,brotli解码支持。如果需要可分别使用下面的命令进行安装

pip install httpx[socks]        # socks代理支持
pip install httpx[brotli]       # brotli解码支持

基本使用
使用httpx发送一个http请求是非常简单的,例如:

import httpx
res = httpx.get(url='https://httpbin.org/get')
print(res.text)

在这里有必要简单介绍一下httpbin.org这个网站,httpbin.org 这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等,且支持 GET、POST 等多种方法。

看到上面的代码,你会发现这和requests库没有什么区别,只不过把requests.get换成了httpx.get。同样,发送其它http请求也是类似的。例如:

import httpx
httpx.put('https://httpbin.org/put', data={'a': 1})
httpx.delete('https://httpbin.org/delete')
httpx.head('https://httpbin.org/get')
httpx.options('https://httpbin.org/get')

httpx支持GET, OPTIONS, HEAD, POST, PUT, PATCH,DELETE请求方法。

传递URL参数
在url中进行参数传递,只需要将代传递的值组装为一个字典,然后传递给相应方法的params参数即可。例如:

import httpx

res = httpx.get(url='https://httpbin.org/get', params={"a":1, "b":2})
print(res.url)

输出的结果是:

https://httpbin.org/get?a=1&b=2

也可以给同一个参数传递多个值,例如:

import httpx

res = httpx.get(url='https://httpbin.org/get', params={"a":1, "b":[2,3,4]})
print(res.url)

输出结果是:

https://httpbin.org/get?a=1&b=2&b=3&b=4

也可以打印res.text,会返现httpbin.org在返回的数据中的args字段中包含了我们传递的参数。

{
    "args": {
        "a": "1", 
        "b": [
        "2", 
        "3", 
        "4"
        ]
    }
...
}

传递表单参数
传递表单参数和传递url参数类似,只需要组装一个字典,然后传递给相应方法的data参数即可。需要注意的是,默认情况下get,options,head请求方法是不能携带请求体(body)的。下面以post请求为例,来传递表单参数。

import httpx

res = httpx.post(url='https://httpbin.org/post', data={'a':1, 'b':2})
print(res.text)

res = httpx.post(url='https://httpbin.org/post', data={'a':1, 'b':[2,3,4]})    # 键有多个值
print(res.text)

输出的关键结果如下:

{
    "args": {}, 
    "data": "", 
    "files": {}, 
    "form": {
        "a": "1", 
        "b": "2"
    }, 
...
}

{
    "args": {}, 
    "data": "", 
    "files": {}, 
    "form": {
        "a": "1", 
        "b": [
            "2", 
            "3", 
            "4"
        ]
    }
...
}

可以看到,httpbin.org给我们返回的信息中表明我们传递的参数确实是在form表单中。

上传文件
上传文件也是组装一个字典,然后传递给相应方法的files参数即可,例如:

import httpx

fb = open('file.txt', 'rb')
files = {'file': fb}
res = httpx.post(url='https://httpbin.org/post', files=files)

fb.close()
print(res.text)

返回的关键响应数据如下:

{
    "args": {}, 
    "data": "", 
    "files": {
        "file": "123\n"  # file.txt文件的内容是123
    },
...
}

表明我们确实是上传了文件。

如果需要指定文件名,文件类型,可以组装一个元组来包含这些信息。例如:

import httpx
fb = open('file.txt', 'rb')
t = ('file.txt', fb, 'text/plain')  # 参数依次是文件名,文件,文件类型
files = {'file': t}
res = httpx.post(url='https://httpbin.org/post', files=files)
fb.close()

print(res.text)

其中文件名是可选的,可以设置为None;文件会被自动编码为UTF-8的字符串;而文件类型如果未指定,httpx将尝试根据文件名猜测 MIME 类型,未知文件扩展名默认为"application/octet-stream"。如果文件名设置为None且没有设置MIME类型,那么httpx将不会自动包含内容类型 MIME 标头字段。

上传多个文件(n个字段,n个文件),如下所示:

fb1 = open('file.txt', 'rb')
fb2 = open('file.txt', 'rb')    # 同一个文件需要打开两次,才能上传两次,否则会出错。
files = {'file1':('file.txt', fb1, 'text/plain'), 'file2': fb2}
res = httpx.post(url='http://httpbin.org/post', files=files)

fb1.close()
fb2.close()

print(res.text)

返回的关键响应如下所示:

{
    "args": {}, 
    "data": "", 
    "files": {
        "file1": "123\n", 
        "file2": "123\n"
    },
    ... 
}

还有一种情况是,一个上传文件字段,可以上传多个文件。这时候需要传递(field, file)列表而不是字典。下面是一个例子:

import httpx

proxy = {
    "http://": "http://127.0.0.1:8080",
}

fb1 = open('file.txt', 'rb')
fb2 = open('file.txt', 'rb')

files = [('file', ('file1', fb1, 'text/plain')), ('file', ('file2', fb2, 'text/plain'))]
res = httpx.post(url='http://httpbin.org/post', files=files, proxies=proxy)

我们使用抓包工具,进行抓包,可以看到结果如下所示:

POST /post HTTP/1.1
Host: httpbin.org
Accept: */*
Accept-Encoding: gzip, deflate
Connection: close
User-Agent: python-httpx/0.22.0
Content-Length: 304
Content-Type: multipart/form-data; boundary=19b9720b981eff788aab5186a1ac07f2

--19b9720b981eff788aab5186a1ac07f2
Content-Disposition: form-data; name="file"; filename="file1"
Content-Type: text/plain

123

--19b9720b981eff788aab5186a1ac07f2
Content-Disposition: form-data; name="file"; filename="file2"
Content-Type: text/plain

123

--19b9720b981eff788aab5186a1ac07f2--

传递json数据
和前面的方式一样,传递json数据,只需要将组装好的字典传递给json参数即可。例如:

import httpx

json = {"name": "jack", "age": 22, 'friends': ['Ulrica', 'William', 'Abel']}

res = httpx.post(url='http://httpbin.org/post', json=json)
print(res.text)

返回的关键结果如下所示:

{
    "args": {}, 
    "data": "{\"name\": \"jack\", \"age\": 22, \"friends\": [\"Ulrica\", \"William\", \"Abel\"]}", 
    "files": {}, 
    "form": {}, 
}

传递其它二进制数据
例如,需要传递xml数据,那么应该传递一个bytes类型的对象或者生成器给content参数。代码如下所示:

import httpx

xml = '''
    <note>
    <to>George</to>
    <from>John</from>
    <heading>Reminder</heading>
    <body>Don't forget the meeting!</body>
    </note>
'''

res = httpx.post(url='http://httpbin.org/post', content=xml.encode(), headers={"Content-Type": "application/xml"})
print(res.text)

我们在传递xml参数的时候,在headers中指明了Content-Type类型是application/xml.

请求头和响应头
如果需要自定义请求头,可以组装一个字典,然后传递给headers参数即可。例如:

import httpx

headers = {
    "sessionid": "ajs1js7sjah879skjs",
    "user-name": "zhaosi",
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36",
    "accept": "application/json"
}

res = httpx.get(url='http://httpbin.org/headers', headers=headers)
print(res.text)

返回的结果如下所示:

{
"headers": {
    "Accept": "application/json", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Host": "httpbin.org", 
    "Sessionid": "ajs1js7sjah879skjs", 
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36", 
    "User-Name": "zhaosi", 
    "X-Amzn-Trace-Id": "Root=1-6246bd43-1f6e39fc117218272b662c52"
    }
}

rfc2616的4.2节指明每个标头字段由一个名称后跟一个冒号(“:”)和字段值组成。字段名称不区分大小写,但是没说值是否区分大小写。

另外,根据RFC 7230,单个响应标头的多个值表示为单个逗号分隔值。例如上面的Accept-Encoding字段,其具有三个值gzip,deflate,br.

返回响应
httpx包含了text, encoding, content, json, status_code等信息。例如:

import httpx

res = httpx.get(url='http://httpbin.org/get')

print(res.encoding)     # 编码方式
print(res.text)         # 以文本方式获取返回内容
print(res.content)      # 以二进制格式获取返回内容
print(res.json())       # 将返回的json数据转为python对象

以文本方式获取返回内容的时候,在某些情况下,响应可能不包含显式编码,在这种情况下httpx将尝试自动确定要使用的编码。另外,如果需要覆盖编码方式,那么可以直接给encoding赋值,例如:

res.encoding = 'ascii'

以二进制方式获取的响应内容,只要是gzip和deflate的HTTP响应编码,httpx都会自动解码。如果brotlipy安装了,那么brotli响应编码也将自动解码。

web api通常以json格式返回数据,为了方便,httpx提供了json()方法来直接将json格式的数据转为python对象。

响应码
httpx使用res.status_code来获取返回的HTTP响应码,同时也提供了httpx.codes.xxx来指代具体的响应码。

import httpx

res = httpx.get(url='http://httpbin.org/get')

print(res.status_code)
if res.status_code == httpx.codes.OK:   #  httpx.codes.OK == 200
    print("OK")

其它一系列的HTTP状态码的名称,可以通过查看codes这个类来获得。部分状态码对应关系如下所示:

# success
OK = 200, "OK"
CREATED = 201, "Created"
ACCEPTED = 202, "Accepted"
NON_AUTHORITATIVE_INFORMATION = 203, "Non-Authoritative Information"
NO_CONTENT = 204, "No Content"
RESET_CONTENT = 205, "Reset Content"
PARTIAL_CONTENT = 206, "Partial Content"
MULTI_STATUS = 207, "Multi-Status"
ALREADY_REPORTED = 208, "Already Reported"
IM_USED = 226, "IM Used"

除此之外,httpx还提供了raise_for_status()方法来引发异常。当状态码是2xx的时候,该方法返回None;而当状态码不是2xx的时候,该方法将抛出异常。例如:

import httpx

res = httpx.get(url='http://httpbin.org/get')
print(res.raise_for_status())

res = httpx.get(url='http://httpbin.org/status/404')
print(res.raise_for_status())   # 返回的状态码是400,抛出异常。

流式响应
对于大型下载,您可能希望使用不会一次将整个响应主体加载到内存中的流式响应。这时候就需要流式响应。我们可以流式响应二进制,文本等。例如:

# 下载wireshark源码
with open('wireshark-3.6.3.tar.xz', 'ab') as fd:
    with httpx.stream("GET", "https://2.na.dl.wireshark.org/src/wireshark-3.6.3.tar.xz") as r:
        for data in r.iter_bytes():
            fd.write(data)
    
print(r.status_code)

对于文本内容,建议使用iter_lines或者iter_text来进行流式响应,而对于二进制文件建议使用iter_bytes进行响应。

httpx还提供了不应用任何 HTTP 内容解码的情况下访问响应中的原始字节的方法iter_raw(),这能方便我们进行一些测试。

如果使用了上述的流式响应,则response.content和response.text属性将不可用,并且在访问时会引发错误。

Cookie
如果要在发送的请求中包含Cookie,那么可以组装一个字段,传递给cookies参数即可。例如:

import httpx

res = httpx.get(url='https://httpbin.org/cookies/set?q=1&w=2')      # 后端设置Cookie
print(res.cookies)

cookies = res.cookies

res = httpx.get(url='https://httpbin.org/cookies', cookies=cookies) # 携带Cookie进行请求
print(res.json())

也可以使用httpx.Cookies()来设置Cookie的属性,然后在传递给cookies参数,例如:

import httpx

cook = httpx.Cookies()
cook.set('cookie_on_domain', 'hello, there!', domain='httpbin.org')
cook.set('cookie_off_domain', 'nope.', domain='example.org')        # 两个domain不一样
res = httpx.get(url='https://httpbin.org/cookies', cookies=cook) # 携带Cookie进行请求httpbin.org
print(res.json())   # 只能访问它本身和父域名的Cookie,除此之外不能访问其它域名下的Cookie。

# 将domain做一些改动,如下所示
cook.set('cookie_on_domain', 'hello, there!', domain='httpbin.org')
cook.set('cookie_off_domain', 'nope.', domain='.org')        # 两个domain不一样
res = httpx.get(url='https://httpbin.org/cookies', cookies=cook) # 携带Cookie进行请求httpbin.org
print(res.json())   # 可以访问父域名的Cookie

重定向
默认情况下,HTTPX不会跟随所有 HTTP 方法的重定向。这点和requests库是不一样的。例如,GitHub 将所有 HTTP 请求重定向到 HTTPS。

import httpx

res = httpx.get("http://github.com/")
print(res.status_code)      # 301,重定向
print(res.history)          # 默认,httpx不会自动跟踪重定向,因此history为空
print(res.next_request)     # 而next_request则是重定向之后的request

### 开启跟踪重定向
res = httpx.get("http://github.com/", follow_redirects=True)        # follow_redirects为True时,跟踪重定向。
print(res.status_code)      # 200,成功
print(res.history)          # 开启了重定向跟踪,history不为空。
print(res.next_request)     # 已经自动重定向过了,因此next_request为空。

开启跟踪重定向只需要将follow_redirects设置为True即可。

超时等待
HTTPX 默认包含所有网络操作的超时时长为5s,我们可以通过timeout参数来修改超时等待时间,也可以将timeout设置为None来完全禁用超时行为。例如:

import httpx

httpx.get('https://github.com/', timeout=0.5)       # 设置超时为0.5s
httpx.get('https://github.com/', timeout=None)      # 完全禁用超时行为

HTTP基本认证
HTTPX 支持基本和摘要 HTTP 身份验证。

基本身份验证凭据,请将纯文本str或bytes对象的 2 元组作为auth参数传递给请求函数。

res = httpx.get("http://httpbin.org/basic-auth/qwe/qwe", auth=("qwe", "qwe"))
print(res.status_code, res.text)

摘要(Digest)身份验证凭据,需要使用httpx.DigestAuth方法将用户名和密码作为参数来实例化一个对象。然后将该对象作为auth参数传递给请求函数。

res = httpx.get("http://httpbin.org/digest-auth/qwe/qwe/qwe", auth=httpx.DigestAuth("qwe", "qwe"))
print(res.status_code, res.text)

参考资料
httpx官方文档

最后修改:2022 年 12 月 22 日
如果觉得我的文章对你有用,请随意赞赏