5月31日 00:57

Scrapy 遇到 JavaScript 动态网页怎么办?

直接答案

Scrapy 本身不会执行 JavaScript,它拿到的是服务器直接返回的 HTML。遇到动态网页时,第一步不是立刻上 Selenium 或 Playwright,而是打开浏览器开发者工具,找页面真正请求的数据接口。如果数据来自 XHR 或 Fetch,请优先用 Scrapy 直接请求接口;只有内容必须经过浏览器渲染、签名依赖运行时环境、或交互流程很重时,才把 Playwright、Selenium、Splash 接进来。这个取舍很重要,因为浏览器渲染的成本通常比普通 HTTP 请求高一个数量级。

python
# settings.py DOWNLOAD_HANDLERS = { "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler", } TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor" PLAYWRIGHT_BROWSER_TYPE = "chromium" # spider.py yield scrapy.Request( "https://example.com/list", meta={"playwright": True, "playwright_include_page": False}, callback=self.parse, )

如果能抓接口,就直接复用接口参数、请求头、分页字段和必要 token。接口方案快、稳定、并发高,也更容易做重试和监控。浏览器方案适合少量复杂页面,但要限制并发、设置超时、关闭页面资源,否则内存会很快涨上去。实际项目里常见的做法是混合:列表接口用 Scrapy 抓,少数详情页或反爬校验页才走 Playwright。

追问

Selenium、Playwright 和 Splash 怎么选?

新项目优先考虑 Playwright,因为它支持现代浏览器、异步能力好,和 Scrapy 集成也更顺。Selenium 生态成熟,适合已有浏览器自动化脚本或必须兼容特定驱动的团队。Splash 较轻,适合简单渲染和 Lua 脚本控制,但面对复杂前端应用时调试体验不如真浏览器。取舍边界是交互复杂度和吞吐量:越像真实用户操作,越偏 Playwright;越像批量渲染 HTML,越要控制成本。

为什么页面在浏览器能看到,Scrapy 里却没有?

因为浏览器看到的是 HTML、JS、接口数据和运行时状态合成后的结果,而 Scrapy 默认只拿第一份 HTML。你应该先查看 Network 面板,过滤 XHR/Fetch,看数据是不是来自 JSON 接口。还要检查接口是否依赖 cookie、Referer、签名参数、时间戳或 Authorization。一个常见坑是只复制接口 URL,没有复制必要请求头,导致接口返回空数据或风控页面。

使用 Playwright 会带来哪些坑?

最大坑是资源泄漏,页面、上下文或浏览器实例没有关闭时,爬虫跑一段时间就会内存暴涨。第二个坑是等待条件写得太宽,比如固定 sleep 三秒,既慢又不稳定。更好的方式是等待某个选择器、接口响应或 DOM 状态出现。还要注意并发数不能沿用纯 Scrapy 的配置,浏览器并发通常要小得多,否则机器 CPU 和目标站风控都会先扛不住。

动态网页一定要完整渲染吗?

不一定。很多页面只是首屏用 JS 拉接口,真正数据在 JSON 里,完整渲染反而是绕远路。只有当数据经过前端计算、加密逻辑藏在 JS 里、或必须点击展开后才出现时,渲染才有价值。边界判断可以很朴素:如果 Network 里能稳定复现接口,就抓接口;如果接口参数无法还原,再考虑浏览器。不要为了“像真人”而默认渲染所有页面,那会让爬虫变慢、变贵、也更难排错。

如何让动态页面爬虫更稳定?

先把请求链路拆清楚:入口页、接口、详情页、登录态、反爬校验分别记录日志和状态码。对浏览器渲染请求设置单独超时和重试,不要让一个页面卡住整个调度器。静态资源如图片、字体、视频可以拦截掉,减少带宽和内存压力。配置上建议把浏览器并发、普通请求并发分开看,别用一个 CONCURRENT_REQUESTS 解决所有问题。

动态网页还有一个现实问题:你看到的“渲染失败”不一定是 JS 没执行,可能是接口被风控、地区不匹配、账号权限不足或首屏骨架屏还没消失。排查时最好保存三类证据:原始 HTML、关键接口响应、渲染后的截图。只看最终选择器为空,很容易误判方向。对于大型采集任务,可以先用少量 URL 跑 Playwright 验证渲染路径,再把能还原的接口逐步替换成普通 Scrapy 请求。这样浏览器只承担兜底角色,整体成本会可控得多。

配置层面还要把浏览器请求单独标记出来,方便统计成功率和耗时。比如在 meta 里加 rendered=True,日志里区分普通请求和渲染请求。遇到超时、空页面、验证码时,不要无限重试浏览器请求,可以降级保存现场交给人工分析。浏览器池本身也要定期重启,否则长时间运行后可能出现句柄泄漏、缓存膨胀和页面上下文污染。

标签:Scrapy