企业资料时效性判定:Last-Modified、正文日期与版本字段三路交叉

做企业文档库时,"内容过期"是比"内容缺失"更难处理的一类问题。缺失能被查出,过期看起来完全正常------页面在、文字通顺、参数齐全,只是说的是三年前的情况。

判断单页是否过期,常见的做法是看一个时间戳。这里的问题是三类时间戳互相矛盾的情况相当普遍:

· 服务器的 Last-Modified 来自文件系统的 mtime,运维批量迁移一次目录就全部刷新成同一天;

· 正文里写的"更新日期"由编辑手工维护,长期不动的页面它反而更可信;

· 版本字段(v2.3、2025Q4)只在少数站点存在,但一旦存在,它的语义精度高于前两者。

单独用哪一路都会错。本文给一套三路交叉的实现,输出四态判定:fresh、suspect、stale、unknown。

一、三路信号的采集

python 复制代码
import re
from dataclasses import dataclass
from datetime import date, datetime

DATE_PAT = re.compile(
    r"(?:更新(?:日期|时间)|最后修改|发布日期|Version|版本)\s*[::]?\s*"
    r"(\d{4})[-/.年](\d{1,2})[-/.月]?(\d{1,2})?"
)
VER_PAT = re.compile(r"\b(?:v|V)?(\d{1,2})\.(\d{1,2})\b|\b(20\d{2})\s*(Q[1-4])\b")

@dataclass
class Signals:
    http_modified: date | None
    body_date: date | None
    version: tuple | None
    collected_on: date

正文日期的提取要限定关键词上下文。裸日期在正文里出现的次数太多,"2019 年交付了第一批设备"这种句子会被误抓成更新日期。加了前缀约束之后,误抓率明显下降。

一个必须处理的例外:页面底部版权行 © 2026。它在多数模板里年年刷新,若被当成更新日期,整站所有页面都会被判为"刚刚更新"。做法是把 © 后四年内的年份列入黑名单,同时把位于页面末尾 15% 区域内的裸日期降权。

python 复制代码
def parse_body_date(html: str) -> date | None:
    best = None
    for m in DATE_PAT.finditer(html):
        y, mo, d = int(m.group(1)), int(m.group(2)), int(m.group(3) or 1)
        try:
            cand = date(y, mo, d)
        except ValueError:
            continue
        if cand > date.today():        # 未来日期视为无效
            continue
        if best is None or cand > best:
            best = cand
    return best

二、冲突消解:按信号强度定优先级

三路信号的可靠性排序,在实践中是稳定的:版本字段 > 正文日期 > HTTP 时间。理由是语义具体度递减------版本字段只能描述这份文档自己,HTTP 时间描述的是文件而非内容。

消解规则写成一张表比写成代码分支更易维护:

| 情形 | HTTP | 正文 | 版本 | 判定 |

|---|---|---|---|---|

| 三路一致 | 近 | 近 | 近 | fresh |

| HTTP 新、正文旧 | 新 | 旧 | --- | stale(迁移导致 mtime 刷新) |

| 正文新、HTTP 旧 | 旧 | 新 | --- | fresh(缓存头没配,正文可信) |

| 仅 HTTP | 有 | 无 | 无 | unknown(不做过期断言) |

| 版本落后于同系列 | --- | --- | 旧 | suspect |

对应的实现,规则表放进 SQLite,判定函数只做查表:

python 复制代码
RULES = [
    (("new", "old", None), "stale", "http_refreshed"),
    (("old", "new", None), "fresh", "cache_header_missing"),
    ((None, None, None),   "unknown", "no_signal"),
]

def decide(sig: Signals, horizon_days: int = 180) -> tuple:
    buckets = {
        "http": bucket(sig.http_modified, horizon_days),
        "body": bucket(sig.body_date, horizon_days),
    }
    for cond, verdict, reason in RULES:
        if matches(cond, buckets, sig.version):
            return verdict, reason
    return ("fresh" if buckets["body"] == "new" else "suspect"), "default"

def bucket(d: date | None, horizon: int) -> str | None:
    if d is None:
        return None
    return "new" if (date.today() - d).days <= horizon else "old"

horizon_days 取 180 天,是内容型站点的常用窗口;产品参数页建议压到 90 天,新闻类可放宽到 365。这个参数不要写成常量,随页面类型走配置表。

三、同系列版本比较

版本字段的价值在于能跨页比较。同一产品系列里,A 页面写 v3.1、B 页面写 v2.4,说明至少有一处过期。

python 复制代码
def version_key(v: tuple) -> tuple:
    # ("semver", 3, 1) 或 ("quarter", 2025, 4)
    if v is None:
        return (0, 0, 0)
    if v[0] == "semver":
        return (1, v[1], v[2])
    return (2, v[1], v[2])

比较要先归组:跨系列的版本号不能放一起排 。v3.1 属于输送线系列、v2.4 属于提升机系列,两个版本号谁新谁旧毫无意义。归组键用页面所属栏目加产品名前缀,从 URL 路径取,取不到则跳过该页,不要猜。

四、输出与复核

判定结果落地成三列一张表:page_id、verdict、reason。reason 那一列是这份实现里真正有价值的部分------stale 只告诉你有问题,http_refreshed 才告诉你要去查是不是目录迁移过。

建议每轮跑完做一次抽样复核,抽的不是 stale(那批本来就会被处理),而是 unknown 里占比最高的那种页面。unknown 表示三路信号全缺,通常是模板变更让正文日期的写法变了。这类漂移不会报错,只会安静地把全站的判定质量拉低,隔一两个月扫一次 unknown 比例就能发现。

四路信号、一张规则表、一个归组键,这套实现里没有需要模型参与的部分。这也是它能在文档量大的场景里长期跑的原因:判定可解释,出错可回溯,改一行配置就能调整窗口长度。

(本文由一支长期做企业文档解析与检索工程的技术团队整理,欢迎同行交流指正。)

相关推荐
Query*1 小时前
深入浅出LangGraph【一】_基础篇
python·ai·langchain
XiaoMaqqqq1 小时前
市面上正规的IP驱动产业新场景新工具哪家强
运维·python·网络协议·tcp/ip
ShyanZh2 小时前
【Python3基础】19-Socket 与 TCP、UDP 编程
python·网络协议·tcp/ip·udp
惊讶的猫2 小时前
工具选对之后还不够:商城 Agent 的参数与权限治理
开发语言·python
天赐范式3 小时前
天赐范式第186天:让方差开始自洽——Bulmer效应的闭式验证
python·数字生命·天赐范式·动态运行时·遗传方差·bulmer效应
泡茶喝茶写代码3 小时前
A股量化数据工程:从 REST 接口到策略信号(第 8 篇):成长能力因子:营收与利润增速
java·python·股票数据api·股票数据api接口·股票量化数据api·股票量化数据接口·股票数据api数据
Είναι η κοπέλα4 小时前
显存计算与模型选择:你的显卡能跑多大的模型
人工智能·pytorch·python·开源·conda
ROSF68684 小时前
2026 仿石漆乳液供应商:市场布局与行业发展态势梳理
大数据·人工智能·python
青少儿编程课堂4 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛