项目复盘 · 2018
数据与 NLP:异构资料如何变成可搜索的知识
从 89 条工单、数据上线规范、领域词典和公告处理源码,还原采集、解析、索引、分词、同义词与实体关联的真实边界。
搜索框背后面对的不是一种“文本”,而是彼此不兼容的资料:公告是 PDF,新闻依赖网页模板,研报带机构与评级字段,专利有申请人与分类,公司还可能同时出现全称、简称、曾用名和证券代码。数据与 NLP 项目的工作,是先把这些差异压进一套可查询的数据契约,再把中文分词、同义词与公司实体连接到检索和图谱。
本文使用四种证据标识:文档事实来自需求、测试与操作说明;代码事实来自保存的 Git 仓库;合理推断是能由多条证据支持但没有验收材料的判断;缺失证据表示当前快照无法确认。
以下是从采集仓 Pipeline 行为整理出的等价伪代码,字段名经过泛化。它强调同一来源地址可能只更新分类元数据,以及文件、正文、索引并非一次原子写入。
等价伪代码
item = spider.parse(response)
existing = index.find(item.source_document_id)
if existing:
if metadata_changed(existing, item):
index.update_metadata(item.source_document_id, item.metadata)
relational_store.update_metadata(item.source_document_id, item.metadata)
stop("duplicate")
file = download(item.attachment, timeout=bounded_timeout)
if not is_pdf(file):
mark_failed(item, "unsupported_document")
paragraphs = pdf_to_positioned_paragraphs(file)
if paragraphs.empty:
mark_failed(item, "parse_empty")
else:
index.write(item.metadata, paragraphs)
relational_store.write(item.metadata)
mark_complete(item)时间线:五个月内从接口打通走向数据上线规范
文档事实|2017 年 12 月。 数据与 NLP 项目在首月创建 42 条事项,先完成分词接口、空查询按时间排序、非空查询按相关性排序、由查询识别公司、证券代码触发图谱和最大粒度分词建索引;随后把研报、新闻、公众号、财报、投融资、工商与专利接入同一搜索面。12 月 23 日的工单已经要求削弱研报权重,并对较旧文档做高斯降权,说明相关性并非后期补丁,而是首版上线即面对的问题。
同月也明确暴露了未完成能力:数字语义索引仍为待办,目标是让“比例”能够匹配百分数;图表抽取同样未完成。混合 XML 统一为 JSON、非法字符处理、新闻正文模板、公司与研报关联则被标记完成。
文档事实|2018 年 1 月至 3 月。 工作重点转向文章内搜索第一页丢失、工商字段为空、来源字段、索引重建、图片容量和爬虫更新。数据上线规范在 1 月形成:新内容类型先从线上查询中隔离,数据入库并检查后再开放来源筛选。3 月还记录过 PDF 生成图片占满磁盘,之后才迁往独立文件服务。这是一次典型的“功能已通,但生命周期治理滞后”。
文档事实|2018 年 4 月至 5 月。 团队形成小型垂直爬虫规范,将新来源拆为 Spider、Item 和 Pipeline 三步,并继续补充停用词、领域词与评测。项目最终留下 89 条事项,其中 85 条完成、2 条待办、2 条处理中。
代码事实。 公告数据仓的提交从 2017 年 8 月延续到 2019 年 1 月,当前快照包含 64 个主要 Python 与 Shell 源文件。另一座第三方组件仓在 2018 年 3 月引入并修改中文分析插件,4 月连续增加词典和停用词;同仓还加入一整套开源神经机器翻译代码,但未找到本项目训练数据、模型产物或线上调用链,不能据此宣称自研或上线了神经翻译。
统一契约不是一个“大 JSON”,而是一组类型模型
代码事实。 采集仓为不同来源定义独立 Item,但共享一组可对齐字段。公告主干是:
- 证券代码、证券名称、板块与机构标识;
- 公告标题、公告类别、发布时间与收录时间;
- 原附件地址、附件大小、附件类型和本地文件列表;
- 结构化正文、总页数、处理结果与错误类型。
研报另外保存发布机构、作者、评级变化、评级、报告类型、盈利预测年份和收益预测;公司对象保存全称、英文名、地址、简称、行业、资本、上市与发行信息;新闻和公众号对象保留来源、标题、正文 HTML、纯文本、封面与发布时间。
这不是为了追求字段齐全,而是把“共同检索字段”和“来源特有字段”分开。标题、时间、来源、公司和正文可以进入统一索引;评级、专利族、发行参数等则保留在各自类型中。
文档事实。 专利合并附件显示,同一专利域内部就存在明显年代差异:世界专利、中国专利、美国早期授权、美国后期授权与申请专利使用不同层级和命名。整理者没有搬运全部三百多个原字段,而是先抽出约十余个公司合并常用字段,如专利标识、标题、摘要、申请或公布时间、分类、专利族、申请人、发明人、国家和来源。
合理推断。 这种“宽原始层 + 窄标准层”是正确的方向。问题在于当前代码没有看到字段契约版本、转换批次和源记录校验和;一旦字段含义改动,标准记录很难回答“由哪一版解析器、从哪条原始记录生成”。
采集、解析、入库的实际调用链
采集仓以 Scrapy 为骨架。一个垂直来源通常经历以下链路:
- Spider 产生来源专用 Item;
DuplicatesPipeline依据来源地址或业务键检查重复;- 下载管线把 PDF 以地址摘要命名,并记录文件路径、原地址与校验标识;
Pdf2Article调用外部 PDF 转 HTML 工具,再解析页面文本块;- 正文被重组为带页码、类别、起止位置、锚点与文本的段落数组;
- 全文索引管线写入搜索引擎,关系管线保存元数据;
- 失败扫描、重抓和重建脚本处理未完成状态。
代码事实。 默认采集并发和同域并发都是 4,请求间隔 5 秒;Cookie 被关闭,下载中间件会轮换浏览器标识。PDF 下载超时设得很长,部分路径关闭了证书校验。错误分为“下载失败”“没有 PDF”等状态,但不同 Pipeline 对异常的处理并不统一:有些丢弃 Item,有些写日志后继续,有些依赖后续扫描器补偿。
代码事实。 PDF 段落重组不是按换行直接切割。转换器读取文本块的页面位置与内部索引,拼接同一段的文字,为首块生成锚点,并保留段落对应的页面。图表管线曾尝试输出 Excel 和表格信息到键值存储,图片也有独立处理标记;这些函数存在不代表批量成功率达标。
文档事实。 上线后的读取链路先通过搜索结果取得文件标识与总页数,再逐页请求 HTML。这说明“索引正文”和“可阅读页面”是两份相关但不同的产物:前者服务搜索,后者服务定位和阅读。
网页新闻依赖模板,而不是通用正文模型
代码事实。 新闻提取器先按主机与地址规则选择模板,再分别通过 CSS 路径取得来源、标题、发布时间和正文。发布时间适配“今天、昨天、前天、若干分钟前、若干天前、下午”等相对表达;正文清理会删除模板指定元素、注释和内联样式,把延迟加载图片地址转成普通地址,并补全相对图片路径。
清理后的 HTML 通过无头浏览器打印为 PDF,再进入与公告类似的阅读链路。模板缺失、标题为空、正文为空或时间无法解析都会使该页失败。
边界。 相对时间用“采集当下”计算,却没有在记录中明确保存解析基准时刻;重放同一原文可能得到不同日期。模板以站点和页面规则硬编码,页面改版后可能静默降低覆盖率。若重建,应同时保存原始响应、模板版本、解析时间和失败原因分布。
分词、同义词和查询排序是一条耦合链
代码事实。 在线服务把标题与 content.text 同时交给中文分析器。默认要求多个查询单元均命中;关闭同义词时改用基础智能分词。智能扩展则复用搜索集群中的分析器与同义词配置,把查询改写成多个“原词或近义词”分组。
代码事实。 第三方组件仓保存两套不同搜索引擎版本的中文分析插件,以及大型主词典、附加词典、单字词典和停用词。提交历史在 2018 年 4 月密集出现“增加新词”和“增加停用词”。这些文件能证明团队维护过领域词典,但不能证明每个词来自人工审核,也没有词条变更说明或回归报告。
文档事实。 固定测试曾出现“加自定义词典后得分反而没有提高”的结果;搜索项目又记录过大粒度分词破坏相关性。原因并不矛盾:领域长词有助于识别公司与技术名,却可能改变普通句子的切分路径;同义词扩大召回,也可能使扩展词压过用户原词。
因此,NLP 链路至少要分层评测:
- 分词层:公司名、长词、歧义词、数字和中英文混合文本;
- 扩展层:正确同义、错误同义、引号内禁止扩展和空扩展;
- 检索层:标题与正文命中、原词与扩展词贡献、不同来源的时间敏感度;
- 实体层:证券代码、全称、简称、曾用名是否落到同一公司。
从查询识别公司,再进入图网络
文档事实。 2017 年 12 月的任务已经要求“根据查询得到公司”,证券代码非空时直接出公司关系,并把研报、投融资与工商资料关联到上市公司。搜索结果还会统计高频公司,弥补已有产业图谱覆盖不足。
代码事实。 在线图谱实现先向图数据库查询一跳邻居,再从最多一千条高相关搜索结果中聚合证券代码。只有单条文档分数超过阈值、至少出现在两份文档中的公司才进入候选,最终最多补十个节点;候选超过 400 时直接返回空,防止噪声主题生成巨大网络。
合理推断。 这是一种实用的弱实体关联:结构化图谱提供确定关系,文档共现提供覆盖率。不过它把搜索相关性阈值当成实体置信度,没有保存“为什么关联”的原始段落,也没有区分同名公司。错误分词、错误公司映射和过宽同义词会逐级放大。
已确认的故障模式和技术债
- 代码事实: 采集配置、接口注释和存储脚本曾混入固定服务位置与访问凭据;部分网络请求关闭证书校验。具体值不应公开,历史凭据应视为失效并轮换。
- 代码事实: 解析、索引和关系库写入不是统一事务。索引更新成功、关系库失败或文件写入成功、正文失败时,需要补偿脚本才能恢复。
- 代码事实: 自定义词典与分析插件直接保存在第三方代码快照中,复制了两个版本及大量构建产物,难以识别真正的业务差异。
- 文档事实: 图片曾耗尽磁盘;说明中间产物没有容量预算、保留周期和失败清理。
- 缺失证据: 没有找到实体识别训练集、标注规范、模型版本、精确率与召回率报告;也没有 PDF 成功率、网页模板覆盖率和索引延迟的历史序列。
- 缺失证据: 神经机器翻译目录主要是上游开源代码,缺少项目模型和调用证据,不应计为确定交付。
可复用的重建方案
若今天恢复这条能力线,应保留当年的“原始层与标准层分离”,但把每条标准记录补上 source_id、原始内容摘要、采集批次、解析器版本、词典版本与索引版本。采集、下载、解析、实体对齐和索引应分别拥有幂等状态、重试次数和死信队列。
词典变更不能再以“文件变大”为验收,而应触发固定语料回放;实体合并必须保存候选、特征、置信度和人工裁决;新来源上线应通过影子索引完成覆盖率、字段完整率、重复率和抽样相关性检查,再切换索引别名。这样,搜索结果中的每个段落才能回溯到一条原始资料和一套处理版本。
证据边界
数据与 NLP 工单覆盖 2017 年 12 月至 2018 年 5 月,共 89 条。Confluence 的 13 个页面或博客保存了数据源、上线步骤、公告读取、公众号接入、专利、召回和小爬虫规范。公告数据仓的 858 次提交能直接证明多源采集、正文模板、PDF 结构化、去重与索引逻辑;第三方组件仓能证明中文词典与分析插件被维护。
现存材料不足以恢复核心实体识别服务和完整评测环境,也不能证明数字语义、图表抽取、神经翻译都完成上线。本文只把可由工单、文档和调用代码相互印证的部分列为确定事实。