跳转到内容
Guides 2 分钟阅读 1285 次浏览

在 Scrapy-Splash 中使用代理渲染 JavaScript 页面

了解如何在 Scrapy-Splash 项目中配置并使用 GProxy,渲染重度依赖 JavaScript 的页面,稳定抓取动态网页内容。

Python Parsing
在 Scrapy-Splash 中使用代理渲染 JavaScript 页面

将代理集成到 Scrapy-Splash 后,来自 Splash 渲染服务的请求会经由中间服务器转发,从而为 JavaScript 渲染的网页实现 IP 轮换、解除地区限制和匿名访问。

理解 Scrapy-Splash 的代理集成

Scrapy-Splash 把 Scrapy 的抓取框架与 Splash 的无头浏览器渲染能力结合在一起。在这套组合中配置代理,意味着 Splash 内部浏览器实例发出的网页请求都会经由指定的代理服务器。这适用于首次页面加载、后续的 AJAX 请求,以及页面 JavaScript 发起的其他所有网络活动。

为什么要在 Scrapy-Splash 中使用代理?

用 Scrapy-Splash 抓取动态内容时,代理承担几项关键作用:
* 绕过基于 IP 的频率限制和封禁: 网站常按来源 IP 地址限制访问。代理可以把请求分散到多个 IP 上,缓解这类限制。
* 访问受地区限制的内容: 位于特定地区的代理可以访问抓取端所在地无法获取的内容。
* 保持匿名: 代理隐藏抓取端的真实 IP 地址,提升运营安全性。
* 分摊负载: 在大规模作业中,代理有助于分摊网络负载,降低单个 IP 被压垮或被标记的概率。

Scrapy-Splash 如何处理代理请求

  1. Scrapy 向 Splash 守护进程发送一个 SplashRequest
  2. Splash 收到请求后,如果带有 proxy 参数,就配置其内部浏览器实例(例如 Chromium),让所有网络流量经由该代理转发。
  3. 浏览器实例访问目标 URL,渲染 JavaScript,并通过配置的代理发起必要的网络调用(例如 XHR、加载资源)。
  4. Splash 把完全渲染后的 HTML、截图或其他所请求的数据返回给 Scrapy。

在 Scrapy-Splash 中配置代理

代理集成的主要方式是 SplashRequest 中的 proxy 参数。

基础代理配置

要为某个请求使用代理,在 SplashRequestargs 字典中传入 proxy 参数。代理 URL 格式为 [protocol://][user:password@]host:port

import scrapy
from scrapy_splash import SplashRequest

class BasicProxySpider(scrapy.Spider):
    name = 'basic_proxy_spider'
    start_urls = ['http://quotes.toscrape.com/js/']

    def start_requests(self):
        # 使用基础 HTTP 代理的示例
        # 替换为你实际的代理 IP 和端口
        yield SplashRequest(
            url=self.start_urls[0],
            callback=self.parse,
            args={
                'wait': 0.5,
                'proxy': 'http://your_proxy_ip:port'
            }
        )

    def parse(self, response):
        title = response.css('title::text').get()
        yield {
            'title': title,
            'url': response.url,
            'proxy_used': response.request.meta.get('splash', {}).get('args', {}).get('proxy')
        }

带认证的代理

对于需要认证的代理,把用户名和密码直接嵌入代理 URL 字符串中。

import scrapy
from scrapy_splash import SplashRequest

class AuthenticatedProxySpider(scrapy.Spider):
    name = 'auth_proxy_spider'
    start_urls = ['http://quotes.toscrape.com/js/']

    def start_requests(self):
        # 替换为你实际的代理信息
        proxy_user = 'your_username'
        proxy_pass = 'your_password'
        proxy_host = 'your_proxy_ip'
        proxy_port = 'port'

        authenticated_proxy_url = f'http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}'

        yield SplashRequest(
            url=self.start_urls[0],
            callback=self.parse,
            args={
                'wait': 0.5,
                'proxy': authenticated_proxy_url
            }
        )

    def parse(self, response):
        title = response.css('title::text').get()
        yield {
            'title': title,
            'url': response.url,
            'proxy_used': response.request.meta.get('splash', {}).get('args', {}).get('proxy')
        }

动态代理选择与轮换

如果每个请求需要不同代理或需要轮换机制,可在 spider 内维护一个代理列表并动态选取。

import scrapy
from scrapy_splash import SplashRequest
import random

class RotatingProxySpider(scrapy.Spider):
    name = 'rotating_proxy_spider'
    start_urls = ['http://quotes.toscrape.com/js/', 'http://toscrape.com/']

    # 定义代理列表(替换为你实际的代理)
    # 带认证的代理写作 'http://user:pass@host:port'
    proxy_list = [
        'http://proxy1_ip:port1',
        'http://user:pass@proxy2_ip:port2',
        'http://proxy3_ip:port3',
    ]

    def start_requests(self):
        for url in self.start_urls:
            selected_proxy = random.choice(self.proxy_list)
            yield SplashRequest(
                url=url,
                callback=self.parse,
                args={
                    'wait': 0.5,
                    'proxy': selected_proxy
                },
                # 也可以传入自定义 meta 来记录使用了哪个代理
                meta={'proxy_selected': selected_proxy}
            )

    def parse(self, response):
        title = response.css('title::text').get()
        yield {
            'title': title,
            'url': response.url,
            'proxy_used': response.request.meta.get('proxy_selected') # 读取自定义 meta
        }

全局代理配置(Splash 守护进程)

可以把 Splash 配置为对所有出站请求使用默认代理,通常在启动 Splash 守护进程前设置 HTTP_PROXYHTTPS_PROXY 环境变量即可。这虽然提供了全局默认值,但在动态抓取任务中,其可控性不如按请求指定代理。

代理类型及其影响

代理类型的选择会影响匿名性、性能和被识别的风险。

项目 数据中心代理 住宅代理
IP 来源 商业数据中心 真实的住宅 ISP
匿名性 中等(IP 常属于已知网段) 高(IP 看起来像普通家庭上网用户)
速度 依托专用基础设施,通常更快 因经由住宅网络转发,可能较慢
成本 每个 IP 更便宜 按 IP 或带宽计费,更贵
被识别风险 更易被成熟的反机器人系统识别和封禁 不易被识别,更难封禁
适用场景 常规抓取、防护较弱站点的高量任务 高度敏感的抓取、绕过高级反机器人系统

代理协议

  • HTTP/HTTPS 代理: 处理标准网页流量。Splash 完整支持这两种协议。
  • SOCKS 代理: SOCKS(SOCKS4、SOCKS5)代理工作在更底层,可处理多种网络协议,不限于 HTTP/HTTPS。要在 Splash 中使用 SOCKS 代理,请在 proxy URL 中指明协议(例如 socks5://user:pass@host:port)。

Sticky 代理与轮换代理

  • Sticky 代理: 在设定时长内(例如几分钟到几小时)或整个会话期间保持同一 IP 地址。适合在要求 IP 一致的目标网站上维持会话状态。
  • 轮换代理: 每次请求或每隔较短的固定间隔分配一个新 IP 地址。适合高量抓取,此时频繁更换来源 IP 以避免封禁至关重要。

排障与最佳实践

验证代理连通性

大规模部署前,先单独测试代理。一条简单的 curl 命令或一段 Python requests 脚本即可确认代理是否可用、是否可达。

curl --proxy http://your_proxy_ip:port http://httpbin.org/ip

查看 Splash 日志

Splash 内与代理连通性或认证相关的问题通常由 Splash 守护进程记录。调试时请查看 Splash 的控制台输出或日志文件。

妥善处理代理错误

实现重试机制或代理轮换逻辑来应对失败的请求。如果某个代理持续失败,就把它移出活跃池,或在一段时间内标记为不健康。Scrapy 的 retry 中间件可以改造使用,但针对代理的失败处理通常需要在 spider 中编写自定义逻辑。

性能考量

代理会增加一次网络跳转,从而抬高延迟。
* 代理池管理: 建立一套机制跟踪代理健康度、响应时间和用量,优先使用更快、更可靠的代理。
* 资源占用: Splash 本身就很吃资源,使用代理还会增加开销。请确保 Splash 守护进程有足够的 CPU 和内存来承担叠加负载。

针对具体站点的反机器人措施

高级反机器人系统识别的模式远不止 IP 地址。即便使用住宅代理,站点仍可能识别出自动化浏览。可微调 user-agentviewportbrowser_params 等 Splash 参数,并使用自定义 Lua 脚本模拟更接近真人的交互来应对。

IP 泄露

确认代理确实屏蔽了抓取端的真实 IP。在 Splash 内使用 http://httpbin.org/iphttps://ipleak.net/ 之类的服务来核对可见的 IP 地址。

# 在 Splash 内检查可见 IP 的 Lua 脚本
lua_script = """
function main(splash)
    splash:set_proxy_auto() -- 若通过 'proxy' 参数设置,则确保使用该代理
    splash:go("http://httpbin.org/ip")
    splash:wait(0.5)
    return splash:html()
end
"""

# 使用该 Lua 脚本的 SplashRequest 示例
yield SplashRequest(
    url="about:blank", # 此处 URL 无关紧要,导航由 Lua 处理
    callback=self.parse_ip_check,
    endpoint='execute',
    args={
        'lua_source': lua_script,
        'proxy': 'http://your_proxy_ip:port',
        'timeout': 90 # 为 Lua 脚本调高超时时间
    }
)

def parse_ip_check(self, response):
    # 解析 httpbin.org/ip 返回的 HTML 以提取 IP
    ip_address = response.css('pre::text').get() # 如果 httpbin 变更,请调整选择器
    self.logger.info(f"Visible IP from Splash via proxy: {ip_address}")
    # 后续处理……
已更新: 04.03.2026
返回分类

试用我们的代理

遍布 100+ 国家的 20,000+ 代理

support_agent
GProxy Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.