Skip to content
Advanced / 4 min read

如何在 Linux 上将 Smartproxy 住宅代理和数据中心代理与 Scrapy 配合使用

构建一个 Scrapy 下载器中间件,在 Linux 上轮换 Smartproxy 住宅和数据中心 HTTP 代理,并使用基于环境的凭据和池回退重试。

Linux HTTP(S) 网页抓取

概述

Scrapy 内置的 HttpProxyMiddleware 可以通过一个 HTTP 代理路由请求,但使用 Smartproxy 进行生产级抓取通常需要两样东西:一个用于地理定位或抗封禁请求的住宅代理池,以及一个用于高吞吐量请求的数据中心代理池。本教程展示如何添加一个自定义下载器中间件,为每个请求选择一个 Smartproxy 端点,在住宅和数据中心代理池之间轮换,并重试常见的代理错误,而无需在你的 Spider 中硬编码凭据。

Smartproxy 支持 HTTP 和 SOCKS5;本指南使用 HTTP/HTTPS 代理端点,因为 Scrapy 内置的代理中间件期望 HTTP 代理 URL。

先决条件

  • 一台装有 Python 3.10 或更高版本的 Linux 机器或容器。
  • 一个拥有住宅和/或数据中心代理凭据的 Smartproxy 账户。
  • 来自 Smartproxy 仪表板的端点详细信息:你计划使用的每个代理池的代理主机、端口、用户名和密码。
  • pip 和 venv 可用。

步骤

步骤 1 — 创建虚拟环境并安装 Scrapy

python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install scrapy python-dotenv

步骤 2 — 将 Smartproxy 凭据存储为环境变量

不要将凭据提交到你的代码仓库。

export SMARTPROXY_RES_HOST='your-residential-host'
export SMARTPROXY_RES_PORT='your-residential-port'
export SMARTPROXY_RES_USERNAME='your-residential-username'
export SMARTPROXY_RES_PASSWORD='your-residential-password'

export SMARTPROXY_DC_HOST='your-datacenter-host'
export SMARTPROXY_DC_PORT='your-datacenter-port'
export SMARTPROXY_DC_USERNAME='your-datacenter-username'
export SMARTPROXY_DC_PASSWORD='your-datacenter-password'

如果你更喜欢 .env 文件,请在 Scrapy 启动前加载它。并将该文件排除在版本控制之外。

步骤 3 — 创建 Scrapy 项目

scrapy startproject smartproxy_scrapy
cd smartproxy_scrapy
scrapy genspider example example.com

步骤 4 — 添加 Smartproxy 轮换中间件

创建 smartproxy_scrapy/middlewares.py,或将其追加到生成的文件中。该中间件会在 Scrapy 的 HttpProxyMiddleware 运行之前设置 request.meta['proxy']。

import os
from urllib.parse import quote

from scrapy import signals
from scrapy.exceptions import NotConfigured
from scrapy.utils.project import get_project_settings


class SmartproxyRotationMiddleware:
    '''Route Scrapy requests through Smartproxy residential or datacenter endpoints.'''

    def __init__(self, settings):
        self.pools = {
            'residential': {
                'host': settings.get('SMARTPROXY_RES_HOST'),
                'port': settings.get('SMARTPROXY_RES_PORT'),
                'username': settings.get('SMARTPROXY_RES_USERNAME'),
                'password': settings.get('SMARTPROXY_RES_PASSWORD'),
            },
            'datacenter': {
                'host': settings.get('SMARTPROXY_DC_HOST'),
                'port': settings.get('SMARTPROXY_DC_PORT'),
                'username': settings.get('SMARTPROXY_DC_USERNAME'),
                'password': settings.get('SMARTPROXY_DC_PASSWORD'),
            },
        }

        if not any(pool['host'] and pool['port'] for pool in self.pools.values()):
            raise NotConfigured('Set at least one Smartproxy pool host and port.')

    @classmethod
    def from_crawler(cls, crawler):
        middleware = cls(crawler.settings)
        crawler.signals.connect(middleware.spider_opened, signal=signals.spider_opened)
        return middleware

    def spider_opened(self, spider):
        spider.logger.info('Smartproxy rotation middleware enabled')

    def process_request(self, request, spider):
        pool_name = request.meta.get('proxy_pool', 'residential')
        pool = self.pools.get(pool_name)

        if not pool or not pool['host'] or not pool['port']:
            return None

        host = pool['host']
        port = pool['port']
        username = quote(pool['username'] or '', safe='')
        password = quote(pool['password'] or '', safe='')

        if username and password:
            proxy_url = f'http://{username}:{password}@{host}:{port}'
        else:
            proxy_url = f'http://{host}:{port}'

        request.meta['proxy'] = proxy_url
        request.meta['smartproxy_pool'] = pool_name
        return None

步骤 5 — 在 Scrapy 的代理中间件之前启用该中间件

编辑 smartproxy_scrapy/settings.py。

DOWNLOADER_MIDDLEWARES = {
    'smartproxy_scrapy.middlewares.SmartproxyRotationMiddleware': 749,
}

HTTPPROXY_ENABLED = True
DOWNLOAD_TIMEOUT = 30
RETRY_HTTP_CODES = [403, 407, 429, 500, 502, 503, 504]
RETRY_TIMES = 2
CONCURRENT_REQUESTS = 16
CONCURRENT_REQUESTS_PER_DOMAIN = 4

Scrapy 内置的 HttpProxyMiddleware 排序为 750,因此 749 可确保你的中间件先设置代理 URL。

步骤 6 — 按请求选择住宅或数据中心代理

对于地理定位或高匿名性页面,使用 meta={'proxy_pool': 'residential'}。对于高吞吐量、稳定的页面,使用 meta={'proxy_pool': 'datacenter'}。

import scrapy


class SmartproxyDemoSpider(scrapy.Spider):
    name = 'smartproxy_demo'
    start_urls = ['https://httpbin.org/ip']

    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(
                url,
                meta={'proxy_pool': 'residential'},
                callback=self.parse,
                dont_filter=True,
            )
            yield scrapy.Request(
                url,
                meta={'proxy_pool': 'datacenter'},
                callback=self.parse,
                dont_filter=True,
            )

    def parse(self, response):
        yield {
            'pool': response.meta.get('smartproxy_pool'),
            'status': response.status,
            'origin': response.text.strip(),
        }

步骤 7 — 在代理池之间添加回退重试

目标网站返回 403 或 429 通常意味着当前 IP 被封禁。在 SmartproxyRotationMiddleware 类中添加此方法,以便失败的请求使用另一个 Smartproxy 代理池重试一次。

    def process_response(self, request, response, spider):
        retryable = {403, 429}
        already_switched = request.meta.get('smartproxy_pool_retry', False)

        if response.status in retryable and not already_switched:
            current = request.meta.get('proxy_pool', 'residential')
            alternate = 'datacenter' if current == 'residential' else 'residential'

            new_meta = request.meta.copy()
            new_meta['proxy_pool'] = alternate
            new_meta['smartproxy_pool_retry'] = True

            spider.logger.warning(
                'Retrying %s with %s pool after HTTP %s',
                request.url,
                alternate,
                response.status,
            )
            return request.replace(meta=new_meta, dont_filter=True)

        return response

如果你使用此回退机制,请将 RETRY_TIMES 保持较低,以免被阻止的请求无限循环。

步骤 8 — 运行 Spider 并保存结果

scrapy crawl smartproxy_demo -O smartproxy_results.json

步骤 9 — 使用 cURL 验证代理出口 IP

在 Scrapy 之外使用相同的 Smartproxy HTTP 端点,以确认凭据和连通性。

curl --proxy 'http://USERNAME:PASSWORD@HOST:PORT' https://httpbin.org/ip

将 USERNAME、PASSWORD、HOST 和 PORT 替换为你正在测试的代理池的值。

故障排除

  • 407 Proxy Authentication Required:检查用户名和密码,确保它们已进行 URL 编码,并确认你为所选代理池使用了正确的主机和端口。
  • Tunnel connection failed 或 TLS 错误:确认 Smartproxy 端点支持通过 HTTP CONNECT 访问 HTTPS 目标。请先使用步骤 9 中的 cURL 命令进行测试。
  • Scrapy 忽略了代理:确认 SmartproxyRotationMiddleware 在 DOWNLOADER_MIDDLEWARES 中的值低于 750,并且 HTTPPROXY_ENABLED 为 True。
  • 频繁出现 403 或 429:降低 CONCURRENT_REQUESTS_PER_DOMAIN,添加 DOWNLOAD_DELAY,切换到住宅代理,并且仅在目标允许的情况下轮换 User-Agent。
  • 响应缓慢:数据中心代理通常更适合原始吞吐量;住宅代理更适合地理定位请求以及会屏蔽数据中心 IP 段的网站。
  • 凭据包含 @、: 或 /:请保留中间件中的 urllib.parse.quote 调用。引用可防止代理 URL 格式错误。

总结

你现在拥有一个 Scrapy 项目,可在 Linux 上通过 Smartproxy 住宅和数据中心 HTTP 代理路由请求。该中间件会为每个请求选择一个代理池,将凭据保存在环境变量中,并可在目标屏蔽某个 IP 时回退到另一个代理池。请从低并发开始,使用 cURL 验证出口 IP,并且仅在爬虫行为稳定后才提高 CONCURRENT_REQUESTS_PER_DOMAIN。