Scrapy与Splash结合爬取JavaScript渲染页面

在网络爬虫的开发过程中,我们经常会遇到一类 "棘手" 的目标网站 ------ 基于 JavaScript 动态渲染的页面。这类网站不会在初始 HTML 中直接返回完整数据,而是通过前端脚本异步加载、渲染内容。传统的 Scrapy 爬虫直接解析响应文本,往往只能拿到空壳结构,无法获取有效信息。此时,将ScrapySplash结合,就成为了攻克这类网站的高效方案。

一、核心原理:为什么需要 Splash?

Scrapy 是 Python 生态中功能强大的爬虫框架,它以高效的异步请求、灵活的数据处理流程著称,但它的短板在于无法执行 JavaScript 代码。当爬虫向目标网站发送请求时,得到的响应是未经渲染的原始 HTML,其中动态加载的列表、数据、按钮等元素都尚未生成。

Splash 则是一个专门用于渲染 JavaScript 页面的轻量级工具,它本质是一个带有 HTTP API 的无界面浏览器,基于 Qt5 和 WebKit 内核。Splash 可以模拟真实浏览器的行为:加载页面、执行 JS 脚本、等待 DOM 渲染完成,然后返回渲染后的完整 HTML 页面,甚至可以截取页面截图、获取页面加载的 HAR 数据。

Scrapy 与 Splash 的结合逻辑非常清晰:

  1. Scrapy 不再直接向目标网站发送请求,而是将请求参数发送给 Splash 服务;
  2. Splash 接收请求后,模拟浏览器加载目标页面,完成 JS 渲染;
  3. Splash 将渲染后的 HTML 页面返回给 Scrapy;
  4. Scrapy 对渲染后的 HTML 进行解析,提取目标数据。

二、环境准备:安装与配置

在开始编写爬虫之前,我们需要完成基础环境的搭建,主要分为Splash 服务部署Python 依赖安装两步。

2.1 部署 Splash 服务

Splash 支持多种部署方式,最推荐的是Docker 容器部署,这种方式无需处理复杂的依赖关系,一键即可启动服务。

  1. 确保本地已安装 Docker 环境,执行以下命令拉取 Splash 镜像: bash

    运行

    复制代码
    docker pull scrapinghub/splash
  2. 启动 Splash 容器,映射端口(默认端口为 8050): bash

    运行

    复制代码
    docker run -p 8050:8050 scrapinghub/splash
  3. 验证服务是否启动成功:打开浏览器访问 http://localhost:8050,若能看到 Splash 的测试页面,则说明部署成功。

2.2 安装 Python 依赖库

需要安装scrapy-splash库,它是 Scrapy 与 Splash 通信的桥梁,提供了专门的下载器中间件和请求类。

bash

运行

复制代码
pip install scrapy-splash

三、Scrapy 项目配置:接入 Splash

创建一个新的 Scrapy 项目后,我们需要修改settings.py配置文件,让 Scrapy 能够使用 Splash 服务。

3.1 核心配置项

打开项目根目录下的settings.py,添加或修改以下配置:

python

运行

复制代码
# 1. 配置Splash服务的地址
SPLASH_URL = 'http://localhost:8050'

# 2. 启用Splash相关的下载器中间件
DOWNLOADER_MIDDLEWARES = {
    'scrapy_splash.SplashCookiesMiddleware': 723,
    'scrapy_splash.SplashMiddleware': 725,
    'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}

# 3. 配置Splash的去重过滤器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

# 4. 配置缓存存储(可选,用于缓存Splash的响应)
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'

这些配置的核心作用是让 Scrapy 的请求走 Splash 通道,并处理 Splash 响应的去重和缓存问题。

四、编写爬虫:实战爬取 JS 渲染页面

接下来,我们以一个简单的 JS 渲染页面为例,编写完整的爬虫代码。假设目标网站的动态数据是通过 Ajax 加载的商品列表,我们需要提取商品名称和价格。

4.1 创建爬虫文件

在项目的spiders目录下创建js_spider.py,代码如下:

python

运行

复制代码
import scrapy
from scrapy_splash import SplashRequest

class JsRenderSpider(scrapy.Spider):
    name = 'js_render_spider'
    allowed_domains = ['example.com']  # 替换为目标域名
    start_urls = ['https://example.com/js-render-page']  # 替换为目标URL

    # 定义Splash的Lua脚本:用于控制页面渲染过程
    lua_script = """
    function main(splash, args)
        -- 设置页面加载超时时间
        splash:set_timeout(10)
        -- 加载目标页面
        assert(splash:go(args.url))
        -- 等待页面渲染完成(等待5秒,或等待指定元素出现)
        assert(splash:wait(5))
        -- 返回渲染后的页面HTML
        return {
            html = splash:html(),
            -- 可选:返回页面截图
            -- png = splash:png(),
        }
    end
    """

    def start_requests(self):
        for url in self.start_urls:
            # 使用SplashRequest替代scrapy.Request
            yield SplashRequest(
                url=url,
                callback=self.parse,
                endpoint='execute',  # 指定Splash的执行端点
                args={
                    'lua_source': self.lua_script,  # 传入Lua脚本
                    'wait': 2  # 额外的等待参数
                }
            )

    def parse(self, response):
        # 解析渲染后的HTML(与普通Scrapy解析方式一致)
        products = response.xpath('//div[@class="product-item"]')
        for product in products:
            yield {
                'name': product.xpath('.//h3/text()').get().strip(),
                'price': product.xpath('.//span[@class="price"]/text()').get().strip()
            }

4.2 代码核心解析

  1. SplashRequest 替代 Request :这是与普通爬虫的核心区别,SplashRequest会将请求发送到 Splash 服务,并携带 Lua 脚本控制渲染流程。
  2. Lua 脚本的作用 :Lua 脚本是 Splash 的灵魂,它可以实现复杂的页面操作:
    • splash:go(args.url):加载目标 URL;
    • splash:wait(5):等待 5 秒,确保 JS 脚本执行完毕、数据加载完成;
    • splash:html():获取渲染后的 HTML 源码;
    • 除此之外,还可以实现click(点击元素)、fill(填写表单)、scroll_position(滚动页面)等操作。
  3. 解析响应:经过 Splash 渲染后的响应,与普通 HTML 响应的解析方式完全一致,我们可以使用 XPath 或 CSS 选择器轻松提取数据。

五、高级技巧:优化渲染与爬取效率

在实际爬取过程中,我们需要根据目标网站的特性优化爬虫,避免出现超时、漏数据等问题。

5.1 智能等待:替代固定等待时间

固定的splash:wait(5)不够灵活,页面加载快时会浪费时间,加载慢时会导致渲染不完整。我们可以通过等待指定元素出现来优化:

lua

复制代码
-- 等待class为"product-list"的元素出现,超时时间10秒
assert(splash:wait_for_element('div.product-list', 10))

5.2 处理动态加载的分页

对于无限滚动的分页页面,可以通过 Lua 脚本模拟滚动操作,触发更多数据加载:

lua

复制代码
function main(splash, args)
    splash:go(args.url)
    splash:wait(2)
    -- 模拟向下滚动3次,每次滚动后等待2秒
    for i=1,3 do
        splash:evaljs("window.scrollTo(0, document.body.scrollHeight)")
        splash:wait(2)
    end
    return splash:html()
end

5.3 禁用图片加载:提升爬取速度

如果不需要页面截图,禁用图片、CSS 等资源的加载,可以大幅提升 Splash 的渲染速度:

lua

复制代码
function main(splash, args)
    splash.images_enabled = false  -- 禁用图片加载
    splash:go(args.url)
    splash:wait(3)
    return splash:html()
end

六、常见问题与解决方案

  1. Splash 服务连接失败

    • 检查 Docker 容器是否正常运行:docker ps
    • 确认SPLASH_URL配置正确,防火墙未拦截 8050 端口。
  2. 渲染后的页面仍无目标数据

    • 延长等待时间或使用wait_for_element等待关键元素;
    • 检查目标网站是否有反爬机制,可通过splash:set_user_agent()设置浏览器 UA。
  3. 爬虫效率过低

    • 禁用图片、CSS 加载;
    • 调整 Scrapy 的并发数(CONCURRENT_REQUESTS),避免 Splash 服务过载。

七、总结

Scrapy 与 Splash 的组合,完美解决了 JavaScript 渲染页面的爬取难题。Scrapy 负责请求调度、数据解析和持久化,Splash 负责模拟浏览器渲染,两者分工明确,极大拓展了爬虫的适用场景。在实际开发中,我们需要根据目标网站的特性编写灵活的 Lua 脚本,同时优化配置和爬取策略,才能在效率和稳定性之间找到最佳平衡点。

需要注意的是,爬虫开发应遵守目标网站的robots.txt协议,避免对服务器造成过大压力,做一个合规的开发者。

相关推荐
AIFQuant1 小时前
2026 澳大利亚证券交易所(ASX)API 接入与 Python 量化策略
开发语言·python·websocket·金融·restful
木头左1 小时前
VIX期货基差异常下的指数期权波动率互换套利策略实现
python
人工干智能2 小时前
python的高级技巧:Pandas中的`iloc[]`和`loc[]`
开发语言·python·pandas
donecoding2 小时前
AI时代程序员的护城河:让AI做创意组合,用标准化工具守住质量底线
javascript·架构·代码规范
未定义.2212 小时前
第5篇:进阶优化:数据驱动+日志体系+失败重试实战
python·ui·自动化·jenkins·集成测试·pytest
明月_清风2 小时前
Async/Await:让异步像同步一样简单
前端·javascript
倔强的钧仔2 小时前
拒绝废话!前端开发中最常用的 10 个 ES6 特性(附极简代码)
前端·javascript·面试
喔烨鸭2 小时前
vue3中使用原生表格展示数据
前端·javascript·vue.js
小白学大数据2 小时前
随机间隔在 Python 爬虫中的应用实践
开发语言·c++·爬虫·python