Task #1680: 修复OCR时间戳识别 - 两遍扫描策略+时间戳豁免

修复内容:
1. 时间戳格式(如9;51.24)不再被误判为OCR残片
   - 在_is_noise_text中添加时间戳格式豁免
2. 两遍扫描策略解决时间戳继承问题
   - 第一遍:计算所有文本的原始新鲜度分数,记录时间戳位置
   - 第二遍:无时间戳的消息继承最近时间戳的新鲜度(前后双向搜索)
   - 原因:QQ时间戳出现在消息之间,第一遍时消息没有前驱时间戳可继承
3. 添加调试日志便于排查问题
This commit is contained in:
youpaishidifu
2026-09-02 14:32:11 +08:00
parent 26c06b00ff
commit b4f8298d32
+51 -19
View File
@@ -8808,6 +8808,13 @@ class MessageMonitor:
if any(kw in text for kw in _intro_keywords): if any(kw in text for kw in _intro_keywords):
logger.debug(f"[ocr] 过滤器2结果: 包含简介关键词,跳过残片判定: {text!r}") logger.debug(f"[ocr] 过滤器2结果: 包含简介关键词,跳过残片判定: {text!r}")
return False return False
# ⭐ Task #1680: 时间戳豁免(2026-09-02)
# OCR可能将时间戳识别为独立片段(如"9;51.24"、"14:00:18")
# 这些是有效的时间戳,不应被判定为OCR残片
_ts_pattern = re.compile(r'^\d{1,2}[;:.]\d{2}[;:.]?\d{0,2}$')
if _ts_pattern.match(text.strip()):
logger.debug(f"[ocr] 过滤器2结果: 检测到时间戳格式,跳过残片判定: {text!r}")
return False
_has_question_word = any(w in text for w in ('吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几', '?', '?')) _has_question_word = any(w in text for w in ('吗', '呢', '多少', '怎么', '如何', '哪', '什么', '几', '?', '?'))
_has_verb = any(v in text for v in ('怎么', '如何', '进行', '设置', '配置', '操作', '使用', '打开', '下载', '安装', '登录', '进入', '查看', '查询', '问', '找', '要', '可以', '能', '会', '请', '帮', '给', '有')) _has_verb = any(v in text for v in ('怎么', '如何', '进行', '设置', '配置', '操作', '使用', '打开', '下载', '安装', '登录', '进入', '查看', '查询', '问', '找', '要', '可以', '能', '会', '请', '帮', '给', '有'))
_has_subject = any(s in text for s in ('我', '你', '他', '我们', '你们', '他们', '玩家', '用户', '管理员')) _has_subject = any(s in text for s in ('我', '你', '他', '我们', '你们', '他们', '玩家', '用户', '管理员'))
@@ -9956,6 +9963,11 @@ class MessageMonitor:
else: else:
# 有结果了,继续处理 # 有结果了,继续处理
logger.debug(f"[{platform}] 获取到后台OCR结果: {len(texts)} 条消息") logger.debug(f"[{platform}] 获取到后台OCR结果: {len(texts)} 条消息")
# ⭐ Task #1680: 调试日志 - 查看texts内容
for i, t in enumerate(texts):
if t.strip():
_t = t.strip()[:60]
logger.debug(f"[{platform}] texts[{i}]: {_t!r}")
except Exception as e: except Exception as e:
logger.error(f"[{platform}] 异步OCR处理异常: {e}") logger.error(f"[{platform}] 异步OCR处理异常: {e}")
@@ -10051,30 +10063,46 @@ class MessageMonitor:
new_items = [] new_items = []
# ⭐ 时间戳新鲜度评分:在关键词打分和查重之前计算,作为加分项 # ⭐ 时间戳新鲜度评分:在关键词打分和查重之前计算,作为加分项
freshness_scores = {} # 存储每条消息的时间戳新鲜度分数 freshness_scores = {} # 存储每条消息的时间戳新鲜度分数
# ⭐ Task #1661: 维护上一条带合法时间戳消息的新鲜度,供无时间戳消息继承 # ⭐ Task #1661+#1680: 两遍扫描策略
# QQ/微信中用户连发多条消息时,仅首条带时间戳,后续紧邻消息无时间戳 # 第一遍:计算每条文本的原始新鲜度分数,记录时间戳位置
# 无时间戳消息继承前驱时间戳新鲜度 → 同批次消息共享时间上下文,避免误处理旧消息 # 第二遍:无时间戳的消息继承最近时间戳的新鲜度(前后双向搜索)
_last_ts_freshness = None # 上一条带合法时间戳消息的新鲜度分数 # 原因:QQ时间戳出现在消息之间(如 msg1, ts1, msg2, ts2, ...)
_last_ts_text = None # 上一条带合法时间戳消息的文本(仅日志用) # 第一遍时msg1没有前驱时间戳可继承,需在第二遍用后继时间戳补救
for t in texts: _ts_positions = [] # [(index, freshness_score)] 时间戳在texts中的位置和分数
for idx, t in enumerate(texts):
ts = t.strip() ts = t.strip()
if not ts: if not ts:
continue continue
# 计算时间戳新鲜度分数(作为后续综合得分的加分项) # 计算时间戳新鲜度分数
freshness_score = self._timestamp_freshness_score(ts) freshness_score = self._timestamp_freshness_score(ts)
# ⭐ Task #1661: 无时间戳(-999.0) → 继承同批次紧邻前驱消息的时间戳新鲜度
if freshness_score == -999.0:
if _last_ts_freshness is not None:
logger.debug(f"[{platform}] 无时间戳消息继承前驱时间戳新鲜度 "
f"({_last_ts_text[:20]!r}→{ts[:20]!r}): {_last_ts_freshness:.1f}")
freshness_score = _last_ts_freshness
# else: 整批都无时间戳,保持-999.0(保守,不补分、扣10分过滤)
else:
# 有合法时间戳:更新前驱状态,供后续无时间戳消息继承
_last_ts_freshness = freshness_score
_last_ts_text = ts
# 存储原始文本和新鲜度分数
freshness_scores[ts] = freshness_score freshness_scores[ts] = freshness_score
if freshness_score != -999.0:
_ts_positions.append((idx, freshness_score))
# ⭐ Task #1680: 调试日志
if len(ts) < 30:
logger.debug(f"[{platform}] freshness_scores构建: {ts!r} -> {freshness_score:.1f}")
# 第二遍:为无时间戳的消息分配最近时间戳的新鲜度
for idx, t in enumerate(texts):
ts = t.strip()
if not ts:
continue
if freshness_scores.get(ts) != -999.0:
continue # 已有合法时间戳,跳过
# 搜索最近的时间戳(前后双向)
best_freshness = None
best_dist = float('inf')
for ts_idx, ts_fresh in _ts_positions:
dist = abs(idx - ts_idx)
if dist < best_dist:
best_dist = dist
best_freshness = ts_fresh
if best_freshness is not None:
logger.debug(f"[{platform}] 无时间戳消息继承最近时间戳新鲜度 "
f"(pos {idx} <- ts pos {idx-best_dist if best_dist <= idx else idx+best_dist}): "
f"{best_freshness:.1f}: {ts[:20]!r}")
freshness_scores[ts] = best_freshness
# else: 整批无时间戳,保持-999.0
# === URL-only 兜底:无中文且含 URL → 碎片 === # === URL-only 兜底:无中文且含 URL → 碎片 ===
if not re.search(r'[\u4e00-\u9fff]', ts) and re.search(r'https?\S', ts, re.IGNORECASE): if not re.search(r'[\u4e00-\u9fff]', ts) and re.search(r'https?\S', ts, re.IGNORECASE):
logger.debug(f"[{platform}] Poll过滤: URL-only碎片 ts={ts!r}") logger.debug(f"[{platform}] Poll过滤: URL-only碎片 ts={ts!r}")
@@ -10756,6 +10784,10 @@ class MessageMonitor:
# ⭐ 时间戳新鲜度评分:使用预先计算的freshness_score # ⭐ 时间戳新鲜度评分:使用预先计算的freshness_score
# 从freshness_scores字典中获取当前消息的时间戳新鲜度分数 # 从freshness_scores字典中获取当前消息的时间戳新鲜度分数
freshness_score = freshness_scores.get(item, 0.0) freshness_score = freshness_scores.get(item, 0.0)
# ⭐ Task #1680: 调试日志 - 查看freshness_score查找
_in_dict = item in freshness_scores
logger.debug(f"[{platform}] freshness_score查找: item={item!r:.30}, score={freshness_score:.1f}, "
f"在字典中={_in_dict}")
# ⭐ Task #1647: 陈旧消息过滤策略重构 # ⭐ Task #1647: 陈旧消息过滤策略重构
# - 超过1小时的消息:freshness_score == -10.0(扣10分) # - 超过1小时的消息:freshness_score == -10.0(扣10分)