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

如何在Scrapy爬虫中实现多代理轮询(无需修改配置文件)

嘿,这个问题我熟!你现在用os.environ设置全局代理的方式确实没法灵活切换,毕竟全局变量是所有请求共享的,要实现多代理轮询+失效自动切换,咱们换个更靠谱的思路就行,而且完全不用改Scrapy的配置文件或者中间件~

推荐方案:用Scrapy请求的meta字段动态指定代理

Scrapy本身就支持给单个请求单独设置代理,通过请求的meta字典里的proxy字段实现,这种方式既灵活又不会有全局变量的线程安全问题,完美适配你的需求。

步骤1:准备代理列表+实现代理管理器

先定义一个代理管理器类,用来维护可用代理池,实现轮询和失效代理移除的逻辑:

from itertools import cycle

class ProxyManager:
    def __init__(self, proxy_list):
        # 初始化可用代理池和轮询迭代器
        self.available_proxies = proxy_list.copy()
        self.proxy_cycle = cycle(self.available_proxies)

    def get_next_proxy(self):
        # 获取下一个轮询的代理
        return next(self.proxy_cycle)

    def remove_failed_proxy(self, proxy):
        # 移除失效的代理,并重新生成轮询迭代器
        if proxy in self.available_proxies:
            self.available_proxies.remove(proxy)
            self.proxy_cycle = cycle(self.available_proxies)
            print(f"代理 {proxy} 已失效,已从可用池移除")

步骤2:在Spider中集成代理管理器

在你的爬虫里初始化代理管理器,然后在生成请求时指定代理,同时添加错误回调来处理代理失效的情况:

import scrapy
from scrapy.exceptions import HttpError

class MyCustomSpider(scrapy.Spider):
    name = "custom_spider"
    start_urls = ["https://target-website.com"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 替换成你的代理列表
        self.proxy_manager = ProxyManager([
            "http://176.58.125.65:80",
            "http://192.168.1.100:8080",
            "http://10.0.0.5:3128",
            # 更多代理...
        ])

    def start_requests(self):
        for url in self.start_urls:
            # 每次请求拿一个新代理
            current_proxy = self.proxy_manager.get_next_proxy()
            yield scrapy.Request(
                url,
                callback=self.parse,
                # 给请求绑定代理
                meta={"proxy": current_proxy},
                # 绑定错误回调,处理代理失效
                errback=self.handle_proxy_failure,
            )

    def parse(self, response):
        # 这里写你的响应处理逻辑
        self.logger.info(f"成功用代理 {response.meta['proxy']} 获取到 {response.url}")
        
        # 生成后续请求时,同样绑定新代理
        next_url = "https://target-website.com/next-page"
        yield scrapy.Request(
            next_url,
            callback=self.parse_next_page,
            meta={"proxy": self.proxy_manager.get_next_proxy()},
            errback=self.handle_proxy_failure,
        )

    def handle_proxy_failure(self, failure):
        # 获取当前失效的代理
        failed_proxy = failure.request.meta.get("proxy")
        if not failed_proxy:
            return

        # 判断是否是代理相关的错误(比如403、502、503,或者连接超时)
        if failure.check(HttpError):
            response = failure.value.response
            if response.status in [403, 502, 503]:
                self.proxy_manager.remove_failed_proxy(failed_proxy)
        elif failure.check(TimeoutError, ConnectionRefusedError):
            self.proxy_manager.remove_failed_proxy(failed_proxy)

        # 重新发起请求,使用下一个代理
        yield failure.request.replace(
            meta={"proxy": self.proxy_manager.get_next_proxy()},
            errback=self.handle_proxy_failure,
        )

    def parse_next_page(self, response):
        # 处理后续页面的逻辑
        pass

如果坚持要用os.environ的方式(不推荐)

如果你不想改原来的环境变量设置逻辑,那得注意Scrapy是多线程的,需要加锁保证线程安全,同时每次请求前切换代理。不过这种方式容易出现多个请求共享同一个代理的问题,不如上面的方案可靠:

import scrapy
import os
import threading
from itertools import cycle

class ThreadSafeProxyCycle:
    def __init__(self, proxy_list):
        self.proxy_cycle = cycle(proxy_list)
        self.lock = threading.Lock()

    def get_next(self):
        with self.lock:
            return next(self.proxy_cycle)

class MySpider(scrapy.Spider):
    name = "env_proxy_spider"
    start_urls = ["https://target-website.com"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.proxy_cycle = ThreadSafeProxyCycle([
            "http://176.58.125.65:80",
            "http://192.168.1.100:8080",
        ])

    def start_requests(self):
        for url in self.start_urls:
            proxy = self.proxy_cycle.get_next()
            # 全局设置代理
            os.environ["http_proxy"] = proxy
            os.environ["https_proxy"] = proxy
            yield scrapy.Request(
                url,
                callback=self.parse,
                errback=self.handle_failure,
                meta={"used_proxy": proxy},
            )

    def parse(self, response):
        # 处理响应,后续请求同样切换代理
        proxy = self.proxy_cycle.get_next()
        os.environ["http_proxy"] = proxy
        os.environ["https_proxy"] = proxy
        yield scrapy.Request(
            "https://target-website.com/next",
            callback=self.parse_next,
            errback=self.handle_failure,
            meta={"used_proxy": proxy},
        )

    def handle_failure(self, failure):
        # 这里可以添加失效代理的移除逻辑(需要修改ThreadSafeProxyCycle来维护可用列表)
        proxy = failure.request.meta.get("used_proxy")
        print(f"代理 {proxy} 失效,切换下一个")
        new_proxy = self.proxy_cycle.get_next()
        os.environ["http_proxy"] = new_proxy
        os.environ["https_proxy"] = new_proxy
        yield failure.request.replace(
            meta={"used_proxy": new_proxy},
            errback=self.handle_failure,
        )

小提示

  • 代理失效的判断要精准,别把目标网站的反爬(比如404)当成代理失效,尽量只针对连接超时、5xx、403这类明确和代理相关的错误。
  • 如果你的代理需要账号密码,格式是http://username:password@ip:port,不管用哪种方式都可以直接用这个格式。

内容的提问来源于stack exchange,提问作者SIM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:00:52