数据源与采集方式:从结构化到非结构化的实战选择

做数据采集这些年,我最大的感触就是:数据源的类型直接决定了你用什么工具、踩什么坑。今天咱们就把这块掰开揉碎聊清楚。

一、数据源的三大分类

先问个问题:你手头的数据长什么样?是Excel表格里规规矩矩的行列?还是网页上东一块西一块的文本?又或者是服务器日志里一行行的记录?

这三种形态,对应着三类数据源:

类型 典型代表 特点 采集难度
结构化数据 关系数据库、CSV、Excel 固定字段、行列整齐、类型明确 低
半结构化数据 JSON、XML、YAML 有标签/标记,但结构可变 中
非结构化数据 文本、图片、音视频、日志 无固定格式,需额外解析 高

核心原则:结构化数据直接查,半结构化数据先解析再查,非结构化数据先提取再分析。顺序别搞反了。

二、结构化数据源:最省心的采集对象

说白了,结构化数据就是一张张表。我早期做项目时,最喜欢遇到的就是这种——直接SQL一把梭,干净利落。

常见的采集方式:

  • JDBC/ODBC直连:Java用JDBC,Python用pyodbc或SQLAlchemy
  • 数据库导出:mysqldump、pg_dump、SQL Server导出CSV
  • API查询:RESTful接口返回JSON数组,本质还是结构化
# Python采集MySQL示例
import pymysql

conn = pymysql.connect(host='localhost', user='root', password='123456', database='orders')
cursor = conn.cursor()
cursor.execute("SELECT order_id, user_id, amount, create_time FROM orders WHERE create_time >= '2024-01-01'")
rows = cursor.fetchall()
for row in rows:
    print(row)
cursor.close()
conn.close()

我的习惯:采集结构化数据时,永远先做一次SELECT COUNT(*)确认数据量,再决定是一次性拉完还是分页拉。否则内存爆了别怪我。

三、半结构化数据源:灵活但需要小心

JSON和XML,现在几乎成了API的标配。我记得有一次对接第三方支付平台,对方返回的JSON字段名居然带空格和特殊字符……嗯,那天的加班餐挺香的。

采集半结构化数据,核心就两步:

  1. 解析:JSON用json.loads(),XML用xml.etree.ElementTree
  2. 展平:把嵌套结构变成扁平的行列
import json
import pandas as pd

# 假设这是API返回的JSON字符串
data = '''
{
  "users": [
    {"name": "张三", "age": 28, "address": {"city": "北京", "district": "海淀"}},
    {"name": "李四", "age": 32, "address": {"city": "上海", "district": "浦东"}}
  ]
}
'''

parsed = json.loads(data)
# 展平address字段
records = []
for user in parsed['users']:
    records.append({
        'name': user['name'],
        'age': user['age'],
        'city': user['address']['city'],
        'district': user['address']['district']
    })

df = pd.DataFrame(records)
print(df)

避坑指南:我曾经遇到过一个XML文件,同一个字段名在不同层级出现,解析时直接覆盖了。后来我养成了习惯——解析前先打印JSON/XML的完整结构,用json.dumps(data, indent=2)看清楚再动手。

四、非结构化数据源:最考验功力的战场

文本、图片、PDF、日志……这些数据没有固定格式,采集起来最头疼。但说实话,最有价值的数据往往藏在这里面。

常见的非结构化采集场景:

  • 文本文件:按行读取,正则提取关键信息
  • PDF/Word:用PyPDF2、python-docx提取文字
  • 图片:OCR识别(Tesseract、百度OCR API)
  • 日志文件:tail -f + 正则匹配
# 日志采集示例:实时监控Nginx访问日志
import re

log_pattern = r'(\d+\.\d+\.\d+\.\d+)\s+-\s+-\s+\[(.+?)\]\s+"(.+?)"\s+(\d+)\s+(\d+)'
with open('/var/log/nginx/access.log', 'r') as f:
    for line in f:
        match = re.match(log_pattern, line)
        if match:
            ip, time, request, status, size = match.groups()
            print(f"IP: {ip}, Time: {time}, Request: {request}")

五、四种采集方式详解

数据源搞清楚了,接下来就是怎么采。我按使用频率排个序:

1. API采集:最规范的方式

现在大部分系统都提供RESTful或GraphQL接口。你想想看,有现成的接口不用,非要去爬网页,那不是自找麻烦吗?

  • REST API:GET/POST请求,返回JSON/XML
  • GraphQL:按需查询,一次拿到所有关联数据
  • WebSocket:实时推送,适合股票、聊天等场景

个人经验:调用API时,一定要处理限流和重试。我习惯用time.sleep()控制请求频率,配合retry库做指数退避重试。

2. Web爬虫采集:最后的武器

只有API拿不到数据时,才考虑爬虫。比如竞品分析、公开数据采集。

常用的工具链:

  • Requests + BeautifulSoup:静态页面,轻量级
  • Selenium/Playwright:动态渲染页面,模拟浏览器
  • Scrapy:大规模爬虫框架,自带调度和去重
# 简单爬虫示例:采集静态页面标题
import requests
from bs4 import BeautifulSoup

url = 'https://example.com/products'
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'})
soup = BeautifulSoup(response.text, 'html.parser')
titles = soup.find_all('h2', class_='product-title')
for title in titles:
    print(title.text.strip())

曾经踩过的坑:爬虫最怕对方反爬。我遇到过IP被封、验证码、动态Token、甚至返回假数据。建议:尊重robots.txt,控制频率,做好异常处理。别为了采集把人家服务器搞挂了。

3. 日志采集:运维的命脉

服务器日志、应用日志、安全日志……这些数据量巨大,但价值极高。我做过一个项目,通过分析用户行为日志,帮业务部门发现了三个转化漏斗的瓶颈。

常用工具:

  • Filebeat/Logstash:轻量级日志采集,发送到Elasticsearch
  • Fluentd:统一日志层,支持多种输出
  • 自定义脚本:tail -F + 正则 + 写入数据库

4. 数据库直连采集:最直接的方式

适合内部系统、数据仓库。注意权限控制和数据量。

六、知识体系总览

下面这张图,把本章的核心逻辑串起来了。建议你保存下来,做项目时对照着看。

数据源与采集方式知识体系 结构化数据源 半结构化数据源 非结构化数据源 API采集 Web爬虫采集 日志采集 采集结果:结构化数据 / 半结构化数据 / 非结构化数据 存储:数据库 / 数据湖 / 消息队列 选择采集方式的原则:有API优先用API,没有API再考虑爬虫,日志类用专用工具

七、实战选型建议

最后,我根据实际项目经验,给你一个选型速查表:

场景 推荐采集方式 工具推荐 注意事项
内部数据库同步 JDBC直连 DataX、Kettle 注意增量采集,避免全量
第三方平台数据 API采集 Requests、Postman 处理限流和认证
公开网页数据 Web爬虫 Scrapy、Playwright 反爬策略、法律风险
服务器日志 日志采集 Filebeat + ELK 日志轮转、磁盘空间
PDF/Word文档 文件解析 PyPDF2、python-docx 格式兼容性、乱码

一句话总结:数据源决定采集策略,采集策略决定工具选型。别一上来就写代码,先花10分钟搞清楚数据长什么样、从哪里来、怎么拿——这10分钟能帮你省下10小时。