没有哪个用爬虫的人没遇到过这种诡异场面:requests.get() 把页面 HTML 完整地拿回来了,状态码 200,编码也对,源码里什么标签都有,看起来一切正常,可你翻遍整段 HTML 却找不到想要的数据。打开浏览器一看,数据明明显示得好好的。这段“浏览器里有、源码里没有”的内容,就是 JavaScript 渲染出来的。处理这类页面的核心手段之一,就是 Selenium——一个原本属于自动化测试的工具,却被爬虫开发者发掘成了对付动态渲染页面的大杀器。这篇博文我会把 Selenium 处理 JavaScript 渲染的完整思路讲透:从底层原理、环境配置、等待机制,到无限滚动页面的实战代码,再到实际项目里踩过的坑和关于风控的一些真实体会。
1. 为什么页面源码总比浏览器里“少一段”:JS 渲染的底层拆解
1.1 浏览器不是“打开文档”,而是“把文档跑起来”
很多刚接触爬虫的人会下意识地把浏览器理解成一个“HTML 阅读器”:输入网址,服务器把文档发过来,浏览器把标签渲染成漂亮的页面。这个理解对 90% 的静态页面没问题,但对现代 Web 应用来说,它忽略了一个极其关键的环节——脚本执行。
真实情况是:浏览器拿到 HTML 之后,会先解析 DOM 树,碰到 <script> 标签就开始执行 JavaScript。而现代前端框架(Vue、React、Angular)几乎都是“数据驱动视图”的模式,HTML 里往往只有一个根节点,页面上的表格、列表、卡片统统是由 JavaScript 在运行时动态创建并塞进 DOM 的。也就是说,你通过 requests 拿到的 HTML 更像是“一座刚浇筑完框架、还没通水电的毛坯房”,而浏览器里的最终页面是 JavaScript 工人进场施工之后的精装成品。爬虫看不到数据,不是因为数据不存在,而是因为你没有执行那一段段决定“家具摆在哪”的 JavaScript。
1.2 常见三类 JS 渲染页面的特征信号
要想快速判断一个页面是否需要 Selenium,最好的办法不是猜,而是“看源码找特征”。实战中我总结了三种高频出现的情况,特征非常明显:
| 页面类型 | 特征信号 | 直观表现 |
|---|---|---|
| 接口渲染型 | HTML 是空的,但代码里能找到大量 fetch(、axios.get(、XMLHttpRequest |
Requests 抓回来的 body 里只有 <div id="app"> 这类空壳节点 |
| 前端框架型 | 引入 vue.js、react.js、app.js 文件,业务逻辑高度集中在一个 JS 包里 |
HTML 中包含大量 {{ }}、v-for、data-xxx 之类的绑定痕迹 |
| 滚动加载型 | 页面底部没有完整翻页按钮,伴随 “正在加载” 或 Infinity Scroll 组件 | 直接请求第二页 URL 无效,数据由滚动事件触发分段加载 |
以第一种最常见。你按 F12 打开开发者工具的 Network 面板,刷新一次页面,耐心盯一下 XHR/Fetch 类型的请求,会发现页面数据几乎一定来自一些 JSON 接口。如果这个接口你拿 requests 直接请求也能通、也没做签名校验,那就完全没有必要上 Selenium。真正需要 Selenium 的场景,是当你分析完这一堆请求之后发现:接口带时间戳签名、带加密参数、带登录态校验,或者你光看 JS 混淆代码就头大的时候——与其逆向半天,不如让浏览器自己帮我们把 JavaScript 全跑了。
1.3 什么时候该选 Selenium:成本比较才是决定因素
这部分我想传递一个不那么“技术崇拜”的观点:使用 Selenium 的本质不是因为它比接口逆向高级,而是因为它把“执行 JavaScript”这件事外包给了浏览器。代价是速度和资源——一个 Chrome 实例动辄占用几百 MB 内存,每次启动和关闭都是实打实的时间开销。
我的判断标准很朴素:当你花 15 分钟分析网络请求能搞定的事,不要碰 Selenium;当你面对一个重度混淆的网页版应用,网络请求里全是加密参数,逆向工程量估计要 2~3 天,而 Selenium 只要半天就能把数据跑出来,那就果断用 Selenium。工具是为人服务的,不是拿来证明技术水平的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么最后挑中 Selenium:老牌工具的生态红利与横向对比
2.1 Selenium 到底在“自动化什么”
Selenium 最初是给测试人员做浏览器自动化回归测试用的。它的核心思路是:通过一套 WebDriver 协议,让外部程序能够像真实用户一样操控浏览器——打开网址、点击按钮、填写表单、滚动页面、读取 DOM。
这里的“像真实用户一样”是理解 Selenium 在爬虫场景价值的关键点。因为它调度的本身就是系统中安装的那个 Chrome 或 Firefox,JavaScript 在这种环境下是完完全全执行的。相比用 requests 模拟每一个异步请求,Selenium 是更高层级的抽象:你不需要关心页面到底发起了多少个 AJAX 请求、如何加密参数,只需要一个指令——把页面整个加载完,然后把渲染后的 DOM 给我。这种粗暴但有效的模式,就是它处理 JS 渲染最核心的逻辑。
2.2 Selenium 4 带来的实际变化
需要补充一点,如果你翻到的教程还在写 driver.find_element_by_xpath(),那可能是 Selenium 3 时代的产物。Selenium 4 里这些 find_element_by_* 方法被彻底移除了,统一改成 driver.find_element(By.XPATH, "...") 的写法。刚切换到 Selenium 4 的同事经常在这报错,所以文章后面涉及定位的代码我都会写成新写法。
Selenium 4 还带来两个对爬虫很有用的特性:一是原生支持了 Chrome DevTools Protocol 的部分能力,比如模拟网络条件、监听网络请求;二是引入了相对定位器,可以通过“在某元素左边/右边/附近”来定位节点。不过说实话,爬虫场景用到这些高级功能的机会并不多,真正要熟悉的反而是等待、切换、执行脚本这三板斧。
2.3 和当前主流方案的横向取舍对比
很多初学者问我:“现在还有必要学 Selenium 吗?大家都说 Playwright 更好用。”我的回答是:有必要,而且学了不亏。
| 方案 | 执行 JS | 上手速度 | 定位粒度 | 资源占用 | 典型场景 |
|---|---|---|---|---|---|
| requests/httpx | 不执行 | 最快 | 接口级 | 极低 | 纯静态页、明文接口 |
| Selenium | 完整执行 | 快 | DOM 级 | 高 | 兼容多浏览器、需要点击/滚动等复杂交互 |
| Playwright | 完整执行 | 较快 | DOM 级+网络级 | 高 | 新项目,需要拦截网络、自动等待更强 |
| Puppeteer | 完整执行 | 中 | DOM 级+网络级 | 中 | 专注 Chrome/Chromium 生态 |
| 逆向接口 | 不执行 | 取决于难度 | 接口级 | 极低 | 大规模采集,追求性能和稳定 |
单看能力,Playwright 和 Puppeteer 在很多维度确实比 Selenium 更现代——它们能拦截响应、修改请求、自动等待网络空闲,内存控制也更出色。但 Selenium 最大的优势在于生态的稳定性和广度:几乎所有编程语言都有官方库,任何一个浏览器都支持,网上的存量案例和排错文章最多。对于一个需要长期交付的项目,“遇到一个诡异报错能在十分钟内搜到答案”本身就是巨大的效率优势。
3. 开工前最关键的 30 分钟:WebDriver 版本与浏览器匹配问题的踩坑实录
3.1 那个“Process finished with exit code 0” 的灵异事件
不少人第一次跑 Selenium 是在 PyCharm 里,写完代码一运行,控制台干干净净,什么输出都没有,只冒出一句 `Process fi
