python爬虫番外篇 | Reuqests库高级用法(2)


在这篇文章中,我们将深入探讨Requests库的高级用法,包括事件钩子、自定义身份验证、流式处理请求、代理使用,以及其他一些强大的特性。

事件钩子(Event Hooks)

Requests库提供了一个强大的钩子系统,允许用户介入请求过程或对信号事件进行处理。钩子可以在请求发出后立即触发,例如响应钩子response

使用钩子

通过将钩子函数传递给请求,可以对请求进行定制化处理。钩子以字典形式传递,如下所示:

hooks = {'response': print_url}

这里的print_url函数将作为响应钩子被调用,打印出响应的URL:

def print_url(r, *args, **kwargs):
    print(r.url)

回调函数必须处理自己的异常。任何未经处理的异常都不会以静默方式传递,因此应由调用 Requests 的代码处理。如果回调函数返回一个值,则假定它是替换传入的数据。如果函数不返回任何事情,其他任何事情都不会受到影响。

def record_hook(r, *args, **kwargs):
    r.hook_called = True
    return r

让我们在运行时打印一些请求方法参数:

requests.get('https://httpbin.org/', hooks={'response': print_url})
https://httpbin.org/
<Response [200]>

可以向单个请求添加多个钩子。让我们一次调用两个钩子:

r = requests.get('https://httpbin.org/', hooks={'response': [print_url, record_hook]})
r.hook_called
True

还可以向实例添加钩子。添加的任何钩子都会在向会议提出的每一项请求时都应被召唤。例如:Session

s = requests.Session()
s.hooks['response'].append(print_url)
s.get('https://httpbin.org/')
 https://httpbin.org/
 <Response [200]>

可以有多个钩子,这些钩子将按顺序调用 它们将被添加。Session

自定义身份验证(Custom Authentication)

Requests支持自定义身份验证机制。通过实现AuthBase类,可以创建特定的认证方式,如下所示的PizzaAuth

from requests.auth import AuthBase

class PizzaAuth(AuthBase):
    def __init__(self, username):
        self.username = username

    def __call__(self, r):
        r.headers['X-Pizza'] = self.username
        return r

使用这种方式,可以轻松地为请求添加自定义的HTTP认证。然后,我们可以使用我们的 Pizza Auth 提出请求:

requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>

流式处理请求(Streaming Requests)

对于需要处理大量数据的API,Requests提供了流式处理功能。通过设置stream=True并使用iter_lines()方法,可以逐行读取响应数据:

import json
import requests

r = requests.get('https://httpbin.org/stream/20', stream=True)

for line in r.iter_lines():

    # filter out keep-alive new lines
    if line:
        decoded_line = line.decode('utf-8')
        print(json.loads(decoded_line))

将 decode_unicode=True 与 或 一起使用时,您将需要 要在服务器未提供回退编码的情况下提供回退编码,请执行以下操作:

r = requests.get('https://httpbin.org/stream/20', stream=True)

if r.encoding is None:
    r.encoding = 'utf-8'

for line in r.iter_lines(decode_unicode=True):
    if line:
        print(json.loads(line))

警告
不是安全的。 多次调用此方法会导致接收到的某些数据迷失了。如果需要从多个地方调用它,请使用生成的迭代器对象:

lines = r.iter_lines()
# Save the first line for later or just skip it

first_line = next(lines)

for line in lines:
    print(line)

代理(Proxies)

Requests允许通过代理发送请求。可以为单个请求设置代理,也可以在Session对象上配置代理,从而影响该会话的所有请求:

import requests

proxies = {
  'http': 'http://10.10.1.10:3128',
  'https': 'http://10.10.1.10:1080',
}

requests.get('http://example.org', proxies=proxies)

或者,可以为整个配置一次:

proxies = {
    'http': 'http://10.10.1.10:3128',
    'https': 'http://10.10.1.10:1080',
}
session = requests.Session()
session.proxies.update(proxies)

警告
设置的行为可能与预期不同。 提供的值将被环境代理覆盖 (由 urllib.request.getproxies 返回的那些)。 为确保在存在环境代理的情况下使用代理, 在所有单个请求上显式指定参数为最初在上面解释过。session.proxies

当代理配置未按请求覆盖时,如上所示, 请求依赖于标准定义的代理配置环境变量。还支持这些变量的大写变体。 因此,可以设置它们来配置请求(仅设置相关的请求 满足您的需求):http_proxy

$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ export ALL_PROXY="socks5://10.10.1.10:3434"

$ python
>>> import requests
>>> requests.get('http://example.org')

要将 HTTP 基本身份验证与代理一起使用,请在上述任何配置条目中使用 http://user:password@host/ 语法:

$ export HTTPS_PROXY="http://user:pass@10.10.1.10:1080"

$ python
>>> proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}

警告
将敏感的用户名和密码信息存储在环境变量或版本控制的文件存在安全风险。

要为特定方案和主机提供代理,请使用密钥的 scheme://hostname 表单。这将匹配对给定方案和确切主机名的任何请求。

proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}

最后,请注意,使用代理进行 https 连接通常需要本地计算机信任代理的根证书。默认情况下,以下列表可以通过以下方式找到受请求信任的证书:

from requests.utils import DEFAULT_CA_BUNDLE_PATH
print(DEFAULT_CA_BUNDLE_PATH)

可以通过将 (or ) 环境变量设置为另一个文件路径来覆盖此默认证书捆绑包:REQUESTS_CA_BUNDLECURL_CA_BUNDLE

$ export REQUESTS_CA_BUNDLE="/usr/local/myproxy_info/cacert.pem"
$ export https_proxy="http://10.10.1.10:1080"

$ python
>>> import requests
>>> requests.get('https://example.org')

SOCKS代理支持

Requests从2.10.0版本开始支持SOCKS代理,这要求安装额外的第三方库。
您可以从以下位置获取此功能的依赖项:pip

$ python -m pip install requests[socks]

一旦安装了这些依赖项,使用 SOCKS 代理就同样简单 使用HTTP的:

proxies = {
    'http': 'socks5://user:pass@host:port',
    'https': 'socks5://user:pass@host:port'
}

使用该方案会导致 DNS 解析在客户端上进行,而不是在代理服务器上进行。这与 curl 一致,curl 使用该方案来决定是在客户端还是代理上执行 DNS 解析。如果要解析代理服务器上的域,请使用 as the scheme。socks5

编码(Encodings)

Requests 库在接收到响应时会尝试猜测适当的编码来解码响应内容。它首先检查 HTTP 头部中是否指定了编码,如果没有找到,它将使用 charset_normalizerchardet 库来尝试确定正确的编码。

# Requests 会检查 HTTP 头部中是否指定了编码
if r.encoding is None:
    # 如果没有找到编码,Requests 会尝试猜测编码
    r.encoding = 'utf-8'

HTTP 动词(HTTP Verbs)

Requests 支持多种 HTTP 动词,允许执行各种类型的 HTTP 请求。以下是使用 GET、POST、OPTIONS 等动词的示例:

# 使用 GET 请求获取资源
r = requests.get('https://api.github.com/repos/psf/requests/git/commits/a050faf')

# 使用 POST 请求提交数据
r = requests.post('https://api.github.com/repos/psf/requests/issues/482/comments', data=body)

# 使用 PATCH 请求更新资源
r = requests.patch('https://api.github.com/repos/psf/requests/issues/comments/5804413', data=body)

# 使用 DELETE 请求删除资源
r = requests.delete('https://api.github.com/repos/psf/requests/issues/comments/5804413')

我们应该确认 GitHub 的响应是正确的。如果有,我们想知道它是什么类型的内容。像这样做:

import requests
r = requests.get('https://api.github.com/repos/psf/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
if r.status_code == requests.codes.ok:
    print(r.headers['content-type'])

application/json; charset=utf-8

因此,GitHub 返回 JSON。这很好,我们可以使用该方法将其解析为 Python 对象。

commit_data = r.json()

print(commit_data.keys())
['committer', 'author', 'url', 'tree', 'sha', 'parents', 'message']

print(commit_data['committer'])
{'date': '2012-05-10T11:10:50-07:00', 'email': 'me@kennethreitz.com', 'name': 'Kenneth Reitz'}

print(commit_data['message'])
makin' history

到目前为止,就是这么简单。好吧,让我们稍微研究一下 GitHub API。现在我们可以查看文档,但如果我们这样做,我们可能会有更多的乐趣。我们可以利用 Requests OPTIONS 动词来 看看我们刚才使用的 url 支持哪些类型的 HTTP 方法。

verbs = requests.options(r.url)
verbs.status_code
500

呃,什么?这是无济于事的!事实证明,GitHub 和许多 API 提供商一样,没有实际实现 OPTIONS 方法。这是一个令人讨厌的疏忽,但好吧,我们可以使用无聊的文档。如果 GitHub 正确实现了 OPTIONS,但是,它们应返回标题,例如

verbs = requests.options('http://a-good-website.com/api/cats')
print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS

转到文档中,我们看到唯一允许的其他方法 commits 的数据类型为POST,它将创建一个新的提交。当我们使用 Requests 存储库时, 我们可能应该避免对它进行笨拙的操作。相反,让我们使用 GitHub 的“问题”功能。

添加此文档是为了响应问题。鉴于这个问题已经存在,我们将以它为例。让我们从获取它开始。

r = requests.get('https://api.github.com/repos/psf/requests/issues/482')
r.status_code
200

issue = json.loads(r.text)

print(issue['title'])
Feature any http verb in docs

print(issue['comments'])
3

很酷,我们有三个评论。让我们来看看其中的最后一个。

r = requests.get(r.url + '/comments')
r.status_code
200

comments = r.json()

print(comments[0].keys())
['body', 'url', 'created_at', 'updated_at', 'user', 'id']

print(comments[2]['body'])
Probably in the "advanced" section

我们可能需要进行身份验证。那会很痛苦,对吧?通过“请求”,可以轻松使用多种形式的身份验证,包括 非常常见的基本身份验证。

from requests.auth import HTTPBasicAuth
auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')

r = requests.post(url=url, data=body, auth=auth)
r.status_code
201

content = r.json()
print(content['body'])
Sounds great! I'll get right on it.

我想知道的最后一件事是我的速率限制有多少。让我们来了解一下。GitHub 在标头中发送该信息,因此我不会下载整个页面,而是发送一个 HEAD 请求来获取头。

r = requests.head(url=url, auth=auth)
print(r.headers)

'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...

自定义动词(Custom Verbs)

如果需要使用非标准 HTTP 动词,可以使用 request 方法直接指定:

# 使用自定义 HTTP 动词 MKCOL
r = requests.request('MKCOL', url, data=data)
r.status_code
200 # Assuming your call was correct

链接头(Link Headers)

Requests 能够解析 HTTP 链接头,并提供了一个易于使用的接口来访问它们:

# GitHub 使用链接头进行分页
url = 'https://api.github.com/users/kennethreitz/repos'
r = requests.head(url)
print(r.links["next"])  # {'url': 'https://api.github.com/users/kennethreitz/repos?page=2', 'rel': 'next'}

请求将自动解析这些链接头,并使它们易于使用:

r.links["next"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'}

r.links["last"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}

传输适配器(Transport Adapters)

Requests 从 v1.0.0 版本开始采用模块化设计,引入了传输适配器的概念。这些适配器允许我们为特定的服务配置特定的行为:

# 创建一个自定义的 SSL 版本传输适配器
class Ssl3HttpAdapter(HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        kwargs['ssl_version'] = ssl.PROTOCOL_SSLv3
        self.poolmanager = PoolManager(*args, **kwargs)

# 使用自定义适配器
s = requests.Session()
s.mount('https://', Ssl3HttpAdapter())

Requests 团队已做出特定选择,使用任何 SSL 版本底层库 (urllib3) 中的默认值。通常这很好,但是有时,你可能会发现自己需要连接到服务终结点使用的版本与默认版本不兼容。

为此,可以使用传输适配器,方法是获取大部分现有的实现 HTTPAdapter,并添加一个参数ssl_version获取 传递到 URLLIB3。我们将制作一个传输适配器,用于指示使用 SSLv3 的库:

import ssl
from urllib3.poolmanager import PoolManager

from requests.adapters import HTTPAdapter


class Ssl3HttpAdapter(HTTPAdapter):
    """"Transport adapter" that allows us to use SSLv3."""

    def init_poolmanager(self, connections, maxsize, block=False):
        self.poolmanager = PoolManager(
            num_pools=connections, maxsize=maxsize,
            block=block, ssl_version=ssl.PROTOCOL_SSLv3)

自动重试(Automatic Retries)

Requests 允许我们实现自动重试机制,以增强连接的鲁棒性:

# 实现自动重试
from urllib3.util import Retry
from requests.adapters import HTTPAdapter

retries = Retry(
    total=3,
    backoff_factor=0.1,
    status_forcelist=[502, 503, 504],
    allowed_methods={'POST'},
)
s = requests.Session()
s.mount('https://', HTTPAdapter(max_retries=retries))

超时设置(Timeouts)

设置超时是避免请求无限挂起的好习惯。对外部服务器的大多数请求都应该附加超时,以防服务器未及时响应。默认情况下,请求不会计时 out 除非显式设置了超时值。如果没有超时,代码可能会挂起几分钟或更长时间。

连接超时是请求等待秒数 client 在套接字上建立与远程机器(对应于 connect()) 调用的连接。设置连接超时是一种很好的做法略大于 3 的倍数,这是默认的 TCP 数据包重传窗口。

一旦客户端连接到服务器并发送了 HTTP 请求,读取超时就是客户端等待服务器的秒数发送响应。(具体来说,它是客户端的秒数 将在从服务器发送的字节之间等待。在 99.9% 的情况下,这是服务器发送第一个字节之前的时间)。可以为连接和读取分别设置超时时间:

# 设置超时
r = requests.get('https://github.com', timeout=5)

如果要单独设置值,请指定一个元组:connectread

r = requests.get('https://github.com', timeout=(3.05, 27))

如果远程服务器速度非常慢,可以告诉请求永远等待响应,通过将 None 作为超时值传递

r = requests.get('https://github.com', timeout=None)

注意
连接超时适用于每次尝试连接到 IP 地址。 如果一个域名存在多个地址,则底层将 按顺序尝试每个地址,直到一个地址成功连接。 这可能会导致有效的总连接超时时间延长数倍超过指定时间,例如,同时具有 IPv4 和 IPv6 的无响应服务器地址的感知超时将增加一倍,因此请考虑到这一点设置连接超时。urllib3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值