in

如何重试失败的Python请求?

如何重试失败的Python请求

Python的requests库被广泛用于各种用途,其中最常见的是网页抓取。与内置的HTTP请求库(urllib)相比,requests库更加简化,使得与服务器和网站的连接变得更加容易。

网页抓取项目通常使用requests库,因为它既简单又高效。此外,它也更容易进行故障排除,这在抓取过程中频繁出现请求失败时尤为有用。


开始使用Requests库

本指南假设你已经具备一些Python基础知识并使用了一个IDE。一旦这些准备工作就绪,你需要安装requests库。(本文中我们以IPRoyal为例)

pip install requests

你的IDE应该会自动下载、解压并安装requests库,然后你就可以用它来发送请求了。

首先,你需要像使用其他Python库一样导入它。

import requests

发送请求很简单,因为它只是对“get”方法(或您需要的任何其他方法)的简单调用。

import requests

def send_get_request(URL):
    r = requests.get(URL)
    print(r.status_code)

send_get_request('https://iproyal.com')

打印响应的状态码稍后将非常重要,因为这些代码可以作为优化失败请求的指示器。您可以通过点击右上角的绿色箭头(对于PyCharm)来测试它。

axcmORO.jpeg


失败请求的响应类型

所有成功和失败的服务器连接尝试都会返回某种形式的HTTP状态码。我们将忽略成功的状态码,因为你不需要重试它们。

403 Forbidden(禁止访问)

你的目标服务器理解了请求,但没有适当响应,因为你没有权限访问该文档(或整个服务器)。这些问题通常难以解决,因为403状态码通常在你需要凭证或已被禁止访问时返回。

如果你有凭证,可以将它们包含在你的GET请求中。

import requests

def send_get_request(URL, credentials):
    r = requests.get(URL, auth=credentials)
    print(r.status_code)

login_details = ('username', 'password')
send_get_request('https://iproyal.com', login_details)

将“login_details”对象的值替换为您的用户名和密码应该允许您访问受保护的文档。

请注意,它只适用于选定的几个网站。现在大多数人使用更复杂的登录方式。

429 Too Many Requests(请求过多)

这是网页抓取时最常见的HTTP错误响应之一,429状态码表示你向同一端点发送了过多请求。

切换代理或实施重试失败请求的策略是最佳选择。

500 Internal Server Error(内部服务器错误)

服务器端出现了故障。简单的重试可能会立即或在几分钟内解决问题。

502 Bad Gateway(错误网关)

与500内部服务器错误几乎相同——上游服务器出现问题,导致请求失败。稍后重试可能会解决问题。

503 Service Unavailable(服务不可用)

表示服务器可能完全宕机或不可用。虽然你可以重试失败的请求,但只有在管理员修复问题后才能解决。

504 Gateway Timeout(网关超时)

表示网络问题,可能由任一端引起。逐渐增加延迟时间进行重试可能会解决问题。


实施失败请求重试策略

Python的requests库提供了所有你需要的工具,能够有效地处理大多数失败的请求。在上述HTTP状态码列表中,只有403和429需要独特的处理方法,尽管429也可以像其他状态码一样解决。

创建Python请求重试策略有两种方法,一种是在设定的间隔内进行简单循环,另一种是使用递增的延迟时间。前者的优点是解决速度更快,但也更容易被检测到。

使用循环重试失败请求

import requests
import time

def send_get_request(URL, retry):
    for i in range(retry):
        try:
            r = requests.get(URL)
            if r.status_code not in [200, 404]:
                time.sleep(5)
            else:
                break
        except requests.exceptions.ConnectionError:
            pass
    print(r.status_code)

send_get_request('https://dashboard.iproyal.com/login', 5)

由于我们将使用sleep函数来创建延迟,因此需要导入time库,这一步紧跟在导入Python requests库之后,虽然顺序并不重要。

在我们的函数中,我们现在包括一个“retry”参数,用于指定我们将重试失败请求的次数。

此外,包含了一个for循环,它接受重试次数并将其用作范围。包含一个“if”语句来验证是否接收到200或404状态码。如果都不是,那么函数将休眠5秒并重复该过程。

如果接收到200或404状态码,函数将停止。此外,如果发生连接错误,它将什么也不做,绕过常规的Python requests错误处理。

最后,如果超时导致问题,你可以通过添加参数“(timeout = N)”来设置自定义的Python requests.get超时函数。

使用HTTPAdapter重试失败请求

对于第二种策略,我们需要导入的不仅仅是Python requests库。

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

HTTPAdapter将允许我们将失败的请求重试策略挂载到会话中。我们的重试策略将由“urllib3”重试实用程序定义。

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def send_get_request(URL):
    sess = requests.session()

    retries = Retry(total = 5,
                    backoff_factor = 1,
                    status_forcelist = [429, 500, 502, 503, 504])

    sess.mount('https://', HTTPAdapter(max_retries=retries))
    get_URL = sess.get(URL)
    print(get_URL.status_code)

send_get_request('https://iproyal.com')

我们的函数现在以一个会话开始,而不是直接发送请求,这是当前失败请求策略所必需的。

接下来,我们定义一个带有几个参数的重试对象。首先,我们将Python重试的总次数设置为5,退避因子设置为1,并指定哪些状态码应被重试。退避因子是一个更复杂的sleep函数,其定义如下:

{backoff factor} * (2 ** ({retry number} - 1))

我们的第一次重试将是立即进行的,但之后的重试将会在越来越长的间隔内进行。

然后,我们将我们的会话挂载到HTTPAdapter上,它将执行所有必要的重试。之后的步骤基本上与其他策略相同。

最后,任何Python请求在继续之前都会等待响应。如果你想并行发送多个请求,则需要使用异步编程

使用代理绕过429

在你的网页抓取项目中集成代理时,有一种独特的方法可以避免429(请求过多),而不是使用Python请求重试策略。

由于429状态码是分配给IP地址的,你可以在收到该HTTP错误码时切换代理来完全避开它。只要你有按需付费的住宅代理,你就可以不断切换IP地址来避免429。

你也可以设置一个失败请求重试策略,作为应对其他错误码的备用方案。

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def send_get_request(URL):
    sess = requests.session()

    proxies = {"http" : "http://USER:PASS@HOST:PORT"}

    retries = Retry(total = 5,
                    backoff_factor = 1,
                    status_forcelist = [500, 502, 503, 504])

    sess.mount('https://', HTTPAdapter(max_retries=retries))
    get_url = sess.get(URL, proxies=proxies)
    if get_url.status_code == 429:
        sess.get(URL, proxies=proxies)
        
    print(get_url.status_code)

send_get_request('https://iproyal.com')

由于我们使用的是轮换的住宅代理,如果收到429错误,只需向相同的端点发送一个新的请求。轮换代理会自动提供一个新的IP地址。

对于粘性会话,你应该生成一个更大的代理列表并将其放入字典对象中,然后使用if语句在收到429错误时切换到新的IP地址。


总    结

这些基本策略应该可以让你自动解决大多数HTTP错误码。避免大多数常见HTTP错误有两种策略。你可以设置一个基本循环来重试失败的请求:

import requests
import time 

def send_get_request(URL, retry): #defines a function to send get requests with two arguments
    for i in range(retry): #sets a range for the amount of retries
        try:
            r = requests.get(URL)
            if r.status_code not in [200, 404]: 
                time.sleep(5) #tries to retrieve the URL, if 200 or 404 is not received, waits 5 seconds before trying again
            else:
                break #stops function if 200 or 404 received
        except requests.exceptions.ConnectionError:
            pass
    print(r.status_code)

send_get_request('https://iproyal.com', 5)

或者你可以使用HTTPAdapter Python请求重试策略,这可能会慢一点,但不易检测:

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def send_get_request(URL): #defines a get request function with one argument
    sess = requests.session() #sets a session object

    retries = Retry(total = 5,
                    backoff_factor = 1,
                    status_forcelist = [429, 500, 502, 503, 504]) #sets the retry amount to 5, backoff_factor to 1, and sets specific HTTP error codes to be retried on

    sess.mount('https://', HTTPAdapter(max_retries=retries)) #mounts HTTPAdapter to the session
    get_URL = sess.get(URL)
    print(get_URL.status_code)

send_get_request('https://iproyal.com')

最后,对于429,每次收到错误代码时都可以切换IP地址。只需要一个if语句和一个新的status_forcelist。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。