简介:专为刚学Java的人准备的一个网页数据抓取小工程,用标准Maven方式组织,结构一目了然,导入IDE就能跑。里面包含src/main/java下的爬虫主逻辑(在novel.spider包里)、src/test/java里的简单测试用例,还有配好的pom.xml——已经写好Jsoup或HttpClient这类基础依赖,不用自己查版本、加配置。项目自带Eclipse支持文件(.classpath、.project等),直接拖进工作区就能编译运行。target目录已按规范建好classes和test-classes输出路径,运行后结果直接打在控制台,方便观察HTTP请求发出、HTML页面解析、文本或链接提取全过程。整个项目不碰验证码识别、IP轮换、登录态维持这些复杂环节,只聚焦最基础的发请求→取HTML→选元素→拿数据这条主线,帮初学者建立对网络爬虫执行流程和Maven工程管理的直观认识。
1. 项目概述:为什么这个小爬虫是Java新手真正需要的“第一块砖”
刚学完Java语法、写了几个控制台计算器和学生管理系统,突然想试试“抓网页”——结果一搜“Java爬虫”,满屏都是Selenium+ChromeDriver配置失败、Jsoup版本冲突报错、Maven依赖树里红得发亮的compile error、还有人说“先学HTTP协议再学TCP/IP栈”……新手直接懵在原地。这不是学不会,是根本找不到从“会写HelloWorld”到“能取到豆瓣电影标题”的那条平缓坡道。我带过十几期Java入门训练营,90%的学员卡在第一个真实项目上,不是因为代码难,而是因为环境没搭对、结构看不懂、错误不知道从哪查起。这个项目就是专治这种“启动焦虑”的:它不叫“分布式网络爬虫系统”,就叫“novel.spider”——一个包名,三段逻辑,五次打印,全部跑通只要3分钟。
核心关键词“Java爬虫,Maven入门,网页抓取”不是标签,是三个必须同时落地的能力锚点。所谓“Java爬虫”,在这里特指用纯Java标准库+一个轻量解析库(Jsoup)完成HTTP请求与DOM操作,不碰浏览器自动化、不写正则硬解析、不处理Cookie持久化;所谓“Maven入门”,是指你打开pom.xml,能看到<groupId>org.jsoup</groupId>这一行时,能立刻对应到本地仓库里那个jar包的物理路径,知道mvn compile背后发生了什么,而不是把它当成IDE里一个自动弹出的神秘按钮;所谓“网页抓取”,就是对着一个小说章节页(比如某免费小说站的/chapter/123.html),用几行代码把<h1 class="title">第一章 风起青萍末</h1>里的标题文字抠出来,再把<a href="/chapter/124.html">下一章</a>里的链接地址存进List——就这么具体,这么窄,这么可触摸。它不教你怎么绕过反爬,因为初学者连“服务器返回了403”和“Jsoup抛出NullPointerException”都分不清;它也不讲怎么存进MySQL,因为第一步先把控制台里打印出“第一章 风起青萍末”这九个字,比建表语句重要十倍。这个项目目录里没有src/main/resources/application.yml,没有Dockerfile,甚至没有README.md——它用最原始的方式告诉你:Java工程的本质,就是.java文件按包路径放好,pom.xml写对依赖,mvn clean compile exec:java敲下去,控制台有输出,你就赢了。
2. 项目整体设计与思路拆解:为什么选Jsoup而非HttpClient?为什么包名叫novel.spider?
2.1 工具链选择:Jsoup是新手唯一该碰的HTML解析库
很多人看到“网页抓取”,第一反应是HttpClient+正则表达式。我试过让零基础学员用这种方式抓取一个天气预报页面:他们花两小时写出Pattern.compile("<div class=\"temp\">(\\d+)℃</div>"),结果第三天网站把class名改成weather-temp,整个程序就废了。而Jsoup用doc.select("div.temp").text(),哪怕class名变一百次,只要HTML结构没大改,selector还能工作。这不是偷懒,是抽象层级的选择——正则处理的是字符串,Jsoup处理的是DOM树。对新手而言,理解“元素有父子关系、有属性、有文本内容”比理解“贪婪匹配与非贪婪匹配的区别”容易十倍。更重要的是,Jsoup内置了HTTP客户端(Jsoup.connect(url).get()),一行代码搞定GET请求+响应体解析+字符编码自动识别(它会读<meta charset="UTF-8">),而HttpClient需要手动设置Charset、处理HttpResponse、调用EntityUtils.toString(entity, "UTF-8")——多出来的5行代码,对新手就是5个放弃的理由。
至于为什么不用Selenium?因为它引入了完全不同的复杂度:你需要下载chromedriver、配置PATH、处理WebDriverException、等待页面加载完成、应对JavaScript动态渲染……这些全是“爬虫之外的问题”。这个项目的目标是建立“发请求→收HTML→抽数据”的最小闭环,Selenium会让闭环变成“装驱动→启浏览器→等JS→找元素→取文本→关浏览器”,中间任何一环断掉,学员都不知道问题出在爬虫逻辑还是环境配置上。Jsoup的connect().get()方法,就像Java里的System.out.println()一样可靠——它要么成功返回Document,要么抛出明确的IOException,没有灰色地带。
2.2 包结构设计:novel.spider不是随便起的名字
src/main/java/novel/spider/这个路径,藏着三个教学意图。第一,“novel”表明领域上下文——我们抓的是小说章节,不是股票行情或微博热搜。小说页面结构简单(标题+正文+上下章链接),HTML标签语义清晰(<h1>标题、<div id="content">正文、<a>导航),没有iframe嵌套、没有Ajax懒加载、没有Vue动态渲染,新手用浏览器开发者工具一眼就能定位目标元素。第二,“spider”直指核心功能,避免用crawler或scraper这类术语制造距离感。第三,二级包名刻意避开com.xxx这种企业级命名,因为新手看到com.example.novel.spider会下意识觉得“这得先注册域名吧?”——其实novel.spider在Java里就是合法包名,编译器不认公司名,只认路径分隔符。我在实际教学中发现,当学员第一次在IDE里右键novel包→New→Class,输入NovelSpiderMain,然后看到生成的package novel.spider;时,那种“原来包名就是文件夹名”的顿悟感,比讲十分钟包机制管用得多。
2.3 Maven结构精简逻辑:删掉所有“看起来有用”的东西
看pom.xml你会发现,除了jsoup和junit,几乎没有其他依赖。没有Log4j(用System.out.println足够)、没有Lombok(不加@Data也能手写getter)、没有Spring Boot(启动类里SpringApplication.run()对新手是黑盒)。这是因为Maven的<dependency>标签,对初学者而言本质是“信任契约”——你相信这个jar包能帮你干活,但你不需要知道它内部怎么实现。引入太多依赖,契约就变复杂了:slf4j-api和slf4j-simple版本不匹配导致日志不输出,commons-lang3和guava都提供了StringUtils造成方法调用歧义……这些都不是爬虫问题,是Maven依赖管理问题。本项目只保留两个契约:Jsoup负责把HTML变成可查询的Document对象,JUnit负责验证抽取结果是否正确。其他所有“锦上添花”的功能,一律砍掉。就连<build>标签里也只配了<plugins>中的maven-compiler-plugin(指定Java 8编译级别),因为新手常犯的错是用Java 17写代码却用Java 8编译,报错信息里一堆Unsupported major.minor version,而这个插件能第一时间暴露问题。
3. 核心细节解析与实操要点:从pom.xml到NovelSpiderMain.java的逐行解读
3.1 pom.xml关键配置:为什么版本号写成1.15.3而不是1.15.x?
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
这里写死1.15.3而非1.15.x或[1.15,1.16),是经过血泪教训的。Jsoup 1.15.0发布时有个严重bug:当HTML里有<meta http-equiv="refresh" content="0;url=xxx">重定向标签时,Jsoup.connect(url).get()会无限重定向直到栈溢出。这个bug在1.15.3才修复。如果写成1.15.x,Maven可能拉取到1.15.0,新手运行时程序卡死,查三天都不知道是Jsoup的锅。写死小版本号,等于给新手一个确定的、可复现的环境。同理,JUnit用4.13.2而非5.x,因为JUnit 5需要maven-surefire-plugin额外配置,而JUnit 4的@Test注解开箱即用,mvn test命令敲下去就能跑。pom.xml里还有一处关键配置:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
这行代码强制编译器用Java 8语法检查源码。很多新手电脑装了Java 17,但没改IDE的编译级别,结果写了var title = doc.title();(Java 10+特性),编译时报错Cannot resolve symbol 'var'。加上这行,错误信息会明确提示“source level 8 doesn’t support var”,比模糊的语法错误好定位十倍。
3.2 NovelSpiderMain.java主逻辑:三步走,每步解决一个认知障碍
public class NovelSpiderMain {
public static void main(String[] args) throws IOException {
// Step 1: 发起HTTP请求,获取Document对象
Document doc = Jsoup.connect("https://example-novel-site.com/chapter/1.html")
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(10000)
.get();
// Step 2: 解析HTML,提取标题和正文
String title = doc.select("h1.title").first().text();
String content = doc.select("div#content").first().text();
// Step 3: 打印结果,验证数据抽取正确性
System.out.println("标题:" + title);
System.out.println("正文前50字:" + content.substring(0, Math.min(50, content.length())));
}
}
这段代码的教学价值不在功能,而在认知阶梯的设计。Step 1解决“怎么拿到网页”的问题:Jsoup.connect()返回Connection对象,.get()执行请求,.userAgent()模拟浏览器(避免部分网站拒绝爬虫),.timeout()防止卡死。这里特意写全参数,而不是封装成工具方法,因为新手需要亲眼看到“发请求”这一步的完整形态。Step 2解决“怎么从HTML里找东西”的问题:select("h1.title")是CSS选择器,和浏览器开发者工具里右键“Copy selector”得到的字符串完全一致——这意味着学员可以边看网页边写代码,所见即所得。.first().text()链式调用,直观展示“先选元素集合,再取第一个,最后拿文本内容”的数据流。Step 3解决“怎么确认我写对了”的问题:System.out.println是最原始的调试方式,但它能让学员立刻看到结果。注意content.substring(0, Math.min(50, content.length()))这行——它处理了“正文可能不足50字”的边界情况,这是新手最容易忽略的健壮性意识。
3.3 测试代码设计:用JUnit验证,而不是靠眼睛看控制台
public class NovelSpiderTest {
@Test
public void testExtractTitle() throws IOException {
// 模拟一个简易HTML片段,不依赖真实网络
String html = "<html><body><h1 class='title'>第一章 青萍之末</h1></body></html>";
Document doc = Jsoup.parse(html);
String title = doc.select("h1.title").first().text();
assertEquals("第一章 青萍之末", title);
}
}
测试代码的价值远超“保证代码正确”。它教会新手两件事:第一,隔离依赖——用Jsoup.parse(html)构造Document,而不是Jsoup.connect(url).get(),这样测试不走网络,秒级完成,不怕网站宕机;第二,定义成功标准——assertEquals("第一章 青萍之末", title)这行代码,把“提取标题”这个模糊需求,变成了可测量的布尔值:true就是成功,false就是失败。我在教学中要求学员必须先写这个测试,再写主逻辑,因为当mvn test报红时,他们就知道问题一定出在select()或text()上,而不是怀疑网络或编码。这种“先定义期望,再实现逻辑”的思维,比爬虫技术本身重要得多。
4. 实操过程与核心环节实现:从导入IDE到控制台看到结果的完整流水线
4.1 Eclipse导入四步法:为什么必须删掉.project再导入?
很多新手直接把项目文件夹拖进Eclipse工作区,结果报错“Project ‘xxx’ is missing required source folder: ‘src/main/java’”。这是因为Eclipse的.project文件里硬编码了项目路径和构建命令,而你的本地路径和原作者不同。正确流程是:
- 删除残留IDE文件:进入项目根目录,手动删掉
.project、.classpath、.settings/这三个文件/文件夹。别怕,它们只是IDE的缓存,Maven的pom.xml才是真相。 - 用Maven命令生成新IDE配置:打开终端,cd到项目根目录,执行
mvn eclipse:eclipse。这条命令会根据pom.xml重新生成.project和.classpath,确保源码路径、依赖jar包路径全部指向Maven仓库。 - Eclipse菜单导入:在Eclipse中,File → Import → Existing Projects into Workspace → 选择项目根目录 → Finish。此时项目图标应该显示为标准的Java项目(蓝色J图标),而不是普通文件夹。
- 验证编译输出:右键项目 → Properties → Java Build Path → Source选项卡,确认
src/main/java和src/test/java已添加,且Output folder指向target/classes和target/test-classes。这是Maven标准约定,也是后续mvn compile能正常工作的前提。
这四步看似繁琐,实则是让新手理解“IDE配置是衍生品,Maven配置才是源头”。删掉.project再生成,就像把汽车仪表盘拆了重装——你得明白油表指针背后连着油箱传感器,而不是相信指针自己会动。
4.2 运行主类的两种方式:为什么推荐命令行而非IDE按钮?
在Eclipse里,你可以右键NovelSpiderMain.java → Run As → Java Application,也能看到控制台输出。但强烈建议新手用命令行:
# 在项目根目录执行
mvn compile
mvn exec:java -Dexec.mainClass="novel.spider.NovelSpiderMain"
原因有三:第一,可见性——命令行输出清晰显示[INFO] BUILD SUCCESS和[INFO] --- exec-maven-plugin:3.1.0:java (default-cli),你知道每一步Maven在干什么;而IDE的Run按钮像黑盒,点击后只有控制台一闪而过。第二,一致性——你在公司服务器上部署时,不可能有Eclipse图形界面,mvn exec:java是跨平台的标准命令。第三,错误定位——如果报错NoClassDefFoundError: org/jsoup/Jsoup,命令行会明确告诉你“找不到jsoup jar”,而IDE有时只显示“Launch failed”,新手根本不知道缺哪个依赖。
执行mvn exec:java时,注意-Dexec.mainClass参数必须写全限定类名(novel.spider.NovelSpiderMain),不能只写NovelSpiderMain。这是Java类加载机制决定的:JVM需要完整的包路径才能定位到.class文件。新手常犯的错是复制粘贴时漏掉novel.spider.,结果报错Could not find or load main class NovelSpiderMain,折腾半小时才发现少写了包名。
4.3 目录结构实战解析:target/classes里到底有什么?
执行mvn compile后,target/classes目录会自动生成。进去看看:
target/classes/
├── novel/
│ └── spider/
│ ├── NovelSpiderMain.class
│ └── NovelSpiderUtil.class
└── META-INF/
└── MANIFEST.MF
这个结构完美映射了src/main/java/novel/spider/的源码路径。.class文件是Java字节码,MANIFEST.MF是jar包元数据(虽然现在还没打包成jar)。重点在于:target/classes就是Java运行时的类路径(classpath)根目录。当你执行java -cp target/classes novel.spider.NovelSpiderMain,JVM会在这个目录下按包路径查找novel/spider/NovelSpiderMain.class。理解这一点,你就懂了为什么mvn test能自动找到测试类(它把target/test-classes也加进了classpath),也懂了为什么Web服务器要把编译后的class放在WEB-INF/classes下。这个目录不是Maven随便建的,它是Java类加载模型的物理体现。
5. 常见问题与排查技巧实录:新手踩过的坑,我都替你试过了
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令/步骤 | 解决方案 |
|---|---|---|---|
Exception in thread "main" java.net.UnknownHostException: example-novel-site.com | 网站域名无法解析,或网址写错了 | ping example-novel-site.com 或 nslookup example-novel-site.com | 换成真实可访问的小说站点,如http://www.biquge.com.cn/,或用本地HTML文件测试 |
Exception in thread "main" java.lang.NullPointerException at doc.select("h1.title").first().text() | select()返回空集合,.first()为null | 在select()后加System.out.println(doc.select("h1.title").size()); | 用浏览器打开目标网页,右键查看源码,确认<h1>标签是否存在,class名是否准确(注意大小写) |
Exception in thread "main" java.nio.charset.UnsupportedCharsetException: gb2312 | 网页用GB2312编码,Jsoup默认UTF-8解析乱码 | System.out.println(doc.charset()); 查看实际编码 | 在connect()后加.charset("GB2312"),或用Jsoup.parse(html, "GB2312") |
Error: Could not find or load main class novel.spider.NovelSpiderMain | 类路径或类名错误 | ls target/classes/novel/spider/ 确认.class文件存在 | 检查-Dexec.mainClass参数是否写全,确认mvn compile已成功执行 |
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0 | 测试中网络请求失败 | 将测试方法里的Jsoup.connect()换成Jsoup.parse(html) | 测试必须脱离网络,用静态HTML字符串模拟 |
5.2 独家避坑技巧:三个让新手少走两天弯路的经验
技巧一:用本地HTML文件代替真实网站做调试
别一上来就抓线上网站。在src/test/resources/下新建test_chapter.html,内容复制一个真实小说章节的源码(右键网页→查看源代码→全选复制)。然后在测试代码里:
// 替换原来的Jsoup.connect()
File input = new File("src/test/resources/test_chapter.html");
Document doc = Jsoup.parse(input, "UTF-8", "");
这样,你的调试完全离线,不受网络波动、网站改版、IP封禁影响。等本地HTML能稳定提取数据了,再换回真实URL。这是专业开发者的惯用手法,叫“Mock测试”。
技巧二:浏览器开发者工具的三个必用功能
新手总以为“看源码”就是右键“查看网页源代码”,其实那是服务器返回的原始HTML。而现代网站大量用JavaScript动态渲染,真实DOM和源码不一样。必须用F12打开开发者工具:
- Elements面板:实时显示当前渲染的DOM树,Ctrl+F搜索<h1 class="title">,确认元素存在;
- Console面板:输入document.querySelector("h1.title").innerText,直接在浏览器里验证CSS选择器是否有效;
- Network面板:刷新页面,看chapter/1.html请求的Response标签页,确认返回的HTML里真有你要的数据。
技巧三:mvn dependency:tree是你的终极依赖侦探
当出现ClassNotFoundException或NoSuchMethodError,八成是依赖冲突。在项目根目录执行:
mvn dependency:tree | grep jsoup
你会看到类似:
[INFO] \- org.jsoup:jsoup:jar:1.15.3:compile
[INFO] +- com.otherlib:other:jar:2.0:compile
[INFO] | \- org.jsoup:jsoup:jar:1.10.2:compile
这说明otherlib偷偷引入了旧版Jsoup,导致类加载器优先加载了1.10.2版本。解决方案是在pom.xml里显式排除:
<exclusions>
<exclusion>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</exclusion>
</exclusions>
这个命令比翻文档快十倍,是每个Java工程师的必备技能。
6. 项目扩展与进阶路径:当控制台第一次打印出“第一章”之后
这个项目不是终点,而是起点。当你成功运行NovelSpiderMain,看着控制台输出“标题:第一章 风起青萍末”,那一刻的认知突破,比学会十个框架都珍贵。接下来,你可以沿着三条清晰路径自然延伸:
路径一:数据持久化——从控制台到文件
把System.out.println(title)改成写入文件:
Files.write(Paths.get("novel_title.txt"), title.getBytes(StandardCharsets.UTF_8));
这会引入java.nio.file包,让你第一次接触Java 7+的IO新API。再进一步,用BufferedWriter追加模式写入多章内容,体会try-with-resources语法如何自动关闭流。
路径二:流程自动化——从单页到全站
修改主逻辑,用循环抓取/chapter/1.html到/chapter/100.html:
for (int i = 1; i <= 100; i++) {
String url = "https://site.com/chapter/" + i + ".html";
Document doc = Jsoup.connect(url).get();
// 提取并保存...
Thread.sleep(1000); // 加1秒延时,尊重网站服务器
}
这里你会遇到InterruptedException,被迫学习try-catch处理,也会意识到Thread.sleep()不是最佳实践——自然引出“定时任务框架Quartz”或“异步HTTP客户端OkHttp”的学习需求。
路径三:结构规范化——从脚本到模块
把重复的Jsoup.connect().userAgent().timeout().get()封装成NovelSpiderUtil工具类:
public class NovelSpiderUtil {
public static Document fetchPage(String url) throws IOException {
return Jsoup.connect(url)
.userAgent("Mozilla/5.0...")
.timeout(10000)
.get();
}
}
这会让你第一次体会到“方法抽取”的意义:代码复用、职责分离、可测试性提升。再往后,NovelSpiderMain只负责流程编排,NovelSpiderUtil专注HTTP,NovelParser专注HTML解析——这就是面向对象设计的萌芽。
最后分享一个小技巧:每次成功抓取一个新网站,立刻截图保存控制台输出,并在项目根目录建个success_log/文件夹存档。半年后回头看,那些密密麻麻的标题:XXX、正文前50字:YYY,就是你从Java新手蜕变为合格开发者的成长年轮。这个项目没有高大上的架构图,没有炫酷的可视化界面,它只做一件事:在你敲下mvn exec:java的瞬间,让代码和世界产生真实的连接——而连接本身,就是编程最原始也最震撼的魔法。
简介:专为刚学Java的人准备的一个网页数据抓取小工程,用标准Maven方式组织,结构一目了然,导入IDE就能跑。里面包含src/main/java下的爬虫主逻辑(在novel.spider包里)、src/test/java里的简单测试用例,还有配好的pom.xml——已经写好Jsoup或HttpClient这类基础依赖,不用自己查版本、加配置。项目自带Eclipse支持文件(.classpath、.project等),直接拖进工作区就能编译运行。target目录已按规范建好classes和test-classes输出路径,运行后结果直接打在控制台,方便观察HTTP请求发出、HTML页面解析、文本或链接提取全过程。整个项目不碰验证码识别、IP轮换、登录态维持这些复杂环节,只聚焦最基础的发请求→取HTML→选元素→拿数据这条主线,帮初学者建立对网络爬虫执行流程和Maven工程管理的直观认识。

9012

被折叠的 条评论
为什么被折叠?



