说到网页抓取,Python 拥有种类繁多的 HTTP 客户端,这使得 Python 成为最受欢迎的选择之一。但这些 HTTP 客户端究竟是什么?又该如何利用它们来构建网页抓取工具呢?在今天的文章中,我们将深入探讨这一话题。读完本文后,您将对以下内容有清晰的了解:
- HTTP 客户端的概述
- 2022年最佳的Python HTTP客户端有哪些
- 为何 Python 是网络爬虫的绝佳选择
- 如何实际使用 HTTP 客户端构建网页爬虫

说到网页抓取,Python 拥有种类繁多的 HTTP 客户端,这使得 Python 成为最受欢迎的选择之一。但这些 HTTP 客户端究竟是什么?又该如何利用它们来构建网页抓取工具呢?在今天的文章中,我们将深入探讨这一话题。读完本文后,您将对以下内容有清晰的了解:
为了更深入地理解互联网的通信原理,我们需要熟悉超文本传输协议(HTTP)。不过,今天我们主要关注的是 Python HTTP 客户端,因此我将假设您已经了解 HTTP。
一般而言,HTTP 客户端是指用于与服务器进行通信的实例或程序。例如,网页浏览器即可视为一种 HTTP 客户端。但作为程序员,我们在构建应用程序时很少直接使用浏览器,除非是在开发网页爬虫或进行研究时。
话虽如此,当我们从编程角度谈论 HTTP 客户端时,通常指的是用于执行 HTTP 请求的方法或类实例。鉴于 Python 无疑是最受欢迎的编程语言之一(也是我个人最喜欢的语言),今天我们将探讨最佳的 Python HTTP 客户端,以及如何在实际项目中实现它们。
在继续之前,尽管我建议大家查阅 HTTP 文档,但请允许我快速回顾一些基本的 HTTP 概念。首先,HTTP 可能是使用最广泛的互联网协议之一。我们每天都在使用它来在客户端和服务器之间交换信息。
为了实现这一功能,HTTP 采用了请求方法。这些方法表明客户端希望在服务器上执行的操作。例如,如果你想从服务器获取一些信息,就会使用 GET;如果你想向服务器发送数据,则使用 POST。以下是最常见的 HTTP 请求方法列表:
首先,Python 拥有出色的语法和更强大的社区。因此,它非常适合学习。我自己刚开始编程时,就选择了 Python。事实上,Python HTTP 客户端是我最早接触的技术之一。但这又是另一个话题了。
本文的目标是确保您不仅能掌握基础理论知识,还能对实际实现有一个全面的了解。
而 Python 在这两方面都表现出色,原因有很多。仅举几例:
在对包进行分类以评选出前三名最佳 Python HTTP 客户端时,我认为这既取决于功能,也取决于个人偏好。因此,准确地说,以下是我个人心目中 Python 的前三名 HTTP 客户端库,而非普遍公认的排名。
Requests 可能是 Python 社区中最受青睐的 HTTP 客户端之一。我也不例外。每当我测试新的网页爬虫时,都会使用 Python 配合 Requests。它简单得只需输入 .get,却强大得如同真正的网页浏览器。
Requests 库提供的功能包括:
以上仅是其中一部分。您可以在此处查看完整的功能列表。现在让我向您展示如何使用 requests:
import requests
r = requests.get("http://google.com")print(r.test)
如您所见,仅需 3 行代码,requests 库就能帮助我们从服务器获取原始 HTML 内容。在上例中,我们向服务器发送了一个 GET 请求,并输出结果。但正如我所说,该库的功能远不止于此。让我们构建一个更复杂的示例,其中将使用代理和 POST 请求等功能:
import requests
def get_params(object):
params = ''
for key,value in object.items():
if list(object).index(key) < len(object) - 1:
params += f"{key}={value}."
else:
params += f"{key}={value}"
return params
API_KEY = '<YOUR_API_KEY>'
TARGET_URL = 'https://httpbin.org/post'
DATA = {"foo":"bar"}
PARAMETERS = {
"proxy_type":"datacenter",
"device":"desktop"
}
PROXY = {
"http": f"http://webscrapingapi.{ get_params(PARAMETERS) }:{ API_KEY }@proxy.webscrapingapi.com:80",
"https": f"https://webscrapingapi.{ get_params(PARAMETERS) }:{ API_KEY }@proxy.webscrapingapi.com:8000"
}
response = requests.post(
url=TARGET_URL,
data=DATA,
proxies=PROXY,
verify=False
)
print(response.text)让我们看看这里具体做了什么:
HTTPX 问世时间相对较短。然而,在极短的时间内,它已成为最受推荐的 Python HTTP 客户端之一。例如,Flask(Python 最大的 Web 框架之一)在其官方文档中推荐使用 HTTPX。
我之所以说 HTTPX 是 Requests 的重构版,是因为这两个库在语法上非常相似。事实上,HTTPX 旨在与 Requests 完全兼容。两者之间仅有少数细微的设计差异,本文将重点介绍这些差异。
以下是 HTTPX 中一个基本的 POST 请求示例:
import httpx
TARGET_URL = 'https://httpbin.org/post'
DATA = {"foo":"bar"}
r = httpx.post(
url=TARGET_URL,
data=DATA,
)
print(r.text)如您所见,与 Requests 的示例相比,我们主要更改的是包名。既然两者如此相似,问题依然存在:为何要选择 HTTPX 而不是 requests?首先,HTTPX 是少数几个提供异步支持的 Python HTTP 客户端之一。总而言之,如果您想重构基于 requests 的代码,HTTPX 是一个绝佳的选择。
Python 拥有几个“urllib”库,这通常会让新手感到困惑。urllib、urllib2 和 urllib3 之间的主要区别在于各自的功能特性。urllib 是 Python 的原始 HTTP 客户端,包含在 Python 1.2 标准库中。urllib2 是其升级版本,于 Python 1.6 引入,旨在取代原始的 urllib。
然而,urllib3 实际上是一个第三方 Python HTTP 客户端。尽管名称相似,但该库与前两个“前身”并无关联。此外,Python 社区有传言称,目前尚无计划将 urllib3 纳入标准库。至少在近期内不会。
尽管该包未与 Python 标准库正式关联,但许多开发者仍在使用它,因为它提供了:
既然我们已经介绍了理论部分,接下来让我们看看实现示例:
import urllib3,json
TARGET_URL = 'https://httpbin.org/post'
DATA = {"foo":"bar"}
http = urllib3.PoolManager()
encoded_data = json.dumps(DATA)
r = http.request('POST', TARGET_URL, body=encoded_data)print(r.data.decode('utf-8'))
让我们讨论一下 urllib3 与 Requests 相比所发现的差异:
最后,我们需要对请求返回的数据进行解码。如您所见,与 Requests 相比,我们的做法有几点不同。
http.client 也是 Python 标准库的一部分。传统上,程序员通常不会直接使用它。例如,urllib 实际上将其作为依赖项,用于处理 HTTP 和 HTTPS 请求。我将其纳入我们的排名,是因为我认为作为程序员,了解我们所用包的“骨架”是有益的。
因此,即使您可能不会实际使用 http.client 来创建项目,这里仍提供一个实现示例,相信它将帮助您更好地理解 Python HTTP 客户端的工作原理:
import http.client
TARGET_URL = 'www.httpbin.org'
http = http.client.HTTPSConnection(TARGET_URL)
http.request("GET", "/get")
r = http.getresponse()print(r.read().decode('utf-8'))
`HTTPSConnection` 实例需要几个参数,你可以在这里查看。在我们的示例中,我们只定义了 `method` 和 `url`(或者更准确地说,是端点)。此外,与 urllib3 类似,http.client 返回的是编码后的响应。因此,在打印之前,我们实际上需要对其进行解码。
既然我们已经掌握了 HTTP 客户端的使用方法,那就给自己安排一个小项目吧。这不仅有助于巩固所学知识,还能为你的编程作品集增添亮点。
由于 Python HTTP 客户端通常用于从服务器收集信息,因此这些技术的最常见用途就是创建网络爬虫。 因此,接下来我们将重点探讨如何使用 Python 的 HTTP 客户端构建网页爬虫。由于我个人偏爱 requests 库,本项目将采用它。不过,你可以以此为起点,甚至进行调整以使用我们之前讨论过的其他技术。事不宜迟,让我们开始编码:
首先,创建一个新目录来存放我们的网页爬虫文件。现在打开一个新的终端窗口,并使用 `cd` 命令进入该目录。在此,我们需要创建一个新的虚拟环境。如果你使用的是类 UNIX 操作系统,可以使用:
~ » python3 -m venv env && source env/bin/activate
接下来只需创建一个用于存放逻辑代码的新 Python 文件,并在你喜欢的 IDE 中打开它。如果你想使用终端,只需粘贴以下命令:
~ » touch scraper.py && code .
我们将使用 pip 安装本项目所需的包。 目前我们已确定将使用 Requests,但这对于网络爬虫而言还不够。网络爬虫还涉及数据处理,这意味着我们需要解析从服务器收集到的 HTML。幸运的是,Python 库提供了丰富多样的包。不过,本项目我们将使用 BeautifulSoup。要安装这些包,只需粘贴以下命令:
~ » python3 -m pip install requests bs4
我们将代码分为两部分:数据提取和数据处理。第一部分由 Requests 包负责,第二部分则由 BeautifulSoup 负责。事不宜迟,让我们直接开始编码,首先从提取部分入手:
import requests
def scrape( url = None ):
# if there is no URL, there is no need to use Python HTTP clients
# We will print a message and stop execution
if url == None:
print('[!] Please add a target!')
return
response = requests.get( url )
return response在本节中,我们将定义一个仅有一个参数的函数:目标 URL。如果未提供 URL,我们将打印一条消息并停止执行。否则,我们将使用 Requests 的 get 方法来获取响应。现在,我们知道 Python HTTP 客户端支持更多方法,因此让我们添加一个条件参数:
import requests
def scrape( method = 'get', url = None, data = None ):
# if there is no URL, there is no need to use Python HTTP clients
# We will print a message and stop execution
if url == None:
print('[!] Please add a target!')
return
if method.lower() == 'get':
response = requests.get( url )
elif method.lower() == 'post':
if data == None:
print('[!] Please add a payload to your POST request!')
return
response = requests.post( url, data )
return response如您所见,我们在函数中添加了几个参数。`method` 参数指定请求应使用的方法。`data` 代表随 POST 请求发送的有效载荷。默认情况下,方法为 GET,因此 `method` 参数并非必需。
挑战:向该函数添加更多方法,从而增强爬虫的功能。这不仅有趣,还是一种很好的学习方式。此外,你可以将代码据为己有,将其添加到个人作品集中。
到目前为止,我们已经介绍了数据提取。接下来让我们解析 HTML 并对其进行处理:
from bs4 import BeautifulSoup
def extract_elements(data = None, el = None):
if data == None:
print('[!] Please add some data!')
return
if el == None:
print('[!] Please specify which elements you are targeting!')
return
soup = BeautifulSoup(data.text, 'html.parser')
elements = soup.find_all(el)
return elements但一个网页爬虫应该能够提取更具体的数据。例如,它应该能够根据 CSS 选择器定位并返回元素。因此,让我们添加处理这一部分的逻辑:
from bs4 import BeautifulSoup
def extract_elements(data = None, el = None, attr = None, attr_value = None):
if data == None:
print('[!] Please add some data!')
return
if el == None:
print('[!] Please specify which elements you are targeting!')
return
soup = BeautifulSoup(data.text, 'html.parser')
elements = soup.find_all(el, { attr : attr_value })
return elementsBeautifulSoup 允许我们根据元素的属性提取特定数据。因此,我们在此添加了两个新参数,它们将帮助我们根据元素的属性定位并提取元素。
现在我们已具备所需的一切。剩下的就是将这两个部分结合起来,我们的网页爬虫就完成了。组装好代码后,只需:
以下是代码中缺失的两部分:
data = scrape('GET', 'https://webscrapingapi.com')print( extract_elements(data, 'ul') )
我相信你已经弄清楚了每个部分的功能,此时无需再做解释。就像我们之前的爬虫一样,我挑战你尝试探索 `extract_elements` 函数,让它不仅能返回元素,还能完成更多任务。
在学习新的编程概念时,我认为最好尝试使用不同的技术进行测试。但在实际构建大型项目的基础架构时,最好先了解每种技术的优缺点,再做出选择。
无论如何,希望本文对你有所帮助,并让你对 Python HTTP 客户端的工作原理有了扎实的理解。我也鼓励你多加尝试,我相信你终将找到最适合自己的包。

Mihnea-Octavian Manolache 是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。