数据抽取
# 📚 概述
数据抽取是指从各种数据源(如网页、API、文件等)中提取有用信息的过程。Hutool 提供了强大的工具类来实现数据抽取功能,主要包括:
- HttpUtil:发送 HTTP 请求,获取网页内容
- ReUtil:使用正则表达式从文本中提取数据
- Db:将提取的数据保存到数据库
通过这些工具的组合使用,可以快速实现网页爬虫、数据采集等功能。
# 🎯 典型应用场景
- 网页数据爬取
- API 数据采集
- 文本信息提取
- 数据清洗和转换
- 数据库批量导入
# 🚀 快速开始
# 1. 添加依赖
<!-- 如果使用MySQL数据库,需要添加MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
# 2. 配置数据库
在 src/main/resources 目录下创建 db.setting 文件:
url = jdbc:mysql://localhost:3306/data_extraction
user = root
pass = 123456
## 可选配置
showSql = true
formatSql = false
showParams = true
sqlLevel = debug
# 📖 核心功能
# 一、使用 HttpUtil 获取网页内容
# 1. 发送简单的 GET 请求
// 获取网页内容
String html = HttpUtil.get("https://www.example.com");
Console.log("网页内容: {}", html);
# 2. 发送带参数的 GET 请求
// 构建请求参数
Map<String, Object> params = MapUtil.ofKvs(false,
"keyword", "Hutool",
"page", 1
);
// 发送带参数的GET请求
String html = HttpUtil.get("https://www.example.com/search", params);
Console.log("网页内容: {}", html);
# 3. 发送 POST 请求
// 构建表单参数
Map<String, Object> params = MapUtil.ofKvs(false,
"username", "admin",
"password", "123456"
);
// 发送POST请求
String response = HttpUtil.post("https://www.example.com/login", params);
Console.log("响应内容: {}", response);
# 4. 设置请求头和超时
// 创建请求并设置参数
String html = HttpUtil.createGet("https://www.example.com")
.header("User-Agent", "Mozilla/5.0")
.header("Accept", "text/html,application/xhtml+xml")
.timeout(5000)
.execute()
.body();
# 5. 处理编码问题
// 指定编码获取网页内容
String html = HttpUtil.get("https://www.example.com", CharsetUtil.CHARSET_UTF_8);
# 二、使用 ReUtil 提取数据
# 1. 提取单个匹配项
String html = "<div class='title'>Hutool是一个优秀的Java工具库</div>";
// 提取标题内容
String title = ReUtil.get("<div class='title'>(.*?)</div>", html, 1);
Console.log("标题: {}", title);
# 2. 提取所有匹配项
String html = "<ul><li>Java</li><li>Python</li><li>Go</li></ul>";
// 提取所有列表项
List<String> items = ReUtil.findAll("<li>(.*?)</li>", html, 1);
Console.log("列表项: {}", items);
# 3. 提取多个分组
String html = "<a href='https://www.example.com'>示例网站</a>";
// 同时提取链接和文本
List<String> results = ReUtil.getAllGroups("<a href='(.*?)'>(.*?)</a>", html);
Console.log("链接: {}, 文本: {}", results.get(0), results.get(1));
# 4. 使用命名分组
String html = "张三,25,工程师";
// 使用命名分组提取信息
Map<String, String> info = ReUtil.extractAll("(?<name>.*?),(?<age>.*?),(?<job>.*?)", html);
Console.log("姓名: {}, 年龄: {}, 职位: {}",
info.get("name"), info.get("age"), info.get("job"));
# 5. 批量提取数据
String html = """
<div class='item'>
<span class='name'>张三</span>
<span class='age'>25</span>
<span class='city'>北京</span>
</div>
<div class='item'>
<span class='name'>李四</span>
<span class='age'>30</span>
<span class='city'>上海</span>
</div>
""";
// 提取所有人员信息
List<String> items = ReUtil.findAll("<div class='item'>.*?</div>", html, 0);
for (String item : items) {
String name = ReUtil.get("<span class='name'>(.*?)</span>", item, 1);
String age = ReUtil.get("<span class='age'>(.*?)</span>", item, 1);
String city = ReUtil.get("<span class='city'>(.*?)</span>", item, 1);
Console.log("姓名: {}, 年龄: {}, 城市: {}", name, age, city);
}
# 三、使用 Db 模块保存数据
# 1. 创建数据库表
CREATE TABLE `user_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`city` varchar(100) DEFAULT NULL,
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 2. 插入单条数据
Db.use().insert(
Entity.create("user_info")
.set("name", "张三")
.set("age", 25)
.set("city", "北京")
);
# 3. 批量插入数据
List<Entity> dataList = CollUtil.newArrayList();
dataList.add(Entity.create("user_info").set("name", "张三").set("age", 25).set("city", "北京"));
dataList.add(Entity.create("user_info").set("name", "李四").set("age", 30).set("city", "上海"));
dataList.add(Entity.create("user_info").set("name", "王五").set("age", 28).set("city", "广州"));
Db.use().insert(dataList);
# 4. 插入并返回自增主键
Long id = Db.use().insertForGeneratedKey(
Entity.create("user_info")
.set("name", "赵六")
.set("age", 35)
.set("city", "深圳")
);
Console.log("插入的主键ID: {}", id);
# 5. 使用事务
Db.use().tx(db -> {
db.insert(Entity.create("user_info").set("name", "张三").set("age", 25).set("city", "北京"));
db.insert(Entity.create("user_info").set("name", "李四").set("age", 30).set("city", "上海"));
});
# 💡 完整示例
# 示例1:简单的网页爬虫
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
public class SimpleWebCrawler {
public static void main(String[] args) {
// 1. 获取网页内容
String url = "https://www.example.com";
String html = HttpUtil.get(url);
// 2. 提取标题
String title = ReUtil.get("<title>(.*?)</title>", html, 1);
Console.log("网页标题: {}", title);
// 3. 提取所有链接
List<String> links = ReUtil.findAll("<a href=\"(.*?)\"", html, 1);
Console.log("找到 {} 个链接", links.size());
// 4. 提取所有图片
List<String> images = ReUtil.findAll("<img src=\"(.*?)\"", html, 1);
Console.log("找到 {} 张图片", images.size());
// 5. 保存结果到文件
JSONObject result = JSONUtil.createObj()
.set("title", title)
.set("links", links)
.set("images", images);
FileUtil.writeUtf8String(result.toStringPretty(), "result.json");
Console.log("结果已保存到 result.json");
}
}
# 示例2:新闻网站数据采集
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpUtil;
public class NewsCrawler {
public static void main(String[] args) {
// 1. 获取新闻列表页
String listUrl = "https://www.example.com/news?page=1";
String html = HttpUtil.get(listUrl);
// 2. 提取新闻列表
List<String> newsItems = ReUtil.findAll("<div class=\"news-item\">(.*?)</div>", html, 0);
Console.log("找到 {} 条新闻", newsItems.size());
// 3. 解析每条新闻并保存
for (String item : newsItems) {
try {
String title = ReUtil.get("<h3><a href=\"(.*?)\">(.*?)</a></h3>", item, 2);
String url = ReUtil.get("<h3><a href=\"(.*?)\">(.*?)</a></h3>", item, 1);
String summary = ReUtil.get("<p class=\"summary\">(.*?)</p>", item, 1);
String dateStr = ReUtil.get("<span class=\"date\">(.*?)</span>", item, 1);
if (StrUtil.isNotBlank(title) && StrUtil.isNotBlank(url)) {
// 保存到数据库
Db.use().insert(
Entity.create("news")
.set("title", title)
.set("url", url)
.set("summary", summary)
.set("publish_date", DateUtil.parse(dateStr))
.set("create_time", DateUtil.date())
);
Console.log("已保存新闻: {}", title);
}
} catch (Exception e) {
Console.error("解析新闻失败: {}", e.getMessage());
}
}
Console.log("新闻采集完成");
}
}
# 示例3:电商商品信息采集
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpUtil;
public class ProductCrawler {
public static void main(String[] args) {
// 1. 获取商品列表页
String listUrl = "https://www.example.com/products?category=electronics";
String html = HttpUtil.get(listUrl);
// 2. 提取商品列表
List<String> products = ReUtil.findAll("<div class=\"product-item\".*?>(.*?)</div>", html, 0);
Console.log("找到 {} 个商品", products.size());
// 3. 解析每个商品
for (String product : products) {
try {
String name = ReUtil.get("<h4 class=\"name\">(.*?)</h4>", product, 1);
String priceStr = ReUtil.get("<span class=\"price\">(.*?)</span>", product, 1);
String originalPriceStr = ReUtil.get("<span class=\"original-price\">(.*?)</span>", product, 1);
String salesStr = ReUtil.get("<span class=\"sales\">已售(.*?)</span>", product, 1);
String ratingStr = ReUtil.get("<span class=\"rating\">(.*?)</span>", product, 1);
if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(priceStr)) {
// 转换价格
double price = NumberUtil.parseDouble(priceStr.replaceAll("[^0-9.]", ""));
double originalPrice = StrUtil.isNotBlank(originalPriceStr)
? NumberUtil.parseDouble(originalPriceStr.replaceAll("[^0-9.]", ""))
: price;
int sales = StrUtil.isNotBlank(salesStr)
? NumberUtil.parseInt(salesStr)
: 0;
double rating = StrUtil.isNotBlank(ratingStr)
? NumberUtil.parseDouble(ratingStr)
: 0.0;
// 保存到数据库
Db.use().insert(
Entity.create("product")
.set("name", name)
.set("price", price)
.set("original_price", originalPrice)
.set("sales", sales)
.set("rating", rating)
.set("discount_rate", NumberUtil.round((1 - price / originalPrice) * 100, 2))
);
Console.log("已保存商品: {} - {}", name, price);
}
} catch (Exception e) {
Console.error("解析商品失败: {}", e.getMessage());
}
}
Console.log("商品采集完成");
}
}
# 示例4:多页面数据采集
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpUtil;
public class MultiPageCrawler {
public static void main(String[] args) {
int totalPages = 10;
int totalItems = 0;
// 遍历所有页面
for (int page = 1; page <= totalPages; page++) {
try {
Console.log("正在采集第 {} 页...", page);
// 1. 获取页面内容
String url = StrUtil.format("https://www.example.com/articles?page={}", page);
String html = HttpUtil.get(url);
// 2. 提取文章列表
List<String> articles = ReUtil.findAll("<article class=\"item\">(.*?)</article>", html, 0);
Console.log("第 {} 页找到 {} 篇文章", page, articles.size());
// 3. 解析并保存每篇文章
for (String article : articles) {
try {
String title = ReUtil.get("<h2><a href=\"(.*?)\">(.*?)</a></h2>", article, 2);
String articleUrl = ReUtil.get("<h2><a href=\"(.*?)\">(.*?)</a></h2>", article, 1);
String author = ReUtil.get("<span class=\"author\">(.*?)</span>", article, 1);
String viewsStr = ReUtil.get("<span class=\"views\">(.*?)</span>", article, 1);
String likesStr = ReUtil.get("<span class=\"likes\">(.*?)</span>", article, 1);
if (StrUtil.isNotBlank(title)) {
int views = StrUtil.isNotBlank(viewsStr)
? Integer.parseInt(viewsStr.replaceAll("[^0-9]", ""))
: 0;
int likes = StrUtil.isNotBlank(likesStr)
? Integer.parseInt(likesStr.replaceAll("[^0-9]", ""))
: 0;
// 保存到数据库
Db.use().insert(
Entity.create("article")
.set("title", title)
.set("url", articleUrl)
.set("author", author)
.set("views", views)
.set("likes", likes)
.set("page", page)
);
totalItems++;
}
} catch (Exception e) {
Console.error("解析文章失败: {}", e.getMessage());
}
}
// 延迟,避免请求过快
Thread.sleep(1000);
} catch (Exception e) {
Console.error("采集第 {} 页失败: {}", page, e.getMessage());
}
}
Console.log("采集完成,共采集 {} 篇文章", totalItems);
}
}
# 示例5:带重试和异常处理的数据采集
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
public class RobustCrawler {
private static final int MAX_RETRY = 3;
private static final int RETRY_DELAY = 2000;
public static void main(String[] args) {
String url = "https://www.example.com/data";
try {
String html = fetchWithRetry(url);
if (html != null) {
processData(html);
}
} catch (Exception e) {
Console.error("数据采集失败: {}", e.getMessage());
}
}
private static String fetchWithRetry(String url) {
for (int i = 0; i < MAX_RETRY; i++) {
try {
Console.log("尝试获取页面 (第 {} 次)...", i + 1);
String html = HttpUtil.get(url);
if (StrUtil.isNotBlank(html)) {
Console.log("页面获取成功");
return html;
}
} catch (HttpException e) {
Console.error("获取页面失败 (第 {} 次): {}", i + 1, e.getMessage());
}
if (i < MAX_RETRY - 1) {
try {
Thread.sleep(RETRY_DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}
Console.error("页面获取失败,已达到最大重试次数");
return null;
}
private static void processData(String html) {
List<String> items = ReUtil.findAll("<div class=\"data-item\">(.*?)</div>", html, 0);
Console.log("找到 {} 个数据项", items.size());
int successCount = 0;
int failCount = 0;
for (String item : items) {
try {
String name = ReUtil.get("<span class=\"name\">(.*?)</span>", item, 1);
String value = ReUtil.get("<span class=\"value\">(.*?)</span>", item, 1);
if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(value)) {
Db.use().insert(
Entity.create("data")
.set("name", name)
.set("value", value)
);
successCount++;
}
} catch (Exception e) {
Console.error("处理数据项失败: {}", e.getMessage());
failCount++;
}
}
Console.log("数据处理完成: 成功 {}, 失败 {}", successCount, failCount);
}
}
# 🎨 高级技巧
# 1. 使用代理服务器
String html = HttpUtil.createGet("https://www.example.com")
.setHttpProxy("127.0.0.1", 7890)
.execute()
.body();
# 2. 设置 Cookie
String html = HttpUtil.createGet("https://www.example.com")
.cookie("session", "abc123")
.cookie("token", "xyz789")
.execute()
.body();
# 3. 处理 JavaScript 渲染的页面
对于需要 JavaScript 渲染的页面,Hutool 的 HttpUtil 无法直接获取,可以考虑:
// 方案1:查找 API 接口直接调用
String apiUrl = "https://www.example.com/api/data";
String jsonData = HttpUtil.get(apiUrl);
// 方案2:使用 Selenium 或 Playwright 等工具
// 这需要额外的依赖和配置
# 4. 数据去重
// 查询已存在的数据
List<Entity> existingData = Db.use().findAll("data");
Set<String> existingNames = existingData.stream()
.map(e -> e.getStr("name"))
.collect(Collectors.toSet());
// 过滤重复数据
List<String> newData = dataList.stream()
.filter(name -> !existingNames.contains(name))
.collect(Collectors.toList());
# 5. 数据清洗
String cleanData = StrUtil.cleanBlank(rawData);
cleanData = StrUtil.trim(cleanData);
cleanData = ReUtil.replaceAll(cleanData, "\\s+", " ");
# 6. 批量插入优化
List<Entity> dataList = CollUtil.newArrayList();
for (String item : items) {
Entity entity = Entity.create("data")
.set("name", item.getName())
.set("value", item.getValue());
dataList.add(entity);
// 每1000条批量插入一次
if (dataList.size() >= 1000) {
Db.use().insert(dataList);
dataList.clear();
}
}
// 插入剩余数据
if (CollUtil.isNotEmpty(dataList)) {
Db.use().insert(dataList);
}
# ⚠️ 注意事项
# 1. 遵守 robots.txt
在爬取网站前,先检查网站的 robots.txt 文件,遵守网站的爬取规则。
# 2. 控制请求频率
避免频繁请求,设置合理的延迟,防止对目标网站造成压力。
Thread.sleep(1000);
# 3. 处理异常
网络请求和数据解析都可能失败,务必做好异常处理。
try {
String html = HttpUtil.get(url);
} catch (HttpException e) {
Console.error("HTTP请求失败: {}", e.getMessage());
}
# 4. 数据验证
在保存数据前,验证数据的完整性和有效性。
if (StrUtil.isNotBlank(name) && StrUtil.isNotBlank(value)) {
Db.use().insert(...);
}
# 5. 使用事务
对于需要原子性的操作,使用事务确保数据一致性。
Db.use().tx(db -> {
db.insert(...);
db.update(...);
});
# 6. 资源释放
对于大文件或长时间运行的程序,注意释放资源。
HttpResponse response = HttpUtil.createGet(url).execute();
try {
String html = response.body();
} finally {
response.close();
}
# 📊 性能优化
# 1. 使用连接池
HttpConfig config = HttpConfig.custom()
.setEngine(new HttpClient5Engine());
String html = HttpUtil.get(url, config);
# 2. 并发采集
List<CompletableFuture<Void>> futures = CollUtil.newArrayList();
for (int i = 1; i <= 10; i++) {
final int page = i;
futures.add(CompletableFuture.runAsync(() -> {
String url = StrUtil.format("https://www.example.com/page/{}", page);
String html = HttpUtil.get(url);
processData(html);
}));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
# 3. 缓存结果
对于不常变化的数据,可以缓存结果,减少重复请求。
# 4. 增量更新
记录最后采集的位置,下次只采集新增的数据。
# 🎯 总结
通过 Hutool 的 HttpUtil、ReUtil 和 Db 模块的组合使用,可以快速实现数据抽取功能:
- HttpUtil:简化 HTTP 请求,支持 GET、POST 等多种方法
- ReUtil:强大的正则表达式工具,方便从文本中提取数据
- Db:简洁的数据库操作,支持增删改查和事务
这种组合方式代码简洁、易于维护,适合中小规模的数据采集任务。对于大规模、高并发的爬虫项目,建议使用专门的爬虫框架如 Scrapy、WebMagic 等。
上次更新: 2026/01/03, 21:59:18