Jsoup爬取网页图片与新闻内容实战
在当今信息爆炸的时代,从海量网页中高效提取结构化数据已成为许多应用场景的基础需求——无论是构建资讯聚合平台、监控竞品动态,还是做舆情分析。而Java生态中的Jsoup,正是这样一个简洁却强大的HTML解析利器。
它不像Selenium那样“笨重”,也不需要复杂的配置,几行代码就能完成一次精准的网页内容抓取。本文将带你深入实战,用真实案例演示如何利用Jsoup稳定、安全地提取新闻正文与配图,并规避常见陷阱。
快速上手:第一个爬虫任务
要开始使用Jsoup,首先确保你的项目已引入依赖。推荐通过Maven管理:
<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency>如果无法使用Maven,也可以手动下载jsoup-1.16.1.jar并添加到项目的构建路径中。
接下来,尝试运行一段最基础的代码:
Document doc = Jsoup.connect("http://www.example.com/news.html").get(); System.out.println(doc.title());这段代码会发起一个HTTP GET请求,获取目标页面的完整HTML文档,并自动处理编码、重定向等问题,最终返回一个可操作的Document对象。
⚠️ 注意:所有网络请求都可能抛出
IOException,务必做好异常捕获和日志记录。
精准提取:定位与清洗内容
真正的挑战从来不是“能不能拿到HTML”,而是“能否准确提取所需内容”。我们以某高校官网的一篇新闻为例(如qfnu.edu.cn),展示完整的提取流程。
第一步:连接并加载文档
Document doc; try { doc = Jsoup.connect("http://www.qfnu.edu.cn/html/xxyw/...html") .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") .timeout(10000) .get(); } catch (IOException e) { System.err.println("页面加载失败:" + e.getMessage()); return; }这里加入了User-Agent和超时设置,提升成功率,避免被服务器识别为爬虫而拦截。
第二步:定位主体内容区域
打开浏览器开发者工具,观察新闻正文所在的容器。通常这类站点会用类似.zw_content这样的class命名:
Elements contentDivs = doc.getElementsByAttributeValue("class", "zw_content"); if (contentDivs.isEmpty()) { System.err.println("未找到内容区域!"); return; } Element contentDiv = contentDivs.first(); // 取第一个匹配块也可以使用更现代的CSS选择器写法:
Element contentDiv = doc.selectFirst(".zw_content");第三步:提取图片链接(含路径转换)
网页中的图片常使用相对路径(如/attach/123.jpg),直接保存会导致链接失效。Jsoup提供了便捷方式自动转为绝对URL:
Elements imgs = contentDiv.getElementsByTag("img"); String firstImg = imgs.size() > 0 ? imgs.get(0).attr("abs:src") : ""; String secondImg = imgs.size() > 1 ? imgs.get(1).attr("abs:src") : "";关键在于使用abs:src而非src属性,Jsoup会根据原始请求URL自动补全域名。
第四步:提取并清洗文本内容
直接调用.text()可以提取所有子节点的文字,但往往包含干扰信息,比如“责任编辑:张三”、“作者:李四”等。
我们可以先过滤掉这些段落:
// 删除含有“责编”或“作者”的p标签 Elements creditParagraphs = contentDiv.select("p:contains(责编), p:contains(作者)"); creditParagraphs.remove(); String cleanText = contentDiv.text().replaceAll("\\s+", " ").trim();这样得到的内容更加干净,适合后续入库或展示。
完整提取示例输出
正文:8月30日,日照市常务副市长王斌一行人来我校进行调研。校长戚万学... 图1:http://www.qfnu.edu.cn/attach/2016/09/02/123920.jpg 图2:批量采集:首页资讯聚合抓取
单篇文章提取只是起点。实际业务中更多是批量抓取列表页上的多条新闻。
假设首页HTML结构如下:
<ul class="news-1-lists"> <li> <img src="/attach/xxx.jpg" title="新闻标题"> <a href="/html/xxyw/...html">新闻标题</a> <p>摘要内容...</p> </li> </ul>我们的目标是从这个列表中提取每一条新闻的缩略图、标题和跳转链接。
实现代码
public static void batchExtractNews() { try { Document doc = Jsoup.connect("http://www.qfnu.edu.cn/") .userAgent("Mozilla/5.0 ...") .timeout(10000) .get(); Elements items = doc.select(".news-1-lists li"); // 直接选中每个条目 List<Map<String, String>> newsList = new ArrayList<>(); for (Element item : items) { String imgUrl = item.selectFirst("img").absUrl("src"); String title = item.selectFirst("img").attr("title"); String link = item.selectFirst("a").absUrl("href"); Map<String, String> newsItem = new HashMap<>(); newsItem.put("title", title); newsItem.put("image_url", imgUrl); newsItem.put("article_url", link); newsItem.put("timestamp", LocalDate.now().toString()); newsList.add(newsItem); System.out.printf("📌 %s\n🖼️ %s\n🔗 %s\n\n", title, imgUrl, link); } // 保存为JSON文件或其他存储形式 saveToJson(newsList, "data/news_data.json"); } catch (IOException e) { e.printStackTrace(); } }存储建议目录结构
data/ ├── raw_html/ # 原始HTML备份 │ └── index_20251212.html ├── images/ # 下载的图片 │ └── 123920.jpg └── news_data.json # 结构化数据对于动态加载的内容(如AJAX请求返回的JSON),可通过抓包分析接口地址,配合ignoreContentType(true)来解析非HTML响应:
String json = Jsoup.connect("https://api.example.com/news") .ignoreContentType(true) .execute() .body();高阶技巧:健壮性与效率优化
1. 安全访问元素:防止越界异常
由于不同新闻的图片数量不一致,盲目调用get(1)极易引发IndexOutOfBoundsException。应封装安全获取方法:
private static String safeGetSrc(Elements imgs, int index) { return imgs.size() > index ? imgs.get(index).absUrl("src") : null; }或者结合Optional风格处理:
Optional<Element> secondImgOpt = imgs.size() > 1 ? Optional.of(imgs.get(1)) : Optional.empty(); String secondUrl = secondImgOpt.map(img -> img.absUrl("src")).orElse(null);2. CSS选择器进阶用法
| 语法 | 含义 | 示例 |
|---|---|---|
div.zw_content | class为zw_content的div | doc.select("div.zw_content") |
img[src] | 具有src属性的img标签 | doc.select("img[src]") |
p span | p内的所有span子孙元素 | doc.select("p span") |
li.news-on | class包含news-on的li | doc.select("li.news-on") |
推荐优先使用类名选择器,性能高且稳定性好:
Elements paragraphs = doc.select(".zw_content p"); Elements images = doc.select(".zw_content img[src]");3. 模拟浏览器行为防屏蔽
很多网站会对无头请求进行限制。合理设置请求头能显著提高成功率:
Connection conn = Jsoup.connect("http://www.qfnu.edu.cn/"); conn.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); conn.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); conn.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"); conn.header("Referer", "http://www.qfnu.edu.cn/"); conn.timeout(10000); Document doc = conn.get();必要时还可携带Cookie维持会话状态(需自行提取)。
4. 数据清洗增强策略
除了移除“责编”信息外,还可以进一步清理:
String text = element.text(); // 移除括号内的无关信息(如[阅读次数:123]) text = text.replaceAll("\\[.*?\\]", "").trim(); // 替换全角空格、换行符为普通空格 text = text.replaceAll("[\\s\\u3000]+", " "); // 截断过长内容(适用于摘要生成) if (text.length() > 500) { text = text.substring(0, 500) + "..."; }实战建议:如何设计可靠的爬虫逻辑
如何选择目标网页?
✅推荐场景:
- 页面为静态HTML渲染,查看源码即可看到内容
- DOM结构清晰,有明确的class/id标识
- 使用UTF-8编码,中文显示正常
- 图文分离良好,便于独立提取
❌应避免的情况:
- 完全依赖JavaScript动态渲染的单页应用(SPA)
- 需登录才能访问的内容(除非你能模拟登录)
- 启用验证码或反爬机制严格的站点
- HTML结构极度混乱、缺乏语义标签
提取策略设计原则
- 先观察再编码:用浏览器“检查元素”功能确认关键节点的选择器是否唯一有效。
- 由外向内逐层解析:先定位大容器(如
.content-area),再在其内部查找具体字段。 - 优先使用class/id定位:比遍历标签更高效稳定。
- 打印中间结果调试:例如输出
doc.select(".news-list").size()验证是否命中。
异常处理必须到位
所有网络操作都应包裹在try-catch中,并区分不同异常类型:
try { Document doc = Jsoup.connect(url).timeout(8000).get(); // 解析逻辑... } catch (SocketTimeoutException e) { System.err.println("请求超时:" + url); } catch (HttpStatusException e) { System.err.println("HTTP错误码:" + e.getStatusCode() + " -> " + url); } catch (ConnectException e) { System.err.println("连接被拒:" + url); } catch (IOException e) { System.err.println("IO异常:" + url + " | " + e.getMessage()); }同时建议记录失败URL,便于后期重试或告警。
性能与资源控制
- 首次验证阶段:快速使用
.select()+.text()验证逻辑正确性。 - 生产环境部署:加入
Thread.sleep(1000~3000)延时,避免高频请求冲击服务器。 - 追求速度时:可启用线程池并发抓取,但需控制并发数(建议≤5)。
- 长期维护性:将CSS选择器写入配置文件或数据库,便于网页改版后快速调整。
常见问题与解决方案
抓不到内容怎么办?
- 确认URL是否可正常访问;
- 查看是否是JS动态加载——使用“查看页面源代码”功能,若源码中无内容则说明是前端渲染;
- 尝试添加User-Agent;
- 打印
doc.html()确认是否成功获取HTML。
图片路径是相对路径怎么解决?
使用abs:src属性自动补全域名:
img.attr("abs:src")同理适用于href等链接属性。
如何防止数组越界?
- 提取前判断
Elements.size() - 使用
safeGet工具方法 - 或用try-catch捕获
IndexOutOfBoundsException
中文乱码怎么处理?
Jsoup通常能自动识别编码。若出现乱码,可尝试指定Content-Type:
Connection conn = Jsoup.connect(url); conn.header("Content-Type", "text/html; charset=utf-8"); Document doc = conn.get();此外,保存文件时也需确保使用UTF-8编码。
被封IP了怎么办?
这是典型的反爬信号。应对措施包括:
- 添加随机延迟:
Thread.sleep(1000 + new Random().nextInt(2000)) - 使用代理IP池(企业级方案)
- 控制并发请求数量
- 遵守
robots.txt协议,尊重网站爬虫规则
性能参考与最佳实践
抓取性能估算
| 场景 | 时间范围 | 说明 |
|---|---|---|
| 单页解析 | 100~500ms | 不含图片下载 |
| 含图片下载 | 1~3秒/条 | 受网络带宽影响大 |
| 批量10条 | 3~8秒 | 建议加1秒间隔保护服务器 |
内存占用一般在50~150MB之间,CPU主要用于I/O等待,整体资源消耗较低。
推荐工作流程
探索阶段
- 手动分析DOM结构,复制片段本地测试
- 编写最小可行脚本验证提取逻辑开发阶段
- 拆分为连接、解析、清洗、存储模块
- 加入日志和异常捕获
- 编写单元测试覆盖边界情况部署阶段
- 使用定时任务调度(如Quartz或Cron)
- 实现增量抓取(基于时间戳或URL去重)
- 添加失败重试机制与邮件通知维护阶段
- 定期检查选择器是否失效
- 更新User-Agent和Headers
- 监控成功率、响应时间和资源占用
写在最后
Jsoup的价值不仅在于其API的简洁易用,更在于它促使开发者回归“结构化思维”——即通过对HTML语义的理解来实现精准提取。这种轻量级、高可控的方式,在大多数非复杂场景下远胜于重型框架。
当然,面对现代Web的复杂性,我们也应理性看待其局限:对于完全依赖JavaScript的页面,仍需结合Puppeteer、Playwright或Selenium等工具。
但如果你的目标是稳定抓取传统新闻站、教育机构官网或政府公告类内容,Jsoup依然是那个值得信赖的老兵。合理运用本文介绍的技巧,你完全可以构建出一个健壮、可维护的信息采集系统。
“好的爬虫不是跑得最快的那个,而是活得最久的那个。”