尧图精选

Selenium驱动的Java新闻爬虫:抓取百度与头条并落库的实战指南

🕒 发布时间:2026/10/1 23:23:38 📁 来源:尧图网络
简介面向 Java 开发者的新闻类爬虫入门资源适合需要按关键词批量抓取百度新闻、今日头条并入库的初学者。内置 16 个 Java 源文件、1 个 Maven 配置文件和 1 个属性配置文件构成完整的 HTTP 请求、页面解析、数据存储流程XML 与 properties 分别用于项目依赖与运行参数配置整体 19KB共 20 个文件结构紧凑便于直接阅读。资源附 README 说明与 .gitignore 等辅助文件可帮助理解工程组织方式。已有 447 人浏览学习。下载后可根据关键词运行抓取任务观察 URL 队列、响应解析与数据库写入的完整链路对爬虫协议、User-Agent 设置及简单反爬应对也有直观体现。适合作为课程设计参考或毕业设计起步模板也可用于快速验证新闻聚合类数据采集思路。1. 先把“关键字新闻爬虫”说清楚这份Java资源能做什么如果你干过一段时间数据采集一定经历过这种场景老板扔过来一个需求说“把百度新闻和今日头条上所有带某某关键字的新闻给我抓下来存库里”。听起来简单真做起来搜索页的HTML结构三天两头变今日头条还得滚动加载百度新闻现在点了链接直接跳转到原网站存进库里的往往是一堆残缺数据。这个资源解的就是这个具体问题。它是一个Java工程用Selenium驱动真实浏览器去抓百度新闻和今日头条按你配置的关键字循环搜索把标题、链接、发布时间、正文摘要和来源站点解析出来去重后批量写入Oracle或MySQL数据库。它不是给只想跑一遍看热闹的人准备的是给需要稳定落库、还要能自己调参修bug的从业者用的。2. 从Java工程到可跑通的爬虫模块拆解与抓取逻辑2.1 工程结构先摸清别一上来就改代码下载下来解压后第一眼看到的是一堆目录。别急着双击运行先花两分钟把结构摸清楚。这个资源是Maven工程标准布局根目录有pom.xml、README.md源码都在src/main下面。pom.xml里管理着所有依赖重点看三个selenium-java版本、数据库驱动Oracle和MySQL各有一个、以及数据库连接池的版本。这几个版本决定了你的运行环境要求。我一般拿到手第一步是打开pom.xml把Maven仓库地址和依赖版本扫一遍确认JDK版本匹配。常见情况是资源作者用的是JDK 8而机器上装的是JDK 11甚至17直接跑会报ClassNotFoundException之类的问题不是代码错了是编译环境和运行环境不一致。2.2 抓取核心逻辑为什么用Selenium而不是HttpClient看代码之前先弄明白一个关键选型问题为什么用Selenium而不是用HttpClient直接发请求百度新闻的搜索结果页和今日头条的信息流页面内容都是动态渲染出来的直接用HTTP请求拿到的HTML里只有一堆JavaScript脚本真正的新闻数据是浏览器执行脚本后才出现在DOM里的。当然可以用Requests或者Jsoup配接口逆向去拿数据但接口签名经常变今天能用明天就报错。Selenium是直接驱动真实浏览器页面怎么渲染它就怎么抓稳定性高很多代价是慢、耗资源。代码主流程是这样的先读取配置文件拿到关键字列表然后为每个关键字构造搜索URL启动浏览器驱动访问页面等待页面加载完成用XPath或CSS选择器定位新闻列表的DOM节点逐条提取标题、链接、时间、摘要。提取完一批后在内存里做一个URL去重——这里用的是HashSet把已经处理过的链接存起来避免同一个新闻重复入库。所有字段封装成一个NewsItem对象最后交给DAO层批量写入数据库。// 以今日头条搜索为例演示Selenium的核心抓取流程 WebDriver driver new ChromeDriver(options); try { driver.get(https://www.toutiao.com/search/?keyword URLEncoder.encode(keyword, UTF-8)); // 今日头条是无限滚动加载需要循环滚动页面触发新内容出现 for (int i 0; i 8; i) { ((JavascriptExecutor) driver).executeScript(window.scrollTo(0, document.body.scrollHeight);); Thread.sleep(1500); // 等新内容渲染完成时间太短会漏数据 } ListWebElement items driver.findElements(By.cssSelector(div[class*articleCard])); for (WebElement item : items) { String title item.findElement(By.cssSelector(a[class*title])).getText(); String url item.findElement(By.cssSelector(a[class*title])).getAttribute(href); String source item.findElement(By.cssSelector(div[class*source])).getText(); if (deduplicatedSet.add(url)) { // 内存去重防止同一链接重复入库 newsList.add(new NewsItem(keyword, title, url, source)); } } } finally { driver.quit(); // 每个关键字抓完就关闭浏览器释放内存 }这里的核心参数有三个。第一个是window.scrollTo(0, document.body.scrollHeight)作用是把滚动条拉到页面底部触发今日头条的懒加载机制新内容才会出现在DOM里。第二个是Thread.sleep(1500)等渲染的时间太短会漏掉还没加载出来的条目太长浪费时间1.5秒是个折中值网络差可以调到3秒。第三个是deduplicatedSet这个去重集合必须放在外层循环里定义如果在每次循环内部新建那同一个关键字下的重复新闻就没法过滤了。2.3 数据落库部分批量提交别一条条插看完抓取逻辑再看存库部分。资源里封装的DAO层写得比较规整核心思路是批量插入。批量插入和单条插入的差别非常大一次性提交100条数据只用一次网络往返逐条提交就要100次在数据量大时性能差距是数量级的。JDBC的批量提交要用addBatch和executeBatch配合同时关闭自动提交最后手动commit。public void batchInsert(ListNewsItem newsList) throws SQLException { String sql INSERT INTO news_data(keyword, title, url, publish_time, source, create_time) VALUES(?,?,?,?,?,?); Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql); conn.setAutoCommit(false); // 关闭自动提交批量执行完再统一提交 for (NewsItem item : newsList) { ps.setString(1, item.getKeyword()); ps.setString(2, item.getTitle()); ps.setString(3, item.getUrl()); ps.setString(4, item.getPublishTime()); ps.setString(5, item.getSource()); ps.setTimestamp(6, new Timestamp(System.currentTimeMillis())); ps.addBatch(); if (newsList.size() % 500 0) { // 每500条刷一次防止内存积压过多 ps.executeBatch(); } } ps.executeBatch(); // 提交剩余未刷的数据 conn.commit(); }这里有个细节很多人没注意批量插入不是一次全塞进去就好。数据库驱动和数据库服务器对单次批量执行的SQL条数有限制一次塞几万条可能内存溢出。所以每500条执行一次executeBatch这是常规做法。另外conn.setAutoCommit(false)必须写在批量操作之前如果漏掉每条插入都会隐式提交批量就没意义了。参数keyword字段一定要存因为一个库里会跑多个关键字后续按关键字筛选数据全靠它。3. 部署与参数调整跑通本地再到服务器3.1 你以为的“直接运行”和实际的运行差距很多人下载这个资源后第一反应是双击运行或者直接java -jar。这个工程不是那种打包好的一体化程序它依赖外部浏览器和数据库部署步骤必须先搞对。完整步骤如下第一步装Chrome浏览器版本无所谓但要和chromedriver匹配。第二步下载对应版本的chromedriver放到指定路径。第三步配置数据库执行工程里的建表SQL。第四步修改配置文件的数据库连接信息。第五步用Maven打包或直接在IDE里运行主类。这里最容易翻车的是chromedriver版本问题。Chrome是自动更新的今天用的99版本明天自动升级到100chromedriver还是99的Selenium启动浏览器时直接报SessionNotCreatedException。代码里通常会写死driver路径你需要把这个路径改成自己机器上的实际位置。# 启动主程序假设已经打成jar包 java -jar news-crawler.jar --spring.profiles.activeprod这个工程的启动入口一般是带main方法的类里面会依次做四件事加载配置文件、初始化数据源、启动抓取循环、结果落库。启动成功后日志会打印当前操作的关键字和抓取条数。如果启动后没有日志输出大概率是配置文件没加载到检查config.properties是不是在classpath下。3.2 数据库连接参数Oracle和MySQL的切换逻辑这个资源同时支持Oracle和MySQL切换方式很直接改配置文件里的驱动类名、连接URL、方言。工程里的DAO层封装了两种方言的适配比如分页语法、自增主键的获取方式Oracle用序列MySQL用自增。切换数据库时不用改代码只改这里的配置。配置项MySQL写法Oracle写法修改位置driverClassNamecom.mysql.cj.jdbc.Driveroracle.jdbc.OracleDriverjdbc.propertiesjdbcUrljdbc:mysql://localhost:3306/news_db?useUnicodetruecharacterEncodingutf8jdbc:oracle:thin:localhost:1521:orcljdbc.properties建表语句id INT AUTO_INCREMENT PRIMARY KEYid NUMBER PRIMARY KEY 序列schema.sql批量提交方言rewriteBatchedStatementstrue默认支持jdbc.propertiesMySQL连接URL里面有一句rewriteBatchedStatementstrue这个参数容易被忽略但很关键。不加这个参数MySQL驱动会一条一条地执行批量SQL性能提升非常有限加上之后驱动才会真正重写SQL为多值INSERT批量速度能提升十几倍。Oracle驱动默认就支持真正的批量不需要额外配置。建表语句在工程里已经给了SQL文件MySQL和Oracle各一份。注意时间字段的类型MySQL用datetimeOracle用TIMESTAMP这两个在Java代码里都映射到Timestamp类型不需要额外处理。唯一要小心的是Oracle的保留字问题表名别用COMMENT、ORDER这种Oracle的保留字否则建表就报ORA-00903。资源的表名是news_data不在保留字列表里可以直接用。3.3 配置文件里的抓取参数频率、超时、重试配置文件是整个资源的调度中心。核心参数有这么几个crawl.interval是每一轮抓取之间休眠的毫秒数默认是60000也就是一分钟crawl.timeout是页面加载超时时间默认30秒crawl.retry是失败重试次数默认3次crawl.keywordsFile是关键字配置文件的路径。这些参数可以直接改不用动代码。关键字配置文件是纯文本格式每行一个关键字。抓取时程序按顺序逐个处理每个关键字抓完一轮后休眠crawl.interval毫秒再抓下一个。如果关键字太多一轮下来可能要跑很久建议把crawl.interval调小或者把不常用的关键字注释掉。头条的搜索结果是无限滚动的抓取深度是硬编码在代码里的8次滚动如果你觉得抓得不够深把循环上限调大即可但注意单次任务跑太久容易被站点限制访问。提示crawl.timeout设置太短会导致页面还没加载完就报超时异常太长会让整体效率变低。建议从默认的30秒开始如果经常出现超时先排查网络代理问题再考虑上调超时时间。4. 避坑手册翻车最多的六个场景与排查方法4.1 现象一Chrome启动报错“unknown error: cannot find Chrome binary”抓取程序突然中断日志里出现WebDriverException: unknown error: cannot find Chrome binary。原因很直接程序里设置System.setProperty(webdriver.chrome.driver, ...)只指定了driver路径但Selenium还需要找到浏览器本身的安装路径。Linux服务器上默认Chrome装的位置在/opt/google/chrome/chrome但某些精简安装或自定义安装路径下Selenium找不到。解决方法是显式设置Chrome二进制路径。在代码或启动参数里加上options.setBinary(/usr/bin/google-chrome-stable)具体路径以你机器实测为准。另外服务器上别装Snap版ChromeSnap版本的路径映射特殊Selenium经常识别不到。4.2 现象二今日头条能打开但抓不到任何内容页面能打开滚动条也滚动了但findElements拿到的是空列表。先在浏览器里按F12看下articleCard这个class是否还存在。头条改版比较勤CSS类名经常变。资源交付时的选择器是当时版本的可能已经失效。解决思路是重新抓取页面看当前DOM里新闻条目的真实选择器修改代码里的By.cssSelector参数。这个坑是爬虫维护中最频繁的没有一劳永逸的解法。我一般会给抓取逻辑加上日志输出每次启动先打印页面上所有符合条件的节点数量看到数字为0就知道选择器失效了不用等运行到落库阶段才发现问题。4.3 现象二百度新闻抓下来的链接是跳转链接百度新闻的搜索结果显示的是百度自己的跳转链接格式类似https://www.baidu.com/link?urlxxxwdxxx直接存库没意义因为要做二次跳转才能到真实新闻页面。这个现象在资源作者写代码时可能还不存在或作者没处理。现在一定要在落库前对URL做二次解析用Selenium访问这个跳转链接等待页面跳转完成读取driver.getCurrentUrl()拿到最终地址再替换掉原链接。private String resolveRedirect(String redirectUrl, WebDriver driver) { try { driver.get(redirectUrl); Thread.sleep(5000); // 等待跳转完成时间不够会拿到中间页的URL return driver.getCurrentUrl(); } catch (Exception e) { return redirectUrl; // 解析失败就保留原链接不阻塞主流程 } }延迟5秒是实测出来的百度跳转页面有时候会先加载一个过渡页再跳走时间太短拿到的还是中间地址。如果你发现存储的URL依然带baidu.com/link前缀就是延迟太短了。这个步骤会明显拖慢抓取效率因为是串行跳转一个关键字几百条新闻就要跑很久可以考虑后用多线程并行解析提升速度。4.4 现象三数据库插入报“ORA-00001: unique constraint violated”跑了几轮之后突然报唯一约束冲突。原因是资源里表设计时给url字段建了唯一索引第一轮抓取的数据还在库里第二轮抓到了相同新闻虽然代码里有HashSet去重但那是内存级别的程序重启后HashSet清空无法和数据库历史比对。解决这道问题的核心就是要有一个持久化的去重方案资源代码里可能没有完整实现。常见做法是插入前先按url查一遍数据库存在就跳过。-- 用一条SQL判断链接是否已存在避免程序查询数据库再判断少了网络往返 INSERT INTO news_data(keyword, title, url, publish_time, source, create_time) SELECT ? , ? , ? , ? , ? , SYSTIMESTAMP FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM news_data WHERE url ?);这个写法在Oracle下可用MySQL下把FROM DUAL去掉SYSTIMESTAMP换成NOW()。利用主键或唯一约束做冲突规避比先查询再插入的线程安全程度更高并发插入时不会被两条相同记录击穿。这样处理之后即使HashSet失效数据库层面也能挡住重复数据。4.5 现象四批量插入大批数据后内存溢出抓取顺利运行半小时后OOM内存溢出。Selenium本身是重资源组件一个浏览器实例大概占200到300MB内存工程默认是串行一个浏览器不存在多浏览器失控的可能。那问题大概率出在newsList上主循环把所有抓到的数据都存在ArrayList里攒够一批才批量提交如果提交间隔太长List无限膨胀。解决思路是控制单轮抓取的上限或者每个关键字抓完后立即落库不要等整轮结束再一次性存。资源代码里的批量提交逻辑如果是在所有关键字循环结束后才执行一次那内存占用会随关键字数量线性增长。我一般会调整成“每处理完一个页面就提交一次”这样List的最大长度可控内存也就稳住了。4.6 现象五定时任务用while(true)硬顶还是直接退出了有人把它接到定时任务里发现程序跑到一半就退出没有异常日志。排查点一般在未捕获的运行时异常上。爬虫代码里网络异常、解析异常是多发的如果主循环没有统一处理异常碰到一次NoSuchElementException整个线程就挂了。解决思路是给主循环加最外层兜底打印堆栈后continue。while (running) { try { crawlAllKeywords(); } catch (Exception ex) { ex.printStackTrace(); // 打点观察是否持续抛错 } Thread.sleep(interval); }这个兜底很关键能保证某个关键字失败时程序继续跑下一个。但要注意永远不能让异常被吞掉后还无限重试时间久了会把站点打到封IP。建议加上失败次数的统计比如某个关键字连续失败5次就跳过它标记为待人工排查。5. 进阶技巧关键字设置、去重优化与合规边界5.1 关键字文件的设计多关键字策略决定抓取质量关键词不是越多越好。在某些搜索场景里过泛的关键字会匹配出大量无关内容数据库里塞满噪音。我一般把关键字拆成三组核心词、修饰词、排除词。核心词是业务必抓的词修饰词用来做“核心词场景词”的组合比如“新能源汽车”“补贴”排除词则靠代码里对标题做过滤比如标题或正文含“辟谣”“招聘”“广告”的条目直接丢弃。资源的抓取循环天然支持多关键字顺序执行你只需要在关键字配置里把组合词每行写一个例如新能源汽车 补贴这种带空格的写法程序搜索时会按空格分词或原样搜索不同站点行为不同。头条的搜索是支持引号精确搜索的关键字带双引号可能拿到更精准的结果。百度新闻则是对空格分词做OR匹配。如果你想做精确短语匹配可以试试给关键字加双引号但要注意个别站点对引号的处理方案不同加了之后可能反而什么都搜不到需要实际测试。5.2 去重逻辑再加强应对标题相同但链接不同的场景URL去重解决不了“同一篇新闻被多个站点转载”的问题。比如某公司发布一条公告新浪发一遍、网易发一遍、腾讯发一遍URL完全不同但标题内容几乎一样。这种场景下需要加一个标题相似度去重把标题里的标点、空格全部去掉后做归一化对归一化后的标题做MD5存一个hash字段。入库前对比hash如果同一批数据里出现相同的hash就保留来源优先级高的那一条。private String normalizeTitle(String title) { // 去掉常见标点和空格让同一标题的不同变体归一化到同一个key return title.replaceAll([\\s·,。!?、\\\\[\\]], ).toLowerCase(Locale.ROOT); } private String getTitleHash(String normalizedTitle) { return DigestUtils.md5Hex(normalizedTitle); // 用MD5做定长hash比对更快 }之所以用MD5而不是原文比对是因为字段索引做等值匹配更高效。数据库加一列title_hash和url联合建唯一索引插入时按hash判断。你会发现加了标题hash去重后库里的数据量会大幅下降但数据质量明显提升——因为一稿多发的重复新闻被过滤掉了。资源原始代码不带这个逻辑但你可以自己在DAO层加字段预留了扩展空间。5.3 频率控制与合规边界爬虫不是越快越好抓取频率控制的逻辑有一段延时参数默认60秒一轮。很多人觉得太慢想调到10秒甚至5秒。这个调整要谨慎。对新闻网站搜索接口短时间内高频访问轻则触发访问拦截验证码重则IP被封。头条对异常请求的封禁力度比较大封了之后别的业务也会受影响。我一般保持在30到60秒范围内宁可慢一点也不冒被封的风险。合规方面多说一句。国内新闻网站的robots.txt对爬虫的约束不同部分站点明确禁止爬虫抓取内容。使用爬虫技术前应当核实目标站点的政策且抓取后的数据不能用于商业转售或公开传播。新闻标题和摘要的合理使用边界比较窄个人学习、内部研究使用问题不大但建库外发就有风险了。这个资源的技术栈是完整的怎么用、用到什么程度是需要自己把握的部分。5.4 高阶玩法把抓取成果对接到数据分析链路数据落库只是第一步真正有价值的是后续分析。资源抓下来的表结构有keyword、title、url、publish_time、source五个核心字段这几列已经足够支撑基础的舆情分析。常见做法是按关键字分组统计新闻数量趋势看热点变化或者按source字段聚合看哪些媒体对某个话题的报道最活跃。头条和百度新闻的时间字段格式不统一写SQL做趋势分析时建议先把publish_time截成日期格式然后按日期分组这样能输出每日新闻量折线图。如果想做更深的内容分析比如情感分类、主题聚类把title和抓取到的摘要字段导出来用Python调大模型接口做批次标注。这个工作适合离线做不要在抓取进程里跑太重了会影响采集稳定性。我从拿到这份资源到完全跑通中间大概折腾了两天。最初也是直接运行翻车后来把chromedriver版本对齐、加了跳转解析、补了标题去重才真正稳定下来。从那以后我每次部署爬虫都强制走一遍“配置检查-驱动版本确认-单关键字试跑-全量抓取”的流程省掉了无数半夜被报警吵醒的麻烦。希望这份工程的每个坑和补丁都能帮你省下这两天的时间把精力花在分析和业务上而不是和选择器搏斗。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →