2. 数据源探索:结构化、半结构化与非结构化

做数据采集,第一件事不是写代码,而是搞清楚你的数据长什么样。我见过太多人上来就写爬虫脚本,结果跑到一半发现字段对不上、编码乱掉、格式全崩——说白了,就是没先摸清数据源的脾气。

这一章,咱们把数据源分成三类来聊:结构化、半结构化、非结构化。每种我都踩过坑,也总结了一些实战经验,希望能帮你少走弯路。

2.1 结构化数据源:关系型数据库

结构化数据,就是那种规规矩矩、行是行列是列的数据。关系型数据库(MySQL、PostgreSQL、Oracle)是典型代表。每张表有固定的字段、固定的类型,数据之间靠外键关联。

我个人习惯,拿到一个数据库先看三样东西:表结构、索引、数据量级。为什么?因为这三样直接决定了你怎么采、采多快、会不会把源库搞崩。

核心原则:采集结构化数据时,永远不要在生产库上跑全表扫描。我曾经因为一个没加索引的 JOIN 查询,把业务库拖挂了 3 分钟——嗯,从那以后我再也不敢轻视索引了。

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)
避坑指南:我曾经接手过一个项目,XML 文件里混着 CDATA 和实体引用,直接用字符串解析全崩了。后来改用 lxml 的 parser,设置 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 就好。数据库里只存元数据:文件名、大小、宽高、格式、采集时间、标签等。

我常用的图片采集流程:

  1. 下载原图到本地或 OSS
  2. 生成缩略图(方便预览)
  3. 提取 EXIF 信息(拍摄时间、设备型号、GPS 等)
  4. 记录元数据到数据库

嗯,这里要注意:图片的 EXIF 信息可能包含隐私数据(比如 GPS 坐标),采集后要根据业务需求决定是否保留。

2.3.3 音视频数据

音视频是数据采集里最「重」的。一个 1080p 的视频,一分钟可能就上百 MB。采集时要注意:

  • 分片下载:用 Range 请求断点续传,避免网络中断导致重下
  • 转码压缩:如果不需要原画质,采集后立即转码成 H.264/AAC,体积能缩小 80%
  • 音频采样率:语音识别场景一般 16kHz 就够了,不用采 48kHz 浪费空间

我建议用 ffmpeg 做音视频处理,命令行或 Python 绑定都行。采集管道里加一个「转码节点」,把原始文件转成统一的编码格式和容器。

2.4 三类数据源对比与选择

为了让你更直观地理解三类数据源的区别,我画了一张对比图:

数据源分类与采集策略 结构化数据 关系型数据库 SQL 查询 + 增量采集 半结构化数据 JSON / XML 解析 + 展平 + Schema推断 非结构化数据 文本 / 图片 / 音视频 流式处理 + 转码 + 元数据 统一采集管道 → 数据湖 / 数据仓库 核心思路 结构化用 SQL 增量,半结构化用解析+展平,非结构化用流式+转码 最终统一到同一个采集管道,方便后续建模和调优

三类数据源没有的优劣,关键看你的业务场景。我个人建议:

  • 如果数据规整、关系明确,优先用结构化存储和采集
  • 如果数据来自 API 或异构系统,半结构化更灵活
  • 如果数据是多媒体内容,非结构化是唯一选择,但要做好元数据管理
一句话总结:数据源探索不是一次性工作。随着业务发展,数据格式会变、量级会涨、采集策略也要跟着调。保持对数据源的敬畏,每次采集前都先「摸一摸」它的脾气,这才是老工程师的习惯。