简介面向Java开发者的Selenium爬虫实战资料包围绕浏览器自动化与网页数据抓取核心场景给出完整可运行的实战代码、Selenium学习笔记、演示视频以及Chrome浏览器与Chromedriver 118.0.5958.0的全平台配套文件适合从零接触Java爬虫或希望系统提升Selenium技能的开发者。资源共56个文件压缩包约706MB文件类型覆盖java源码、class字节码、js脚本、pom.xml配置、html/css静态页面、woff/ttf字体、mp4操作演示和Markdown学习笔记等可支撑从环境配置、依赖管理到页面解析、运行调试的完整学习链路。配套Chromedriver与Chrome均提供linux、mac、win多架构版本其中Chrome为测试版且不会自动更新仅适用于自动化测试场景。学习笔记侧重梳理常用API、常见异常与踩坑点演示视频直观展示自动化操作过程实战代码包含登录、翻页、数据抓取与存储等典型逻辑。目前已有120人学习下载既适合初学者按步骤入门也适合有经验者快速搭建Selenium环境。1. 基于Selenium的Java爬虫实战这套方案到底解决什么问题一个资讯类页面上商品价格全是异步渲染用Java的HttpClient去拉只能拿到空壳HTML数据全在XHR请求里。这时候想靠“改请求头模仿浏览器”去抠接口往往又被签名参数和风控挡回来。基于Selenium的Java爬虫实战解决的就是这类问题用Selenium Java API驱动本地谷歌浏览器Chrome让Chromedriver版本118.0.5958.0这类配套驱动打开真实浏览器内核执行页面渲染再收割DOM里的最终数据。这套方案能覆盖登录态采集、滚动加载列表、前端计算后展示的内容适合有Java基础、打算把动态页面抓取做成定时任务的从业者如果目标只是纯静态接口那用OkHttp就够了没必要请出Selenium。2. WebDriver协议与版本选型为什么Chromedriver 118.0.5958.0要单独拿出来说2.1 Selenium到Chrome之间隔着一个Chromedriver很多新手以为Selenium是个浏览器其实不是。Selenium是一套API标准它通过WebDriver协议把指令发给ChromedriverChromedriver再转成Chrome能理解的DevTools协议去操作浏览器。也就是说真正执行JavaScript、渲染页面、执行点击的是Chrome进程本身Selenium只负责发号施令Chromedriver是传令官。理解这条链路对排查问题很重要。你在Java代码里写的driver.get(url)会依次经过Selenium Client、Chromedriver、Chrome三个环节任何一个环节版本对不上或者环境变量缺失都会报出一堆看似莫名其妙的异常。大部分启动失败都不是代码写错而是Chromedriver和Chrome匹配出了问题。2.2 Selenium 4的Java使用边界Selenium 4要求JDK 11以上低于这个版本连编译都过不去。如果你还在用JDK 8写老项目要么升级JDK要么退回Selenium 3.x但Selenium 4的ChromeDriver构造方式、WebDriverWait的API都和3.x不同网上能找到的旧教程大概率不适用。Java技术栈里做浏览器自动化Selenium依然是事实标准。Playwright的Java版API虽然也很顺但社区资料量和踩坑记录明显不如Selenium厚实Crawler4j这类框架偏静态抓取处理不了重渲染页面。更实际的一点是Selenium数据和Spring Boot、Quartz调度、MyBatis存储能无缝接到同一个工程里不用为爬虫单独搭一套Python环境。dependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.15.0/version /dependency这个依赖会把你需要的selenium-api、selenium-chrome-driver、selenium-support全部带进来。4.15.0这个版本对应Chrome 118时代的API和标题里的Chromedriver版本配合起来不用改额外配置。如果只是做轻量任务也可以只引selenium-chrome-driver但写实战代码时ExpectedConditions那些工具类都在selenium-support里建议直接引全量包。2.3 为什么这套组合要绑定版本号118Chromedriver和Chrome的大版本必须保持一致这是整个方案里最容易被忽视的规则。Chromedriver 118.0.5958.0只能驱动Chrome 118如果浏览器自动升级到了119启动时会直接抛SessionNotCreatedException报错信息会明确提示“This version of ChromeDriver only supports Chrome version 118”。版本号也不难读懂118.0.5958.0里最前面的118就是大版本只需拿这个数和Chrome的版本号前两位做匹配。查看Chrome版本的方式是地址栏输入chrome://version页面里会显示完整的版本号。Chromedriver有自己的公开索引接口可以按版本查下载地址但实际开发里更省事的做法是把Chromedriver固定在一个tools目录下浏览器禁止自动升级让版本号长期锁死而不是每次上线都去对一遍。Chrome 大版本Chromedriver 版本规则备注118取118.x系列标题给出的118.0.5958.0即为该系列本文所有代码按此版本验证其他大版本必须取同大版本对应的Chromedriver例如Chrome 119就换119.x驱动需要提醒的是Chrome 118之后浏览器还在持续迭代这套组合里的版本号会过时。所以工程里不要硬编码驱动路径建议把webdriver.chrome.driver指向一个环境变量或者配置文件日后升级只改一处。3. 环境搭建与启动验证Chromedriver匹配Chrome 118的最小可运行工程3.1 三件套的安装顺序这套方案需要三样东西JDK 11、Chrome 118本体、Chromedriver 118.0.5958.0。安装顺序建议先装Chrome再下载对应驱动最后配Java工程。很多人先下载了Chromedriver发现Chrome没装或者装了最新版Chrome导致驱动不匹配都属于顺序没理清。Chrome装完之后第一件事是记住完整版本号不要凭印象去下载驱动。Windows下打开设置看“关于Chrome”即可Linux下执行google-chrome --version也能拿版本号。拿到之后再去下载同大版本的Chromedriver这是唯一可靠的原则。3.2 Windows下Chromedriver配置Windows下Chromedriver是一个独立的exe文件下载对应win32.zip后解压。建议放在一个固定的根目录比如D:\tools\chromedriver\chromedriver.exe然后把D:\tools\chromedriver加进PATH环境变量。加PATH不是必须的因为Java代码里可以显式指定路径但加了之后能用命令行验证安装是否正确。C:\ chromedriver --version ChromeDriver 118.0.5958.0 (...)能打印出版本号说明驱动本身没问题。接着验证Chrome版本地址栏输入chrome://version看第一行的完整版本号确认大版本是118。这一步做完环境层面就算通了。3.3 Linux服务器最小环境生产环境跑采集通常是无头服务器没有图形界面。Linux下安装Chrome和Chromedriver要注意依赖问题缺lib会导致启动时闪退且不报Java异常。# 安装 Chrome 本体及运行依赖 sudo apt-get update sudo apt-get install -y libxss1 libappindicator1 libindicator7 unzip wget -q -O chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i chrome.deb sudo apt-get -f install -y # 下载与 Chrome 大版本一致的 Chromedriver放 /usr/local/bin unzip chromedriver_linux64.zip -d /usr/local/bin/ chmod x /usr/local/bin/chromedriver chromedriver --version这里有个细节用root用户跑Chrome必须加--no-sandbox参数不加会报沙箱权限错误。Chrome的沙箱机制需要setuid辅助进程容器环境里往往没配好这个参数后面写ChromeOptions时始终带着能少踩很多坑。3.4 第一个能跑通的启动用例环境配好后用一段最小代码验证整条链路。这段代码的目的不是抓数据而是确认Selenium、Chromedriver、Chrome三者能协同工作。import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; public class FirstRun { public static void main(String[] args) { // 指定本机 chromedriver 路径避免 Selenium Manager 自动下载错版本 System.setProperty(webdriver.chrome.driver, D:/tools/chromedriver/chromedriver.exe); ChromeOptions options new ChromeOptions(); options.addArguments(--headlessnew); options.addArguments(--no-sandbox); options.addArguments(--disable-dev-shm-usage); WebDriver driver new ChromeDriver(options); driver.get(https://example.com); System.out.println(driver.getTitle()); driver.quit(); } }webdriver.chrome.driver这个系统属性是手动指定驱动路径的入口建议始终显式设置防止Selenium Manager自动下载一个和你环境不匹配的版本。--headlessnew是Chrome 109之后的新无头模式比旧的无头模式更接近真实浏览器渲染结果采集动态页面时优先用它。--disable-dev-shm-usage解决的是Linux容器里/dev/shm太小导致的崩溃问题Windows下加了也没副作用。如果这段代码能打印出Example Domain说明整套环境已经跑通可以开始写正式采集逻辑。如果抛异常八成是版本匹配问题先回第2章的对照表查一遍再看后面的避坑章节。4. 实战代码把动态页面数据稳定抓下来的三种套路4.1 浏览器参数组合先做减法再做加法正式写采集代码前先把ChromeOptions参数固定下来。参数不是越多越好而是要根据运行环境做减法。默认的Chrome图形界面在服务器上没有任何意义还会拖慢速度所以无头模式是必开的。ChromeOptions options new ChromeOptions(); options.addArguments(--headlessnew); options.addArguments(--disable-gpu); options.addArguments(--no-sandbox); options.addArguments(--disable-dev-shm-usage); options.addArguments(--window-size1920,1080); options.addArguments(--langzh-CN);--disable-gpu在无头模式下能避免部分显卡驱动异常虽然现在Chrome的无头模式已经不怎么依赖GPU但加上不亏。--window-size很重要很多页面会根据可视窗口宽度决定是否加载某些模块窗口太小可能漏掉懒加载内容。--langzh-CN是为了让页面返回中文文案有些站点会根据语言环境渲染不同结构。这些参数里最常用到的是--headlessnew旧的无头模式是--headless两者在渲染行为上有差异。如果你发现某个元素在无头模式下定位不到但本机开着Chrome就能找到优先检查是不是无头模式导致的换成--headlessnew往往就解决了。4.2 等页面渲染完再动手别用Thread.sleepSelenium最容易被诟病的点是慢但更多时候是“等”的姿势不对。新手喜欢在get之后直接Thread.sleep(3000)这样既浪费时间又可能在网络慢时依然取不到元素。正确的做法是显式等待让程序等到目标元素出现再继续。import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.By; WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(.content)));Duration.ofSeconds(10)是Selenium 4的写法老版本里是int秒数。这里10秒是上限不是固定等10秒只要元素提前出现就会立刻继续。presenceOfElementLocated只检查元素是否出现在DOM里不保证可见如果目标元素在折叠区域里建议用visibilityOfElementLocated。实际采集时等待条件往往要串联多个。先等列表容器出现再等列表项数量达到预期每一步都精准卡住渲染节点整体效率反而比固定sleep高。4.3 滚动加载与翻页用JavaScript驱动页面资讯流和商品列表基本都带“下拉加载更多”或“下一页”两种分页方式。下拉加载在Selenium里靠模拟滚动实现但滚动本身是异步渲染需要循环滚动几次直到取满数据再退出。JavascriptExecutor js (JavascriptExecutor) driver; // 循环滚动到底部每次留出渲染时间 for (int i 0; i 10; i) { js.executeScript(window.scrollTo(0, document.body.scrollHeight);); Thread.sleep(1200); // 数据够了就提前退出避免无效滚动 if (driver.findElements(By.cssSelector(.item)).size() 50) { break; } }scrollTo(0, document.body.scrollHeight)是滚到页面最底部的标准写法。每次滚动后等1200毫秒是为了给懒加载请求留时间具体数值取决于目标站点的接口响应速度不固定。循环里加一个数量判断是防止死循环——有些页面会无限制加载自己设一个上限再退出。翻页场景更推荐找下一页按钮的href属性直接改URL而不是每次都去点按钮因为点击会触发额外的动画和事件回调拖慢速度。如果必须点击点完之后要等下一页的第一个元素出现while (true) { // 处理当前页数据 ListWebElement items driver.findElements(By.cssSelector(.item)); // 业务逻辑... WebElement nextBtn driver.findElement(By.cssSelector(a.next)); if (nextBtn null || !nextBtn.isDisplayed()) { break; } nextBtn.click(); // 等待下一页的列表项替换掉上一页的DOM wait.until(ExpectedConditions.stalenessOf(items.get(0))); }stalenessOf等待的是旧元素从DOM中移除这个条件最适合翻页场景因为翻页后上一页元素必然被替换。比固定sleep要准得多。4.4 登录态复用Cookie写文件免每次扫码很多目标数据要登录才能看。Selenium里模拟登录要做的事太多输账号、过滑块、等短信每次都走一遍就是灾难。比较合理的做法是手动登录一次把Cookie序列化到本地文件后续采集直接加载Cookie跳过登录步骤。import org.openqa.selenium.Cookie; import com.google.gson.Gson; // 保存Cookie到JSON文件 public void saveCookies(WebDriver driver, String path) throws IOException { SetCookie cookies driver.manage().getCookies(); try (Writer writer new FileWriter(path)) { new Gson().toJson(cookies, writer); } } // 加载Cookie并注入浏览器 public void loadCookies(WebDriver driver, String path) throws IOException { if (!new File(path).exists()) { return; } try (Reader reader new FileReader(path)) { Cookie[] cookies new Gson().fromJson(reader, Cookie[].class); for (Cookie ck : cookies) { driver.manage().addCookie(ck); } } }加载Cookie有个前置条件必须先driver.get(https://目标站点首页)打开一次域名然后才能addCookie否则浏览器会拒绝向未访问过的域名写入Cookie。另一个坑是Cookie对象里有expiry字段Gson直接序列化会把Date转成字符串反序列化时很可能格式不匹配。建议保存前把expiry转成long时间戳读取时再转回Date或者干脆过滤掉过期Cookie。做完这步日常采集基本就只需要一个带登录态的持久化会话配合Quartz定时跑能省掉大部分人工介入。5. 避坑实录Selenium爬虫最常踩的6个坑与排查方法5.1 报错SessionNotCreatedException提示版本不支持现象启动ChromeDriver时直接抛异常信息里有This version of ChromeDriver only supports Chrome version 118。原因Chrome偷偷自动升级了大版本从118跳到119而Chromedriver还是118.0.5958.0。新版Chrome不会再乖乖停在旧版本上这是此类报错最常见来源。解决第一选择是锁版本。Windows下用组策略或关闭更新服务停掉Chrome自动升级Linux下用apt-mark hold google-chrome-stable固定版本。每次采集任务开始前先用一条命令读取当前Chrome版本号和chromedriver --version输出对比不一致就直接抛异常提醒人工介入。5.2 无头模式启动闪退且Java侧没有异常现象Linux服务器上代码跑起来之后进程直接消失没有堆栈输出。原因缺系统依赖库是头号嫌疑。Chrome靠libnss3、libxss1这些库渲染页面少了任何一层都会在启动时静默退出。解决先手动执行chromedriver看能否正常启动再用ldd检查Chrome依赖是否完整。缺失的库按提示用apt补装装完再跑一次。另一个常见原因是容器/dev/shm太小加--disable-dev-shm-usage参数可规避。5.3 本机调试时Chrome窗口一闪而过现象Windows本地跑有头模式Chrome窗口刚出现就消失代码也没报错。原因默认情况下ChromeDriver会启动一个临时用户目录任务结束时整个临时配置被销毁窗口自然关闭。还有一个可能是你的代码在get()之后没有阻塞等待main线程跑到结尾把driver对象带崩了。解决调试阶段指定一个固定用户目录比如--user-data-dirD:/temp/chrome-profile。这样还能保存登录状态和页面缩放设置窗口关闭后重开会恢复原样。另外在main方法末尾加一个System.in.read()让进程挂起窗口就不会自己消失。5.4 滑块验证过不去现象登录页面有滑块拖拽用click()和sendKeys()都没反应代码倒是域名打开了。原因滑块验证设计上就是故意拦截自动化工具的。识别到WebDriver特征后拖拽逻辑会不响应或者一直报“验证失败”这不是代码细节问题而是对抗思路错了。解决Selenium硬刚滑块是最低效的做法我一般会绕过登录环节而不是绕过滑块。方案是人工在真实Chrome里登录一次把登录后的Cookie导出来让Selenium直接复用会话。如果平台强制每次请求都要过滑块那就该评估是否换数据源或换合法接口而不是在滑块上死磕。5.5 采集程序常驻后服务器内存被拖垮现象任务跑了两天ps看到几十个chrome进程内存占用飙升最终整机卡死。原因异常路径上没有调用driver.quit()Chromedriver退出时也没有带走Chrome子进程。每次启动一个ChromeDriver实例就会产生至少一个Chrome进程异常越多残留越多。解决把quit()写进finally块不管业务代码是否抛异常都必须执行。进程粒度上再加一道保险定时任务启动前执行一次清理残留Chrome进程的命令pkill -f chrome.*--headless注意别误杀本机正在用的浏览器。5.6 选择器只对一半页面生效现象同一套CSS选择器在A页面能取到元素在B页面就抛NoSuchElementException两个页面看起来结构一致。原因站点往往存在两套模板PC端和移动端、登录前和登录后的DOM结构不同。你写的选择器恰好命中了其中一个模板另一个模板的class命名规则不一样。解决把两个模板各自的选择器都试一遍哪个取到用哪个。更省事的做法是找一个两个页面都包含且文本固定的锚点元素比如面包屑里的“首页”链接然后基于锚点做相对定位。遇到这种情况别急着改选择器先打开两个页面对比一下DOM比盲改高效很多。6. 快照留证、数据兜底与脚本维护进阶的四个实用技巧6.1 异常时截图留证采集脚本跑在无人值守环境里报错信息再详细也不如一张截图直观。Selenium自带的TakesScreenshot接口可以在异常抛出前抓下当前页面状态。import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.OutputType; File src ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); Files.copy(src.toPath(), Paths.get(/data/screenshots/ System.currentTimeMillis() .png));我习惯在catch块里先截图再输出异常日志这样第二天打开监控面板看缩略图就能判断是页面改版还是网络抖动不用再登服务器复现。6.2 用executeScript收割整页数据当页面列表项很多逐条findElement再逐条解析太慢时可以直接在浏览器上下文里执行JavaScript一次取回整个数组。这个技巧在采集表格和列表页时效率提升明显。JavascriptExecutor js (JavascriptExecutor) driver; ListObject titles (ListObject) js.executeScript( return Array.from(document.querySelectorAll(.item .title)).map(e e.innerText) );executeScript的返回值如果是数组Java侧会收到ListObject每条数据就是页面里对应元素的文字内容。比逐条遍历快一个数量级。这个手段也适合作为HttpClient接口采集的兜底通道接口反爬严的时候至少浏览器渲染后的最终数据还能拿到。6.3 单线程排队跑任务Selenium不是为高并发设计的工具。开多线程去new多个ChromeDriver实例每个无头Chrome至少占用几百MB内存并发上去机器先扛不住。我在生产环境只用单线程执行采集任务把任务丢进一个队列里串行消费。ExecutorService pool Executors.newSingleThreadExecutor();调度频率也控制一下列表页一小时一轮足够详情页三五分钟一轮别把单机当压测工具使。数据量真的上来之后优先考虑多台机器分工而不是在一台机器上叠加线程数。6.4 把版本检查写进启动脚本Chromedriver和Chrome的匹配关系是这套方案里唯一会随时间失效的部分。我的维护习惯是每个采集工程里放一个启动脚本任务拉起来先做一次版本核对不一致就直接停掉任务而不是跑一半才报错。chrome_version$(google-chrome --version | awk {print $3}) driver_version$(chromedriver --version | awk {print $2}) if [[ $chrome_version ! $driver_version ]]; then echo 版本不匹配停止任务 exit 1 fi这个检查放在定时任务的最前面配合第5章的SessionNotCreatedException解法基本能把版本问题扼杀在变成故障之前。Chrome每年升几次大版本118早晚要退休把这条检查沉淀成脚本以后换版本只是改一行路径的事。我自己的经验是爬虫工程里最难维护的从来不是抓取逻辑而是浏览器环境本身。装Chromedriver、锁版本、处理僵尸进程这些脏活累活第一次做很烦但做成脚本后就一劳永逸。希望这份实战笔记能帮你把环境一次搭对把更多时间留给数据本身。本文还有配套的精品资源点击获取
