简介:网页更新监控提醒工具是一套可本地运行的网页动态监测软件,面向网店运营、价格追踪、新闻追踪、项目协作等需要紧盯页面变化的用户。其核心机制为定期抓取目标页并与历史版本比对,在发现标题、价格、订单状态等关键内容变化时,通过提示音、弹窗、邮件等方式提醒,同时可过滤广告等无关噪声。压缩包共8个文件,包含可执行主程序、运行组件、配置文件、网页说明文档、提示音文件及界面图片,整体仅803KB,便于快速部署。目前已有1506人学习下载。使用者可直接启动监控程序,借助配置文件灵活设定检查频率、监控类型和提醒方式,并参照附带网页文档了解常见设置技巧;该工具能替代手工反复刷新,提升信息获取效率,适合个人及小团队在日常业务中使用。
1. 网页更新提醒工具:把“人工盯页面”变成自动化巡检
天天手动刷新订单后台的运营同学,最清楚网页更新提醒的价值:同一套程序盯着页面状态,一旦出现新订单或价格变动,马上推到手机上。这个网页动态监控工具包,就是帮你把“盯页面”这件事自动化。它不追求大而全,正好适合那些没有开放 API、又需要秒级感知变化的场景。无论是电商后台的订单列表,还是内部系统的公告页,只要你能用浏览器打开,它就能帮你做网页更新提醒。适合两类人:不想写爬虫又想快速监控的运营,以及想给业务加一道巡检的开发者。
2. 网页动态监控的核心原理:轮询、指纹与渲染选型
2.1 监控的本质:把“变化”变成“可判断的信号”
网页监控的基本模型是轮询:每隔 N 秒抓一次页面内容,和上一次内容做比较,不同就触发提醒。实现层面最大的分歧是“怎么比”。最土的办法是下载整个页面算 MD5,页面里任何字节变化都会报警,包括广告位随机ID、统计脚本的时间戳甚至 token。这样误报率极高。好一点的做法是只对目标区域的文本做哈希,比如只抓div.order-item里的文字,生成一个短指纹。只要订单列表的结构没变,只有订单数量或状态变化时指纹才会变。
我一般会把指纹算法选成 SHA-256,而不是 MD5。原因不是安全,而是 SHA-256 在 Python 里不需要额外装库,且碰撞概率低到可以忽略。实际使用中你需要的不是一个密码学算法,而是一个稳定的、能表达“内容变没变”的摘要。具体实现也很简单:把选中的 DOM 节点转成纯文本,去掉所有空白字符,再编码计算。需要注意不要去比对整个 HTML,因为很多框架会在每次请求时生成不同的 hidden input,导致指纹永远在变。
下面是一个最核心的指纹函数。代码里的selector参数允许你只监控页面的一部分:
import hashlib from bs4 import BeautifulSoup def get_fingerprint(html: str, selector: str = "") -> str: if selector: soup = BeautifulSoup(html, "html.parser") nodes = soup.select(selector) text = "".join(node.get_text(strip=True) for node in nodes) else: text = html return hashlib.sha256(text.encode("utf-8")).hexdigest()这段代码的逻辑是:传进来完整的 HTML 和一个 CSS 选择器。如果选择器为空,就整页计算指纹;如果给了div.order-item,就先解析页面,把这些节点下的纯文本拼接起来,再算 SHA-256。第一个参数html是 requests 返回的response.text,第二个参数selector是配置里写的目标区域。实际调的时候,selector越精确,误报越少,但代价是当页面结构改版时,选择器失效,监控就会“瞎掉”。
2.2 三种抓取方案选型:requests、Playwright 与第三方服务
定好指纹算法后,下一个问题是“怎么把页面取回来”。这套工具包在抓取层做了三个可选方案,按灵活度从低到高排序。
第一种是纯requests拉取 HTML。这种方式最轻,部署时只依赖 Python 标准库加requests,适合服务端渲染的页面,比如许多传统后台管理界面。缺点是遇到 React/Vue 这类前端框架渲染的页面,拿到手的 HTML 里只有空壳,所有内容都是 JS 动态塞进去的,直接算指纹等于什么都没监控。
第二种是接入playwright做无头浏览器渲染。它会启动一个 Chromium 实例,等页面执行完 JS 后再 dump 最终的 DOM。这样能监控到 SPA 页面里的真实内容,但资源开销大,一个浏览器实例跑起来可能占 200MB 内存。如果同时监控 20 个页面,就得考虑用并发数限制和请求超时。我自己的经验是:能用 requests 解决的绝不先上 Playwright,否则光浏览器升级就能折腾你半天。
第三种是调用第三方网页监控服务,比如把页面提交给现成的在线监控平台,让它们替你去跑。这种方案的优势是免维护,劣势是定制化差,而且页面内部需要登录态时很难处理。工具包里虽然留了一个remote_api的配置项,但实际我只在监控公开页面时用过。下面是三种方案的对比表:
| 方案 | 适用页面 | 资源开销 | 维护成本 | 登录态支持 |
|---|---|---|---|---|
| requests | 服务端渲染 | 低 | 低 | 需手动带 Cookie |
| playwright | SPA / JS 渲染 | 高 | 高 | 可持久化上下文 |
| 第三方服务 | 公开静态页面 | 无 | 中等 | 通常不支持 |
选型时还有一个容易被忽略的点:目标网站的稳定性。如果对方三天两头改前端框架,用 requests 直拉的成本会很低,大不了就改 URL 和 selector;但如果用的是 Playwright,每次页面改版都可能要跟着调等待逻辑。所以我会先拿目标页面在浏览器里右键“查看网页源代码”,如果能看到实际订单数据,就选 requests;如果源代码只有 JS 文件引用,再考虑 Playwright。
2.3 监控频率与资源消耗:别把服务器当浏览器
轮询频率是网页动态监控里最需要克制的地方。许多第一次做监控的同学会把间隔设成 1 秒,生怕漏掉订单。实际上目标网站只要稍微做点防护,10 秒内几十次请求就会触发风控,轻则弹验证码,重则封 IP。我一般遵循“业务容忍度 + 服务器成本”两把尺子来定间隔。
订单类提醒通常容忍 30 秒以内的延迟,我会把间隔设成 30 到 60 秒;公告类页面一天更新几回,间隔拉到 5 分钟也够了。对应地,每个监控目标在配置里加一个interval字段,单位为秒。调度器会为每个目标单独计时,而不是所有目标共享一个循环。这样可以避免某个慢页面拖垮其他监控。
资源消耗方面,requests 方案非常省,一个目标跑 24 小时大概只会消耗几十 MB 带宽和很少的 CPU。但如果切到 Playwright,就要给每个浏览器实例留出至少 512MB 内存,并且强制设置page.set_extra_http_headers模拟真实浏览器。还有一点容易被忽略:无头浏览器在 Linux 服务器上还需要安装一堆系统依赖,比如libnss3、libatk。工具包里提供了一个install_chromium_deps.sh脚本,但你还是要在自己的服务器上跑一次才能用。
3. 快速上手一套可用的监控脚本:安装、配置与第一个示例
3.1 工具包结构与初始化配置
这套工具包解压后就是一个普通 Python 项目,结构很直接。根目录下有config.yaml、monitor.py、notify.py、requirements.txt和logs目录。monitor.py是主入口,负责抓取和比对;notify.py负责把变化消息推送到 IM 或邮箱;config.yaml是唯一的配置文件,你要监控哪些页面、多久跑一次、变化后通知谁,全在这里。
安装依赖只需要一条命令:
pip install -r requirements.txtrequirements.txt里包含了requests、beautifulsoup4、pyyaml,如果你要用 Playwright 方案,还要手动执行playwright install。第一次跑起来之前,建议先改config.yaml里的监控目标,不要直接拿示例配置里的网址去测,因为示例 URL 大概率已经失效。
下面是一个最小化的初始配置,只监控一个公开页面,通知先不用开:
notify: webhook: "" smtp: host: "" port: 465 user: "" pass: "" to: "" targets: - name: "Example Order" url: "https://example.com/orders" method: "GET" headers: User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" Accept: "text/html,application/xhtml+xml" interval: 60 selector: "div.order-item" fingerprint: "sha256" timeout: 10配置里headers段的User-Agent是必须写的,很多网站会拦截空 UA 的请求。timeout是单次请求的最大等待秒数,我习惯设成 10 秒,超过就跳过本轮,避免某个慢请求阻塞整个监控。selector如果为空,表示整页监控。这里notify.webhook留空时,脚本只把变化记到日志文件,不发送任何通知,适合先跑通流程。
3.2 核心监控代码:指纹比对与通知触发
monitor.py的核心逻辑可以浓缩成一个循环。我先贴出主循环代码,然后逐行解释参数:
import time import logging import requests import config as cfg from notify import send_notification from monitor_core import get_fingerprint, render_with_playwright logging.basicConfig(filename="logs/monitor.log", level=logging.INFO) def check_target(target): try: if target.get("render") == "playwright": html = render_with_playwright(target["url"], target.get("wait_for_selector", "")) else: resp = requests.get(target["url"], headers=target.get("headers", {}), timeout=target.get("timeout", 10)) resp.raise_for_status() html = resp.text fingerprint = get_fingerprint(html, target.get("selector", "")) cache_file = f"cache/{target['name']}.txt" try: with open(cache_file, "r", encoding="utf-8") as f: old_fingerprint = f.read().strip() except FileNotFoundError: old_fingerprint = "" if old_fingerprint != fingerprint: with open(cache_file, "w", encoding="utf-8") as f: f.write(fingerprint) send_notification(target, fingerprint, old_fingerprint) return True return False except Exception as e: logging.error(f"[{target['name']}] check failed: {e}") return False if __name__ == "__main__": targets = cfg.load_config()["targets"] while True: for t in targets: check_target(t) time.sleep(1)这段代码里每个参数都有讲究。render_with_playwright是工具包里另一个模块的函数,只有配置里写了render: playwright才会走无头浏览器分支。cache_file放在cache目录下,记录了上一次的指纹,只要项目里没有这个目录,第一次运行时会自动创建。这里用文件存储指纹,而不是存内存,是因为脚本会重启;重启后如果指纹都存在内存里,第一次全部误报,就吵翻天了。
check_target函数的返回值会被主循环忽略,因为每个目标是否发生变化都写到了日志里。如果你只想在发生变化时打印一行,可以在send_notification里增加控制台输出。注意主循环末尾的time.sleep(1)只是防止 CPU 空转,真正的间隔由配置里的interval控制;但为了简单,示例里没有做限速,需要你自己加上“距离上次请求已过 interval 秒再执行”的判断。
3.3 第一次运行:控制台输出与日志解读
运行监控脚本的方式很简单:
python monitor.py第一次运行时,因为缓存目录里没有旧指纹,脚本会把当前指纹写入cache/文件,不触发任何通知。这是一个很关键的行为:它不会在第一次就把页面当成“变化”来报警。如果没有任何报错,终端会一直挂着,看起来像“卡住”了,其实是正常的,因为每个循环都没有打印内容。
想确认脚本确实在工作,可以打开logs/monitor.log看一眼。下面是一个正常的日志片段:
2025-05-10 10:00:02 INFO [Example Order] fingerprint updated, change detected 2025-05-10 10:01:02 INFO [Example Order] no change 2025-05-10 10:01:32 INFO [Example Order] request failed, skip日志里每个时间点对应一次检查。第一条表示首次写入指纹,或者检测到变化;第二条表示页面没变;第三条是请求超时或网络错误,脚本会跳过这一轮,不会误报。如果你看到一堆request failed,大概率是目标网站把你的 UA 拉黑了,或者代理网络不稳定。
4. 网页订单提醒实战:从监控到通知的完整链路
4.1 订单页面的监控目标拆解
网页订单提醒是这套工具最常见的用途。实际使用前,先要把“订单变了”这个抽象需求拆成具体的监控条件。我接触过的案例里,大多数运营关心的无非三件事:新订单产生、订单状态从待付款变为已付款、订单金额发生变化。这三件事在页面里的表现各不相同,但都可以用 CSS 选择器划出监控区域。
假如订单后台是一个表格,每个tr.order-row代表一单。新订单产生时,table.order-list里的行数变多;状态变更时,某一行里的.status文本变化。这种情况下,你的selector应该设置为tr.order-row .status, tr.order-row .order-id,把状态和单号拼进指纹。这样数量不变但状态变了,指纹也会变;若只是页面上的统计数字跳动,不会误报。
另外要注意一个极端情况:很多后台页面会有一个“当前时间”的显示位,如果监控区域不小心包含了它,那么每分钟都会告警。我在某次对接时就吃过这个亏,后来所有 selector 里都强制排除time, .clock, .now这类节点。工具包支持在配置里加一个exclude_selector字段,代码会先选目标区域,再移除排除节点,然后再算指纹。
4.2 配置 IM 机器人或邮件通知
提醒要发出去才有价值。工具包的notify.py目前接入了两类通知:Webhook 机器人和 SMTP 邮件。Webhook 的配置只需一个 URL,比如常见的企业 IM 群机器人地址。邮件则需要 SMTP 服务器信息。下面是两种方式的配置示例:
notify: webhook: "https://example-im.com/custom/hook?token=abc123" smtp: host: "smtp.example.com" port: 465 user: "monitor@example.com" pass: "your-password" to: "ops@example.com"notify.py里的推送逻辑会优先使用 webhook,如果 webhook 为空再尝试 SMTP。webhook 的推送格式是标准 JSON,里面包含监控目标名、新指纹和触发时间。SMTP 的邮件标题固定为[监控提醒] {target_name} 内容已变化,正文里会附上目标 URL 和本地日志路径。
如果你既没有 webhook 也没有邮件配置,脚本只会把告警写入日志文件,不会做任何推送。我第一次部署时就是忘了配 webhook,然后傻乎乎等了半小时没收到提醒,后来才意识到日志文件才是最诚实的。
4.3 参数调优:轮询间隔、去重和重试策略
订单提醒场景下,轮询间隔不能设太小,建议至少 30 秒。如果目标后台本身有登录态,requests 方案需要手动带上Cookie请求头,否则会一直跳转登录页。你可以先在浏览器开发者工具里找到登录后的 Cookie 字符串,粘贴到配置headers.Cookie字段里。这样做有一个隐患:Cookie 过期后监控会静默失败,表现为页面一直返回登录页。要解决这个问题,需要额外写一个“登录态检测”,就是检查返回页面里是否含有“登录”字样,如果有就报错通知你。这是我在后面避坑章里重点想说的内容。
去重逻辑方面,工具包默认只在“新指纹 != 旧指纹”时发通知。如果页面连续三分钟都处于变化状态(比如订单不断进来),那么每分钟都会发一条提醒,这会让群机器人很吵。我在生产环境里会加一个cooldown参数:某个目标触发告警后,N 分钟之内即使再次变化也不重复发送,把cooldown设成 300 秒比较合适。实现方式是在 cache 目录里额外存一个{name}.last_notify时间戳文件。
重试策略也需要单独调:如果目标网站偶尔超时,直接跳过就好,不用告警;但连续 5 次失败就要发一条“监控异常”消息。工具包里提供了一个简单的计数器,每次失败加一,成功时清零。超过阈值后,notify.py会发一条不同于“内容变化”的异常通知。这里的关键是不要把“监控挂了”和“页面变了”混为一谈,否则半夜收到报警会分不清是该高兴还是该紧张。
5. 避坑指南:网页监控最容易翻车的五个问题
5.1 现象:页面内容抓不全或抓空
第一次用 requests 去抓一个订单后台,返回的 HTML 里可能根本找不到div.order-item,指纹计算结果是固定空串。原因是这个页面是前端 JS 异步渲染的,直接拉 HTML 只能拿到空骨架,所有订单数据都是后续接口返回的。
解决:先看页面源码,确认目标数据是否在首屏 HTML 里。如果不在,有两种走法:一是用 Playwright 渲染,配置里加render: playwright,并设置wait_for_selector,等订单节点出现后再取内容;二是找到页面背后真正返回订单数据的 JSON 接口,直接监控那个接口的 URL。我通常更倾向后者,因为接口返回的数据结构稳定,计算指纹更准确。
5.2 现象:IP 被目标站点封禁
监控跑了半天,突然所有请求都返回 403,页面变成验证码。原因几乎都是请求频率太高,或者 UA 太明显。有些网站会对同一个 IP 的短时间高频率请求做限流,即使你设置了 30 秒间隔,如果你同时监控 20 个页面,每个页面独立发起请求,那对目标站点来说就是 20 倍的访问压力。
解决:第一,全局限制同一域名下的最大请求频率,比如每 10 秒最多 2 次。第二,在 headers 里带上完整的浏览器指纹字段,包括Accept-Language、Referer,别只靠一个 UA。第三,把interval拉大到 60 秒以上。如果目标站点是订单后台那种需要登录的系统,还要考虑用固定的出口 IP,或者切换到 Playwright 浏览器上下文以降低风控概率。
5.3 现象:内容没变却一直报警
页面内容每次刷新都会生成随机 token,导致整页指纹每次不同。或者监控区域里包含了动态时间、用户昵称、CSRF token 等会变但不代表业务变化的节点。
解决:不要把整页作为监控目标,改用精确的selector圈出关键区域。同时用exclude_selector把时间、token 所在的 DOM 节点排除。如果还是误报,就把指纹算法从“纯文本拼接”改成“关键属性拼接”,比如只取>python -m http.server 8000
先写一个page.html,内容是old order;运行monitor.py,确认没有告警;再改成new order,等待 60 秒,检查是否收到提醒。这个测试做完,我心里就有底了。从那以后,我每次改完选择器或通知配置后都强制走一遍这个流程,不跳步。希望帮到你。
本文还有配套的精品资源,点击获取