Java爬取网页表格实例:MyEclipse环境下Jsoup+JDBC全流程解析

发布时间:2026/10/15 23:52:43
Java爬取网页表格实例:MyEclipse环境下Jsoup+JDBC全流程解析 简介一份面向Java初学者的网页表格爬取示例基于MyEclipse环境实现适合正在学习网络爬虫、数据采集或HTML解析的开发者参考。示例以Jsoup为核心重点演示静态网页表格的拉取与解析流程从建立URL连接、获取Document对象到使用CSS选择器定位表格、遍历行与单元格并涉及数据存储与try-catch异常处理帮助理解爬虫开发中的关键环节。压缩包共24个文件包含7个java源码、7个class编译产物和7个依赖jar包如jsoup-1.7.2、httpclient、commons-logging等另附项目配置与类路径文件整体仅1.15MB导入MyEclipse即可直接运行。已有710人学习下载。通过阅读和调试这套示例可快速掌握Java爬虫的基本套路理解动态页面与静态页面的差异并复用其代码结构完成更多表格类数据采集任务。1. 一个Java爬取网页表格的例子为什么从MyEclipse起步不少同学找我聊java爬取网页表格的例子第一反应是去找Jsoup的文档、爬虫框架的教程结果发现大部分例子都是Maven工程、IDEA环境真放到MyEclipse里一跑光是导包和构建路径就能卡掉半天。这个项目当初的设计目标很朴素在一个传统的MyEclipse环境下用Java标准库加一个Jsoup把某个页面的表格数据完整地抓下来、解析成结构化数据、再存进MySQL整个过程不依赖Maven不用Spring不用任何重型框架。这个方案适合两类人。一类是做课设或毕设的学生需要一份能直接运行、能讲清楚每一步原理的完整示例另一类是在校生学完Java SE想看看URL连接、输入流、字符串处理、JDBC这些零散知识点怎么串成一个真实任务的开发者。这套方案会把技术选型为什么是这样、代码为什么这么写、出了问题怎么看全部讲透。2. 总体架构设计抓取、解析、存储三层与选型理由2.1 三层结构的职责边界为什么必须分开做爬网页表格的例子最忌讳的写法是把所有代码堆在main方法里打开连接、读流、正则匹配、拼SQL、执行插入一气呵成。这种写法跑通一次没问题但一旦目标页面结构变了或者想换个网页抓改动成本非常高而且答辩时很难讲清楚设计思想。所以这里采用经典的三层结构抓取层负责根据URL发起HTTP请求拿到完整的HTML源码并对状态码、超时、异常做统一处理解析层负责从HTML里定位表格节点提取表头和单元格数据组织成二维数组行、列结构存储层负责把二维数组写入MySQL包括数据库连接管理、批量插入、查询验证。三层之间用方法调用衔接不搞复杂的接口抽象但每个方法只做一件事。比如抓取层只返回HTML字符串不关心里面有没有表格解析层只处理字符串不关心数据从哪来存储层只接收ListList 不关心HTML结构。这样的好处是换一个目标网页只需要改解析层的选择器换一种存储方式只需要动存储层。2.2 技术选型在MyEclipse里做爬虫的最稳组合技术选型的核心原则是“在传统Java环境下能跑、代码量可控、原理讲得清”。对比过几个方案方案优点在MyEclipse里的问题HttpURLConnectionJDK自带零依赖代码稍微啰嗦但可控性强HttpClient / OkHttp功能强大API友好需要额外下载jar和依赖链MyEclipse老版本容易冲突正则表达式解析不需要额外jar表格嵌套、属性顺序变化时极易翻车调试成本高Jsoup选择器语法简单容错强只需要一个jar手动导入即可最终选定HttpURLConnection Jsoup JDBC MySQL这个组合。HttpURLConnection虽然在写法上不如HttpClient优雅但它是JDK自带的不用处理传递依赖课设环境里最省心。Jsoup负责解析它内部会把HTML解析成DOM树用类似jQuery的选择器就能定位table、tr、td节点远比正则表达式可靠。MySQL负责存储答辩时可以直接写SQL查询验证抓取结果比存成文本文件有说服力得多。2.3 开发环境清单与工程初始化这个例子不涉及Web页面所以不需要建Web工程在MyEclipse里创建一个普通的Java Project即可。环境清单如下JDK 1.8或以上版本MyEclipse任一近期版本支持Java Project类型即可MySQL 5.7或8.x本地安装即可jsoup-1.x系列jar包一个单独下载后手工导入。工程里的包结构建议按层划分com.demo.crawler.http放抓取相关类com.demo.crawler.parser放解析相关类com.demo.crawler.store放存储相关类com.demo.crawler.main放入口类。包名可以从cn、com等常见前缀中选一个自己习惯就好。3. 核心实现HTTP抓取与表格解析的完整代码3.1 抓取模块HttpURLConnection请求与超时处理抓取模块的核心方法接收一个URL字符串返回HTML源码。这里有几个必须处理的细节设置请求头伪装成浏览器、设置连接超时和读取超时、处理HTTP状态码、正确读取输入流。package com.demo.crawler.http; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; public class HttpFetcher { public static String fetchHtml(String urlStr) throws Exception { URL url new URL(urlStr); HttpURLConnection conn (HttpURLConnection) url.openConnection(); // 设置请求参数模拟浏览器访问 conn.setRequestMethod(GET); conn.setConnectTimeout(8000); conn.setReadTimeout(8000); conn.setRequestProperty(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36); conn.setRequestProperty(Accept, text/html,application/xhtmlxml); conn.setInstanceFollowRedirects(true); int code conn.getResponseCode(); if (code ! 200) { throw new RuntimeException(HTTP状态码异常 code); } // 读取响应体 InputStream in conn.getInputStream(); ByteArrayOutputStream out new ByteArrayOutputStream(); byte[] buffer new byte[4096]; int len; while ((len in.read(buffer)) ! -1) { out.write(buffer, 0, len); } in.close(); conn.disconnect(); // 此处先按UTF-8处理下一节细说编码判断 return new String(out.toByteArray(), UTF-8); } }这段代码里setConnectTimeout是建立连接的超时时间setReadTimeout是等待服务器返回数据的超时时间两个都设8秒是为了避免目标网站无响应时程序长时间卡死。setRequestProperty设置User-Agent很重要很多服务器会拒绝不带UA的请求返回403或503。setInstanceFollowRedirects(true)是让连接自动跟随302跳转否则一些做了域名跳转的网站拿不到真实页面。提示如果目标网站响应慢可以把超时时间调大到15000毫秒但不要设成00表示无限等待程序会挂在那里。3.2 编码处理从响应头到页面meta的字符集判定上一步直接按UTF-8解析字符串但很多中文网站用的是GBK或GB2312直接UTF-8解码会出现乱码。正确的做法是先判断字符集再解码。判断优先级如下从HTTP响应头Content-Type里取charset参数如果响应头没有就在HTML源码的meta标签里找charset两个都找不到时按照目标网站的实际情况在配置里手工指定。private static String detectCharset(HttpURLConnection conn, byte[] data) { // 先从响应头取字符集 String contentType conn.getContentType(); if (contentType ! null) { String[] parts contentType.split(charset); if (parts.length 1) { return parts[1].trim(); } } // 再从HTML源码的meta标签里找 String html new String(data, ISO-8859-1); java.util.regex.Pattern p java.util.regex.Pattern .compile(charset[\]?([a-zA-Z0-9-])); java.util.regex.Matcher m p.matcher(html); if (m.find()) { return m.group(1); } return UTF-8; }这个方法的思路是先用ISO-8859-1解码字节数组因为这个编码不会把字节破坏每个字节都对应成一个字符然后再用正则从这串字符里找charset声明最后用真正的字符集解码一次。注意Content-Type头里的charset优先因为服务器明确告诉的编码一般是对的。3.3 解析模块用JSoup把表格还原成二维数组拿到HTML字符串后开始解析表格。Jsoup的用法很简单把HTML字符串传进去得到一个Document对象然后就能用选择器查节点了。这里最核心的问题是表格里的colspan列合并和rowspan行合并属性如果忽略它们单元格数量对不上行和列就错位了。package com.demo.crawler.parser; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; public class TableParser { public static ListListString parseTable(String html) { Document doc Jsoup.parse(html); // 取页面中第一个表格实际使用时按需要选择 Element table doc.select(table).first(); if (table null) { throw new RuntimeException(页面中未找到表格节点); } ListListString rows new ArrayListListString(); // 记录当前列位置需要补几个来自上方行的单元格 MapInteger, Integer pendingRowspan new HashMapInteger, Integer(); for (Element tr : table.select(tr)) { ListString row new ArrayListString(); int col 0; for (Element td : tr.select(td, th)) { // 先补全上一行rowspan遗留的空位 while (pendingRowspan.containsKey(col) pendingRowspan.get(col) 0) { row.add(rows.get(rows.size() - 1).get(col)); pendingRowspan.put(col, pendingRowspan.get(col) - 1); col; } int colspan 1; int rowspan 1; if (td.hasAttr(colspan)) { colspan Integer.parseInt(td.attr(colspan)); } if (td.hasAttr(rowspan)) { rowspan Integer.parseInt(td.attr(rowspan)); } String text td.text().trim(); for (int i 0; i colspan; i) { row.add(text); if (rowspan 1) { pendingRowspan.put(col, rowspan - 1); } col; } } // 当前行结束时如果还有遗留的rowspan未补先补全 while (pendingRowspan.containsKey(col) pendingRowspan.get(col) 0) { row.add(rows.get(rows.size() - 1).get(col)); pendingRowspan.put(col, pendingRowspan.get(col) - 1); col; } rows.add(row); } return rows; } }这段代码的核心是pendingRowspan这个Map它记住“当前列的某个位置还有几行需要补单元格”。每处理一个新单元格时先检查当前列是否欠着上方行的合并单元格欠了就补进去列号加一直到把欠的补完再开始处理当前单元格。colspan的处理则是把相同文字重复加入当前行这样二维数组的每一行长度一致写入数据库或Excel都很方便。注意td.text()取到的是单元格内所有文本拼接后的结果如果单元格里有嵌套标签比如a链接或span要按需处理。默认做法是取纯文本这对大多数课设场景已经够用。4. 数据持久化MySQL表设计、JDBC批量写入与数据验证4.1 两张核心表的设计任务表与数据明细表抓下来的表格数据要入库至少需要两张表一张记录“抓取任务”本身一张记录“表格里的每一行每一列”。任务表的作用是给数据分组方便以后查“哪批数据是哪个URL抓来的”。CREATE DATABASE IF NOT EXISTS crawler_db DEFAULT CHARSET utf8mb4; USE crawler_db; CREATE TABLE crawl_task ( task_id INT PRIMARY KEY AUTO_INCREMENT, task_name VARCHAR(100) NOT NULL, target_url VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE table_data ( id INT PRIMARY KEY AUTO_INCREMENT, task_id INT NOT NULL, row_index INT NOT NULL, col_index INT NOT NULL, cell_text VARCHAR(2000), CONSTRAINT fk_task FOREIGN KEY (task_id) REFERENCES crawl_task(task_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个设计决策值得说。第一数据明细表用row_index和col_index两个数字来定位单元格而不是按“表头数据行”的结构存储优点是通用于任何表格不关心表头有几个、数据有几行。第二cell_text定成VARCHAR(2000)如果网页里的单元格文本特别长批量插入时一定要做截断处理否则会报Data too long。第三外键约束保证每条数据都归属于一个有效的抓取任务不会出现孤儿数据。4.2 JDBC工具类连接参数与常用写法JDBC的写法在MyEclipse环境里存在一个经典坑驱动类名因MySQL版本不同而不同。MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.x用com.mysql.cj.jdbc.Driver且8.x要求URL里带上时区参数否则连接会直接抛异常。下面的工具类按MySQL 8.x的标准写法实现。package com.demo.crawler.store; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class DBHelper { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/crawler_db ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动加载失败, e); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(URL, USER, PASSWORD); } }连接串里的参数逐个解释一下。useUnicodetruecharacterEncodingutf8是让连接的字符集为UTF-8避免中文乱码useSSLfalse是关闭SSL加密本地开发环境下能省去证书配置的麻烦serverTimezoneAsia/Shanghai是MySQL 8.x必须带的它告诉驱动服务器所在时区否则会报时区异常allowPublicKeyRetrievaltrue是MySQL 8.x连接时偶尔会遇到的公钥检索问题加上这个参数能直接避免。4.3 批量插入的实现与查询验证解析层返回的ListListString要写入数据库最直接的方式是遍历每一行、每一列逐条执行INSERT。但几千条数据逐条插入会非常慢这里改用addBatch和executeBatch批量执行。package com.demo.crawler.store; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.List; public class TableDataStore { public static int saveTableData(int taskId, ListListString rows) throws Exception { String sql INSERT INTO table_data(task_id, row_index, col_index, cell_text) VALUES(?, ?, ?, ?); Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql); int count 0; for (int i 0; i rows.size(); i) { ListString row rows.get(i); for (int j 0; j row.size(); j) { String cell row.get(j); if (cell.length() 2000) { cell cell.substring(0, 2000); } ps.setInt(1, taskId); ps.setInt(2, i); ps.setInt(3, j); ps.setString(4, cell); ps.addBatch(); count; // 每500条执行一次避免批次过大占用内存 if (count % 500 0) { ps.executeBatch(); } } } ps.executeBatch(); ps.close(); conn.close(); return count; } public static void printTaskData(int taskId) throws Exception { String sql SELECT row_index, col_index, cell_text FROM table_data WHERE task_id ? ORDER BY row_index, col_index; Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, taskId); ResultSet rs ps.executeQuery(); while (rs.next()) { System.out.println(行 rs.getInt(row_index) 列 rs.getInt(col_index) rs.getString(cell_text)); } rs.close(); ps.close(); conn.close(); } }批量插入的写法在课设答辩里很加分因为能体现性能意识。addBatch把SQL语句攒在内存里攒够了500条才executeBatch提交一次这样网络往返次数从几千次降到了几次。插入前对cell_text做长度截断这一步很多人会漏掉实际抓取的网页里偶尔会出现超长文本漏了这行代码程序就会中断在中间。插入完成后提供printTaskData方法做回读验证确保数据真的写进去了而不是只在控制台打印了一堆“插入成功”。5. 高频问题排查五个最容易翻车的点5.1 依赖没有真正进入运行环境ClassNotFoundException现象是程序一跑起来控制台直接抛java.lang.ClassNotFoundException: org.jsoup.Jsoup。原因是jsoup.jar虽然复制到了项目文件夹里但没有加进Java Build Path。MyEclipse里有一个容易混淆的点把jar拖进lib目录只是“文件存在于项目中”要让编译器能找到类必须右键项目 - Build Path - Configure Build Path - Libraries - Add JARs选中jsoup.jar。另外如果之前加过jar又移动了位置也要重新配置否则同样的报错会再出现。解决方式每次换环境或换工程都检查Build Path里是否有jsoup.jar。用最笨的办法验证展开jar包找到org/jsoup/Jsoup.class这个路径确认它在Libraries列表里。5.2 JS渲染的动态页面拿回来的HTML里根本没有表格现象是HTML成功抓回来了但用Jsoup解析时报“页面中未找到表格节点”打开浏览器却明明能看到表格。原因是HttpURLConnection拿到的是服务器返回的原始HTML浏览器里看到的表格可能是JavaScript动态渲染到页面上的原始源码里只有一堆script标签和数据接口地址。解决方式先检查原始HTML如果发现表格不存在就去找页面背后的数据接口。常见思路是用浏览器开发者工具里的网络面板刷新页面时观察有哪些响应是JSON或HTML片段直接抓那个接口地址往往比抓页面本身更简单。对于课设来说能说清楚“这个页面是动态渲染的所以我改抓数据接口”本身就是加分项。5.3 中文乱码锟斤拷是怎么出现的现象是解析出来的中文变成“锟斤拷”或者一串问号。原因是字符串解码用的字符集和网页实际编码不一致。前面3.2节已经写了字符集探测方法但还有一层坑out.toByteArray()转出来的字节数组在调用new String(data, charset)时如果charset传错整个页面的中文都会错乱。解决方式先打印detectCharset方法返回的编码值确认它确实是“GBK”或“UTF-8”而不是null。如果还是乱码检查数据库连接串的characterEncoding参数以及表结构的DEFAULT CHARSET三处字符集必须统一。我一般会先控制台输出网页源码片段看乱不乱再决定是显示层的问题还是存储层的问题。5.4 合并单元格表格行列错位的元凶现象是数据对不上表头有的行多几列有的行少几列存进数据库后错位严重。原因是目标表格使用了rowspan或colspan属性普通遍历td节点会忽略合并逻辑导致每一行的单元格数量不一致。解决方式是用3.3节里那套pendingRowspan的补齐逻辑。如果不想写这么复杂的代码也可以退而求其次解析前先判断表格里有没有rowspan或colspan属性如果有就提示“该表格含合并单元格暂不支持”至少比错位入库好。但既然代码已经写出来了建议直接保留这套逻辑因为真实页面里合并单元格很常见。5.5 MySQL连接失败驱动版本与URL参数现象是连接数据库时报CommunicationsException或Access denied for user。原因是两类。第一类是驱动版本和MySQL版本不匹配MySQL 8.x还在用com.mysql.jdbc.Driver会直接报错要换成com.mysql.cj.jdbc.Driver。第二类是URL参数不全缺serverTimezone、useSSL或allowPublicKeyRetrieval这三项是MySQL 8.x连接时最容易出问题的地方。解决方式连接前先确认MySQL版本在命令行执行select version();然后按版本修改驱动类和URL参数。如果报Access denied检查用户名密码注意root用户默认可能只允许localhost连接连接串里写localhost不要写127.0.0.1部分MySQL配置下这两个地址权限判定不一样。6. 运行验证与答辩这样演示才算完整交付6.1 验收清单从功能到代码规范完整项目的验收不能只停留在“能跑出结果”下面这张表是课设或毕设答辩前必须过一遍的准则。验收项验收方法通过标准抓取功能运行入口类传入一个静态HTML页面的URL控制台打印HTML长度无超时和状态码异常解析功能对含3行以上数据的表格页解析二维数组行数、列数与页面一致编码处理分别用UTF-8页面和GBK页面测试中文无乱码入库功能执行保存方法再查询MySQLtable_data表行数等于解析行数×列数异常处理传入不存在的URL程序抛出明确异常信息不堆栈崩溃代码规范检查类名、方法名、包结构包按层划分方法不超过50行答辩演示的时候第一步跑主程序第二步打开MySQL命令行执行select * from table_data limit 20;这两步就能直观证明抓取、解析、存储全链路跑通了。6.2 演示流程一条主线加一条意外支线演示不要只准备一个成功路径。建议准备两个目标URL一个是正常的表格页面一个是带rowspan合并单元格的页面。先演示正常流程让所有人看到数据入库再演示合并单元格的页面解释行列对齐逻辑。这两步走完评委对方案的理解会明显不一样。另外准备一个“异常演示”很有必要故意把URL改成一个不存在的地址运行后展示异常信息然后说明“这里我会捕获HTTP状态码非200的情况抛出带状态码的异常”。这比全程一路顺畅更有说服力能体现你考虑过真实场景中的失败情况。6.3 答辩问题应对与交付习惯答辩时最常被问的问题第一个是“为什么不用Python写爬虫”回答重点是这个项目的定位是Java全流程实践重点在HttpURLConnection、Jsoup解析、JDBC持久化这些Java核心技术栈的串联而不是工具选型的优劣之争。第二个问题是“如果网站有反爬措施怎么办”回答思路是先做UA伪装和超时控制遇到验证码或访问频率限制时说明爬虫有严格的robots协议约束课设场景只针对公开数据页面。以前我做类似项目也图省事把所有代码塞在main方法里跑通就交付。后来复盘才发现分层清晰、异常处理完整、批量入库这三件事才是让这份代码能从“作业”变成“方案”的关键。从那以后我每完成一版都强制自己按验收清单走一遍改一个URL、换一种编码、断一次数据库三个用例跑完才敢说完工。完整的可运行工程包已经整理好给做毕设或课设的同学省点时间。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询