尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

changedetection.io 插件开发指南:从 Stats Tab 到完整 Hook 体系

发布时间:2026/9/10 19:52:11

资讯中心
01
ARTICLE

changedetection.io 插件开发指南:从 Stats Tab 到完整 Hook 体系

changedetection.io 插件开发指南:从 Stats Tab 到完整 Hook 体系
changedetection.io 插件开发指南从 Stats Tab 到完整 Hook 体系【免费下载链接】changedetection.ioBest and simplest tool for website change detection, web page monitoring, and website change alerts. Perfect for tracking content changes, price drops, restock alerts, and website defacement monitoring—all for free or enjoy our SaaS plan!项目地址: https://gitcode.com/GitHub_Trending/ch/changedetection.io导读本文以仓库根目录下 PLUGIN_README.md 为骨架系统讲解 changedetection.io 的插件机制。文章先手把手带你编写一个能向监控项Watch编辑页 Stats 标签注入自定义统计信息的 UI Stats Tab 插件再深入剖析插件的加载方式、内部两大 PluginManager 的职责划分以及源码中已提供的十余个 Hook钩子接口——让你不仅能复刻文档示例还能理解内置插件如词频统计、Levenshtein 相似度、requests 抓取器是如何借助同一套机制实现的。读完本文你将掌握如何编写并加载一个插件、如何通过ui_edit_stats_extras定制编辑页统计面板、如何利用其余 Hook 扩展内容抓取、处理器、设置页与页面head以及如何测试与排查插件问题。一、插件机制概览什么是 changedetection.io 插件changedetection.io 的插件系统基于 pluggy 构建。插件以 Python 模块为单位通过实现特定名称的 Hook 函数来挂钩进应用的生命周期从而在不修改核心代码的前提下扩展功能。从 pluggy_interface.py 可以看到项目定义了一个全局插件命名空间# Global plugin namespace for changedetection.io PLUGIN_NAMESPACE changedetectionio hookspec pluggy.HookspecMarker(PLUGIN_NAMESPACE) hookimpl pluggy.HookimplMarker(PLUGIN_NAMESPACE)其中hookspec用于声明应用会调用哪些 Hook规格hookimpl用于插件标记我实现了哪个 Hook实现。同一模块可以同时声明多个hookimpl函数实现多种扩展能力。小提示插件目录下还存在另一套命名空间changedetectionio_conditions见 conditions/pluggy_interface.py专门用于扩展 JSON Logic 条件引擎注册算子、字段、注入数据。在插件源码中经常能看到一个文件同时用conditions_hookimpl和global_hookimpl两个标记器例如 wordcount_plugin.py这正是 PLUGIN_README.md 未展开但实际存在的机制后文会结合示例说明。二、UI Stats Tab 插件向编辑页注入自定义统计2.1 什么是 Stats Tab每个监控项Watch的Edit编辑页面中有一个Stats统计标签页用于展示该监控项的运行指标例如History length历史快照数Last fetch duration最近抓取耗时Notification alert count通知触发次数等见 edit.html。Stats Tab 插件的作用就是往这个标签页追加你自己的 HTML 内容例如自定义统计、图表或分析面板。在 edit.html 中模板会在系统统计表格之后渲染插件输出的内容{% if ui_edit_stats_extras %} div classplugin-stats-extras !-- from pluggy plugin -- {{ ui_edit_stats_extras|safe }} /div {% endif %}ui_edit_stats_extras这个模板变量由 edit.py 通过collect_ui_edit_stats_extras(watch)收集而来它会聚合所有已注册插件返回的 HTML 片段用换行拼接后注入模板。注意模板使用|safe过滤器直接输出因此插件返回的 HTML 必须经过自身充分校验与转义避免 XSS 风险。2.2 创建插件三步入门按 PLUGIN_README.md 的步骤创建插件只需三步在插件系统会扫描的目录中创建一个 Python 文件扫描目录的配置方法见第三节用global_hookimpl装饰器实现ui_edit_stats_extras(watch)Hook函数返回一段 HTML 字符串即可出现在 Stats Tab 中。最小可运行示例import pluggy from loguru import logger global_hookimpl pluggy.HookimplMarker(changedetectionio) global_hookimpl def ui_edit_stats_extras(watch): Add custom content to the stats tab # Calculate or retrieve your stats my_stat calculate_something(watch) # Return HTML content as a string html f div classmy-plugin-stats h4My Plugin Statistics/h4 pMy statistic: {my_stat}/p /div return html关于这个 Hook 的规格可对照 pluggy_interface.py 中的ChangeDetectionSpec.ui_edit_stats_extras(watch)声明入参watch是当前被编辑的 Watch 对象返回值是待插入到 Stats 标签页的 HTML 字符串。2.3 完整实战统计最新快照的单词数PLUGIN_README.md 给出了一个完整示例——统计监控项最新历史快照中的单词数。这里给出完整代码并逐段注释import pluggy from loguru import logger global_hookimpl pluggy.HookimplMarker(changedetectionio) def count_words_in_history(watch): Count words in the latest snapshot try: if not watch.history.keys(): return 0 latest_key list(watch.history.keys())[-1] latest_content watch.get_history_snapshot(timestamplatest_key) return len(latest_content.split()) except Exception as e: logger.error(fError counting words: {str(e)}) return 0 global_hookimpl def ui_edit_stats_extras(watch): Add word count to the Stats tab word_count count_words_in_history(watch) html f div classword-count-stats h4Content Analysis/h4 table classpure-table tbody tr tdWord count (latest snapshot)/td td{word_count}/td /tr /tbody /table /div return html要点说明watch.history是监控项的历史快照字典键为时间戳、值为内容watch.get_history_snapshot(timestamp...)用于按时间戳取出快照内容单词数通过split()按空白切分计算逻辑简单直观函数捕获所有异常并记录日志、返回 0保证插件失败不会影响页面渲染返回的 HTML 使用了 Pure CSS 的pure-table类与 changedetection.io 前端样式体系基于 pure-min.css保持一致。2.4 仓库中的真实实现wordcount_plugin 与 levenshtein_plugin该示例并非凭空设计——仓库内置的 wordcount_plugin.py 正是它的生产级实现。它与文档示例的关键差异在于同时支持两套插件系统同一函数体分别被conditions_hookimpl和global_hookimpl标记因此无论走条件引擎命名空间还是全局命名空间都能生效使用 Flask-Babel 国际化from flask_babel import gettext as _, lazy_gettext as _l文案通过_(Content Analysis)等调用自动适配多语言本项目支持 18 种语言同时服务于条件引擎通过register_field_choices()注册word_count字段Word count of content并通过add_data()在每次检查时把当前文本的单词数注入ephemeral_data供 JSON Logic 条件如单词数大于 N 才触发使用。另一内置插件 levenshtein_plugin.py 展示了更复杂的 Stats Tab 输出它比较最近两次快照展示编辑距离Raw distance相似度比值Similarity ratio相似百分比Percent similar三项指标并且包含严谨的防御逻辑——历史快照不足 2 个时提示Not enough history、快照超过LEVENSHTEIN_MAX_LEN_FOR_EDIT_STATS 100000字符时跳过计算以避免算法卡死。这些细节可作为你编写生产级插件时的参考范式。三、插件加载机制两类来源与加载流程PLUGIN_README.md 明确指出插件可从两个来源加载下面结合源码逐一展开。3.1 来源一内置插件目录在 pluggy_interface.py 中load_plugins_from_directories()遍历plugin_dirs列表中声明的(python_package_prefix, filesystem_path)对扫描其中所有非__init__.py的.py文件逐个importlib.import_module后用plugin_manager.register(module, module_name)注册plugin_dirs [ ( changedetectionio.conditions.plugins, os.path.join(os.path.dirname(__file__), conditions, plugins), ), ]因此往changedetectionio/conditions/plugins/目录放入一个.py文件重启应用后即被自动加载——这是文档步骤 1 所说的被插件系统扫描的目录的默认配置。文档提到要添加新的插件目录请修改pluggy_interface.py中的plugin_dirs字典。源码注释还揭示了一个重要细节processors/restock_diff/plugins 被刻意排除在目录扫描之外因为restock_diff/__init__.py → model.Watch → content_fetchers → pluggy_interface存在循环导入风险该插件改由register_builtin_restock_plugins()pluggy_interface.py在导入完成后显式注册模块名为llm_restock。3.2 来源二外部包setuptools entry points外部 Python 包可通过声明 setuptools entry points 提供插件。在 pluggy_interface.py 中plugin_manager.load_setuptools_entrypoints(PLUGIN_NAMESPACE)pluggy 会查找已安装包中在changedetectionio命名空间下注册的 entry points 并自动加载。配合 setup.py 可以看到项目自身也使用 entry_points 暴露命令行入口changedetection.iochangedetectionio:main这是 Python 打包生态的标准做法。外部插件的setup.py/pyproject.toml中类似声明如下示意非仓库现有代码[options.entry_points] changedetectionio my_plugin my_plugin_module3.3 内置 fetcher 与 processor 的插件化注册除了目录扫描和 entry points源码还展示了第三种注册途径——由应用代码在合适时机显式注册内置插件用于规避循环导入register_builtin_fetchers()pluggy_interface.py在content_fetchers/__init__.py导入完成后注册 requests、playwright、puppeteer、webdriver_selenium 四个内置抓取器为插件名称分别为builtin_requests等。以 requests.py 为例其RequestsFetcherPlugin.register_content_fetcher()返回(html_requests, fetcher)正是实现了register_content_fetcher这个 Hookregister_builtin_restock_plugins()注册llm_restock插件见上文 3.1。这意味着自定义内容抓取器同样可以做成插件Hook 详见 4.2。3.4 datastore 注入部分插件需要访问全局数据存储设置、监控项数据。pluggy_interface.py 的inject_datastore_into_plugins(datastore)会在应用初始化后为所有datastore属性为None的插件注入全局ChangeDetectionStore实例。如果你的插件需要读取全局设置可在模块级定义datastore None占位属性由框架自动注入。四、完整的 Hook 清单不止 Stats TabPLUGIN_README.md 仅详细介绍了ui_edit_stats_extras但 ChangeDetectionSpec 中声明了 12 个 Hook 规格覆盖抓取、处理、设置、UI 注入等全流程。下表汇总Hook 名称作用返回值ui_edit_stats_extras(watch)向编辑页 Stats 标签注入 HTMLstrregister_content_fetcher()注册自定义内容抓取器(fetcher_name, fetcher_class)名称须以html_开头类须继承changedetectionio.content_fetchers.base.Fetcherfetcher_status_icon(fetcher_name)为抓取器返回状态图标 HTML 属性str空串表示无plugin_static_path()返回插件静态文件目录绝对路径str 或 Noneget_itemprop_availability_override(...)自定义商品 availability/price 提取内置方法找不到数据时作为回退dict 或 Noneplugin_settings_tab()向设置页新增标签页设置以独立 JSON 文件存入 datastoredict 或 Noneregister_processor()注册外部处理器processor与内置处理器一起被发现dict 或 Noneupdate_handler_alter(update_handler, watch, datastore)在perform_site_check执行前包装/修改处理句柄可链式叠加object 或 Noneupdate_finalize(update_handler, watch, datastore, processing_exception)监控项处理完成后成功或失败执行清理/度量/日志Noneget_html_head_extras()向每个页面的head注入script/style/linkstr 或 None4.1 生命周期 Hookupdate_handler_alter 与 update_finalize这两个 Hook 分别对应监控项一次检查的处理前和处理后阶段update_handler_alter在perform_site_check实例创建后、调用call_browser()与run_changedetection()之前触发可用于包装处理句柄加日志/度量、修改配置或注入预处理逻辑。多个插件的返回值会按注册顺序链式传递见apply_update_handler_alterpluggy_interface.pyupdate_finalize在finally块中调用processing_exception非 None 表示处理失败。apply_update_finalize()pluggy_interface.py会捕获插件自身的异常并记录日志确保插件错误不会导致 worker 崩溃。4.2 抓取器与 availability 回退 Hookregister_content_fetcher使第三方可以注册全新的抓取后端。get_fetcher_capabilities(watch, datastore)pluggy_interface.py在解析监控项使用的抓取器时会先查内置抓取器再遍历插件注册结果说明插件抓取器与内置抓取器在能力查询上地位对等get_itemprop_availability_override用于商品监控场景价格、库存。get_itemprop_availability_from_plugin()pluggy_interface.py会在内置提取逻辑未找到有效数据时调用所有插件返回第一个包含有效price或availability的字典可选含currency、llm_intent透传。4.3 设置页插件plugin_settings_tabplugin_settings_tab()允许插件在应用设置页新增自己的标签页规格见 pluggy_interface.py。返回的字典须包含plugin_id唯一标识、tab_label标签显示名、form_classWTForms 表单类可选template_path未提供时使用默认表单渲染器。设置项通过load_plugin_settings(datastore_path, plugin_id)/save_plugin_settings(datastore_path, plugin_id, settings)pluggy_interface.py以plugin_id.json独立文件持久化到 datastore 目录。4.4 页面级注入get_html_head_extras该 Hook 在每个页面的head中注入内容经base.html生效通过collect_html_head_extras()pluggy_interface.py聚合它由 Flask 模板全局变量调用因此总是在请求上下文中执行可以安全使用url_for()。规格文档pluggy_interface.py给出了两条实践建议少量 CSS/JS 直接内联返回无需文件服务较大资源建议在插件模块内注册轻量 Flask 路由并用url_for()引用以兼容 nginx 反向代理子路径部署USE_X_SETTINGS/X-Forwarded-Prefix ProxyFix 设置SCRIPT_NAME——直接写死路径会导致子路径部署下资源 404。五、Testing验证你的插件按 PLUGIN_README.md 的Testing Your Plugin步骤验证流程为将插件文件放入插件系统扫描的目录默认可放changedetectionio/conditions/plugins/或通过 entry points 安装重启 changedetection.io插件在启动时加载见load_plugins_from_directories()打开任意监控项的 Edit 页面切到Stats标签页即可看到插件输出的内容。如果你想验证插件是否被成功注册可以观察启动日志中的load_plugins_from_directories()输出——加载失败ImportError/AttributeError时会打印Error loading plugin {module_name}提示。get_active_plugins()pluggy_interface.py会列出所有活动插件排除builtin_前缀的内置项并优先使用模块 docstring 首行作为插件描述可用于在应用内展示插件列表。仓库自身还提供了插件相关测试可作参考例如 test_plugins.py处理器插件测试与 test_html_head_extras.pyhead 注入测试可在 changedetectionio/tests/plugins 目录查看。六、调试与常见问题插件未生效确认文件名非__init__.py、位于扫描目录内或已通过 entry points 正确安装并重启应用检查启动日志是否有Error loading plugin。HTML 未显示确认函数名与签名严格匹配 Hook 规格如ui_edit_stats_extras(watch)且返回值非空字符串collect_ui_edit_stats_extras会跳过空结果见 pluggy_interface.py。XSS 与转义Stats Tab 与head注入均以|safe/ 原始 HTML 方式输出插件返回的任何用户数据必须先转义。循环导入如果你的插件模块被__init__.py链路间接导入请参照 restockllm_restock插件的做法改为在应用初始化后期显式注册。国际化复用flask_babel的gettext让插件文案支持多语言与内置插件保持一致。七、结语从 PLUGIN_README.md 出发可以看到changedetection.io 的插件体系远不止一个 Stats Tab Hook它以 pluggy 为底座通过changedetectionio全局与changedetectionio_conditions条件引擎两套命名空间将内容抓取、处理器注册、商品 availability 提取、设置页扩展、生命周期钩子与页面注入等能力全部开放给插件。理解 pluggy_interface.py 中ChangeDetectionSpec的每一行规格配合 wordcount_plugin.py 与 levenshtein_plugin.py 两个内置实现你就能写出与官方插件同等质量、可随项目升级长期维护的扩展。【免费下载链接】changedetection.ioBest and simplest tool for website change detection, web page monitoring, and website change alerts. Perfect for tracking content changes, price drops, restock alerts, and website defacement monitoring—all for free or enjoy our SaaS plan!项目地址: https://gitcode.com/GitHub_Trending/ch/changedetection.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。