数据源接入与ETL:API数据采集、数据库连接、CSV/Excel批量导入、增量更新策略
数据源接入,说白了就是解决「数据从哪来」的问题。我做了这么多年金融数据处理,见过太多团队在第一步就栽跟头——API限流没处理好、数据库连接池爆了、CSV编码搞错导致乱码……这些坑我基本都踩过一遍。今天咱们就把这些环节掰开揉碎,讲清楚。
API数据采集:别让接口把你卡住
金融数据API有个特点——量大、频次高、动不动就限流。我刚开始对接某交易所行情接口时,没做限流控制,结果对方直接把我IP封了24小时。嗯,从那以后我再也不敢小看API调用的优雅性了。
核心要点就三个:
- 认证与鉴权:API Key + Secret 是标配,有些还要OAuth2.0。我习惯把密钥放在环境变量里,绝不硬编码。
- 限流与重试:用令牌桶或滑动窗口控制请求频率。遇到429(Too Many Requests)要指数退避重试。
- 数据格式解析:JSON/XML/Protobuf,金融行情常用Protobuf,解析效率高很多。
实战经验:我曾在某私募项目中,用asyncio + aiohttp 同时采集5个交易所的行情数据,配合信号量控制并发数,单机就能扛住每秒2000+次请求。关键代码就几十行。
import aiohttp
import asyncio
async def fetch_price(session, symbol):
url = f"https://api.example.com/v1/quote/{symbol}"
async with session.get(url) as resp:
data = await resp.json()
return data['price']
async def main():
symbols = ['BTC/USDT', 'ETH/USDT', 'BNB/USDT']
async with aiohttp.ClientSession() as session:
tasks = [fetch_price(session, s) for s in symbols]
prices = await asyncio.gather(*tasks)
print(prices)
asyncio.run(main())
小技巧:API返回的数据最好先做校验——字段是否存在、类型对不对、时间戳是否合理。我曾经因为某交易所突然改了字段名,导致下游策略直接跑偏,亏了十几万。从那以后我每条数据都加了一层schema校验。
数据库连接:MySQL vs PostgreSQL 怎么选?
金融数据存储,MySQL和PostgreSQL是两大主流。我个人习惯:高频交易日志用MySQL,复杂分析查询用PostgreSQL。为什么?
| 对比维度 | MySQL | PostgreSQL |
|---|---|---|
| 写入性能 | 高(InnoDB引擎优化好) | 中等(但支持并行写入) |
| 复杂查询 | 一般 | 强(窗口函数、CTE、数组类型) |
| 金融时序支持 | 需额外设计分区表 | 原生支持TimescaleDB扩展 |
| 连接池 | 推荐PyMySQL + DBUtils | 推荐psycopg2 + SQLAlchemy |
连接数据库时,我建议用连接池而不是每次新建连接。你想想看,金融数据ETL任务动不动就跑几百万条,每次新建连接的开销太浪费了。
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
# MySQL连接池
mysql_engine = create_engine(
'mysql+pymysql://user:pass@host/db',
poolclass=QueuePool,
pool_size=10,
max_overflow=20
)
# PostgreSQL连接池
pg_engine = create_engine(
'postgresql+psycopg2://user:pass@host/db',
pool_size=10,
max_overflow=20
)
注意:数据库连接字符串中的密码不要明文写!我见过有人把生产库密码直接写在代码里,然后上传到GitHub……后果你懂的。用环境变量或者密钥管理服务(如Vault)来管理。
CSV/Excel批量导入:看似简单,坑最多
CSV和Excel是金融数据交换的「通用语言」。但说实话,我处理过的CSV文件里,十有八九都有问题——编码不对、分隔符不统一、日期格式乱七八糟。
我的标准处理流程:
- 检测编码:用chardet库自动检测,避免乱码
- 统一分隔符:CSV不一定是逗号,也可能是制表符或分号
- 类型推断:日期、数值、字符串要明确指定,别让pandas猜
- 分批写入:大文件不要一次性读入内存,用chunksize分批处理
import pandas as pd
from sqlalchemy import text
# 分批读取CSV并写入数据库
chunksize = 10000
for chunk in pd.read_csv('trade_data.csv', chunksize=chunksize, encoding='utf-8'):
# 类型转换
chunk['trade_date'] = pd.to_datetime(chunk['trade_date'])
chunk['amount'] = chunk['amount'].astype(float)
# 写入数据库
chunk.to_sql('trades', engine, if_exists='append', index=False)
print(f"已写入 {len(chunk)} 条")
避坑指南:Excel文件要注意sheet名称和列名中是否有空格或特殊字符。我曾经遇到一个Excel文件,列名是「成交金额(元)」,括号导致SQL语句报错。后来我统一用正则把列名中的特殊字符替换成下划线。
增量更新策略:别每次都全量跑
金融数据是持续产生的,每天全量更新太傻了。我常用的增量策略有三种:
- 时间戳增量:记录上次同步时间,只拉取该时间之后的数据。最简单,但要求源数据有时间字段。
- 偏移量增量:用自增ID或序列号,记录上次处理到的位置。适合日志类数据。
- 变更数据捕获(CDC):监听数据库的binlog或WAL日志,实时捕获变更。适合对实时性要求高的场景。
我个人最常用的是时间戳增量,配合一个「增量状态表」来记录每个数据源的同步进度。
-- 增量状态表
CREATE TABLE sync_status (
source_name VARCHAR(100) PRIMARY KEY,
last_sync_time TIMESTAMP,
last_offset BIGINT,
status VARCHAR(20)
);
-- 查询上次同步时间
SELECT last_sync_time FROM sync_status WHERE source_name = 'trade_api';
实战经验:在某量化平台项目中,我们每天要同步200+个数据源。如果全量跑,需要6小时。改成增量后,平均只要15分钟。而且增量更新失败时,可以回滚到上次成功点,不会影响已有数据。
增量更新还有个关键点——去重。金融数据经常有重复推送,我习惯在写入前用主键或唯一索引做upsert(插入或更新)。PostgreSQL的ON CONFLICT语法很好用,MySQL也有ON DUPLICATE KEY UPDATE。
-- PostgreSQL upsert
INSERT INTO trades (trade_id, symbol, price, volume, trade_time)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (trade_id)
DO UPDATE SET price = EXCLUDED.price, volume = EXCLUDED.volume;
知识体系总览
下面这张图,是我梳理的数据源接入与ETL核心逻辑。你可以把它当作一个检查清单——每次接入新数据源时,对着走一遍,基本不会漏。
这张图把整个流程分成了四层:数据源层、采集连接层、转换清洗层、存储层。增量策略贯穿其中,不是单独一个环节。你想想看,如果每次采集都全量拉,那连接层和转换层的压力会非常大。所以增量策略要从源头就开始设计。
最后说一句:数据源接入没有银弹。不同的数据源、不同的业务场景,策略都不一样。我的建议是——先跑通最小闭环,再逐步优化。别一开始就想搞个完美的通用方案,那往往是最不实用的方案。