news 2026/10/6 16:16:48

Java Selenium爬虫实战:Chromedriver 118版本匹配与反爬工程化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Selenium爬虫实战:Chromedriver 118版本匹配与反爬工程化

简介:这是一套面向Java爬虫初学者与自动化测试入门者的Selenium实战资源,围绕Chrome 118.0.5958.0测试版环境搭建与Java爬虫开发展开,帮助读者解决浏览器与驱动版本不匹配、环境配置繁琐等常见问题。资源包共56个文件,约706.55MB,包含9个java源码与9个class文件构成的核心爬虫工程、12个js与2个html/css组成的页面资源、2个properties配置及pom.xml构建文件,另附Selenium学习笔记、演示视频和覆盖Windows、Linux、Mac多平台的Chrome与Chromedriver压缩包。已有121人学习下载。读者可获得可直接运行的Java爬虫实战代码、分模块的学习笔记、完整操作录屏,以及免去版本适配烦恼的浏览器与驱动组合,适合边看边练、快速上手Selenium自动化与爬虫开发。

1. 版本锁死这件事,为什么在 Selenium 爬虫里是生死线

很多人第一次写 Selenium 爬虫,代码跑不起来,报的错五花八门:session not created、This version of ChromeDriver only supports Chrome version XX、浏览器一闪就退。折腾半天,最后发现根本不是代码问题,而是谷歌浏览器和 Chromedriver 版本对不上。这个标题里特意把版本号 118.0.5958.0 写出来,就是在提醒一件事:Selenium 爬虫的稳定性,一半靠代码,一半靠环境对齐。

这篇讲的是用 Java 写 Selenium 爬虫的完整落地路径,从环境搭建、驱动匹配、页面元素定位,到反爬对抗和工程化封装。适合两类人:一是 Java 后端想快速搞一套可控的采集工具,二是从 Python 爬虫转过来、发现 Java 生态在多线程和工程管理上更顺手的同学。核心不是教你 Selenium 的 API 怎么调,而是把版本匹配、驱动管理、元素定位、异常处理这几件真正会翻车的事讲透。

2. 环境搭建:JDK、Maven 与 Chromedriver 118 的版本对齐

2.1 为什么 Chromedriver 版本必须和 Chrome 严格对应

Chromedriver 本质是一个实现了 WebDriver 协议的服务端,它通过 DevTools 协议和 Chrome 浏览器通信。每次 Chrome 大版本更新,DevTools 协议可能有破坏性变更,所以 Chromedriver 的版本号前三位必须和 Chrome 完全一致。118.0.5958.0 这个版本意味着:你的 Chrome 主版本必须是 118,Chromedriver 也必须是 118 系列。

常见做法是去 Chromedriver 的版本索引页找到对应大版本下的最新小版本。注意,Chromedriver 只保证大版本匹配,小版本号不需要完全一致,但为了减少玄学问题,我一般会尽量对齐到标题里这个具体版本。

提示:Chrome 浏览器设置里「关于 Chrome」可以看到完整版本号。如果显示 118.0.5958.0,那就下 118 系列的 Chromedriver,不要用 119 或 117。

2.2 Maven 依赖与驱动路径配置

Java 项目用 Maven 管理依赖是最省事的。Selenium 的 Java 包在 Maven 中央仓库有,直接写 pom 就行。

<dependencies> <!-- Selenium Java 核心包,4.x 版本内置了 Selenium Manager,但国内网络环境下建议手动管理驱动 --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.15.0</version> </dependency> <!-- 如果要做页面解析,jsoup 比直接用 Selenium 的 getText 更灵活 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency> </dependencies>

Selenium 4.x 引入了 Selenium Manager,理论上可以自动下载匹配的驱动。但实际用下来,国内网络环境下自动下载经常超时,而且它下载的版本不一定是你想要的 118。所以我一般会手动指定驱动路径。

import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; public class DriverFactory { public static WebDriver createDriver() { // 手动指定 Chromedriver 可执行文件的绝对路径 System.setProperty("webdriver.chrome.driver", "/opt/drivers/chromedriver-118.0.5958.0/chromedriver"); ChromeOptions options = new ChromeOptions(); // 无头模式,服务器上跑必须加 options.addArguments("--headless=new"); // 禁用 GPU,容器环境里不加会报错 options.addArguments("--disable-gpu"); // 禁用沙箱,Docker 里跑必须加 options.addArguments("--no-sandbox"); // 设置窗口大小,避免响应式布局导致元素定位失败 options.addArguments("--window-size=1920,1080"); // 禁用自动化特征,降低被检测概率 options.addArguments("--disable-blink-features=AutomationControlled"); return new ChromeDriver(options); } }

这段代码里几个参数值得展开说。--headless=new是 Chrome 112 之后的新无头模式,比老的--headless更接近真实浏览器行为,很多老教程还在用旧写法,会导致部分页面渲染异常。--disable-blink-features=AutomationControlled是去掉navigator.webdriver这个自动化标志的关键参数,不加的话很多网站直接识别你是机器人。--no-sandbox在 Docker 里不加会直接启动失败,报DevToolsActivePort file doesn't exist,这个坑我踩过不止一次。

2.3 验证环境是否跑通的最小命令

写完工厂类,先别急着写业务逻辑,跑一个最小验证。

public class SmokeTest { public static void main(String[] args) { WebDriver driver = DriverFactory.createDriver(); try { driver.get("https://www.example.com"); // 打印页面标题,确认浏览器真的加载了页面 System.out.println("Page title: " + driver.getTitle()); // 打印当前 URL,确认没有跳转到错误页 System.out.println("Current URL: " + driver.getCurrentUrl()); } finally { // 必须关闭,否则进程残留,下次启动端口被占用 driver.quit(); } } }

如果这一步能打印出标题和 URL,说明 JDK、Maven、Chromedriver、Chrome 四者版本对齐了。如果报session not created,九成是版本不匹配;如果报connection refused,检查 Chromedriver 路径是否写对;如果浏览器启动后立刻退出,加--no-sandbox和--disable-dev-shm-usage。

3. 页面元素定位:从 By 选择器到显式等待的工程化写法

3.1 八种定位方式的适用场景与优先级

Selenium 提供了八种By定位方式,但实际项目里常用的就四种:By.id、By.cssSelector、By.xpath、By.className。优先级我一般是:id > cssSelector > xpath > className。

id 最稳,但现代前端框架生成的 id 往往是动态的,比如id="app-3f2a1b",这种就不能用。cssSelector 性能最好,语法也够用,div.content > ul.list li.item这种层级选择很直观。xpath 功能最强,支持文本匹配和轴定位,但性能差一些,而且页面结构一变就容易断。className 最不稳定,因为一个元素经常有多个 class,而且 class 名可能被压缩混淆。

// id 定位,最优先 WebElement searchBox = driver.findElement(By.id("search-input")); // cssSelector 定位,次优先,支持层级和属性 WebElement submitBtn = driver.findElement( By.cssSelector("form#search-form button[type='submit']")); // xpath 文本定位,适合按钮文字固定的场景 WebElement loginLink = driver.findElement( By.xpath("//a[contains(text(),'登录')]")); // xpath 轴定位,适合表格里找特定行 WebElement targetCell = driver.findElement( By.xpath("//tr[td[text()='张三']]/td[3]"));

xpath 的contains(text(),'登录')比text()='登录'更实用,因为按钮文字前后可能有空格或换行。轴定位//tr[td[text()='张三']]/td[3]这种写法在抓表格数据时特别有用,意思是「找到包含张三这个单元格的行,然后取该行第三个单元格」。

3.2 显式等待:为什么 sleep 是万恶之源

新手最爱用Thread.sleep(3000),觉得等三秒页面肯定加载完了。实际项目中,这个三秒要么不够导致元素找不到,要么太长导致整体速度慢得无法接受。Selenium 提供了WebDriverWait做显式等待,条件满足就立刻继续,不满足才等到超时。

import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.ExpectedConditions; import java.time.Duration; // 创建等待对象,最长等 10 秒,每 500 毫秒轮询一次 WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10)); wait.pollingEvery(Duration.ofMillis(500)); // 忽略找不到元素的异常,继续轮询 wait.ignoring(NoSuchElementException.class); // 等待元素可点击,适合按钮 WebElement btn = wait.until( ExpectedConditions.elementToBeClickable(By.id("submit"))); // 等待元素可见,适合文本内容 WebElement content = wait.until( ExpectedConditions.visibilityOfElementLocated( By.cssSelector("div.result-list"))); // 等待 URL 包含特定字符串,适合页面跳转 wait.until(ExpectedConditions.urlContains("/search?q="));

elementToBeClickable比visibilityOfElementLocated更严格,它要求元素既可见又可交互。如果按钮被遮罩层挡住,前者会继续等,后者可能直接返回但点击失败。pollingEvery默认是 500 毫秒,一般不用改,改太小会增加浏览器压力,改太大响应变慢。

注意:显式等待和隐式等待不要混用。隐式等待是全局的,显式等待是局部的,两者叠加会导致实际等待时间不可预测,出现「明明设了 10 秒却等了 30 秒」的玄学问题。

3.3 用 Page Object 模式封装页面操作

当爬虫要操作的页面超过三个,代码里到处是findElement会变得难以维护。Page Object 模式把每个页面封装成一个类,元素定位和操作都放在类里,业务代码只调方法。

public class SearchPage { private final WebDriver driver; private final WebDriverWait wait; // 元素定位用 By 对象存起来,页面结构变了只改这里 private final By searchInput = By.id("search-input"); private final By searchButton = By.cssSelector("button.search-btn"); private final By resultItems = By.cssSelector("div.result-item"); public SearchPage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } public void search(String keyword) { WebElement input = wait.until( ExpectedConditions.elementToBeClickable(searchInput)); // clear 防止输入框有默认值 input.clear(); input.sendKeys(keyword); driver.findElement(searchButton).click(); } public List<String> getResults() { // 等待结果列表出现 wait.until(ExpectedConditions.visibilityOfElementLocated(resultItems)); return driver.findElements(resultItems) .stream() .map(WebElement::getText) .collect(Collectors.toList()); } }

这样业务代码就变成new SearchPage(driver).search("关键词"),页面改版时只需要改 SearchPage 里的 By 对象。clear()这一步很多人会漏,如果输入框有 placeholder 或者上次搜索的残留值,不 clear 会导致搜索词拼接错误。

4. 反爬对抗:从 webdriver 检测到请求头指纹的排查清单

4.1 现象:页面能打开但数据是空的

这是最典型的反爬表现。浏览器正常加载了页面,但关键数据区域是空白,或者返回一个验证码页面。原因通常是网站检测到了navigator.webdriver === true,直接返回了阉割版页面。

解决分三步。第一步,加--disable-blink-features=AutomationControlled启动参数。第二步,用 CDP 注入脚本覆盖 webdriver 属性。

import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.devtools.DevTools; import org.openqa.selenium.devtools.v120.page.Page; ChromeDriver driver = new ChromeDriver(options); DevTools devTools = driver.getDevTools(); devTools.createSession(); // 在页面加载前注入脚本,覆盖 navigator.webdriver devTools.send(Page.addScriptToEvaluateOnNewDocument( "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})" ));

第三步,检查 User-Agent。无头模式的 UA 里会带HeadlessChrome,必须手动覆盖。

options.addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36");

4.2 现象:请求频率一高就被封 IP

Selenium 启动的浏览器每次都是新会话,但出口 IP 是同一个。如果目标网站有频率限制,连续请求几十次后就会返回 403 或验证码。

常见做法是加随机延迟,模拟人类操作节奏。

// 每次操作之间随机等待 1 到 3 秒 private void randomDelay() { try { long delay = 1000 + (long)(Math.random() * 2000); Thread.sleep(delay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }

但随机延迟只能缓解,不能根治。如果目标网站风控严格,需要考虑用代理池。代理池的搭建涉及 IP 轮换和健康检查,这里不展开,核心思路是每次创建 Driver 时从池里取一个可用代理,通过--proxy-server参数传入。

4.3 现象:Chromedriver 进程残留导致内存暴涨

长时间运行的爬虫,如果driver.quit()没有在 finally 里调用,Chromedriver 和 Chrome 进程会一直残留。跑几个小时下来,服务器内存被吃满。

WebDriver driver = null; try { driver = DriverFactory.createDriver(); // 业务逻辑 } catch (Exception e) { // 记录日志,不要吞异常 logger.error("Crawl failed", e); } finally { if (driver != null) { try { driver.quit(); } catch (Exception e) { logger.warn("Failed to quit driver", e); } } }

quit()和close()的区别:close()只关闭当前窗口,quit()关闭整个浏览器并结束 Chromedriver 进程。爬虫里必须用quit()。另外,在 Linux 上可以用pkill -f chromedriver做兜底清理,但这是最后手段,正常流程还是靠 finally 保证。

5. 工程化收尾:多线程采集与异常重试的落地参数

5.1 用 ThreadPoolExecutor 控制并发数

单线程 Selenium 爬虫速度受限于页面加载时间,一个页面平均 2 到 3 秒,一天跑不了多少数据。多线程是必须的,但线程数不是越多越好。每个 Chrome 实例占 200 到 400 MB 内存,开太多会 OOM。

// 根据服务器内存定线程数,8G 内存一般开 4 到 6 个 int threadCount = 5; ExecutorService executor = new ThreadPoolExecutor( threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100), // 线程工厂,给线程起名字方便排查 new ThreadFactoryBuilder().setNameFormat("crawler-%d").build(), // 队列满了由调用线程执行,不丢任务 new ThreadPoolExecutor.CallerRunsPolicy() );

每个线程独立创建 Driver,不要共享。Selenium 的 WebDriver 不是线程安全的,共享会导致命令错乱。CallerRunsPolicy是当队列满时让提交任务的线程自己执行,这样不会丢任务,但会阻塞提交,相当于自动降速。

5.2 失败重试:哪些异常该重试,哪些不该

不是所有异常都值得重试。NoSuchElementException可能是页面还没加载完,重试有意义。SessionNotFoundException说明浏览器已经崩了,重试也没用,应该重建 Driver。TimeoutException看情况,如果是网络慢可以重试,如果是元素真的不存在就是浪费。

public <T> T retry(Callable<T> task, int maxRetries) { int attempt = 0; while (true) { try { return task.call(); } catch (NoSuchElementException | TimeoutException e) { attempt++; if (attempt >= maxRetries) { throw new RuntimeException("Retry exhausted", e); } // 指数退避,第一次等 1 秒,第二次 2 秒,第三次 4 秒 long backoff = (long) Math.pow(2, attempt - 1) * 1000; try { Thread.sleep(backoff); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("Interrupted", ie); } } catch (Exception e) { // 其他异常直接抛出,不重试 throw new RuntimeException(e); } } }

指数退避比固定间隔更合理,因为如果是服务端限流,连续快速重试只会加重封禁。第一次等 1 秒,第二次 2 秒,第三次 4 秒,给服务端足够的恢复时间。

5.3 数据落库前的去重与字段校验

采集到的数据在入库前必须做两件事:去重和字段校验。去重可以用 URL 的 MD5 做唯一索引,字段校验检查必填项是否为空。

// 用 URL 的 MD5 作为唯一键,避免重复插入 String urlHash = DigestUtils.md5Hex(url); // 字段校验,空值直接跳过 if (StringUtils.isBlank(title) || StringUtils.isBlank(content)) { logger.warn("Skip empty record, url={}", url); return; }

数据库层面加唯一索引UNIQUE KEY uk_url_hash (url_hash),插入时用INSERT IGNORE或ON DUPLICATE KEY UPDATE。这样即使多线程同时插入同一条数据,也不会产生重复记录。

6. 版本升级时怎么不翻车:Chromedriver 118 的兼容性验证清单

Chrome 自动更新是爬虫最大的敌人。某天早上起来,Chrome 悄悄升到了 119,Chromedriver 还是 118,所有爬虫全部报session not created。这种事我经历过三次,后来总结了一套升级验证流程。

第一步,关闭 Chrome 自动更新。Windows 上改注册表,Linux 上锁定版本。但这不是长久之计,安全补丁还是要打。第二步,建立版本映射表,把 Chrome 版本、Chromedriver 版本、Selenium 版本、验证状态记录在案。

Chrome 版本Chromedriver 版本Selenium 版本验证状态备注
118.0.5958.0118.0.5958.04.15.0通过当前生产版本
119.0.6045.0119.0.6045.04.15.0待验证测试环境
117.0.5938.0117.0.5938.04.14.0通过旧版本备份

第三步,升级前跑回归测试。不要只测一个页面,要把所有目标网站的采集脚本都跑一遍。重点看三类页面:需要登录的、有懒加载的、有验证码的。这三类最容易受版本影响。

第四步,准备回滚方案。Chromedriver 是绿色文件,直接替换就行。Chrome 降级麻烦一些,Linux 上用apt install chromium=版本号可以指定版本,Windows 上需要卸载后装离线包。所以升级前一定要把旧版本的安装包留着,这就是后悔药。

提示:如果用的是 Docker,把 Chrome 和 Chromedriver 打包进镜像,版本就锁死了。每次升级重新构建镜像,回滚就是换回旧镜像,比在宿主机上折腾省心得多。

我现在的习惯是:每次 Chrome 大版本更新后,先在一台测试机上跑一周,确认所有目标网站都能正常采集,再更新生产环境。升级当天不跑重要任务,留出回滚窗口。这套流程看起来麻烦,但比半夜被报警叫起来修爬虫强得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 16:15:38

校园悬赏小程序实战:Spring Boot+微信原生+状态机设计

简介&#xff1a;这是一套面向高校计算机专业学生与微信小程序初学者的毕业设计/课程设计实战项目&#xff0c;完整实现校园场景下的悬赏信息发布与接单闭环管理。资源涵盖前端小程序、后端Java服务及MySQL数据库三端协同开发&#xff0c;包含前台用户端&#xff08;悬赏大厅、…

作者头像 李华
网站建设 2026/10/6 16:15:37

Win32 API Hook 屏幕取词实战:绕过 UIA 与 OCR 的纯用户态文本捕获

简介&#xff1a;本资源是一份基于Windows平台的API Hook技术实战源码包&#xff0c;面向中高级C开发者及系统编程学习者&#xff0c;聚焦屏幕取词这一典型应用场景&#xff0c;解决如何拦截系统级API调用以捕获用户选中文本的核心问题。压缩包共31个文件&#xff0c;包含5个cp…

作者头像 李华
网站建设 2026/10/6 16:14:09

图书管理系统毕业设计源码+论文zip使用指南:SSM+MySQL环境配置到跑通

简介&#xff1a;这是一份完整的图书管理系统毕业设计资料包&#xff0c;内含可运行的源代码与配套毕业论文&#xff0c;面向计算机相关专业学生&#xff0c;尤其适合需要完成课程设计或毕业设计、希望理解软件工程与数据库管理实际应用的读者。系统源代码覆盖用户注册登录与权…

作者头像 李华
网站建设 2026/10/6 16:11:51

深度学习笔记-FCN

FCN模型是让 Backbone Decoder 这一套工程范式在分割领域真正成型的关键模型&#xff0c;有32s、16s、8s三种规格&#xff0c;8s的结构示意图如下&#xff1a;这个模型逻辑很容易理解。它直接复用以前的分类网络&#xff08;alexnet、vgg之类&#xff09;&#xff0c;将这些网…

作者头像 李华
网站建设 2026/10/6 16:09:45

2026 AI Agent 家庭托管实战:远程终端、CLI、端口映射与网络代理完整指南 从“必须守着电脑”到“随时接管”:把家里的 Windows PC 变成可观察、可恢复、可远程操作的个人 Age

2026 AI Agent 家庭托管实战&#xff1a;远程终端、CLI、端口映射与网络代理完整指南从“必须守着电脑”到“随时接管”&#xff1a;把家里的 Windows PC 变成可观察、可恢复、可远程操作的个人 Agent 节点人工智能 AI Agent Claude Code Codex 远程开发 Windows CLI 端…

作者头像 李华