
本文介绍如何通过行级差异比对替代全页哈希校验,实现对目标网页(如新闻列表页)新增文章的精准检测,避免因页眉、时间戳等动态元素导致的误报,并提供可落地的 Python 实现方案。
本文介绍如何通过行级差异比对替代全页哈希校验,实现对目标网页(如新闻列表页)新增文章的初步检测,避免因页眉、时间戳等动态元素导致的误报,并提供可落地的 python 实现方案。
在网页变更监控场景中,直接对整页 HTML 进行 SHA224 哈希比对虽实现简单,但极易产生大量误报——例如页面嵌入的实时时间戳、广告位、统计脚本、CDN 缓存标识或响应头注入的动态元信息,都会导致每次请求返回的 HTML 字符串不同,即使核心内容(如文章列表)未变。根本问题不在于“子域名 vs 高层域名”,而在于缺乏语义感知的变更过滤能力。
更稳健的思路是:将 HTML 视为文本序列,逐行比对历史快照与当前快照,识别实际发生变动的行号范围,再结合网页结构特征(如 以下是一个精简、可扩展的监控脚本示例,已优化原始逻辑缺陷(如重复请求、无异常兜底、缺少内容聚焦): 此方法将监控粒度从“整页二进制”下沉至“语义区块文本行”,兼顾实现简易性与工程实用性,是构建轻量级网页更新通知系统的核心基础。import difflib
import time
from urllib.request import urlopen, Request
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
def fetch_html(url: str) -> str:
"""安全获取网页HTML(含UA伪装与异常处理)"""
try:
req = Request(url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'})
with urlopen(req, timeout=10) as response:
return response.read().decode('utf-8')
except Exception as e:
logging.error(f"Failed to fetch {url}: {e}")
return ""
def extract_content_region(html: str, start_tag: str = "<main", end_tag: str = "</main>") -> str:
"""
粗粒度过滤:提取主体内容区域(如<main>、<article>或ID为'content'的区块)
实际使用时建议用BeautifulSoup精准定位,此处为简化演示
"""
start_idx = html.find(start_tag)
if start_idx == -1:
return html # 退化为全页比对
end_idx = html.find(end_tag, start_idx)
return html[start_idx:end_idx + len(end_tag)] if end_idx != -1 else html[start_idx:]
def detect_line_changes(old_html: str, new_html: str) -> list:
"""返回发生变化的行号列表(基于context_diff)"""
old_lines = old_html.splitlines(keepends=True)
new_lines = new_html.splitlines(keepends=True)
diff = difflib.context_diff(
old_lines, new_lines,
fromfile='old', tofile='new',
lineterm='', n=0 # n=0 表示显示所有差异行,不省略上下文
)
changed_lines = set()
for line in diff:
# 匹配形如 "*** 470,475 ****" 或 "--- 575,580 ----" 的行号标记
if line.startswith('*** ') or line.startswith('--- '):
parts = line.strip().split()
if len(parts) >= 2 and ',' in parts[1]:
try:
line_range = parts[1].split(',')[0]
changed_lines.add(int(line_range))
except (ValueError, IndexError):
continue
return sorted(changed_lines)
# ===== 主监控逻辑 =====
URL = "https://example-news-site.com/articles/" # 替换为目标URL
CHECK_INTERVAL = 60 # 检测间隔(秒),生产环境请勿低于30秒
MAX_RUNTIME = 3600 # 最大运行时长(秒),防无限循环
logging.info(f"Starting monitor for {URL}, interval={CHECK_INTERVAL}s")
old_content = fetch_html(URL)
if not old_content:
logging.error("Initial fetch failed. Exiting.")
exit(1)
start_time = time.time()
while time.time() - start_time < MAX_RUNTIME:
time.sleep(CHECK_INTERVAL)
new_content = fetch_html(URL)
if not new_content:
continue
# 【关键改进】聚焦主体内容区域,排除页眉页脚干扰
old_main = extract_content_region(old_content)
new_main = extract_content_region(new_content)
changed_lines = detect_line_changes(old_main, new_main)
if changed_lines:
logging.info(f"Change detected at lines: {changed_lines}")
# 进阶提示:此处可触发BeautifulSoup解析,定位新增<article>节点
# 例如:soup = BeautifulSoup(new_content, 'html.parser')
# new_articles = soup.select('article:not([data-checked])')
# ... 标记并通知
else:
logging.debug("No content change detected.")
old_content = new_content # 更新基准快照⚠️ 重要注意事项










