返回博客
指南
Mihnea-Octavian ManolacheLast updated on Mar 31, 20263 min read

网络爬虫领域排名前三的 Python HTTP 客户端

网络爬虫领域排名前三的 Python HTTP 客户端

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

  • HTTP 客户端的概述
  • 2022年最佳的Python HTTP客户端有哪些
  • 为何 Python 是网络爬虫的绝佳选择
  • 如何实际使用 HTTP 客户端构建网页爬虫

什么是 Python HTTP 客户端以及如何使用它们

为了更深入地理解互联网的通信原理,我们需要熟悉超文本传输协议(HTTP)。不过,今天我们主要关注的是 Python HTTP 客户端,因此我将假设您已经了解 HTTP。

一般而言,HTTP 客户端是指用于与服务器进行通信的实例或程序。例如,网页浏览器即可视为一种 HTTP 客户端。但作为程序员,我们在构建应用程序时很少直接使用浏览器,除非是在开发网页爬虫或进行研究时。

话虽如此,当我们从编程角度谈论 HTTP 客户端时,通常指的是用于执行 HTTP 请求的方法或类实例。鉴于 Python 无疑是最受欢迎的编程语言之一(也是我个人最喜欢的语言),今天我们将探讨最佳的 Python HTTP 客户端,以及如何在实际项目中实现它们。

理解 HTTP 协议

在继续之前,尽管我建议大家查阅 HTTP 文档,但请允许我快速回顾一些基本的 HTTP 概念。首先,HTTP 可能是使用最广泛的互联网协议之一。我们每天都在使用它来在客户端和服务器之间交换信息。 

为了实现这一功能,HTTP 采用了请求方法。这些方法表明客户端希望在服务器上执行的操作。例如,如果你想从服务器获取一些信息,就会使用 GET;如果你想向服务器发送数据,则使用 POST。以下是最常见的 HTTP 请求方法列表:

  • GET - 从服务器获取数据
  • HEAD - 仅获取头部信息,不包含正文(实际数据)
  • POST - 向服务器发送信息
  • PUT - 向服务器发送信息并替换资源的所有当前表示
  • PATCH - 向服务器发送信息并部分修改资源
  • DELETE - 从服务器删除资源

为何选择 Python 进行 HTTP 请求

首先,Python 拥有出色的语法和更强大的社区。因此,它非常适合学习。我自己刚开始编程时,就选择了 Python。事实上,Python HTTP 客户端是我最早接触的技术之一。但这又是另一个话题了。 

本文的目标是确保您不仅能掌握基础理论知识,还能对实际实现有一个全面的了解。 

而 Python 在这两方面都表现出色,原因有很多。仅举几例:

  • 语法——编写 Python 代码就像写英语一样。因此,阅读 Python 脚本有助于你将理论概念与实际实现联系起来。 
  • 支持——Python 拥有庞大的开发者社区。大多数情况下,如果你遇到瓶颈,只需在 StackOverflow 上简单提问,就能找到问题的答案。 
  • 可用性——Python 的包库堪称最庞大之一。例如,仅 Python HTTP 客户端这一类,就有十几个包。但今天我们将重点关注其中最流行的几个。 

3(+1) 最佳 Python HTTP 客户端

在对包进行分类以评选出前三名最佳 Python HTTP 客户端时,我认为这既取决于功能,也取决于个人偏好。因此,准确地说,以下是我个人心目中 Python 的前三名 HTTP 客户端库,而非普遍公认的排名。

1. Requests - 强大而简单

Requests 可能是 Python 社区中最受青睐的 HTTP 客户端之一。我也不例外。每当我测试新的网页爬虫时,都会使用 Python 配合 Requests。它简单得只需输入 .get,却强大得如同真正的网页浏览器。 

Requests 库提供的功能包括:

  • SSL 验证 
  • HTTPS 代理支持
  • Cookie 持久化和会话管理
  • Keep-Alive 功能
  • 自定义身份验证

以上仅是其中一部分。您可以在此处查看完整的功能列表。现在让我向您展示如何使用 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)

让我们看看这里具体做了什么:

  • 我们正在定义 `get_params` 函数,该函数接受一个对象,并将其转换为 URL 参数字符串返回。
  • 我们定义了以下变量: undefinedundefinedundefinedundefinedundefined
  • 我们使用 Requests 库中的 `post` 方法发送 HTTP POST 请求。
  • 我们正在输出响应正文

2. HTTPX - 重新定义 Requests

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 是一个绝佳的选择。 

3. urllib3 - 线程安全的连接

Python 拥有几个“urllib”库,这通常会让新手感到困惑。urllib、urllib2 和 urllib3 之间的主要区别在于各自的功能特性。urllib 是 Python 的原始 HTTP 客户端,包含在 Python 1.2 标准库中。urllib2 是其升级版本,于 Python 1.6 引入,旨在取代原始的 urllib。

然而,urllib3 实际上是一个第三方 Python HTTP 客户端。尽管名称相似,但该库与前两个“前身”并无关联。此外,Python 社区有传言称,目前尚无计划将 urllib3 纳入标准库。至少在近期内不会。 

尽管该包未与 Python 标准库正式关联,但许多开发者仍在使用它,因为它提供了:

  • 线程安全
  • 客户端 SSL/TLS 验证
  • 对 HTTP 和 SOCKS 的代理支持
  • 完整的测试覆盖率

既然我们已经介绍了理论部分,接下来让我们看看实现示例:

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 相比所发现的差异:

  • `http` - `PoolManager` 方法的实例,负责处理线程安全与连接池的细节
  • `encoded_data` - 经过转换的 JSON 字符串,包含我们要发送的有效载荷
  • `r` - 借助 urllib3 发出的实际 POST 请求。这里我们使用了 `PoolManager` 实例的 `request` 方法。

最后,我们需要对请求返回的数据进行解码。如您所见,与 Requests 相比,我们的做法有几点不同。

特别提及:http.client - 传统的 Python HTTP 客户端

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 返回的是编码后的响应。因此,在打印之前,我们实际上需要对其进行解码。

用例:使用 Requests 构建爬虫

既然我们已经掌握了 HTTP 客户端的使用方法,那就给自己安排一个小项目吧。这不仅有助于巩固所学知识,还能为你的编程作品集增添亮点。 

由于 Python HTTP 客户端通常用于从服务器收集信息,因此这些技术的最常见用途就是创建网络爬虫。 因此,接下来我们将重点探讨如何使用 Python 的 HTTP 客户端构建网页爬虫。由于我个人偏爱 requests 库,本项目将采用它。不过,你可以以此为起点,甚至进行调整以使用我们之前讨论过的其他技术。事不宜迟,让我们开始编码:

1. 项目设置

首先,创建一个新目录来存放我们的网页爬虫文件。现在打开一个新的终端窗口,并使用 `cd` 命令进入该目录。在此,我们需要创建一个新的虚拟环境。如果你使用的是类 UNIX 操作系统,可以使用:

~ » python3 -m venv env && source env/bin/activate              

接下来只需创建一个用于存放逻辑代码的新 Python 文件,并在你喜欢的 IDE 中打开它。如果你想使用终端,只需粘贴以下命令:

~ » touch scraper.py && code .                                         

2. 安装依赖项

我们将使用 pip 安装本项目所需的包。 目前我们已确定将使用 Requests,但这对于网络爬虫而言还不够。网络爬虫还涉及数据处理,这意味着我们需要解析从服务器收集到的 HTML。幸运的是,Python 库提供了丰富多样的包。不过,本项目我们将使用 BeautifulSoup。要安装这些包,只需粘贴以下命令:

~ » python3 -m pip install requests bs4                                    

3. 编写逻辑

我们将代码分为两部分:数据提取和数据处理。第一部分由 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 elements

BeautifulSoup 允许我们根据元素的属性提取特定数据。因此,我们在此添加了两个新参数,它们将帮助我们根据元素的属性定位并提取元素。

现在我们已具备所需的一切。剩下的就是将这两个部分结合起来,我们的网页爬虫就完成了。组装好代码后,只需:

  • 创建一个新变量,用于存储通过 Requests 提取的数据 
  • 输出 BeautifulSoup 返回的元素

以下是代码中缺失的两部分:

data = scrape('GET', 'https://webscrapingapi.com')
print( extract_elements(data, 'ul') )

我相信你已经弄清楚了每个部分的功能,此时无需再做解释。就像我们之前的爬虫一样,我挑战你尝试探索 `extract_elements` 函数,让它不仅能返回元素,还能完成更多任务。 

结论

在学习新的编程概念时,我认为最好尝试使用不同的技术进行测试。但在实际构建大型项目的基础架构时,最好先了解每种技术的优缺点,再做出选择。 

无论如何,希望本文对你有所帮助,并让你对 Python HTTP 客户端的工作原理有了扎实的理解。我也鼓励你多加尝试,我相信你终将找到最适合自己的包。

关于作者
Mihnea-Octavian Manolache, 全栈开发工程师 @ WebScrapingAPI
Mihnea-Octavian Manolache全栈开发工程师

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

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。