Kettle数据抽取实战:从文本、JSON到增量同步的完整指南
2026/9/20 0:15:14 网站建设 项目流程

简介:清华大学精品大数据之数据清洗课程第五章数据抽取PPT课件,面向大学生、职场人士及大数据从业者,系统讲解数据清洗中的关键环节——数据抽取。课程围绕文本文件抽取、Web数据抽取、数据库数据抽取和增量数据抽取四大主题展开,结合Kettle工具演示文本分隔符识别、字段类型设置与数据预览,并介绍HTML中正则表达式提取、JSON与XML解析,以及数据库连接配置和增量更新策略等实用技能。资源为1个pptx课件文件,压缩包大小3.78MB,共48页,内容结构清晰,配有课后习题,适合教学、自学或考前复习。已有1088人学习,是高性价比的大数据数据清洗学习资料。通过这份课件,读者可系统掌握数据抽取的常见场景与Kettle操作要点,为后续数据清洗与数据分析打下扎实基础。

1. 数据清洗第一步:把“脏乱差”的源数据变成可用的表

数据清洗在整条大数据链路里,被重视的程度往往与其实际重要性不匹配。多数人把时间花在建模和可视化上,等真正跑数时才发现,源头数据一塌糊涂:日志文件的分隔符五花八门、网页接口返回的 JSON 嵌套了四层、数据库同步一次要把几千万行全量拉一遍。这套清华大学大数据课程里的第五章《数据抽取》,恰恰卡在数据清洗的最前端——数据还没进清洗流程,得先有办法把它从各种存储形态里“捞”出来,转成结构化的二维表。

课程用 Kettle 作为落地工具,这是目前最老牌的开源 ETL 引擎之一,纯 Java 实现,自带图形化设计器,社区版足够应付大部分抽取场景。你不需要写 Spark 代码,也不用配 Flink 任务,鼠标拖拽就能把文本、接口、数据库三类数据源串成一条抽取链路。本文会把第五章的内容拆开来讲:分隔符文件的抽取范式、JSON/XML 这类半结构化数据的路径配置、关系库迁移的注意事项,以及被很多人忽略的增量抽取方案。

2. 文本文件抽取:分隔符分析是第一道坎

文本文件是所有数据源里最“原始”的一种,没有 schema,没有类型声明,只有一堆字符和换行符。要想把文本变成能参与计算的表,第一步必须搞清楚一个关键问题:字段之间靠什么隔开。

2.1 手动分析分隔符,不要一上来就想写正则

课程里的案例用了一个TxtExtract_test.txt文件,内容格式是name|id|date这种以竖线分隔的行。处理思路非常简单——打开文件看几行,确认分隔符是|,然后告诉 Kettle“按这个符号切分”。

但实际项目里,分隔符没有这么友好。常见的情况有三种:

  • 定长分隔符,比如|,\t,肉眼可辨,处理最简单;
  • 多字符分隔符,比如||#||#,Kettle 的“文本文件输入”步骤支持在分割符框里直接填多字符;
  • 混合分隔符,行内既有逗号又有制表符,此时不能只配一个分隔符,得先用“字段拆分”或者写 Java 脚本预处理。

文本文件输入控件在 Kettle 的“核心对象 → 输入”里,拖进转换工作区后双击,会看到三个关键页签。File 页签选文件,Content 页签定分隔符,Fields 页签定义目标字段的名字和类型。

文件:TxtExtract_test.txt 内容:张三|1001|2024-03-15 李四|1002|2024-03-16 Content 页签配置: - 分隔符:| (来自人工分析) - 头部:不勾选(文件第一行不是表头) - 编码:UTF-8,如果乱码则切换 GBK - 是否允许重复字段:默认即可 Fields 页签配置: - name:String - id:String(学号类字段不要设成 Integer,前导零会丢失) - date:Date,格式 yyyy-MM-dd

提示:点击“预览记录”前,最好先确认文件编码。Windows 生成的文本文件经常是 GBK/GB2312,Kettle 默认 UTF-8 会直接乱码,这种错误在真实项目里最常见的表现是“抽取成功了但全是问号”。

2.2 制表符与 CSV 的边界问题

课程里专门把制表符拎出来讲了一段,原因是 TAB 键在文本编辑里看起来是“空白”,但实际是一个完整的控制字符\t。用制表符做分隔的优点是视觉上对齐,不容易被误改;缺点是某些文本编辑器会自动把 TAB 转成空格,一旦发生这种转换,分隔就失效了。

Kettle 的文本文件输入对话框中,分隔符栏里可以直接贴一个 TAB 字符,但更稳妥的做法是填\t两个字符(有些版本支持转义符,有些版本不支持,需要实测)。这里有个判断技巧:预览结果里如果所有字段都挤在第一列,说明 Kettle 没有识别到分隔符,大概率是 TAB 被当成普通字符了。

另一个被反复问到的坑是 CSV 文件的引号转义。Kettle 在 Content 页签有一个“引号”选项,默认是"。如果文本里本身有双引号包裹的字段(比如"张三","1001"),必须把“封闭符”设置成双引号,同时打开“允许字段中出现分隔符”选项,否则被逗号切断的字段会对不上列数。

2.3 从文本文件到 MySQL 的完整落地流程

课程案例没有停留在预览这一步,而是要求把抽取结果真正写入数据库,这一步在真实项目中才是终点。流程很简单:

  1. 在 Kettle 主对象树里新建转换,命名如trans_txtExtract_test
  2. 双击“DB 连接”新建 MySQL 连接,填 URL、用户名、密码;
  3. 执行前先确认 MySQL 里有目标数据库,比如 test 库,否则报 “UnKnown Database”;
  4. 从“输出”分类下拖入“表输出”步骤,与“文本文件输入”建立 Hop 连线;
  5. 在“表输出”里指定目标表,勾选“指定数据库字段”后可以手动映射字段来源。
文本文件输入 (步骤名: Txt Input) ↓ 数据流: name, id, date 表输出 (步骤名: MySQL Output) 表输出配置: - 连接:MySQL test 库 - 目标表:user_info - 提交记录数:500(批量插入的批次大小) - 指定数据库字段:勾选,映射 name->name, id->id, date->date
参数名推荐值说明
提交记录数200~1000太小频繁网络往返,太大可能导致内存堆积
数据库连接池大小5~10并行度高的时候调大
批量插入开启很多数据库驱动默认不启用 rewriteBatchedStatements,需要拼接 JDBC 参数

这里有个实操经验:如果文本有几百万行,直接跑“表输出”速度会非常慢。我通常会在文本输入和表输出之间加一个“复制记录到结果”或“空操作”,把提交记录数调大,同时把 MySQL JDBC URL 加上rewriteBatchedStatements=true,插入性能会提升好几个量级。

3. Web 数据抽取:HTML 靠正则、JSON 靠路径、XML 靠控件

Web 数据比文本文件好一点:它有结构。但坏消息是,Web 的结构是为浏览器渲染设计的,不是为数据提取设计的。课程把 Web 数据抽取分成 HTML、JSON、XML 三类分别处理,逻辑清晰,实操上却有各自的坑。

3.1 HTML 抽取:正则匹配是最低门槛,但未必是最佳选择

人工从 HTML 里抽数据,本质上是分析网页源码中专属于数据的标签结构,然后用正则表达式把目标位置的文本抠出来。比如要抓一个商品列表页里的所有价格,常见的做法是搜索<span class="price">这个模式,把紧跟其后的数字用捕获组取出来。

Kettle 里对应的控件是“正则表达式”步骤,属于“转换”分类。用法是输入字段走进去,定义正则表达式和输出字段,Kettle 会按捕获组位置生成新字段。

输入字段: html_content 正则: <span class="price">([0-9.]+)</span> 输出字段: price (String) 捕获组说明: - 整个表达式匹配到完整的 <span> 标签 - 第一个捕获括号 ([0-9.]+) 只会扣出价格数字 - 如果页面结构变了,正则立即失效,需要重新分析

但正则方式在大型爬虫工程里极度脆弱:网站改版一次,class 名变了,正则直接匹配不到;而 HTML 本身容错性差,标签嵌套多一层,匹配也会出错。如果页面量级大且更新频繁,我一般会用 Kettle 配合 Jsoup 这类 HTML 解析库,把缺失标签自动补全后再做 DOM 查询,而不是直接对原始 HTML 跑正则。

提示:课程里把 HTML 抽取定位成“人工分析源码”,这个思路在小型任务里完全正确。不要一开始就引入 Selenium 或 Playwright,先确认静态源码里有没有数据,动态渲染页面才考虑浏览器自动化。

3.2 JSON 抽取:路径写错是重灾区

JSON 的抽取是三种 Web 数据里最容易上手的,也是配置错误率最高的。Kettle 里有两个专门的输入控件:JSON InputJSON Input Stream。前者适合从文件读取,后者适合从字段读取。课程演示的是文件读取场景,但水最深的是字段路径配置。

课程案例文件是chinacitylist.js,内容大概是省级城市的 JSON 数组。在 Kettle 的JSON Input里配置字段时,路径必须遵循 JSONPath 符号规范。这个规范与 XPath 类似,但符号体系完全不同,常见配置:

JSON 数据片段: { "province": "广东省", "cities": [ {"name": "广州", "level": "省会"}, {"name": "深圳", "level": "计划单列市"} ] } JSONPath 配置: - 字段 root:$.province - 字段 city_name:$.cities[*].name - 字段 city_level:$.cities[*].level
JSONPath 符号含义示例
$根对象$
.子节点$.province
[*]数组内所有元素$.cities[*].name
[0]数组第一个元素$.cities[0].name

这个步骤最大的坑是路径不对时,Kettle 不会报错,只会默默输出空值。我在项目里排查过很多次,最后发现是数组符号写成了*,输出结果是 null。另一个常见错误是把路径写成了$.data.cities.name,这只有在cities是对象而非数组时才可能成立,放到数组上会直接取不到字段。

3.3 XML 抽取:两个控件选哪个,取决于数据规模

Kettle 里读取 XML 有两个思路:Get data from XML适合小文件,XPath 写起来直观;XML Input Stream (StAX)适合大文件,流式解析不占内存。课程把两者并列提出来,背后是性能考量。

Get data from XML是 DOM 方式,一次性把整个 XML 加载进内存。文件几 MB 时没问题,到了几百 MB 时直接 OOM。这个控件的配置核心是 XPath 循环路径和节点下的字段路径:

XML 结构: <root> <item> <name>苹果</name> <price>5.5</price> </item> </root> XPath 配置: - 循环读取路径:/root/item - name 字段路径:name - price 字段路径:price

XML Input Stream (StAX)则是逐节点扫,内存占用低得多,适合 GB 级文件。缺点是字段配置方式更琐碎,需要分别指定元素路径和元素层级。我的经验法则是:文件小于 50 MB 直接用Get data from XML;超过 50 MB 直接切 StAX,别犹豫。

4. 数据库数据抽取:全量导入导出只是入门,异构迁移才是硬骨头

数据库抽取不像文件抽取那样需要解析分隔符,毕竟表结构已经完整定义了,但正因为结构“太完整”,跨库迁移时反而层层受限。课程把数据库抽取拆成导入导出、ETL 工具抽取、SQL 到 NoSQL 抽取三类,每一类的技术假设都不相同。

4.1 同构数据库的导入导出,别用 Kettle,用原生工具

如果源端和目标端是同一类数据库,比如 MySQL 到 MySQL、Oracle 到 Oracle,最合理的做法是用原生工具做备份和还原。MySQL 的mysqldump导出 SQL 文件,再在目标库执行导入,速度最快,且对表结构、索引、触发器的还原最完整。

# 导出 test 库全部表结构和数据 mysqldump -h 192.168.1.10 -u root -p test > test_backup.sql # 导入到目标库 mysql -h 192.168.1.20 -u root -p test < test_backup.sql # 只导出数据不导出结构 mysqldump -h 192.168.1.10 -u root -p --no-create-info test > test_data.sql

这种方式的限制是,导出文件里包含了建表语句和插入语句,要求目标库的数据库版本兼容、字符集兼容。一旦源端是 MySQL 5.7、目标端是 MySQL 8.0,mysql_native_password这些认证插件问题就会跳出来;再遇到大小写敏感配置不同,导入后表名都找不到。

4.2 异构数据库用 ETL,核心是类型映射

课程里强调了一个事实:不同 DBMS 之间的 SQL 语法和变量类型存在差异,每一种数据库的脚本都是专用的,无法直接迁移。比如 Oracle 的NUMBER(10,2)在 PostgreSQL 里对应NUMERIC(10,2),在 MySQL 里对应DECIMAL(10,2),在 SQL Server 里是DECIMAL(10,2)。这还只是数值类型,日期、布尔、大对象各自的映射规则完全不同。

Kettle 在表输入步骤里执行源库查询,在表输出步骤里把数据写到目标库,中间通过“字段选择”或者“值映射”步骤完成类型转换。这个链路每个环节都需要配置:

表输入 (源库 Oracle) SQL: SELECT id, name, hire_date, salary FROM emp 字段选择 (转换层) - id: NUMBER → Integer(如果 id 超过 21 亿,改为 Long) - name: VARCHAR2 → String - hire_date: DATE → Date(yyyy-MM-dd HH:mm:ss) - salary: NUMBER(10,2) → BigDecimal 表输出 (目标库 MySQL) - 目标表:emp - 目标字段类型由 MySQL 侧决定,Kettle 自动做类型匹配 - 如果目标表不存在,勾选“建表”选项让 Kettle 生成 DDL

类型映射是最容易踩坑的地方。比如 Oracle 的CLOB字段,如果使用默认映射,Kettle 在 MySQL 侧会生成TEXT类型,这没问题。但如果 CLOB 里存的是超过 64KB 的超长文本,MySQL 的TEXT装不下,必须手动改成LONGTEXT。这种问题只有在插入阶段才会暴露,错误信息往往极其隐晦,比如“Data too long for column”。

4.3 SQL 到 NoSQL 抽取:逃不开的序列化问题

课程把“SQL 到 NOSQL 抽取”单独列出来,这在业务场景里对应的是把关系型数据库的数据同步到 Elasticsearch、MongoDB、ClickHouse 之类的大数据存储。这类迁移的最大问题不是读取,而是格式再造:关系表是扁平的,而 NoSQL 里的文档往往是嵌套的。

Kettle 里常见的做法是把关系表的多行 Join 成一层结构,再用“行扁平化”或“JavaScript 代码”把行转为 JSON 文档写入 NoSQL。比如 MySQL 里订单表和订单明细表是一对多关系,同步到 MongoDB 时要把明细数组嵌进订单文档里。这里的核心是搞清楚哪张表是主表、哪个字段做关联键、明细表最多可能展开多少行。

MySQL 源: orders: id, user_id, total_amount, created_at order_items: id, order_id, sku, quantity, price MongoDB 目标文档: { "_id": 1001, "user_id": 20001, "total_amount": 199.00, "items": [ {"sku": "A100", "quantity": 2, "price": 50.00}, {"sku": "B200", "quantity": 1, "price": 99.00} ] }

这种转换在 Kettle 里实现不复杂,但性能需要控制:如果明细表行数极多,内存必然吃紧。如果数据量上亿,我建议把 Kettle 只用来做开发联调,生产环境改用 Spark 或 Flink 写批处理任务。ETL 工具的定位是中小规模数据、快速交付场景,它不是万能的。

5. 增量数据抽取策略:时间戳、日志解析与 Kettle 实战

如果只是学习阶段,跑一次全量抽取就完事了。但生产环境里,数据每天在涨,全量抽取不可能天天做——太慢、太耗资源、对源库压力也大。增量抽取的思路是每次只取发生变化的数据。课程第五章把它放在末尾,实际上是整个章节中最有工程价值的部分。

5.1 三种常用增量策略,各有利弊

增量抽取不是单一技术,而是针对不同的源数据特性选择不同的策略。核心有三种思路:

策略实现方式优点缺点
时间戳增量表里加 last_modified/update_time 字段,抽取时取大于上次最大值的数据简单直观,最常用依赖业务表必须有时间字段;删除的数据不会被捕获
全表比对抽取前先比对源表和目标表的哈希或行数准确,能发现删除大表代价极大,基本只用于小表
日志解析解析数据库 binlog/WAL 日志,记录每一次 insert/update/delete最实时、最轻量需要开通日志权限,解析复杂,基础设施成本高

课程上下文里没有给出具体的增量实现案例,但按“合格从业者最可能用的方案”,时间戳增量是大多数项目的起步选择。实现的难点在于“从哪里拿上次的时间水位线”。通常做法是把水线持久化到一张控制表里,每次抽取开始时先查这张表,结束后把本次最大时间戳写回去。

5.2 Kettle 实现时间戳增量,比写代码更快

Kettle 里做时间戳增量不需要写 Java,直接用“表输入”步骤和控制表配合就能完成。控制表的字段只有两个:job_namelast_etl_time。每次跑作业的第一步是读取控制表,把时间水线变成变量,然后传给数据抽取 SQL。

-- 控制表结构 CREATE TABLE etl_watermark ( job_name VARCHAR(64) PRIMARY KEY, last_etl_time DATETIME ); -- 初始化 INSERT INTO etl_watermark VALUES ('sync_orders', '2024-01-01 00:00:00');

Kettle 作业流程如下:

  1. “获取变量”步骤,从控制表读出last_etl_time存入变量watermark
  2. “表输入”步骤执行增量查询:
SELECT id, user_id, amount, create_time FROM orders WHERE update_time > ?

这里的?由变量watermark绑定; 3. 数据写入目标表; 4. “更新变量”步骤,查询源表最大update_time,写回控制表。

提示:真正生产级实现里,需要注意时区一致性问题。如果源库和目标库时区不同,update_time最大值写回控制表后,下一次抽取可能漏数据。稳妥做法是整个链路统一用 UTC 存储。

5.3 遇到删除操作怎么办:软删除替代物理删除

时间戳增量有一个致命缺陷:删除的数据不会被捕获。业务表里某行被物理删除后,目标表里仍然保留旧数据,这会导致数据不一致。

最常见的补救方案是软删除:业务上不执行DELETE,而是把deleted字段置为 1,同时更新update_time。这样删除也变成了一次“更新”,增量逻辑不用改动。如果无法推动业务做软删除,那就只能切换到 binlog 解析方案——Canal 监听 MySQL binlog,把删除事件单独拿出来处理。这一步从 Kettle 跳到了实时同步领域,技术栈变重,但对账需求强烈的场景(比如财务、库存)必须上。

5.4 增量任务失败后的续跑逻辑

增量抽取比全量麻烦的地方在于失败恢复。假设任务跑到一半挂了,源表里一部分数据已经写过目标表,一部分还没写。重跑时如果还是按“大于上次水线”来查,已写的那部分会被再查一遍,一般没大问题;但如果没有主键去重,目标表就会出现重复记录。

解决思路有两个层次:

  • 目标表有唯一键时,用insert or update方式写入,重复数据被更新而非新增;
  • 目标表没有唯一键时,确保每次增量跑前先从目标表删除“本次增量时间窗内”的旧数据,再重新插入。

后一种做法有时被称为“窗口重刷”,实现简单且可靠。时间窗口要稍微往前留一点余量,比如这次从水线 - 5分钟开始捞,避免数据库事务提交时间与写入时间不一致导致数据落在缝隙里。

5.5 不要迷信单一策略,混合是常态

真实系统里,一张订单表可能同时有多个变更来源:业务系统直接改库、后台人工修正、定时任务批量刷新。这些操作都会更新update_time,时间戳策略都能兜住。但如果源库是 Oracle,或者表设计不规范没有update_time,时间戳策略就失效了。

此时可以退而求其次做“全量比对增量”:每次把源表全量读出来,和目标表做 Left Join,差异数据写进去。这种方案在数据量几十万行以内还能跑,上了千万行就非常吃力。另一个思路是让 DBA 打开数据库的审计日志,把 SQL 记录里的变更语句筛选出来作为增量源,但这种方案依赖 DBA 配合,沟通成本不低。

增量抽取是一条很长的技术链路,Kettle 只能覆盖时间戳增量这一层。再往下走,无论是 Canal、Debezium、Flink CDC,它们解决的本质问题都是一样的:如何以最低的成本捕获数据变化事件并送达下游。学完课程这章后,我建议你在本地用 MySQL 搭两张表,试着把 Kettle 的增量作业跑通,再手动插入、更新、删除几行数据,观察目标库的表现。这比看十遍课件都有用,因为你会在测试中亲眼看到时间戳增量在删除操作面前的无能为力,也会发现某个时间字段为 NULL 时整行数据被跳过的奇怪问题——而这些,恰好是生产环境里返工率最高的两类故障。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询