pyspider 实战:用 PhantomJS 渲染 JavaScript 页面(Level 3 教程详解)

📅 发布时间:2026/9/21 18:01:44
pyspider 实战:用 PhantomJS 渲染 JavaScript 页面(Level 3 教程详解)
pyspider 实战用 PhantomJS 渲染 JavaScript 页面Level 3 教程详解【免费下载链接】pyspiderA Powerful Spider(Web Crawler) System in Python.项目地址: https://gitcode.com/gh_mirrors/py/pyspider本篇是 pyspider 官方教程的 Level 3 深化版围绕fetch_typejs与js_script两条主线讲解如何让爬虫在真实浏览器内核中执行 JavaScript从而抓取 AJAX 异步加载、无限滚动等普通 HTTP 请求无法获取的内容。读完本文你将掌握 PhantomJS 的启动验证、在self.crawl中启用 JS 渲染的完整参数体系、用注入脚本模拟滚动等交互行为的写法并理解从 Fetcher 到 PhantomJS 代理的底层调用链。为什么需要 PhantomJS从 Level 2 的 AJAX 说起在 Level 2AJAX and More HTTP 中我们讨论过现代网页大量使用 AJAX 技术异步加载数据页面返回的 HTML 里根本没有目标内容数据是通过 XMLHttpRequest 拉取 JSON 后由客户端 JavaScript 渲染出来的。此时直接抓取 HTML 会得到空壳页面。处理这类问题通常有两种思路逆向找 APILevel 2 的做法用 Chrome DevTools 的 Network 面板过滤 XHR 请求找到真正的数据接口直接请求 JSON。让浏览器帮我们渲染本教程的做法当页面过于复杂、接口难以定位或页面逻辑如滚动加载、点击展开无法用简单 HTTP 模拟时使用 PhantomJS 这个无头浏览器真正打开页面、执行全部 JavaScript再把渲染后的 DOM 交给 pyspider 解析。本教程原文档 Render-with-PhantomJS 给出了两个典型场景Twitch 频道列表AJAX 页面和 Pinterest 图片流无限滚动下面逐一展开并补充源码级实现细节。前置准备安装并启动 PhantomJS 代理安装 PhantomJS要使用 PhantomJS首先需要安装 PhantomJS 本体官方安装包或系统包管理器均可。安装完成后将可执行文件加入PATH环境变量。按 pyspider 的设计如果你以all模式运行 pyspider只要PATH中能找到phantomjs可执行文件PhantomJS 就会自动被启用无需额外配置。验证 PhantomJS 是否可用运行以下命令验证$ pyspider phantomjs如果一切正常你会看到类似输出Web server running on port 25555其中25555是 pyspider 约定的 PhantomJS 代理默认端口。看到这行输出说明 PhantomJS 代理已成功监听可以继续后续教程。从源码看该命令由 run.py 中的phantomjs子命令 实现它定位仓库自带的 pyspider/fetcher/phantomjs_fetcher.js 脚本拼出类似phantomjs --ssl-protocolany --disk-cachetrue phantomjs_fetcher.js 25555的命令行并启动子进程如果PATH中找不到 phantomjs抛OSError则打印phantomjs not found, continue running without it.并返回pyspider 其余组件照常运行只是 JS 渲染功能不可用。同时该子命令还支持--phantomjs-path指定 phantomjs 路径、--port默认 25555和--auto-restart崩溃后自动重启等参数详见 docs/Command-Line.md。关联到 Fetcher--phantomjs-proxy在all模式下pyspider 会自动把127.0.0.1:25555写入全局配置的phantomjs_proxy。如果手动拆分组件运行也可以通过全局参数--phantomjs-proxy ip:port显式指定 PhantomJS 代理地址详见 docs/Command-Line.md 中--phantomjs-proxy一节。Fetcher 在初始化时读取该配置并保存为self.phantomjs_proxy见 pyspider/fetcher/tornado_fetcher.py。启用 JS 渲染fetch_typejs当 PhantomJS 与 pyspider 成功连接后只需在self.crawl中增加一个参数fetch_typejs该 URL 的抓取就会被转交给 PhantomJS 渲染。在 pyspider/fetcher/tornado_fetcher.py 中可以看到 Fetcher 的分发逻辑任务task携带的fetch字典中fetch_type为js或phantomjs时走phantomjs_fetch为splash时走 Splash为puppeteer时走 Puppeteer否则走普通 HTTP。也就是说fetch_typejs与fetch_typephantomjs等价本教程以js为准。fetch_type只是开启渲染的开关真正决定“渲染成什么样”的是配套的 JS 相关参数详见 docs/apis/self.crawl.md参数作用默认值fetch_type设为js启用 JavaScript FetcherNonejs_script页面加载前/后执行的 JavaScript需包装成function() { ... }无js_run_atjs_script在document-start还是document-end执行document-endjs_viewport_width/js_viewport_height设置渲染视口viewport的宽高影响页面布局1024 / 768*3load_images渲染时是否加载图片False这些字段全部被登记在 pyspider/libs/base_handler.py 的fetch_fields中即它们会从self.crawl的 kwargs 被提取并写入任务 fetch 配置最终由 Fetcher 原样转发给 PhantomJS。案例一抓取 Twitch 频道列表AJAX 页面Twitch 的频道目录页http://www.twitch.tv/directory/game/Dota%202正是上一章提到的典型 AJAX 页面——频道列表由页面 JavaScript 从接口动态加载直接抓 HTML 得不到任何频道数据。现在用 PhantomJS 直接渲染它class Handler(BaseHandler): def on_start(self): self.crawl(http://www.twitch.tv/directory/game/Dota%202, fetch_typejs, callbackself.index_page) def index_page(self, response): return { url: response.url, channels: [{ title: x(.title).text(), viewers: x(.info).contents()[2], name: x(.info a).text(), } for x in response.doc(.stream.item).items()] }要点说明fetch_typejs是唯一的开关只要加上它pyspider 就不再走普通 HTTP 抓取而是让 PhantomJS 打开页面、等待脚本执行完成后再取回渲染后的完整 DOM。response.doc用法不变渲染完成后response.doc中就是包含动态内容的完整 DOM可以像 Level 1HTML and CSS Selector一样用 PyQuery 选择器提取。这里用.stream.item定位每个频道条目再分别取标题、观众数、主播名。官方提示PyQuery 的完整 API 可参考其官方文档本项目教程中x(.info).contents()[2]这类操作就是 PyQuery 的节点内容访问。案例二抓取 Pinterest 无限滚动页面注入js_script有些页面的内容不是一次性加载而是靠用户操作触发的例如 Pinterest 的http://www.pinterest.com/categories/popular/初始只显示 25 张图片滚动到页面底部才会继续加载更多。对于这类需要“交互”才能加载完的页面可以用js_script参数注入一段包装成函数的 JavaScript模拟滚动操作class Handler(BaseHandler): def on_start(self): self.crawl(http://www.pinterest.com/categories/popular/, fetch_typejs, js_script function() { window.scrollTo(0,document.body.scrollHeight); } , callbackself.index_page) def index_page(self, response): return { url: response.url, images: [{ title: x(.richPinGridTitle).text(), img: x(.pinImg).attr(src), author: x(.creditName).text(), } for x in response.doc(.item).items() if x(.pinImg)] }使用js_script时有两点官方特别提醒执行时机脚本默认在页面加载完成后document-end执行也可以通过js_run_at参数改为document-start即在页面初始化阶段就执行详见 docs/apis/self.crawl.md 中js_run_at一节。多次滚动上面只滚动了一次。如果需要滚动多次可以在脚本里嵌套使用setTimeout递归滚动。PhantomJS 会在超时前尽量抓取到所有已加载的条目——也就是说setTimeout能让你模拟“滚到底 → 等加载 → 再滚到底”的循环直到拿到尽可能多的内容。脚本返回值Response.js_script_resultjs_script中return的值会被回传到 Python 侧通过Response.js_script_result访问。例如 docs/apis/self.crawl.md 中js_script一节的示例def on_start(self): self.crawl(http://www.example.org/, callbackself.callback, fetch_typejs, js_script function() { window.scrollTo(0,document.body.scrollHeight); return 123; } ) def callback(self, response): # 脚本返回的 123 会出现在这里 print(response.js_script_result)从实现上看js_script_result是 Response 对象的正式字段见 pyspider/libs/response.py它由 PhantomJS 代理在返回的 JSON 中携带见下文_make_resultFetcher 透传后由 Processor 装配进 Response。你可以在脚本里返回任何可序列化的值用于把浏览器侧的计算结果比如某个动态生成的 token带回 Python 逻辑。深入原理Fetcher 与 PhantomJS 代理的协作调用链总览从self.crawl(..., fetch_typejs)到拿到渲染结果完整链路如下Handler 侧BaseHandler._crawl按fetch_fields白名单提取参数含fetch_type、js_script、js_run_at等写入任务见 pyspider/libs/base_handler.py。Fetcher 侧TornadoFetcher.async_fetch根据fetch_type路由到phantomjs_fetch见 pyspider/fetcher/tornado_fetcher.py。代理侧phantomjs_fetch将整个 fetch 配置 JSON 序列化后以 HTTP POST 形式发送给self.phantomjs_proxy默认127.0.0.1:25555见 pyspider/fetcher/tornado_fetcher.py。PhantomJS 进程仓库自带的 pyspider/fetcher/phantomjs_fetcher.js 在 25555 端口起了一个 Web 服务收到 POST 后创建webpage对象打开 URL执行渲染与注入脚本最后把结果 JSON 写回响应。phantomjs_fetcher.js 内部流程phantomjs_fetcher.js 是整个 JS 渲染能力的关键实现值得逐段理解视口与资源设置page.viewportSize由fetch.js_viewport_width || 1024、fetch.js_viewport_height || 768*3决定page.settings.loadImages由fetch.load_images控制resourceTimeout由fetch.timeout秒乘以 1000 得到默认 20 秒见 L60-L74。脚本执行时机page.onInitialized中判断若js_script存在且js_run_at document-start则在文档初始化阶段用page.evaluateJavaScript执行page.onLoadFinished中处理document-end的情况执行后记录script_result见 L80-L97。结果收尾make_result中有一个wait_before_end 1000毫秒的等待窗口——页面加载完成后不会立刻返回而是等待 1 秒左右给页面上的异步渲染留出时间同时只要还有资源在传输page.onResourceReceived会重置end_time就会继续等待直到安静下来或达到resourceTimeout见 L7、L94-L126。结果组装_make_result把页面最终 URL、状态码、page.content渲染后的完整 HTML、cookies、headers、耗时以及script_result打包成 JSON 返回见 L196-L208。尚未启用 PhantomJS 时的行为如果在未启动 PhantomJS或未配置--phantomjs-proxy的情况下提交了fetch_typejs的任务phantomjs_fetch会直接返回一个status_code 501、content phantomjs is not enabled.的结果见 pyspider/fetcher/tornado_fetcher.py。这个 501 响应会进入回调函数方便你在脚本中做兜底处理或排查部署问题。代理的代理请求头、Cookie、robots.txtphantomjs_fetch在转发任务前还会做一些兼容处理把 Python 侧的 cookies 转成 Cookie 头合并进fetch[headers]见 pyspider/fetcher/tornado_fetcher.py支持proxy字段透传phantomjs_fetcher.js 中通过page.setProxy设置若任务开启了robots_txt还会先进行 robots.txt 检查再放行。也就是说你在self.crawl里熟悉的headers、cookies、proxy、timeout等参数在 JS 渲染模式下同样生效。测试验证仓库中如何验证 PhantomJS 功能仓库的测试套件 tests/test_fetcher.py 为 PhantomJS 渲染提供了可参考的验证用例测试启动时尝试拉起真实的phantomjs子进程运行pyspider/fetcher/phantomjs_fetcher.js找不到则跳过相关用例见 tests/test_fetcher.py。test_90_phantomjs_js_script构造fetch_typephantomjs的任务并注入js_script function() { document.write(binux) }随后断言响应中包含js_script_result字段见 tests/test_fetcher.py这与本文Response.js_script_result的说明一一对应。test_69_no_phantomjs在把phantomjs_proxy置空后提交fetch_typephantomjs任务验证“未启用时返回 501”的分支见 tests/test_fetcher.py。此外还有test_70_phantomjs_url、test_75_phantomjs_robots、test_80_phantomjs_timeout、test_a100_phantomjs_sharp_url等用例分别覆盖 URL 抓取、robots.txt、超时与特殊 URL 场景。这些测试一方面印证了本文描述的调用行为另一方面也可以作为你自己扩展脚本逻辑时的对照样本。使用建议与注意事项js_run_at的选择document-start适合需要在页面脚本运行前改环境如拦截网络请求、改写全局变量的场景默认的document-end适合绝大多数“等页面渲染完再提取”的需求。两者切换只需改一个参数。控制load_images与视口图片加载会显著拖慢渲染并增加内存占用默认关闭而 Pinterest 这类瀑布流页面如果视口太小可能影响“滚动触发加载”的效果必要时用js_viewport_height调高视口。渲染是有代价的每个fetch_typejs任务都要启动一个真实浏览器内核耗时与资源开销远高于普通 HTTP 抓取。建议只在“必须渲染”的页面上开启能用 Level 2 的接口逆向方案解决的优先用接口。超时与无限滚动Pinterest 例子中多次setTimeout滚动会拉长整个渲染时间注意任务级的timeout默认 120 秒与 PhantomJS 侧resourceTimeout的配合避免超时取回不完整内容。与crawl_config配合如果整个项目都需要 JS 渲染可以把fetch_typejs等参数放进Handler.crawl_config作为项目级默认值避免每个self.crawl重复书写详见 docs/apis/self.crawl.md 中Handler.crawl_config一节。至此pyspider 三级教程的能力已经齐备Level 1 解决“页面是静态 HTML”的解析Level 2 解决“数据在 AJAX 接口里”的逆向抓取Level 3 则用 PhantomJS 兜底所有“必须由真实浏览器执行 JavaScript”的场景。官方还提供了在线 demohttp://demo.pyspider.org/debug/tutorial_pinterest供对照学习。掌握fetch_typejsjs_scriptjs_run_at这套组合绝大多数动态页面都不再是抓取障碍。【免费下载链接】pyspiderA Powerful Spider(Web Crawler) System in Python.项目地址: https://gitcode.com/gh_mirrors/py/pyspider创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考