Hotdry.
systems

CIA Factbook 归档的全文检索与时间维度数据模型工程实践

解析 CIA World Factbook 1990-2025 归档项目,探讨多格式 ETL 管道、字段名标准化、SQLite FTS5 全文搜索与时间序列数据模型的工程实现。

当我们谈论历史数据的结构化归档时,CIA World Factbook 是一个独特的案例。这款美国中央情报局自 1962 年起发布的全球地缘政治情报手册,在 2026 年 2 月 4 日正式停更。一个开源项目成功将其 36 年的历史版本(1990-2025)完整保存为可搜索的结构化数据:281 个政治实体、9,500 条国家 - 年份记录、超过 106 万个解析字段。这个名为 CIA World Factbooks Archive 的项目不仅完成了数据 preservation,更构建了一套可复用的多格式 ETL 管道、字段名标准化体系和全文检索架构。本文从工程技术角度剖析其实现细节,为类似的历史文档归档项目提供可落地的参数与实践参考。

一、数据规模与时间维度挑战

该归档项目的数据规模体现了时间维度数据模型的核心挑战。从 1990 年到 2025 年,项目覆盖了 36 个版本的 CIA Factbook,平均每年解析约 29,500 个字段。值得注意的是,数据量并非线性增长:1990 年仅有 15,750 个字段,而 2021 年达到峰值 39,714 个字段。这一变化既反映了 CIA 报告口径的扩展,也意味着数据模型必须具备向前兼容性 —— 后期的字段可能在前期的版本中完全不存在,而历史字段也可能被重新命名或合并。

项目定义了 281 个实体类型,包括 192 个主权国家、65 个海外领土、7 个 misc 条目(如海洋、世界)、6 个争议地区、3 个皇家属地、3 个自由联合体、2 个特别行政区、2 个已解体实体(荷兰 Antilles、塞尔维亚和黑山)以及南极洲。每条记录需要关联年份、实体类型、COCOM(美国国防部战斗指挥部)区域等元数据,这要求数据库设计具备明确的维度建模结构。

二、多格式 ETL 管道设计

理解 CIA Factbook 的原始数据格式是构建 ETL 管道的前提。项目团队识别出了至少 10 种不同的格式变体,这使得传统的通用解析器无法工作。他们将数据源分为三个时代:

纯文本时代(1990-2001):数据主要来自 Project Gutenberg 的 /plain text 版本,但 decade 内部存在 4 种不同的格式约定。1990-1993 年使用 Country: Name 标题格式,1994 年引入带标记的分隔符(_@__*__#_),1996 年改为无标记的裸章节标题,1999 年再次变更定界符方案。2001 年的 HTML zip 档案在 Wayback Machine 中损坏,团队不得不回退到 Project Gutenberg 文本版本 —— 这是唯一需要混合使用 HTML 和文本管道的年份。

HTML 时代(2000-2020):CIA 在这 20 年间至少重新设计了 5 次网站布局。2000 年使用 <b>FieldName:</b> 内联格式,2004 年切换到 <td class="FieldLabel"> 表格布局,2008 年引入 CollapsiblePanel JavaScript 控件,2014 年改为可折叠的 <h2> 区块结构,2017 年又迁移到 field-anchor <div> 结构。每个年份可能使用不同的布局版本,因此解析器必须具备布局检测能力。

JSON 时代(2021-2025):2021 年起数据迁移到 factbook/cache.factbook.json 仓库,以结构化 JSON 格式发布。团队通过 checkout 年末 git commit 并解析 JSON 加载数据,同时剥离嵌入的 HTML 标签。

针对这些异构数据源,项目实现了 7 个专用 Python 脚本:build_archive.py(986 行,处理 2000-2020 年 HTML)、load_gutenberg_years.py(1,043 行,处理 1990-2001 年文本)、reload_json_years.py(413 行,处理 2021-2025 年 JSON)、build_field_mappings.py(783 行,字段名标准化)、classify_entities.py(283 行,实体分类)、repair_1996_truncated.py(189 行,修复 1996 年 7 个国家的截断数据)、validate_integrity.py(296 行,数据质量校验)。

三、字段名标准化:1,090 到 414 的映射

时间序列数据分析的最大障碍是字段名的漂移。CIA 从未维护稳定的数据模式,字段被静默重命名、拆分或合并。例如「GDP - real growth rate」后来改为「Real GDP growth rate」,「Telephones」被拆分为「Telephones - fixed lines」和「Telephones - mobile cellular」,石油相关子字段被合并为「Petroleum」。

项目团队构建了一套 7 层规则系统来处理 1,090 个原始字段名变体,最终映射到 414 个规范名称:identity(184 个,现代字段名未变)、rename(159 个,CIA 重命名)、dash_format(64 个,连字符格式差异)、consolidation(48 个,子字段合并)、country_specific(354 个,地区子条目)、noise(281 个,解析伪影)。这使得跨年份的时间序列查询成为可能,用户可以追踪某个国家 36 年来任何指标的变化趋势。

四、SQLite FTS5 全文搜索实现

Web 应用基于 SQLite 数据库运行,关键特性是集成了 FTS5(Full-Text Search 5)虚拟表来实现全文搜索。FTS5 是 SQLite 3.27.0(2019 年)引入的内置全文索引引擎,相比传统 LIKE 查询具有显著的性能优势。

搜索界面支持 Z39.58 语法 —— 这是美国国会图书馆制定的文献检索标准语法。用户可以使用布尔运算符(AND、OR、NOT)、短语匹配(双引号包裹)和截断符。例如搜索 GDP AND "real growth" NOT 2020 可以找出除 2020 年外所有包含 GDP 和实际增长短语的记录。在 106 万 + 字段规模下,FTS5 可以在毫秒级完成这类复杂查询。

FTS5 的配置参数值得注意:项目使用了 tokenize=unicode61 remove_diacritics=1 来实现不区分音调符号的 Unicode 分词,这对于处理全球地名尤为重要。索引构建时使用 content= 选项关联主表,通过 content_rowid 实现与主表的外键关联。对于中文、日文等 CJK 字符,SQLite 3.30.0 起支持 tokenize=unicode61cjk_tokenize=1 选项,但该项目中主要处理英文字段,未启用此选项。

数据库大小约 324 MB,包含 5 张表和对应的 FTS5 索引。相比 SQL Server 版本(约 263 MB 分拆为 36 个 gzipped SQL 文件),SQLite 的单文件分发更适合轻量级部署和快速查询。

五、时间维度数据模型

数据库设计采用星型模式(Star Schema)处理时间维度。主表包括:MasterCountries(281 个规范实体定义)、Countries(9,500 条国家 - 年份记录)、Categories(83,599 条类别记录)、FieldNameMappings(1,090 条字段名映射规则)、CountryFields(1,061,341 条实际字段数据)。

时间序列查询是该模型的核心场景。以查询中国历年人口为例:

SELECT cf.Year, cf.Value 
FROM CountryFields cf
JOIN MasterCountries mc ON cf.CountryID = mc.CountryID
JOIN FieldNameMappings fnm ON cf.FieldID = fnm.FieldID
WHERE mc.CanonicalName = 'China' 
  AND fnm.CanonicalName = 'Population'
ORDER BY cf.Year;

变更检测功能则通过对比相邻年份的字段值实现:

SELECT cf1.Year AS Year1, cf2.Year AS Year2,
       cf1.Value AS Value1, cf2.Value AS Value2,
       cf2.Value - cf1.Value AS Change
FROM CountryFields cf1
JOIN CountryFields cf2 ON cf1.CountryID = cf2.CountryID 
                       AND cf1.FieldID = cf2.FieldID
WHERE cf2.Year = cf1.Year + 1;

六、数据导出管道

项目提供了多格式导出能力:CSV、Excel(.xlsx)、PDF。导出管道的实现值得关注的参数包括:CSV 使用 Python 的 csv 模块,编码指定为 UTF-8 with BOM(避免 Excel 打开中文时的乱码);Excel 通过 openpyxl 库生成,支持多工作表和格式化;PDF 则通过 weasyprintpdfkit 将 HTML 模板转换为 PDF,同时保留基本的样式和布局。

对于批量导出,命令行工具 scripts/factbook_search.py 支持直接调用:

python factbook_search.py --country China --field Population --format csv --output china_population.csv

七、工程实践参数参考

对于计划构建类似历史数据归档系统的团队,以下参数值得参考:

数据库选型方面,SQLite + FTS5 适合 500 万字段以下的中小规模全文检索需求;超过此规模建议使用 PostgreSQL + pg_trgm 或 ElasticSearch。字段名标准化应在 ETL 管道早期完成,映射表应作为独立维度表维护,便于后期规则迭代。数据源归档应保留原始文件(如 HTML zip、JSON)而不仅解析结果,便于未来格式变化时重新解析。版本标记每个字段记录应包含 SourceEdition 字段,标识数据源自哪个原始版本,便于溯源和误差排查。性能基准方面,106 万记录的 FTS5 索引构建时间约 30 秒(SSD),单次全文搜索响应时间 < 50ms。

八、总结

CIA World Factbook 归档项目展示了处理异构历史数据的完整工程路径:从多格式原始数据的 ETL 管道、字段名的跨版本标准化、到基于 FTS5 的全文搜索架构,每个环节都有明确的决策依据和可调参数。该项目的数据已于 2026 年 2 月 Factbook 停更后成为唯一的完整历史查询渠道,其工程实践对于政府文档归档、新闻史料数字化、企业历史报表结构化等场景具有直接的参考价值。

资料来源:项目 GitHub 仓库 MilkMp/CIA-World-Factbooks-Archive-1990-2025,在线演示 cia-factbook-archive.fly.dev

查看归档