Hutool Hutool
(opens new window)
🏡首页
📖指南
🎢最佳实践
💖支持
💡javaDoc (opens new window)
⏳更新记录 (opens new window)
  • 🍎gitee (opens new window)
  • 🍏github (opens new window)
(opens new window)
🏡首页
📖指南
🎢最佳实践
💖支持
💡javaDoc (opens new window)
⏳更新记录 (opens new window)
  • 🍎gitee (opens new window)
  • 🍏github (opens new window)
  • 最佳实践
  • 数据抽取
    • 📚 概述
    • 🎯 典型应用场景
    • 🚀 快速开始
      • 1. 添加依赖
      • 2. 配置数据库
    • 📖 核心功能
      • 一、使用 HttpUtil 获取网页内容
      • 1. 发送简单的 GET 请求
      • 2. 发送带参数的 GET 请求
      • 3. 发送 POST 请求
      • 4. 设置请求头和超时
      • 5. 处理编码问题
      • 二、使用 ReUtil 提取数据
      • 1. 提取单个匹配项
      • 2. 提取所有匹配项
      • 3. 提取多个分组
      • 4. 使用命名分组
      • 5. 批量提取数据
      • 三、使用 Db 模块保存数据
      • 1. 创建数据库表
      • 2. 插入单条数据
      • 3. 批量插入数据
      • 4. 插入并返回自增主键
      • 5. 使用事务
    • 💡 完整示例
      • 示例1:简单的网页爬虫
      • 示例2:新闻网站数据采集
      • 示例3:电商商品信息采集
      • 示例4:多页面数据采集
      • 示例5:带重试和异常处理的数据采集
    • 🎨 高级技巧
      • 1. 使用代理服务器
      • 2. 设置 Cookie
      • 3. 处理 JavaScript 渲染的页面
      • 4. 数据去重
      • 5. 数据清洗
      • 6. 批量插入优化
    • ⚠️ 注意事项
      • 1. 遵守 robots.txt
      • 2. 控制请求频率
      • 3. 处理异常
      • 4. 数据验证
      • 5. 使用事务
      • 6. 资源释放
    • 📊 性能优化
      • 1. 使用连接池
      • 2. 并发采集
      • 3. 缓存结果
      • 4. 增量更新
    • 🎯 总结
  • 数据清洗
  • 最佳实践
Hutool
2026-01-03
目录

数据抽取

# 📚 概述

数据抽取是指从各种数据源(如网页、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
最佳实践
数据清洗

← 最佳实践 数据清洗→

Theme by Vdoing | Copyright © 2025-2026 Hutool | Apache-2.0
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式