简介:面向需要在 MyEclipse 环境下用 Java 快速抓取网页表格数据的开发者,这份示例代码包围绕 Jsoup 解析静态 HTML 表格展开,完整覆盖连接目标页面、按选择器定位表格、遍历行与单元格、提取并存储数据以及异常处理的实现思路,可作为 Java 爬虫入门或日常网页信息抽取的参考实现。包体共 24 个文件,压缩后约 1.15MB,其中 7 个 Java 源文件是示例核心逻辑,7 个 class 为编译产物,7 个 jar 打包了 Jsoup、HttpClient、HttpCore 等依赖库,.project、.classpath、.prefs 等配置文件便于 MyEclipse 直接导入运行。资源已有 708 人学习下载。通过阅读和运行这套代码,可以快速掌握 Java 爬虫的基本流程与排错方法,尤其适合刚接触网页爬虫的 Java 初学者作为练手项目;稍加改造即可迁移到其他表格型网页数据抓取场景,并可作为后续扩展为数据库存储、定时抓取或数据可视化等功能的起点,为数据分析与自动化采集打好基础。 这项目我太熟了,当年刚学Java的时候,拿着MyEclipse到处找能练手的例子,表格抓取算是性价比很高的一类——代码量不大,但网络请求、HTML解析、数据组织全都覆盖到了。后来带新人,我也经常拿这个当入门实战题。
先说清楚这个项目是干嘛的:用Java写一个小程序,传入一个网页地址,自动把页面里的表格数据抓下来。运行环境是MyEclipse,适合刚学完Java基础、想接触真实网络编程的初学者,也适合需要批量采集公开表格数据(比如行业统计表、课程表、价格表)的办公场景。手动复制几百行表格简直反人类,写个爬虫一次搞定才是正路。
下面我把整个项目的设计思路、环境配置、核心代码、坑点排查完整过一遍。
1. 项目设计与技术选型
1.1 需求拆解:要爬的不是网页,是表格
这个需求乍一听很简单——把网页表格爬下来。但真正动手前,你得想清楚三个问题。
第一,目标网页是静态的还是动态的。静态网页的表格就写在HTML源码里,请求一次拿到就能解析;动态网页的表格是JavaScript在浏览器里渲染出来的,直接请求HTML拿不到数据。这个例子我用静态表格打底,后面再聊动态方案。
第二,表格长什么样。HTML里的表格是<table>标签,里面按<tr>分行,按<td>或<th>分单元格。只要页面结构标准,解析就不难。但实际网页往往嵌套、合并单元格、混乱class,所以解析代码要写得健壮一点。
第三,抓到的数据放哪。控制台打印是最简单的演示方式,但实用场景通常要落盘,我会额外演示一个写CSV文件的方法,这样Excel能直接打开。
1.2 方案选型:为什么是jsoup
我见过不少人在这个例子上用HttpClient或者HttpURLConnection手写解析,其实没必要。
Java原生可以做HTTP请求,但拿到HTML字符串之后,你还需要一个能按标签结构提取内容的工具。自己写正则匹配<table>?能写,但非常脆弱——页面加个换行、属性顺序变一下,正则就废了。
我的选择是jsoup。它是一个轻量级HTML解析库,核心优势有三个:
- 自带HTTP客户端,
Jsoup.connect(url).get()一行搞定请求,不用额外引HttpClient。 - 选择器语法和jQuery很像,
doc.select("table")、element.select("tr")这种写法,语义非常清楚。 - 容错能力强,能修正很多不规范的HTML,哪怕页面源码缺标签漏引号,也能尽力解析出结构。
有人会问,为什么不用Selenium?Selenium确实能处理动态网页,但它要启动一个浏览器,内存开销大、速度慢,对初学者来说环境配置也复杂。我的原则是:先判断页面结构,能用轻量工具解决的绝不上重型框架。
1.3 整体流程设计
整个程序分成四步:发送HTTP请求拿到HTML文档;用选择器定位<table>;遍历行和单元格提取文本;把数据打印或写入文件。
用一句话概括:请求是入口,解析是核心,输出是结果。后续写代码就按这条线走,不容易乱。
2. 开发环境准备与依赖配置
2.1 MyEclipse里的工程搭建
MyEclipse虽然是老牌IDE,但新建项目的思路和Eclipse一脉相承,照下面操作就行。
打开MyEclipse,选择File -> New -> Java Project,填个项目名,比如TableCrawlerDemo。JRE环境默认就行,建议JDK 8及以上,能兼容新版本的jsoup。如果出现"Unbound classpath"之类的报错,多半是JRE没配置好,右键项目选Build Path -> Configure Build Path,在Libraries里重新Add一个JRE System Library。
建完项目后在src目录下新建包,我习惯用com.demo.crawler,包名规范一点后面维护省事。
2.2 引入jsoup依赖
jsoup不需要Maven,直接下载jar包扔进项目即可。去jsoup官网下载最新版,比如jsoup-1.16.1.jar。
jar包放哪?我的习惯是在项目根目录建一个lib文件夹,把jar包丢进去,然后右键jar包选择Build Path -> Add to Build Path。这样项目移机或者分享给别人的时候,依赖一目了然。
顺便说一下,如果MyEclipse里无法下载jar包(某些内网环境),也可以用Maven引入依赖:
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency>不过这个项目用外部jar的方式最直观,也方便观察classpath机制。
2.3 准备一个测试页面
真实网站的页面会经常变动,而且不一定方便调试。所以我建议先做一个本地HTML文件用来验证代码逻辑。
我当时的测试文件长这样,存成D:/table.html:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>示例数据表</title> </head> <body> <table id="dataTable" border="1"> <tr> <th>城市</th> <th>最高温度</th> <th>最低温度</th> <th>天气</th> </tr> <tr> <td>北京</td> <td>28</td> <td>19</td> <td>晴</td> </tr> <tr> <td>上海</td> <td>26</td> <td>21</td> <td>小雨</td> </tr> <tr> <td>广州</td> <td>30</td> <td>24</td> <td>多云</td> </tr> </table> </body> </html>这个页面简单但结构规范,表头用<th>,数据用<td>,后面我会用代码同时兼容这两种标签。
3. 核心代码实现与步骤拆解
3.1 第一步:构造请求获取Document
jsoup把请求和解析封装得极其友好。获取页面内容的代码如下:
Document doc = Jsoup.connect(url) .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .timeout(8000) .get();这一串链式调用里面有几个细节值得讲。
userAgent一定要设。很多网站会拦截无UA的请求,设成浏览器的UA能让对方认为你是正常访客。timeout设成8000毫秒,网络不好时避免无限等待。8秒是经验值,太短容易超时,太长影响体验。
解析本地文件则更简单,不需要走网络:
Document doc = Jsoup.parse(new File("D:/table.html"), "UTF-8");注意,本地文件路径里的反斜杠在Java字符串里要写成"D:/table.html"或"D:\\table.html",用正斜杠最省事。
3.2 第二步:定位表格与遍历行列
拿到Document之后,核心来了——怎么找到目标表格。
最直接的方法是doc.select("table"),它返回所有<table>元素组成的集合。大多数情况下页面只有一个表格,直接用tables.first()取出第一个即可。如果页面有多个表格,就要靠id或者class精确锁定,比如doc.select("#dataTable")。
拿到表格元素后,遍历逻辑是这样的:
Elements rows = table.select("tr"); for (Element row : rows) { Elements cells = row.select("th, td"); List<String> rowData = new ArrayList<String>(); for (Element cell : cells) { rowData.add(cell.text().trim()); } System.out.println(String.join(" | ", rowData)); }这里有两个经验点。
第一,row.select("th, td")这行是一次遍历同时抓表头和数据单元格,因为很多表格的<th>不止在第一行出现,比如某些表格左侧也有表头。用逗号分隔的选择器可以一次选中两类标签。
第二,cell.text()拿到的是单元格里的纯文本,而且jsoup会自动处理内部的嵌套标签。比如单元格里有个<span>,.text()会忽略标签只保留文字,非常省事。.trim()是为了去掉首尾空白,因为HTML源码里的换行和缩进经常混进文本里。
3.3 第三步:数据封装与输出
控制台打印只是第一步,实用场景里更常见的是把数据存起来。我建议先定义一个简单的行数据类,然后放进List,后面想输出成什么格式都方便。
我这里采用List<List<String>>来存整个表格,每行又是一个列表。完整代码我整合如下:
package com.demo.crawler; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; public class TableCrawlerDemo { public static void main(String[] args) { // 方式一:解析本地HTML文件,跑通逻辑 String localPath = "D:/table.html"; List<List<String>> localData = parseLocalFile(localPath); printTable(localData); writeToCsv(localData, "D:/table_out.csv"); // 方式二:抓取在线网页(目标URL按需替换) // String onlineUrl = "https://example.com/data.html"; // List<List<String>> onlineData = parseOnline(onlineUrl); // printTable(onlineData); } public static List<List<String>> parseLocalFile(String filePath) { try { File input = new File(filePath); Document doc = Jsoup.parse(input, "UTF-8"); return extractTableData(doc); } catch (IOException e) { e.printStackTrace(); return new ArrayList<List<String>>(); } } public static List<List<String>> parseOnline(String url) { try { Document doc = Jsoup.connect(url) .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .timeout(8000) .get(); doc.setCharset("UTF-8"); return extractTableData(doc); } catch (IOException e) { e.printStackTrace(); return new ArrayList<List<String>>(); } } public static List<List<String>> extractTableData(Document doc) { List<List<String>> tableData = new ArrayList<List<String>>(); Elements tables = doc.select("table"); if (tables.isEmpty()) { System.out.println("页面中没有找到 table 标签,请检查选择器。"); return tableData; } Element table = tables.first(); Elements rows = table.select("tr"); for (Element row : rows) { Elements cells = row.select("th, td"); List<String> rowData = new ArrayList<String>(); for (Element cell : cells) { rowData.add(cell.text().trim()); } tableData.add(rowData); } return tableData; } public static void printTable(List<List<String>> tableData) { for (int i = 0; i < tableData.size(); i++) { System.out.println("第" + (i + 1) + "行: " + String.join(" | ", tableData.get(i))); } } public static void writeToCsv(List<List<String>> tableData, String filePath) { try (FileWriter writer = new FileWriter(filePath)) { for (List<String> row : tableData) { writer.write(String.join(",", row)); writer.write("\n"); } System.out.println("CSV文件已写入: " + filePath); } catch (IOException e) { e.printStackTrace(); } } }这个代码覆盖了三个动作:解析、打印、落盘。extractTableData是核心逻辑,printTable和writeToCsv是输出工具,结构上清晰,以后复用也方便。
3.4 编码处理要提前做
中文乱码是爬虫最常见的坑,很多时候不是代码逻辑错了,而是编码没对上。
网页的编码五花八门:UTF-8最常见,但有些老网站是GBK、GB2312,甚至页面里meta声明的编码和实际不一致。jsoup的做法是尝试从HTML的meta标签读取charset,但这个探测不是百分百可靠。
我的经验是在拿到Document之后,主动调用doc.setCharset("UTF-8")。如果发现输出乱码,再根据网页实际情况改成GBK。
还有一种情况:你用Jsoup.connect().get()时,jsoup会根据响应头里的Content-Type自动识别编码,通常问题不大。但本地解析文件时,Jsoup.parse(File, charset)的第二个参数必须和文件实际编码一致,否则解析出来的就是乱码,这一点要特别留意。
4. 运行调试与常见问题实录
4.1 MyEclipse运行操作
代码写完后,右键TableCrawlerDemo.java,选择Run As -> Java Application。如果一切正常,控制台会打印本地表格数据,同时在D:/table_out.csv生成CSV文件。
如果提示找不到main方法,八成是类选中错了,或者项目编译没通过。先看Project菜单下Clean...一下,再重新编译运行。
4.2 必踩的三个运行异常
我当年做这个例子的时候,几乎把新手能踩的坑全踩了一遍,挨个说。
第一个是java.lang.NoClassDefFoundError: org/jsoup/Jsoup。这个错误的意思是类路径里没有jsoup的类,说白了就是jar包没引进Build Path。很多人引了jar但只复制到了lib文件夹,没有右键Add to Build Path,运行时就找不到类。解决方式很简单:右键项目 ->Build Path -> Configure Build Path -> Libraries,确认jsoup-xxx.jar在列表里。
第二个是java.net.ConnectException: Connection timed out: connect。这个通常是目标URL访问不了、网络不通、或者请求超时时间设得太短。排查步骤:先用浏览器手动打开那个URL,确认能访问;接着检查代码里的URL有没有拼错http/https;再把timeout调到8000以上。如果你所在网络环境必须走代理,还需要加上代理设置,这个看具体网络情况。
第三个是javax.net.ssl.SSLException: PKIX path building failed。访问某些HTTPS证书校验严格的网站时会出现,本质是证书链不信任。如果你只是做学习测试,建议换一个正规证书的网站,或者改用HTTP协议的页面地址。我不建议在代码里写一行代码信任所有证书,那会带来安全隐患,学习阶段没必要养成那种习惯。
4.3 表格为什么抓不到数据
代码没报错,但输出是空的,这个问题往往更让人头疼。
我总结过四个常见原因,可以逐个排查:
- 页面里的表格是JavaScript动态生成的。你用浏览器开发者工具的Elements面板看到的数据,不代表HTML源码里有。遇到这种情况,先
Ctrl+U查看网页源代码,如果源代码里没有<table>里的数据,那说明表格是渲染出来的。 - 表格藏在iframe里。页面源码里有
<iframe>标签,表格在另一个URL中,需要先解析iframe的src,再对这个地址发第二次请求。 - 选择器选错了表格。页面有多个
<table>,但你抓取的是第一个,而目标数据在第二个甚至第三个。解决办法是用table#id、table.className或tables.get(index)精确指定。 - 有嵌套表格。某些页面里一个
<table>里套着一个<table>,导致行遍历混乱。这种情况建议用更精确的选择器定位外层或内层表格,然后单独处理。
排查的时候,我最常用的方法是打印中间结果:先System.out.println(doc)把整个Document打印出来,看看请求回来的页面到底长什么样。这一步直接帮你判断问题出在请求还是解析。
4.4 CSV文件Excel打开乱码
程序本身输出CSV正常,但用Excel打开乱码,这个问题很多新手都会遇到。
原因是Excel默认用ANSI编码打开CSV,而Java的FileWriter默认写出来的是系统平台编码,在中文Windows上通常是GBK。如果项目文件编码是UTF-8,反而是可能的乱码来源。
最简单的处理方式:换用FileWriter时就按系统默认编码写;如果你一定要输出UTF-8的CSV,可以在文件开头写入BOM头\uFEFF,这样Excel会识别成UTF-8。我一般图省事,直接用系统默认编码写,因为目标是本机打开不出乱码。
5. 扩展思路与实操体会
5.1 从单表到全站的多页爬取
这个例子只是爬一个页面的一个表格,但真实需求往往是一个网站多个页面的同类表格。
扩展思路并不复杂:如果你的目标URL带分页参数,比如https://example.com/data?page=1,只需要循环拼接URL,反复调用parseOnline,每页数据累加到同一个List里。但有一点必须注意——控制请求频率。给每次请求之间加一个Thread.sleep(1000)的延迟,既是对目标网站的基本尊重,也能有效降低被封锁的概率。
我在实际工作中见过一个反面教材:有人用双线程并行爬一个站点,不加任何限速,几分钟内请求了几千次,结果IP被对方防火墙封了一天,数据没爬完还把公司网络搞瘫痪了。爬虫的本质是以技术手段获取公开信息,但不能无节制,遵守robots协议、设置合理频率、只爬公开数据,这是底线。
5.2 动态渲染表格的替代方案
前面提到动态页面直接抓HTML拿不到数据,这里说一下解决方案的信号。
当你确认目标表格是AJAX动态加载的,第一优先级的方案是找接口。按F12打开开发者工具,切到Network面板,刷新页面,重点看XHR或Fetch请求。表格数据往往通过一个JSON接口返回,前端拿到JSON之后渲染成表格。你只要直接请求这个JSON接口,用fastjson或Jackson解析,比解析HTML还简单。
如果找不到接口,或者页面交互太复杂,才考虑Selenium这类浏览器自动化工具。它能真实驱动浏览器打开页面,等JavaScript执行完成之后再取表格内容。缺点是慢、吃内存、环境重。所以我的优先级永远是:静态HTML最简单,JSON接口次之,浏览器自动化是兜底方案。
5.3 个人实操建议
最后说几个我在实际项目中积累的体会,比较零碎,但都值得记住。
第一,代码里不要写死文件路径。比如D:/table.html这种写法,在你自己机器上跑没问题,换个电脑就报文件不存在。哪怕是个练习项目,也建议把路径提到方法参数里,或者用config.properties配置文件维护。
第二,写爬虫一定要输出日志。控制台打印在数据量少的时候很直观,但爬几百页的时候,日志和进度输出能救命。我在练习项目里会随手加一个简单的行号计数,不追求日志框架,但会确保每处理完一页有一条输出,方便定位问题。
第三,结构化HTML解析的时候,优先用稳定的锚点。table是结构性很强的标签,比定位div稳得多。有些数据明明不是表格,却用表格布局,这种页面爬下来后清洗成本很高。真要接长时间运行的任务,我会先人工看一下页面结构再写选择器,绝不盲写代码。
这个例子虽然简单,但把Java网络编程、HTML解析、文件写入这几个核心技能串起来了。如果你能把这个跑通,再自己加一个分页爬取或者JSON接口采集,Java爬虫的基础就算真正打牢了。
本文还有配套的精品资源,点击获取