766a573989
- adapters/openfoodfacts.py: OFF API 适配器(限速+User-Agent) + JSONL/.gz dump 读取 - etl/transform.py: 纯函数转换(GTIN 校验/净含量解析+归一/营养 per_100g 双能量/过敏原添加剂清洗/关键词分类映射) - etl/load.py: psycopg upsert(product/food_detail/product_image) + product_source 字段级溯源 - jobs/seed_off.py: CLI(--barcodes API / --dump 文件 / --limit) - 测试: 13 个离线 transform 单测(fixture) + 可跳过的 DB 集成测试 - 依赖: 增加 psycopg[binary] - 实跑: 从 OFF API 拉 5 个真实条码入本地 postgres 验证通过 - docs/etl-openfoodfacts.md: 流程/运行/字段映射 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1.9 KiB
1.9 KiB
ETL: Open Food Facts 导入 (M2)
把 Open Food Facts (OFF, ODbL 许可) 的食品数据采集、转换并入库。只有 Python 采集侧写库,每条记录都以 openfoodfacts 为来源记录字段级溯源。
流程
OFF API / dump(jsonl[.gz])
→ adapters/openfoodfacts.py # 读取(限速 + User-Agent) / 解析 dump
→ etl/transform.py # 字段映射 + 单位归一 + 营养 per_100g + 分类映射(关键词)
→ etl/load.py # psycopg upsert(product/food_detail/product_image) + product_source 溯源
运行
先确保本地依赖与迁移就绪:docker compose up -d postgres + migrate ... up。
# 用 OFF API 拉指定条码(客户端限速, 默认 4s/次)
python -m opengoods.jobs.seed_off --barcodes 3017624010701 5449000000996
# 用下载好的 OFF dump 批量导入(可 .gz), 限制条数
python -m opengoods.jobs.seed_off --dump products.jsonl.gz --limit 1000
DSN 默认读 OPENGOODS_DATABASE_URL。
字段映射要点
| OFF | OpenGoods | 处理 |
|---|---|---|
code |
product.gtin |
GTIN-8/12/13/14 校验位验证, 不合法则不作为 gtin |
product_name_zh/_/_en |
product.name |
优先中文 |
brands |
brand |
取第一个, normalized_name 去重 |
quantity |
net_content_* |
解析 "500 g"/"1,5 L" → 经 units.py 归一(原始+归一双存) |
nutriments.*_100g |
food_detail.nutriments |
per_100g; 能量 kJ/kcal 双存, 缺一自动换算 |
allergens_tags/additives_tags |
allergens/additives |
去 en: 前缀 |
nutriscore_grade |
nutri_score |
大写单字母 |
categories*/name |
category_id |
关键词映射到自建品类树(起步版, 后续换 OFF 分类→GPC 映射表) |
image_front_url |
product_image |
标 CC-BY-SA 许可 |
全量 dump 约数 GB;CI 与单测用 fixture 离线验证 transform,DB 集成测试在无库时自动跳过。