使用urllib3在Python中发送HTTP请求的指南

1,381 阅读3分钟

简介

网络上的资源位于某种_网络地址_之下(即使它们无法访问),通常被称为 URL(统一资源定位器.在大多数情况下,这些资源是由终端用户使用.NET来操作的(检索、更新、删除等)。 HTTP协议通过各自的 HTTP方法.

在本指南中,我们将看看如何利用urllib3 库,它允许我们通过 Python 以编程方式发送 HTTP 请求。

注意: urllib3 模块只能在 Python 3.x 中使用。

什么是HTTP?

HTTP (HyperText Transfer Protocol)是一个数据传输协议,通常用于传输超媒体文件,如HTML,但也可用于传输JSON、XML或类似格式。它被应用于OSI模型应用层,与其他协议一起,如 FTP (文件传输协议)SMTP (简单邮件传输协议).

HTTP是我们今天所知的万维网的支柱,它的主要任务是通过HTTP请求HTTP响应的生命周期,在网络_浏览器_和_网络服务器之间_建立一个通信渠道,这是HTTP的基本通信组件。

它是基于客户**-服务器**模型,_客户请求_一个资源,而_服务器响应_资源--或不响应。

一个典型的_HTTP请求_可能看起来像。

GET /tag/java/ HTTP/1.1
Host: stackabuse.com
Accept: */*
User-Agent: Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail Firefox/firefoxversion

如果服务器_找到了_资源,_HTTP响应的标题_将包含关于请求/响应周期如何的数据。

HTTP/1.1 200 OK
Date: Thu, 22 Jul 2021 18:16:38 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
...

而_响应体将_包含实际的资源--在这种情况下是一个HTML页面。

<!DOCTYPE html>
<html lang="en">
   <head>
      <meta name="twitter:title" content="Stack Abuse"/>
      <meta name="twitter:description" content="Learn Python, Java, JavaScript/Node, Machine Learning, and Web Development through articles, code examples, and tutorials for developers of all skill levels."/>
      <meta name="twitter:url" content="https://stackabuse.com"/>
      <meta name="twitter:site" content="@StackAbuse"/>
      <meta name="next-head-count" content="16"/>
   </head>
...

_urllib3_模块

urllib3 模块是为 Python 开发的最新的 HTTP 相关模块,是urllib2 的继承者。它支持多部分编码的文件上传、gzip、连接池和线程安全。它通常预装在Python 3.x中,但如果你不是这种情况,它可以很容易地安装到。

$ pip install urllib3

你可以通过访问该模块的__version__ ,检查你的版本urllib3

import urllib3

# This tutorial is done with urllib3 version 1.25.8
print(urrlib3.__version__)

另外,你也可以使用_Requests_模块,它建立在 urllib3 的基础上。它更直观,更以人为本,并允许更广泛的HTTP请求。如果你想阅读更多关于它的信息--请阅读我们的《Python 中的 Requests 模块指南》

HTTP 状态代码

每当一个HTTP请求被发送时--响应,除了所请求的资源(如果可用和可访问),还包含一个HTTP状态代码,表示操作的情况。最重要的是,你要知道你得到的状态代码意味着什么,或者至少它大致上意味着什么。

_是否有问题?如_果有,是由于请求、服务器还是我的原因?

有_五组_不同的_响应代码_。

  1. 信息性代码(在100和199之间
  2. 成功代码(在200和299之间)--_200_是最常见的一个
  3. 重定向代码(在300和399之间
  4. 客户端错误代码(介于400和499之间)--_404_是最常见的一个。
  5. 服务器错误代码(在500和599之间)--_500_是最常见的。

为了使用urllib3 发送请求,我们使用PoolManager 类的一个实例,它为我们处理实际的请求--很快就会涉及。

对这些请求的所有响应都被打包到一个HTTPResponse 实例中,其中自然包括该响应的status

import urllib3 

http = urllib3.PoolManager()

response = http.request("GET", "http://www.stackabuse.com")
print(response.status) # Prints 200

你可以使用这些状态来改变代码的逻辑--如果结果是200 OK ,可能不需要进一步做什么。然而,如果结果是405 Method Not Allowed 响应--你的请求可能构造得很糟糕。

然而,如果一个网站响应的状态代码是418 I'm a teapot ,尽管很罕见--它在让你知道你不能用茶壶冲咖啡。在实践中,这通常意味着服务器_不想_响应这个请求,而且永远不会。如果是对某些请求的暂时停止--503 Service Unavailable 状态代码就更合适了。

注意: 418 I'm a teapot 状态代码是一个真实的但却很好玩的状态代码,是作为愚人节的玩笑添加的。

连接池管理器

_连接池_是一个连接的缓存,在未来的请求中需要时可以重复使用,在无数次执行某些命令时用于提高性能。同样地--在发送各种请求时,_连接池_也是如此,以便某些连接可以被重复使用。

urllib3 通过ConnectionPoolHTTPConnection 类来跟踪请求和它们的连接。由于手工制作这些会导致大量的模板代码--我们可以将全部的逻辑委托给PoolManager ,它可以自动创建连接并将它们添加到池中。通过调整num_pools 参数,我们可以设置它将使用的池的数量。

import urllib3

http = urllib3.PoolManager(num_pools=3)

response1 = http.request("GET", "http://www.stackabuse.com")
response2 = http.request("GET", "http://www.google.com")

只有_通过_ PoolManager ,我们才能发送一个request() ,传入HTTP动词和我们要发送请求的地址。不同的_动词_标志着不同的_意图_--你是否想GET 一些内容,POST 到一个服务器,PATCH 一个现有的资源或DELETE 一个。

如何使用_urllib3_在 Python 中发送 HTTP 请求

最后,让我们看一下如何通过urllib3 发送不同的请求类型,以及如何解释返回的数据。

发送 HTTP GET 请求

当客户端请求从服务器上获取数据时,就会使用_HTTP GET_请求,而不会以任何方式、形状或形式对其进行修改。

要在Python中发送一个_HTTP GET_请求,我们使用PoolManager 实例的request() 方法,传入适当的HTTP verb和我们要发送请求的资源。

import urllib3

http = urllib3.PoolManager()

response = http.request("GET", "http://jsonplaceholder.typicode.com/posts/")

print(response.data.decode("utf-8"))

在这里,我们发送了一个_GET请求_到 {JSON}占位符.这是一个网站,生成假的JSON数据,在响应的主体中发送回来。通常情况下,该网站被用来测试HTTP请求,存根响应。

HTTPResponse 实例,即我们的response 对象持有响应的主体。它可以通过data 属性访问,该属性是一个bytes 流。由于一个网站可能会使用我们不适合的编码进行响应,并且由于我们无论如何都要将bytes 转换为str - 我们decode() ,并将其编码为UTF-8,以确保我们可以连贯地解析数据。

如果你想阅读更多内容,请阅读我们关于在Python中转换字节到字符串的指南。

最后,我们打印响应的正文。

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
...

发送带参数的 HTTP GET 请求

我们很少_不在_请求中添加某些参数。路径变量和请求参数是非常常见的,它们可以实现动态链接结构和组织资源。例如--我们可能想通过一个API搜索某个帖子的_特定_评论--http://random.com/posts/get?id=1&commentId=1

当然,urllib3 允许我们通过fields 参数向_GET_请求添加参数。它接受一个参数名称和它们的值的字典。

import urllib3 

http = urllib3.PoolManager()

response = http.request("GET",
                        "http://jsonplaceholder.typicode.com/posts/", 
                        fields={"id": "1"})

print(response.data.decode("utf-8"))

这将只返回一个对象,id1

[
	{
  		"userId": 1,
  		"id": 1,
  		"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  		"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas 				totam\nnostrum rerum est autem sunt rem eveniet architecto"
	}
]

HTTP POST请求

_HTTP POST_请求用于从客户端向服务器端发送数据。它最常见的用法是文件上传或表格填写,但也可以用来发送_任何_数据到服务器,并带有有效载荷。

import urllib3

http = urllib3.PoolManager()
response = http.request("POST", "http://jsonplaceholder.typicode.com/posts", fields={"title": "Created Post", "body": "Lorem ipsum", "userId": 5})

print(response.data.decode("utf-8"))

尽管我们是在与同一个网络地址通信,但由于我们是在发送一个POST 请求,fields 参数现在将指定要_发送到_服务器的数据,而不是检索。

我们已经发送了一个JSON字符串,表示一个具有titlebodyuserId 的对象。{JSON}占位符_服务也存根了_添加实体_的功能,所以它返回一个响应,让我们知道我们是否已经能够 "添加 "到数据库,并返回 "创建 "帖子的_ID

{
  "id": 101
}

HTTP DELETE请求

最后,为了发送_HTTP DELETE_请求,我们只需将动词修改为"DELETE" ,并通过其id ,以特定的帖子为目标。让我们删除所有具有ids的帖子1..5

import urllib3

http = urllib3.PoolManager()
for i in range(1, 5):
    response = http.request("DELETE", "http://jsonplaceholder.typicode.com/posts", fields={"id": i})
    print(response.data.decode("utf-8"))

由于资源被删除,所以会返回一个空的主体。

{}
{}
{}
{}

当创建一个REST API时--你可能想给出一些状态代码和消息,让用户知道一个资源已经被成功删除。

发送HTTP PATCH请求

虽然我们可以使用POST 请求来更新资源,但如果我们保持POST 请求只用于_创建_资源,这被认为是一个好的做法。相反,我们可以发射一个PATCH 请求来更新一个现有的资源。

让我们得到第一个帖子,然后用一个新的titlebody 来更新它。

import urllib3

data = {
    'title': 'Updated title',
    'body': 'Updated body'
}

http = urllib3.PoolManager()

response = http.request("GET", "http://jsonplaceholder.typicode.com/posts/1")
print(response.data.decode('utf-8'))

response = http.request("PATCH", "https://jsonplaceholder.typicode.com/posts/1", fields=data)
print(response.data.decode('utf-8'))

这样做的结果应该是。

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
{
  "userId": 1,
  "id": 1,
  "title": "Updated title",
  "body": "Updated body"
}

使用_urllib3_在 Python 中发送安全 HTTPS 请求

urllib3 模块还为安全的HTTP连接提供了客户端的SSL验证。我们可以在另一个模块的帮助下实现这一点,这个模块叫做certifi ,它提供了标准的 Mozilla 证书包。

它的安装非常简单,通过pip

$ pip install certifi

通过certifi.where() ,我们引用了已安装的 证书颁发机构(CA).这是一个颁发数字证书的实体,可以被信任。所有这些可信的证书都包含在certifi 模块中。

import urllib3
import certifi

http = urllib3.PoolManager(ca_certs=certifi.where())
response = http.request("GET", "https://httpbin.org/get")

print(response.status)

现在,我们可以向服务器发送一个_安全_请求。

使用 urllib3 上传文件

使用urllib3 ,我们也可以向服务器_上传文件_。要上传文件,我们将数据编码为multipart/form-data ,并将文件名及其内容作为file_name: file_data 的一个元组传入。

要读取一个文件的内容,我们可以使用Python内置的read() 方法。

import urllib3
import json

with open("file_name.txt") as f:
    file_data = f.read()

# Sending the request.
resp = urllib3.request(
    "POST",
    "https://reqbin.com/post-online",
    fields= {
       "file": ("file_name.txt", file_data),
    }
)

print(json.loads(resp.data.decode("utf-8"))["files"])

为了这个例子的目的,让我们创建一个名为file_name.txt 的文件并添加一些内容。

Some file data
And some more

现在,当我们运行这个脚本时,它应该打印出来。

{'file': 'Some file data\nAnd some more'}

当我们使用urllib3 发送文件时,响应的data 包含一个附加在它上面的"files" 属性,我们通过resp.data.decode("utf-8")["files"] 访问它。为了使输出更易读,我们使用json 模块来加载响应并将其显示为一个字符串。

你也可以为元组提供第三个参数,它指定了上传文件的MIME类型。

... previous code
fields={
  "file": ("file_name.txt", file_data, "text/plain"),
}

总结

在本指南中,我们已经看了如何使用urllib3 发送 HTTP 请求,这是一个强大的 Python 模块,用于处理 HTTP 请求和响应。

我们还看了什么是HTTP,期望的状态码是什么,如何解释它们,以及如何使用certifi 上传文件和发送安全请求。