2. 数据源探索:结构化、半结构化与非结构化
做数据采集,第一件事不是写代码,而是搞清楚你的数据长什么样。我见过太多人上来就写爬虫脚本,结果跑到一半发现字段对不上、编码乱掉、格式全崩——说白了,就是没先摸清数据源的脾气。
这一章,咱们把数据源分成三类来聊:结构化、半结构化、非结构化。每种我都踩过坑,也总结了一些实战经验,希望能帮你少走弯路。
2.1 结构化数据源:关系型数据库
结构化数据,就是那种规规矩矩、行是行列是列的数据。关系型数据库(MySQL、PostgreSQL、Oracle)是典型代表。每张表有固定的字段、固定的类型,数据之间靠外键关联。
我个人习惯,拿到一个数据库先看三样东西:表结构、索引、数据量级。为什么?因为这三样直接决定了你怎么采、采多快、会不会把源库搞崩。
2.1.1 表结构分析
用 DESCRIBE 或 SHOW CREATE TABLE 看字段名、类型、是否为空、默认值。重点关注:
- 主键:通常是自增 ID 或 UUID,用来做增量采集的断点
- 时间戳字段:比如
created_at、updated_at,是增量更新的关键 - 外键:关联查询时要注意,避免 N+1 问题
2.1.2 索引与性能
采集数据时,你的 SQL 能不能走索引,直接决定了采集速度。我建议先跑一次 EXPLAIN:
EXPLAIN SELECT * FROM orders WHERE created_at > '2024-01-01';
如果看到 type: ALL,那就是全表扫描,数据量大时必死。加个索引就能变成 type: range 或 ref。
2.1.3 增量采集策略
全量采集只适合小表(百万级以下)。大表必须做增量。常用的方式:
| 策略 | 适用场景 | 注意事项 |
|---|---|---|
| 时间戳增量 | 有更新时间字段的表 | 注意时区、时间精度(秒/毫秒) |
| 自增ID增量 | 只追加不更新的表 | 记录上次最大ID,下次从该ID+1开始 |
| Binlog监听 | 实时同步场景 | 需要开启数据库 binlog,有一定性能开销 |
| 全量+快照 | 数据量小或首次采集 | 建议在从库或低峰期执行 |
2.2 半结构化数据源:JSON 与 XML
半结构化数据,说白了就是「有结构但不固定」。JSON 和 XML 是典型代表。字段可以嵌套、可以缺失、类型可以不一致。你想想看,一个 JSON 里某个字段今天是字符串,明天可能就变成数组了——这种事儿我遇到过不止一次。
2.2.1 JSON 数据采集
JSON 现在几乎是 API 接口的标配。采集时要注意:
- 嵌套层级:超过 3 层就要考虑展平(flatten),否则存到关系型数据库会很痛苦
- 数组字段:比如
tags: ["a", "b"],是存成 JSON 字符串还是拆成子表?看查询需求 - 动态键名:有些 API 返回的键名是动态的(比如按日期分组的统计),采集时要做好 schema 推断
我常用的 JSON 解析工具链:Python 的 json 标准库 + pandas.json_normalize() 做展平。如果数据量超大,用 ijson 做流式解析,避免内存爆炸。
2.2.2 XML 数据采集
XML 虽然老了,但在金融、医疗、政府系统里还很常见。采集 XML 时,我建议用 lxml 而不是 xml.etree.ElementTree——前者性能好得多,而且支持 XPath 和 XSLT。
一个常见的坑:XML 命名空间。很多新手直接按标签名取数据,结果取不到,就是因为没处理 namespace。我的做法是先用 nsmap 把命名空间映射好:
from lxml import etree
tree = etree.parse('data.xml')
ns = {'ns': 'http://example.com/namespace'}
items = tree.xpath('//ns:item', namespaces=ns)
resolve_entities=False 才搞定。记住:XML 的「半结构化」体现在它的灵活性,但也意味着你要处理更多边界情况。
2.3 非结构化数据源:文本、图片、音视频
非结构化数据,就是那些没有固定格式、没法直接塞进表格里的数据。文本、图片、音频、视频,都属于这一类。这类数据的特点是:体积大、语义丰富、但机器理解难。
2.3.1 文本数据
文本采集看起来简单——不就是读文件吗?但实际坑很多。编码问题首当其冲。我建议统一用 UTF-8,遇到乱码就用 chardet 库自动检测编码:
import chardet
with open('file.txt', 'rb') as f:
raw = f.read()
result = chardet.detect(raw)
text = raw.decode(result['encoding'])
另外,文本数据的分词、去停用词、正则清洗,这些预处理步骤最好在采集阶段就做一部分,不要全堆到模型训练前。我习惯在采集管道里加一个「文本清洗节点」,统一处理 HTML 标签、特殊符号、多余空白。
2.3.2 图片数据
图片采集的核心是:不要直接存二进制到数据库。存路径或对象存储的 URL 就好。数据库里只存元数据:文件名、大小、宽高、格式、采集时间、标签等。
我常用的图片采集流程:
- 下载原图到本地或 OSS
- 生成缩略图(方便预览)
- 提取 EXIF 信息(拍摄时间、设备型号、GPS 等)
- 记录元数据到数据库
嗯,这里要注意:图片的 EXIF 信息可能包含隐私数据(比如 GPS 坐标),采集后要根据业务需求决定是否保留。
2.3.3 音视频数据
音视频是数据采集里最「重」的。一个 1080p 的视频,一分钟可能就上百 MB。采集时要注意:
- 分片下载:用 Range 请求断点续传,避免网络中断导致重下
- 转码压缩:如果不需要原画质,采集后立即转码成 H.264/AAC,体积能缩小 80%
- 音频采样率:语音识别场景一般 16kHz 就够了,不用采 48kHz 浪费空间
我建议用 ffmpeg 做音视频处理,命令行或 Python 绑定都行。采集管道里加一个「转码节点」,把原始文件转成统一的编码格式和容器。
2.4 三类数据源对比与选择
为了让你更直观地理解三类数据源的区别,我画了一张对比图:
三类数据源没有的优劣,关键看你的业务场景。我个人建议:
- 如果数据规整、关系明确,优先用结构化存储和采集
- 如果数据来自 API 或异构系统,半结构化更灵活
- 如果数据是多媒体内容,非结构化是唯一选择,但要做好元数据管理