如何在 Python 中实现请求重试

评论: 0

网络抓取是从网络中提取数据的有效方法。许多开发人员喜欢使用 Python 请求库来执行网络抓取项目,因为它简单而有效。然而,请求库虽好,也有其局限性。我们在网络搜刮中可能会遇到的一个典型问题就是请求失败,这通常会导致数据提取不稳定。在本文中,我们将介绍在 Python 中实现请求重试的过程,这样您就可以处理 HTTP 错误,并保持网络搜刮脚本的稳定和可靠。

开始使用请求库

让我们先设置一下环境。确保你已经安装了 Python 和你选择的 IDE。然后安装请求库(如果还没有)。

pip install requests

安装完成后,让我们使用 Python 的 requests 模块向 example.com 发送一个请求。下面是一个简单的函数,就可以实现这个功能:

import requests

def send_request(url):
    """
    向指定 URL 发送 HTTP GET 请求,并打印响应状态代码。
    
    Parameters:
        url (str): 发送请求的 URL。
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

send_request('https://example.com')

代码输出如下所示:

How to implement request retries in Python.png

让我们进一步了解 HTTP 状态代码,以便更好地理解它们。

了解 HTTP 状态代码

服务器响应 HTTP 请求时会给出一个状态代码,表明请求的结果。以下是简要介绍:

  1. 1xx(信息):已收到请求,正在继续处理。
  2. 2xx(成功):请求已收到、理解并接受。
    • 200 OK:请求成功。这是 HTTP 状态代码中的绿灯。
  3. 3xx(重定向):需要进一步操作才能完成请求。
  4. 4xx(客户端错误):请求出现错误,通常是由于客户端的某些原因造成的。
  5. 5xx(服务器错误):服务器端出错,无法满足有效请求。
    • 500 内部服务器错误:服务器无法完成请求。这表明服务器遇到了意外情况,无法完成请求。这相当于交通红灯的 HTTP 状态代码。
    • 504 网关超时:服务器没有及时收到上游服务器的响应。这相当于候车室超时红绿灯的 HTTP 状态代码。

在我们的例子中,状态代码 200 表示对 https://example.com 的请求已经完成。服务器用这种方式表示:"一切正常,您的请求成功"。

这些状态代码还可以在僵尸检测中发挥作用,并在出现类似僵尸的行为时显示访问限制。

以下是主要因僵尸检测和身份验证问题而出现的 HTTP 错误代码的快速概览。

  1. 429 请求过多:该状态代码表示用户在给定时间内发送了过多请求("速率限制")。当机器人超过预定义的请求限制时,这是一种常见的响应。
  2. 403 禁止:当服务器拒绝执行请求时会返回此代码。如果服务器根据 User-Agent 或其他标准怀疑请求来自僵尸,就会出现这种情况。
  3. 401 未授权:如果访问需要机器人不具备的身份验证,可能会使用这种状态。
  4. 503 服务不可用:有时用于表示服务器暂时无法处理请求,在自动流量激增时可能会出现这种情况。

用 Python 实现重试机制

现在,让我们用 Python 编写一个简单的重试机制,利用请求库发出 HTTP GET 请求。有时,由于网络问题或服务器过载,网络请求会失败。因此,如果请求失败,我们应该重试这些请求。

基本重试机制

函数 send_request_with_basic_retry_mechanism 在向给定 URL 发送 HTTP GET 请求时使用了基本重试机制,只有在遇到网络或请求异常(如连接错误)时才会重试。重试请求的次数最多为 max_retries。如果所有尝试都因此类异常而失败,它会引发最后一次遇到的异常。

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
    向带有基本重试机制的 URL 发送 HTTP GET 请求。
    
    Parameters:
        url (str): 发送请求的 URL。
        max_retries (int): 重试请求的最大次数。

    Raises:
        requests.RequestException: 如果所有重试都失败,则引发最后一个异常。

    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break  # 如果请求成功,则退出循环
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # 重试前等待
            else:
                print("Max retries exceeded.")
                # 如果达到最大重试次数,则重提最后一次异常
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

提前重试机制

现在,让我们调整基本的重试机制,以处理我们试图抓取的网站实施了僵尸检测机制而可能导致阻塞的情况。要处理这种情况,我们需要多次努力重试请求,因为它们可能不仅仅是僵尸检测阻塞,也可能是网络或服务器问题造成的。

下面的函数 send_request_with_advance_retry_mechanism 向提供的 URL 发送 HTTP GET 请求,可选择重试次数和重试延迟。它会按照指定的尝试次数多次发送请求,如果请求成功获得响应,则打印响应状态代码。如果在请求操作过程中遇到错误,它会打印错误信息并重试。每次重试之间会等待指定的重试延迟时间。如果在指定的重试次数后请求仍然失败,则会引发上次遇到的异常。

延迟参数很重要,因为它可以避免在很近的时间间隔内向服务器发出多个请求。相反,它会等待服务器有足够的时间来处理请求,让服务器认为是人类而不是机器人在发出请求。因此,重试机制应该延迟,以避免服务器过载或服务器响应缓慢而触发反僵尸机制。

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
   通过高级重试机制向指定 URL 发送 HTTP GET 请求。
    
    Parameters:
        url (str): 发送请求的 URL。
        max_retries (int): 重试请求的最大次数。默认为 3。
        delay (int): 重试之间的延迟(秒)。默认为 1。

    Raises:
        requests.RequestException: 如果所有重试都失败,则引发最后一个异常。
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # 针对 4xx 或 5xx 状态代码引发异常
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        except requests.RequestException as e:
            # 如果请求失败,打印错误信息和尝试次数
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                # 打印重试信息并在重试前等待
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                #如果超过了最多重试次数,则打印信息并重新提出异议
                print("Max retries exceeded.")
                raise

# 使用示例
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

下面是这种实施方法的缺点:

  • 所有属于 4xx 和 5xx 范围的状态代码都要重试。但是,状态代码为 404(未找到)的请求无需重试。
  • 有些僵尸检测服务可能会以状态代码 200(OK)进行响应,但响应内容可能有所不同。目前的实现没有处理这种情况。实施内容长度验证可以解决这个问题。

以下是更正后的代码,以及针对缺点的注释:

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    通过高级重试机制向指定 URL 发送 HTTP GET 请求。

    Parameters:
        url (str): 发送请求的 URL。
        max_retries (int): 重试请求的最大次数。默认为 3。
        delay (int): 重试之间的延迟(秒)。默认为 1。
        min_content_length (int): 认为有效的最小回复内容长度。默认为 10。

    Raises:
        requests.RequestException: 如果所有重试都失败,则引发最后一个异常。
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # 针对 4xx 或 5xx 状态代码引发异常
            response.raise_for_status()
            
            # 检查响应状态代码是否为 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # 404 错误的退出循环
            
            # 检查回复文本的长度是否小于指定的最小内容长度
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # 重试请求
            
            print('Response Status Code:', response.status_code)
            # 如果满足条件,则跳出循环
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # 如果达到最大重试次数,则重提最后一次异常
                raise

# 使用示例
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

使用代理处理特定 HTTP 错误

对于某些错误(如 429 请求过多),使用旋转代理可以帮助分配请求,避免速率限制。

下面的代码在使用代理的同时,还实现了一种高级重试策略。这样,我们就可以实现 Python 请求重试机制。使用高质量的网络搜索代理也很重要。这些代理应具有良好的代理轮换算法和可靠的池。

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    通过高级重试机制向指定 URL 发送 HTTP GET 请求。

    Parameters:
        url (str): 发送请求的 URL。
        max_retries (int): 重试请求的最大次数。默认为 3。
        delay (int): 重试之间的延迟(秒)。默认为 1。
   
    Raises:
        requests.RequestException: 如果所有重试都失败,则引发最后一个异常。
    """
    
    proxies = {
        "http": "http://USER:PASS@HOST:PORT",
        "https": "https://USER:PASS@HOST:PORT"
    }
    
    for attempt in range(max_retries):
        try:
            response = requests.get(url, proxies=proxies, verify=False)
            # 针对 4xx 或 5xx 状态代码引发异常
            response.raise_for_status()
            
            # 检查响应状态代码是否为 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # 404 错误的退出循环
            
            # 检查回复文本的长度是否少于 10 个字符
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue  # 重试请求
            
            print('Response Status Code:', response.status_code)
            # 如果满足条件,则跳出循环
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # 如果达到最大重试次数,则重提最后一次异常
                raise

send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Python 中的请求重试对于有效的网络刮擦至关重要。我们讨论的重试管理方法有助于防止阻塞,提高数据收集的效率和可靠性。实施这些技术将使您的网络抓取脚本更加健壮,不易被僵尸保护系统检测到。

评论:

0 评论