源码深读 · 2019
法律文书动态解析与并发索引源码深读
解析网页脚本模拟、正文语义分段、人员与法条抽取、中文全文映射以及生产者—消费者批量索引。
为什么需要执行网页脚本
来源详情不是干净 JSON,而是一段依赖浏览器对象和页面函数的 JavaScript。解析器在 Go 中嵌入 JavaScript 运行时,预先伪造文档对象、选择器函数和页面插件;选择器的 html、val、attr 与摘要回调都把值写入统一 map。原始脚本先删除异常转义、注释和脚本标签,再执行并序列化 map。
这是一个很有创造力的兼容层:不必完整启动浏览器,也不必为每次页面字段变更重写正则。但它执行来源脚本,隔离和资源上限是必要条件;当前代码没有看到执行时限或内存限制。
结构化字段如何生成
第一层直接读取文书、法院、案件和发布日期隐藏字段,并解析嵌套案件信息。关联信息按键分派为案件类型、案由、当事人、审理程序和裁判日期。未知键只记录日志,不阻断整条文书。
正文 HTML 使用 DOM 解析器遍历锚点和容器,以锚点名为状态,把后续文本归入文首、当事人、诉讼记录、基本事实、裁判理由、判决结果和落款七段。法规内容清理转义符和来源换行标记后,保留法规名、法条名和法条正文。
人员抽取采用分层启发式:先用已知姓名在行内定位并拆角色与属性;否则识别冒号结构;再匹配原告、被告、代理人、执行人等角色前缀;最后以“人”字边界兜底。裁判人员按全角空格拆为角色和姓名。若新解析没有得到当事人,则回退到来源结构化列表。
索引与搜索
映射对法院、案由、人员、法规和七段正文分别建字段。名称、案号、枚举和正文使用不同的 keyword、细粒度中文或智能中文分析方式;日期独立建型。批量写入以文书标识作为索引文档标识,因此重复导入理论上覆盖同一文书。
搜索请求支持裁判/公开日期范围、当事人、地域、案件类型、案由、程序和正文。当多个条件出现时,历史代码使用 dis-max 取最高相关分支,而不是所有筛选同时满足。这会把用户理解的“组合过滤”变成“任一条件高分”,属于语义级风险。
更严重的是搜索字段名与新映射存在代际错位:查询仍引用旧的地域、人员和正文名称,可能导致部分条件无结果。这个问题说明映射和查询缺少同版本契约测试。
并发管线
同步器按日期范围扫描键值存储,原始记录进入带缓冲输入通道;多个工作协程执行脚本提取和结构化,输出通道由单一写入协程消费。写入端按固定批量或半秒定时刷新,并在内存中按文书标识去重;全文索引失败会立即重试一次。
设计正确地分离了 I/O、CPU 解析和批量写入,但终止条件依赖通道当前长度并额外等待一秒,没有关闭通道、等待组或最终确认;生产完成时正在处理的协程可能尚未输出,主程序就误判为空。内存去重表也会随全量任务持续增长。
MongoDB 版本还有一处高风险:批量更新条件使用固定字段值,而不是当前文书标识,可能不断更新同一目标。连接异常采用跳转重试和重新拨号,旧会话生命周期不清晰。
重建原则
来源脚本必须在受限运行时执行;每个字段保存来源片段和解析规则版本;阶段状态显式记录已发现、已下载、已解析、已索引与失败;协程使用关闭通道、等待组和错误通道完成收敛;映射与查询由同一 schema 生成,并用每个字段至少一个固定文书样本做契约测试。
证据边界
报告可确认动态脚本执行、分段与启发式抽取、索引映射和并发代码。来源覆盖率、失败样本比例以及历史线上索引是否使用同一映射版本无法从仓库独立确认。