You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy爬虫爬取约727页后持续返回429错误求助

Scrapy爬取约727条后稳定触发429错误的排查与解决思路

我用Scrapy爬虫爬取3000条URL,不管爬取速度是0.8页/秒还是1.5页/秒,爬取约727页后总会开始返回429错误。打乱URL多次测试,3次恰好爬727条,其余几次在690-730条左右触发。已经配置了请求头、随机代理及随机User-Agent,求有类似经验的开发者指点。

我的代码如下:

import scrapy
import json
import csv
import re
import random


class website(scrapy.Spider): 

    name = "website"

    country_id = 'US'  

    custom_settings = {
        'CONCURRENT_ITEMS': 40,
        'CONCURRENT_REQUESTS': 20,
        'CONCURRENT_REQUESTS_PER_DOMAIN': 20,
        'DOWNLOAD_DELAY': 0.9,
        'FEED_EXPORT_ENCODING': 'utf-8',
        'RANDOMIZE_DOWNLOAD_DELAY': True,

        'LOG_LEVEL': 'INFO',
        'FEEDS': {
            f'data/{name}.jl': {
                "format": "jsonlines"
            }
        }
    }

    def __init__(self):
        self.headers = {
                        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                        "accept-language": "en-US,en;q=0.9",
                        "cache-control": "no-cache",
                        "pragma": "no-cache",
                        "sec-fetch-dest": "document",
                        "sec-fetch-mode": "navigate",
                        "sec-fetch-site": "none",
                        "sec-fetch-user": "?1",
                        "upgrade-insecure-requests": "1"
                        }
        
        with open('data/missing_website_data_urls.txt') as csv_file:
            csv_reader = csv.reader(csv_file, delimiter=',')
            for row in csv_reader:
                self.missing_website_data_urls = row
        
        
    def start_requests(self):
        for search_url in self.missing_website_data_urls:
            search_url = search_url[:32] + re.sub('[^0-9a-zA-Z ]+', '', search_url[32:])
            #search_url = search_url.replace('&', '').replace('$', '').replace('+', '').replace(',', '').replace('/', '').replace('?', '').replace('=', '').replace('@', '').replace(':', '').replace(';', '')
            yield scrapy.Request(url=search_url,
                                 method='GET',
                                 headers=self.headers,
                                 callback=self.parse,
                                 cb_kwargs={'url': search_url},
                                 meta={'proxy_country': self.country_id
                                       }
                                )

排查方向与解决方法

1. 确认UA/代理的随机化真的生效了

从代码看,你定义的self.headers是固定值,没有体现UA随机切换的逻辑——如果只是说配置了但代码里没做,那等于没生效。另外代理部分只用了meta={'proxy_country': self.country_id},得确认代理中间件是否真的每次请求都分配了新IP,有没有代理池里的IP本身已经被目标站拉黑。

处理建议:

  • 新增UA列表,每次请求随机选一个注入headers:
    def __init__(self):
        # 原有代码...
        self.user_agents = [
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0"
            # 多补充不同设备、浏览器的UA
        ]
    
    def start_requests(self):
        for search_url in self.missing_website_data_urls:
            # 原有URL处理代码...
            # 复制headers并随机替换UA
            req_headers = self.headers.copy()
            req_headers['User-Agent'] = random.choice(self.user_agents)
            yield scrapy.Request(url=search_url,
                                 headers=req_headers,
                                 # 其他参数...
                                 )
    
  • 检查代理中间件的日志,确认每次请求的代理IP都在变化;定期更新代理池,剔除无效IP。

2. 目标站可能按请求数限流,而非速度

不同爬取速度都卡在700条左右触发429,说明目标站大概率设置了固定请求数阈值(比如单个IP/会话允许700次请求),和请求间隔无关。哪怕放慢速度,达到请求数就触发限流。

处理建议:

  • 扩容代理池的IP数量,把每个代理的请求数控制在600以内(比如每爬600条就切换一批代理);
  • 给每个代理绑定独立的cookie会话,避免不同请求共用同一个会话标识被追踪。

3. 请求头固定字段太显眼

你的headers里sec-fetch-site固定为none,正常用户访问时这个字段会根据场景变化(比如站内跳转是same-origin,外部跳转是cross-site);另外cache-control和pragma都设为no-cache,也不符合真实浏览器的请求习惯,容易被识别为爬虫。

处理建议:

  • 随机化部分请求头字段:
    # 在start_requests里
    sec_fetch_site_options = ['none', 'same-origin', 'cross-site']
    req_headers['sec-fetch-site'] = random.choice(sec_fetch_site_options)
    # 可选:随机去掉cache-control或pragma字段,或者改为max-age=0等其他值
    if random.random() > 0.5:
        del req_headers['cache-control']
    
  • 不要完全覆盖Scrapy的默认请求头,保留部分自动生成的字段,让请求更接近真实用户。

4. URL处理后的规律特征被识别

你对URL做了强正则替换,把32位之后的非字母数字字符全部删掉,导致大量URL的结构高度一致,和正常用户访问的URL差异明显,容易被目标站的反爬规则捕捉。

处理建议:

  • 对比正常用户访问的URL,保留必要的参数分隔符(比如&、=),不要过度清洗;
  • 测试不做URL处理的情况下,是否还会在相同位置触发429,排除URL特征的问题。

5. 并发设置过高

CONCURRENT_REQUESTS_PER_DOMAIN设为20,哪怕加了0.9秒的下载延迟,单域名并发20请求还是太激进——如果代理IP是共用的,多个用户的请求叠加后,目标站会识别到单IP的高并发请求,直接触发限流。

处理建议:

  • 降低并发数,调整配置:
    custom_settings = {
        # 原有设置...
        'CONCURRENT_REQUESTS': 10,
        'CONCURRENT_REQUESTS_PER_DOMAIN': 5,
        'DOWNLOAD_DELAY': 1.5,
        # 开启自动节流,让Scrapy自适应调整速度
        'AUTOTHROTTLE_ENABLED': True,
        'AUTOTHROTTLE_START_DELAY': 1,
        'AUTOTHROTTLE_MAX_DELAY': 5,
        'AUTOTHROTTLE_TARGET_CONCURRENCY': 1.0,
    }
    

内容的提问来源于stack exchange,提问作者Sergio A. Gutierrez Pacheco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 23:31:04