Scrapy爬虫大面积报错Timeout/403?彻底解决代理IP失效导致的“丢数据”痛点

本文涉及的产品
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 爬虫开发常遇代理失效、重试漏判、403/429不重试等痛点,导致数据大量丢失。本文深度剖析Scrapy默认机制缺陷,手写高鲁棒性代理重试中间件:统一捕获Twisted底层异常、扩展重试状态码(含403/429)、自动轮换隧道IP,真正实现“丢包不丢数”。

做爬虫开发的兄弟们肯定都经历过这种绝望时刻:周五下班前满心欢喜地部署了一个包含几十万URL的爬虫任务,挂上代理池,看着控制台刷刷地跑,安心回家过周末。结果周一早上回来一看数据库,本该有30万条数据,却只存了区区5000条。

打开日志一看,满屏血红:

  • [scrapy.downloadermiddlewares.retry] Gave up retrying <GET ...> (failed 3 times): TCP connection timed out
  • [scrapy.core.engine] DEBUG: Crawled (403) <GET ...>
  • twisted.internet.error.ConnectionLost: Connection to the other side was lost in a non-clean fashion.

为什么明明接了代理IP,爬虫还是会大面积崩溃、丢数据?今天我们就来彻底扒一扒Scrapy默认代理和重试机制的坑,并手写一个深度定制的中间件来解决它。

痛点案例分析:Scrapy默认机制为什么不够用?

Scrapy内置了 HttpProxyMiddlewareRetryMiddleware,对于跑跑小众网站勉强够用,但在高并发和复杂反爬环境下,简直漏洞百出。

案例一:代理节点的“薛定谔状态”(网络层异常丢请求)

现象: 使用动态转发代理(每次请求随机切换IP),有时某个底层的IP节点不稳定,导致请求一直卡在建立连接的阶段,最后抛出 TimeoutErrorTunnelError
Scrapy的默认处理: RetryMiddleware 确实会捕获一部分异常,但对于Twisted底层爆出的一些冷门网络错误(如 TCPRoutedConnectionLost),它可能会直接漏掉。更致命的是,如果代理质量差,连续3次重试都碰上了坏节点,Scrapy就会直接抛弃(Gave up)这个URL,导致数据永久丢失

案例二:服务器的“温柔一刀”(状态码异常不重试)

现象: 目标网站并没有断开你的连接,而是直接返回了一个 403 Forbidden,或者 429 Too Many Requests,甚至有些网站会针对代理IP返回 502 Bad Gateway
Scrapy的默认处理: RetryMiddleware 默认只针对 [500, 502, 503, 504, 522, 524, 408] 进行重试。如果你遇到了 403429,Scrapy会认为这是一个“正常的响应状态”,直接放行给Spider去解析。Spider找不到数据节点,最终导致解析报错或者存入空数据。

案例三:“死磕到底”的盲目重试

现象: 使用固定代理池时,IP已经被目标网站拉黑(封禁了)。
Scrapy的默认处理: 带着这个已经被封的IP连续重试3次,白白浪费请求次数和时间,没有任何意义。正确的做法应该是:一旦发现被封,立刻丢弃该IP,换一个新代理再次重试

终极解决方案:深度定制代理中间件

为了解决上述痛点,我们需要打破 Scrapy “代理是代理,重试是重试” 的隔离机制,将代理注入、状态码校验、异常捕获、自动重试全部收拢到一个自定义的 Downloader Middleware 中。

下面我们以接入“爬虫代理”(账密隧道转发模式)为例,直接上企业级解决代码。

1. 编写定制化中间件 (middlewares.py)

import base64
import logging
from scrapy.utils.response import response_status_message
from scrapy.core.downloader.handlers.http11 import TunnelError
from twisted.internet import defer
from twisted.internet.error import (
    TimeoutError, DNSLookupError, 
    ConnectionRefusedError, ConnectionDone, ConnectError, 
    ConnectionLost, TCPRoutedConnectionLost
)

logger = logging.getLogger(__name__)

class RobustProxyMiddleware:
    """
    终极防丢数据:自定义代理与重试中间件
    """

    def __init__(self, settings):
        # 1. 初始化代理配置 (参考亿牛云爬虫代理标准版)
        self.proxy_host = settings.get('PROXY_HOST', 'proxy.16yun.cn')
        self.proxy_port = settings.get('PROXY_PORT', '8100')
        self.proxy_user = settings.get('PROXY_USER', '16YUN')
        self.proxy_pass = settings.get('PROXY_PASS', '16IP')
        self.proxy_url = f"http://{self.proxy_host}:{self.proxy_port}"

        # 构造鉴权头
        auth_bytes = f"{self.proxy_user}:{self.proxy_pass}".encode('utf-8')
        self.proxy_auth_header = f"Basic {base64.b64encode(auth_bytes).decode('utf-8')}"

        # 2. 核心痛点解决:扩大状态码重试范围,加入403和429
        self.retry_http_codes = set(int(x) for x in settings.getlist('RETRY_HTTP_CODES', [403, 429, 500, 502, 503, 504, 521]))

        # 3. 核心痛点解决:全面捕获Twisted底层网络异常,防止漏网之鱼
        self.exceptions_to_retry = (
            defer.TimeoutError, TimeoutError, DNSLookupError,
            ConnectionRefusedError, ConnectionDone, ConnectError,
            ConnectionLost, TCPRoutedConnectionLost, TunnelError
        )

        # 最大重试次数
        self.max_retry_times = settings.getint('RETRY_TIMES', 5)

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings)

    def process_request(self, request, spider):
        """挂载代理"""
        if 'dont_proxy' not in request.meta:
            request.meta['proxy'] = self.proxy_url
            request.headers['Proxy-Authorization'] = self.proxy_auth_header

    def process_response(self, request, response, spider):
        """
        拦截响应:解决状态码异常不重试的问题
        """
        if response.status in self.retry_http_codes:
            reason = response_status_message(response.status)
            logger.warning(f"【拦截异常状态码】 {response.status} - 触发重试: {request.url}")
            return self._retry(request, reason, spider) or response
        return response

    def process_exception(self, request, exception, spider):
        """
        拦截异常:解决网络层抖动导致的丢包问题
        """
        if isinstance(exception, self.exceptions_to_retry):
            logger.warning(f"【拦截网络异常】 {exception} - 触发重试: {request.url}")
            return self._retry(request, exception, spider)

    def _retry(self, request, reason, spider):
        """
        重试调度核心逻辑
        """
        retries = request.meta.get('retry_times', 0) + 1
        if retries <= self.max_retry_times:
            logger.debug(f"重试 {retries}/{self.max_retry_times}: {request.url}")
            retryreq = request.copy()
            retryreq.meta['retry_times'] = retries
            retryreq.dont_filter = True # 必须设置为True,防止重试的URL被去重器过滤掉

            # 由于使用的是隧道代理,重新发起请求时,代理服务端会自动分配一个新的底层IP,
            # 从而完美避开了“死磕同一个被封IP”的窘境。
            return retryreq
        else:
            # 达到最大重试次数,建议在此处将URL记录到Redis或死信队列,人工介入
            logger.error(f"【放弃重试】达到最大次数 {self.max_retry_times}: {request.url}")
            return None

2. 生效配置 (settings.py)

写好代码后,最关键的一步是替换掉Scrapy自带的残疾组件

# 亿牛云爬虫代理标准版
PROXY_HOST = 'proxy.16yun.cn'
PROXY_PORT = '8100'
PROXY_USER = 'your_username'
PROXY_PASS = 'your_password'

# 自定义我们要拦截重试的状态码 (务必加上你的目标网站喜欢返回的反爬码)
RETRY_HTTP_CODES = [403, 408, 429, 500, 502, 503, 504]
RETRY_TIMES = 5 # 建议适当调大重试次数,以空间换成功率

# 关闭默认组件,启用我们的终极防御中间件
DOWNLOADER_MIDDLEWARES = {
   
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
    'myproject.middlewares.RobustProxyMiddleware': 543, # 数值设置在中间位置即可
}

DOWNLOAD_TIMEOUT = 15 # 隧道代理建议设置10-15秒超时

效果对比与总结

换上这套定制中间件后,你的控制台输出会发生根本性的变化:

  • 以前: 遇到 TimeoutError,红字报错,进程继续,数据悄悄丢失。
  • 现在: 控制台输出黄字警告 【拦截网络异常】 TimeoutError - 触发重试...,请求被打回调度器,利用隧道代理的新IP重新采集,最终返回 200 OK数据一条不丢
  • 以前: 网站返回 403,直接进入 Spider,Xpath提取不到元素,爆出 AttributeError 异常终止。
  • 现在: 中间件在 Download 环节直接截杀 403,输出 【拦截异常状态码】 403 - 触发重试...,自动洗白IP后再次请求,Spider只负责处理干净的 200 数据。

做爬虫,把异常处理和重试机制牢牢抓在自己手里,才是保证数据完整性的唯一出路。

相关文章
|
15天前
|
数据采集 JavaScript 前端开发
基于CefSharp内核与动态隧道的金融海量行情抓取架构方案
本文详解CefSharp在金融高频数据抓取中的两大代理难题:一是代理认证头(Proxy-Authorization)因跨进程失效导致直连暴露IP;二是HTTPS Keep-Alive锁定出口IP,无法动态轮换。提出通过自定义RequestHandler手动注入认证头与Proxy-Tunnel隧道标识的双重方案,实现稳定代理穿透与毫秒级IP切换,助力高效获取海外行情数据。
113 1
|
21天前
|
数据采集 Rust 网络协议
学术文献抓取 OOM 崩溃与 403 风暴
学术文献抓取进程因内存泄漏和代理IP切换问题导致效率下降。通过使用Rust和Reqwest重写核心模块,隔离Cookie Jar,修复后内存稳定,抓取率提高至92%,延迟降低。
112 4
|
14天前
|
数据采集 JavaScript 前端开发
PHP也可以写高性能爬虫:Swoole协程与代理IP的奇妙化学反应
本文探讨了利用PHP和Swoole协程技术构建高效社交媒体舆情监控系统的方法。通过非阻塞IO和IP代理,解决了高数据通量和反爬策略问题,适用于纯HTTP数据采集。
|
3月前
|
人工智能 运维 架构师
智能体来了:重新定义 AI Agent 搭建师的核心能力与职业路径
随着AI Agent成为企业智能化闭环核心,AI Agent搭建师应运而生。本文从工程化视角解析其全链路职责,并指出在技术平民化趋势下,真正不可替代的是需求抽象、系统架构、风险治理与跨域适配四大体系化能力,呼吁构建T型/π型知识结构,实现从“编码者”向“系统架构师”跃迁。(239字)
241 6
智能体来了:重新定义 AI Agent 搭建师的核心能力与职业路径
|
2月前
|
机器学习/深度学习 人工智能 算法
PPO算法全解:让AI“学步”更稳的强化学习秘诀
本文用“教孩子骑车”比喻,生动解析PPO算法如何通过“信任区域”约束与Clipping裁剪机制,实现稳定高效的强化学习。避开复杂数学,讲清其在RLHF、大模型对齐中的核心作用,并提供可运行代码与调参指南。(239字)
|
2月前
|
域名解析 网络协议 安全
详细介绍Linux命令dig和nslookup
本文介绍 Linux 下两大 DNS 查询工具:dig(功能强大,支持详尽选项与追踪)和 nslookup(简洁易用,含交互模式)。涵盖安装、常用语法、记录类型(A/MX/NS等)、典型示例及输出解析,助你高效诊断域名解析与网络问题。(239字)
891 3
|
15天前
|
人工智能 分布式计算 DataWorks
阿里云大数据 AI 产品月刊-2026年3月
阿里云大数据& AI 产品技术月刊【2026 年 3 月】,涵盖 3 月技术速递、产品和功能发布、市场和客户应用实践等内容,帮助您快速了解阿里云大数据& AI 方面最新动态。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
别再说“AI听不懂人话”:从0到1手把手搭一个意图识别 + 槽位提取系统
别再说“AI听不懂人话”:从0到1手把手搭一个意图识别 + 槽位提取系统
409 11
|
1月前
|
弹性计算 Java 关系型数据库
学生开发者指南:如何用最低成本在阿里云部署可访问的Web项目(最新版)
本文详细介绍Spring Boot + Vue项目部署到阿里云ECS的完整流程,包含Nginx反向代理、Systemd服务配置、RDS数据库连接等实操内容。适合课程设计、毕业设计、个人项目演示场景,配合智码方舟等AI工具可进一步提升开发效率,月度成本控制在50元以内。
|
1月前
|
机器学习/深度学习 SQL 人工智能
自然语言查数技术路线对比:本体神经网络如何实现企业级精准问数
本文剖析NL2SQL、RAG、预制指标与本体神经网络四大技术路线,指出后者(Palantir、UINO采用)以ABC范式实现高准确率(95%+)、线性维护成本、跨库多模态精准问数,真正支撑企业级智能分析。

热门文章

最新文章