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中进行参数传递,只需要将代传递的值组装为一个字典,然后传递给相应方法的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参数即可。例如:
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)
同时,httpx还提供了iter_text()和iter_lines()来进行流式的文本内容返回以及单行的文本内容返回。可以使用下面的代码进行测试:
import httpx
# 流式的文本内容返回
with httpx.stream("GET", "https://www.python-httpx.org/quickstart/#streaming-responses") as r:
for data in r.iter_text():
print('-----------------------------------------------------------------------------------------')
print(data)
# 单行的文本内容返回
with httpx.stream("GET", "https://www.python-httpx.org/quickstart/#streaming-responses") as r:
for data in r.iter_lines():
print('-----------------------------------------------------------------------------------------')
print(data)
对于文本内容,建议使用iter_lines或者iter_text来进行流式响应,而对于二进制文件建议使用iter_bytes进行响应。
httpx还提供了不应用任何 HTTP 内容解码的情况下访问响应中的原始字节的方法iter_raw(),这能方便我们进行一些测试。
如果使用了上述的流式响应,则response.content和response.text属性将不可用,并且在访问时会引发错误。
如果要在发送的请求中包含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) # 完全禁用超时行为
HTTPX 支持基本和摘要 HTTP 身份验证。
res = httpx.get("http://httpbin.org/basic-auth/qwe/qwe", auth=("qwe", "qwe"))
print(res.status_code, res.text)
res = httpx.get("http://httpbin.org/digest-auth/qwe/qwe/qwe", auth=httpx.DigestAuth("qwe", "qwe"))
print(res.status_code, res.text)