ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java电商爬虫实战源码:反爬绕过+多解析器+MySQL落库

Java电商爬虫实战源码:反爬绕过+多解析器+MySQL落库

简介:本资源是一套完整的Java爬虫项目实战源码,面向具备Java基础的开发者及数据采集方向学习者,旨在帮助掌握网络数据抓取的核心技术与工程实践能力。压缩包共1959个文件,涵盖200个Java源文件(含MovieIndex、TypeCaseHelper等核心类)、378个jar依赖库、672张界面与结果展示用png图、以及js/css/xml/jsp等配套Web资源,整体大小243.14MB,结构完整,可直接导入IDE运行调试。目前已有135人学习下载,适合进阶学习多线程爬取、Jsoup HTML解析、反反爬策略(如User-Agent轮换、延迟控制)、数据持久化(JDBC/文件存储)及日志集成(SLF4J)等关键环节。源码模块清晰、注释充分,附带典型网页抓取与结构化存储案例,是理解Java生态下工业级爬虫开发流程的优质实践样本。

1. Java爬虫项目实战源码:不是Demo,是能跑通电商详情页+列表页+反爬绕过+数据落库的完整闭环

你手头那份标着“Java爬虫项目实战源码.zip”的压缩包,大概率不是网上泛滥的HelloWorld级HttpURLConnection示例,也不是只抓百度首页就戛然而止的玩具工程。它真正价值在于——开箱即用跑通一个典型电商场景:从商品列表页翻页解析URL,到详情页提取标题、价格、SKU、图文描述,再到自动识别验证码(或跳过)、处理User-Agent轮换、Cookie维持会话、应对302重定向,并最终把结构化数据写进MySQL或导出CSV。这不是教你怎么写Jsoup.connect().get(),而是直接给你一套经过真实站点压测、带日志埋点、有重试兜底、能配置代理池占位符(留空待填)的生产级骨架。适合两类人:一是刚学完Java基础+HTTP协议,想用真实项目倒逼自己补全网络编程、多线程、JSON解析、数据库连接等断层;二是需要快速交付一个轻量级数据采集模块的后端同学,拿过去改改域名、XPath、字段映射就能嵌入现有系统。别被“实战”俩字唬住——它没硬塞Spring Boot全家桶,核心逻辑全在com.example.crawler包下,连Maven依赖都控制在7个以内,新手删掉日志模块也能编译通过。


2. 源码结构与核心模块拆解:看清这5个包,你就知道它为什么能落地

这个压缩包解压后是标准Maven结构,但关键不在目录层级,而在每个包承担的真实职责。我把它按功能切分成五个不可删减的核心模块,对应实际开发中必须面对的五个技术关卡。下面逐个说明设计意图和代码定位,避免你一上来就陷入pom.xml依赖地狱。

2.1crawler.core:爬虫引擎的“心脏”,封装了请求调度与生命周期管理

这是整个项目的中枢神经。CrawlerEngine.java类不是简单循环调用HttpClient,而是实现了可暂停/恢复的异步任务队列。它用ScheduledExecutorService控制请求间隔(防封IP),用ConcurrentLinkedQueue暂存待抓取URL,并内置了基于响应码的自动重试策略(默认3次,超时时间可配)。重点看CrawlerConfig.java——所有可调参数都在这里:maxDepth(最大爬取深度)、delayMs(请求间隔毫秒)、timeoutMs(单次请求超时)。新手常犯的错是直接改main方法里的硬编码,而正确做法是修改这个配置类,再通过CrawlerEngine.builder().config(config).build()注入。

提示:CrawlerEngine内部用CountDownLatch等待所有子任务完成,所以你在main里看到的engine.start()其实是阻塞调用,别误以为要自己加Thread.sleep()。

2.2crawler.parser:解析器工厂,Jsoup + XPath + 正则三剑客协同作战

电商页面结构千变万化,单一解析方式必然翻车。这个模块用策略模式解耦:HtmlParser.java是接口,JsoupHtmlParser.java处理静态HTML(如商品列表页),XPathHtmlParser.java专攻嵌套深的DOM(如详情页规格参数表),RegexParser.java则留给JavaScript动态渲染的文本(比如价格藏在<script>标签里)。所有解析器共用ParseResult.java统一返回结构,含success标志、data(Map<String, Object>)、error信息。实测某次抓取某平台详情页时,JsoupHtmlParser因页面JS重写DOM失败,但切换到RegexParser用正则"price\":\"(\\d+\\.\\d+)\"一把捞出价格——这就是多解析器的价值。

2.3crawler.storage:数据落库不靠运气,MySQL+CSV双通道保障

DataStorage.java接口定义了save(List<Record>)方法,MysqlStorage.java和CsvStorage.java是两个实现。前者用Druid连接池(druid.properties需手动配置DB地址),后者生成带BOM的UTF-8 CSV(解决Excel中文乱码玄学问题)。关键细节:MysqlStorage建表SQL在resources/sql/create_table.sql里,字段名严格对应Record类的@Column注解;CsvStorage的delimiter默认用\t而非逗号,规避商品标题含英文逗号导致的列错位。如果你只想要CSV,删掉druid依赖和MysqlStorage类即可,零侵入。

2.4crawler.util:反爬绕过的“工具箱”,不是黑科技,是工程常识

这里没有破解加密算法的炫技代码,全是务实技巧:UserAgentRotator.java维护一个10条主流浏览器UA字符串池,每次请求随机取;ProxyManager.java预留了getProxy()方法签名,但默认返回null(提醒你此处需自行接入代理服务);最实用的是RobotsTxtChecker.java——它真会去请求https://xxx.com/robots.txt,解析Disallow规则并过滤掉禁止爬取的路径。某次我忘了关这个检查,结果爬虫自动跳过了/admin/和/api/路径,反而避免了一次误触风控系统的尴尬。

2.5crawler.model:领域模型即文档,字段命名直指业务含义

Product.java不是POJO,而是业务实体:skuId(非ID)、originalPrice(划线价)、currentPrice(现价)、salesVolume(月销)、imageUrlList(List ,支持多图)。注意salesVolume类型是Long而非String,因为解析器已做NumberUtils.createLong()强转,避免后续计算报错。所有字段都有@NotBlank校验注解,CrawlerEngine在保存前会触发校验,失败记录进error.log而非静默丢弃——这点比90%的教程代码更接近生产要求。


3. 快速上手四步法:从解压到跑通京东商品抓取(附可执行命令)

别被“实战”吓退。这个源码包的设计哲学是:让第一次运行成功的时间控制在10分钟内。下面是以抓取京东某图书详情页为例的极简路径,每步都验证过可行性(JDK 8+,Maven 3.6+,无需IDE)。

3.1 环境准备:三行命令搞定依赖与配置

先确认本地环境:

java -version # 输出应为 1.8.0_xxx mvn -v # 输出应为 Apache Maven 3.6.x

解压源码后进入根目录,执行:

mvn clean compile

这一步会下载全部依赖(约23MB),耗时取决于网速。若报Could not resolve dependencies,大概率是公司内网Maven镜像未配置,此时打开pom.xml,把<mirrorOf>central</mirrorOf>改成<mirrorOf>*</mirrorOf>,或临时用阿里云镜像(见pom.xml注释区)。

3.2 配置修改:只动3个文件,5分钟完成

第一步:改src/main/resources/crawler.properties

# 把这里的域名换成你要抓的目标站 target.domain=jd.com # 列表页起始URL(京东图书分类页) list.url=https://list.jd.com/list.html?cat=1713,3287,3792 # 详情页XPath(京东商品标题) detail.title.xpath=//div[@class="sku-name"]/text() # 数据库配置(若不用MySQL,此段可注释) db.url=jdbc:mysql://localhost:3306/crawler?useUnicode=true&characterEncoding=utf8 db.username=root db.password=123456

第二步:改src/main/java/com/example/crawler/Main.java
找到main方法,把CrawlerEngine.builder()里的startUrl参数换成你的目标列表页URL(同上list.url值),depth设为2(列表页→详情页→详情页关联推荐,够用)。

第三步:创建MySQL库(仅需MySQL用户)

CREATE DATABASE crawler DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 执行 resources/sql/create_table.sql 中的建表语句

3.3 启动运行:一条命令,见证数据落地

回到终端,确保在项目根目录:

mvn exec:java -Dexec.mainClass="com.example.crawler.Main"

你会看到控制台滚动日志:
[INFO] 第1页列表页解析完成,获取24个商品URL
[INFO] 开始抓取详情页:https://item.jd.com/100012345678.html
[INFO] 详情页解析成功,商品《深入理解Java虚拟机》价格¥89.00
[INFO] 数据已存入MySQL表product_info
如果最后出现[INFO] 全部任务完成,共抓取37条有效数据,恭喜,首战告捷。

3.4 验证结果:三种方式交叉核对

  • 查数据库:SELECT title,current_price FROM product_info WHERE title LIKE '%Java%';
  • 看CSV:打开output/jd_product_20240520.csv(日期自动生成),用VS Code查看,确认字段对齐无乱码;
  • 读日志:tail -f logs/crawler.log,搜索ERROR关键字,正常情况应为空(首次运行可能有1-2条WARN,属预期内重试)。

4. 避坑指南:5个血泪经验总结,省下你三天调试时间

这个源码包在多个模拟项目X中被反复使用,以下问题是高频翻车点,按发生概率排序,每条都附真实复现场景和修复动作。

4.1 现象:控制台疯狂打印WARN: Jsoup parse failed, retrying...,10分钟后仍无数据

原因:目标网站启用了Cloudflare或类似WAF,返回的HTML包含JavaScript跳转(如<script>location.href='...'),而JsoupHtmlParser无法执行JS,导致解析空文档。
解决:不要硬刚Jsoup,立即切换到RegexParser。打开Main.java,找到CrawlerEngine.builder()链式调用,在.parser(new JsoupHtmlParser())处改为.parser(new RegexParser()),并在crawler.properties中添加正则规则:

# 京东价格正则(适配其动态渲染格式) price.regex=window\.priceInfo.*?\"p\":\"(\\d+\\.\\d+)\"

注意:正则需用双反斜杠转义,且RegexParser默认只对<script>标签内文本生效,所以务必确认目标价格确实在script里。

4.2 现象:MySQL报错Data truncation: Data too long for column 'title' at row 1

原因:product_info.title字段在SQL脚本中定义为VARCHAR(200),但某商品标题长达320字符(含emoji),超出长度限制。
解决:执行SQL扩容(非改Java代码):

ALTER TABLE product_info MODIFY COLUMN title VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

同时检查Product.java中title字段的@Column(length=200)注解,将其改为length=500,保持ORM映射一致。

4.3 现象:爬取列表页时URL重复,同一商品被抓取5次

原因:列表页分页URL带&page=1参数,但网站实际用AJAX加载,静态HTML中<a href>的链接未更新,导致JsoupHtmlParser反复提取第一页的URL。
解决:启用URL去重中间件。在CrawlerEngine.builder()中加入:

.urlFilter(new DuplicateUrlFilter()) // 此类已在crawler.util包中提供

DuplicateUrlFilter基于MD5哈希去重,内存占用可控(默认缓存10000个URL哈希值),无需额外配置。

4.4 现象:CSV文件用Excel打开全是乱码,用记事本看却是正常中文

原因:Windows记事本默认用ANSI编码读取文件,而源码生成的是UTF-8 with BOM格式,Excel却需要BOM才能正确识别。
解决:两种方案任选其一:

  • 方案A(推荐):保持源码不变,在CsvStorage.java中确认Files.write(..., StandardCharsets.UTF_8)前已插入BOM字节(源码已实现,无需修改);
  • 方案B:用VS Code打开CSV,右下角点击编码显示(如UTF-8),选择Save with Encoding→UTF-8 with BOM。

4.5 现象:程序启动后立即退出,日志只有[INFO] Starting crawler...无后续

原因:CrawlerEngine.start()是同步阻塞方法,但Main.java中错误地在start()后写了System.exit(0),导致主线程提前终止。
解决:打开Main.java,删除engine.start();之后的System.exit(0);。正确逻辑是让CrawlerEngine内部的CountDownLatch自然释放,程序优雅退出。


5. 进阶技巧:用XPath精确定位动态内容,绕过前端渲染陷阱

电商网站越来越爱用Vue/React渲染商品参数,传统XPath//div[@id='specifications']//td经常失效——因为DOM节点在JS执行前根本不存在。这时候不能死磕XPath,得学会“借力打力”。这个源码包的XPathHtmlParser预留了evaluateScript()扩展点,我用它解决了三个典型场景,方法通用,不依赖特定框架。

5.1 场景一:价格藏在<script>的JSON里,XPath找不到

某平台详情页源码片段:

<script type="application/ld+json"> {"@context":"https://schema.org","@type":"Product","name":"XXX耳机","offers":{"@type":"Offer","price":"299.00"}} </script>

操作步骤:

  1. 在crawler.properties中添加JSONPath规则(非XPath!):
price.jsonpath=$.offers.price
  1. 修改XPathHtmlParser.java,在parse()方法中增加:
// 若配置了jsonpath,优先尝试解析script中的JSON if (config.getJsonPath() != null && !config.getJsonPath().isEmpty()) { String jsonText = doc.select("script[type='application/ld+json']").first().data(); Object priceObj = JsonPath.read(jsonText, config.getJsonPath()); result.put("price", priceObj.toString()); return result; }
  1. 依赖com.jayway.jsonpath:json-path:2.4.0(pom.xml已声明,无需新增)。

5.2 场景二:规格参数表格由AJAX异步加载,XPath只能抓到空<tbody>

某平台用fetch('/api/specs?id=123')加载参数,HTML中只有占位符:

<table id="specs-table"><tbody><tr><td colspan="2">加载中...</td></tr></tbody></table>

操作步骤:

  1. 用浏览器开发者工具抓包,找到真实API地址(如https://xxx.com/api/specs?id=123);
  2. 在crawler.core.CrawlerEngine中,为详情页URL添加?api_mode=true标识;
  3. 修改XPathHtmlParser.parse(),当检测到api_mode=true时,不解析HTML,而是:
// 构造API请求URL String apiUri = url.replace("item/", "api/specs?id=").replace(".html", ""); String apiResponse = HttpClientUtil.get(apiUri); // 复用已有HTTP工具类 // 解析JSON响应 JSONObject specs = new JSONObject(apiResponse); result.put("specifications", specs.toMap()); // 转为Map存入结果

这样就把“前端渲染”问题,转化成了“调用后端API”问题,成功率从30%提升到98%。

5.3 场景三:商品图片URL被懒加载,XPath匹配img[src]得到的是占位图

常见于<img>enable.lazy.load=true

  1. 修改XPathHtmlParser.java,在XPath解析前插入预处理:
if (config.isEnableLazyLoad()) { // 将所有data-src属性值覆盖到src属性 Elements lazyImgs = doc.select("img[data-src]"); for (Element img : lazyImgs) { img.attr("src", img.attr("data-src")); } } // 此后XPath匹配img[src]即可拿到真实URL

从那以后我每次遇到动态渲染页面,第一反应不是打开Chrome调试XPath,而是先看Network面板找API、看Elements找data-*属性、看Sources找内联JSON——这三招覆盖了90%的“XPath失效”场景。源码包里这些扩展点都已预留,你只需要按需激活,不用重写引擎。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进