ArchiveBox 插件发现机制深度解析:archivebox.plugins.discovery 源码级指南

ArchiveBox 插件发现机制深度解析:archivebox.plugins.discovery 源码级指南 后端数据工程【免费下载链接】ArchiveBox Open source self-hosted web archiving. Takes URLs/browser history/bookmarks/Pocket/Pinboard/etc., saves HTML, JS, PDFs, media, and more...项目地址https://gitcode.com/gh_mirrors/ar/ArchiveBox点击查看免费下载本篇技术指南以 ArchiveBox 的插件发现核心模块 archivebox/plugins/discovery.py对应 API 文档 docs/apidocs/archivebox/archivebox.plugins.discovery.md为骨架系统讲解 ArchiveBox 如何发现、枚举、筛选并渲染第三方与内置插件。读完本文你将掌握插件目录的两大来源、PluginCatalog统一目录的作用、USE_/SAVE_开关如何过滤插件、{PLUGIN}_ENABLED/{PLUGIN}_TIMEOUT/{PLUGIN}_BINARY三组特殊配置键的命名约定以及插件图标与卡片模板的渲染管线并能在自己的 ArchiveBox 部署中定位、安装与调试自定义插件。模块定位ArchiveBox 与 abx-dl 之间的发现适配层从源码注释可以看到ArchiveBox 的插件运行时发现与执行由框架无关的abx-dl库拥有ArchiveBox 只保留了一个轻量的 Django 投影适配层与面向应用的 URL 输出读取器见 archivebox/plugins/hooks.py 顶部模块文档Discovery and execution are owned by abx-dl. ArchiveBox keeps only the small Django projection adapter and its application-specific URL-output reader.因此discovery.py承担的角色非常纯粹把 abx-dl 的PluginCatalog插件目录和PluginConfigResolver配置解析器投影成 ArchiveBox 自身所需的 Python API供 Django 表单、管理后台视图、模板标签、搜索后端等模块消费。该模块的全部核心逻辑集中在 archivebox/plugins/discovery.py全文约 182 行依赖abx_plugins的get_plugins_dir与abx_dl.catalog的PluginCatalog、PluginConfigResolver两个外部组件。从文档列出的 API 清单看该模块公开了 2 个类ConfigLookup、PluginSpecialConfig、11 个函数iter_plugin_dirs、get_plugins、get_plugin_name、get_enabled_plugins、get_search_backends、discover_plugin_configs、get_plugin_special_config、get_plugin_template、get_plugin_icon等和 3 个模块级数据BUILTIN_PLUGINS_DIR、USER_PLUGINS_DIR、DEFAULT_TEMPLATES。其中文档中同时列出的get_plugin_models与discover_plugins_that_provide_interface属于旧版接口的文档残留——在当前源码实现中已不存在相关能力被统一收敛到了PluginCatalog/PluginConfigResolver之上阅读历史代码时可留意这一演进。两大插件来源BUILTIN_PLUGINS_DIR 与 USER_PLUGINS_DIR模块顶部定义了两个目录常量它们是整个发现过程的起点BUILTIN_PLUGINS_DIR Path(get_plugins_dir()).resolve() USER_PLUGINS_DIR CONSTANTS.USER_PLUGINS_DIRBUILTIN_PLUGINS_DIR内置插件目录直接取自abx_plugins.get_plugins_dir()并做resolve()规范化。它与abx-plugins仓库abx_plugins/plugins/绑定随包分发。USER_PLUGINS_DIR用户自定义插件目录映射到 ArchiveBox 数据目录下的custom_plugins子目录。从 archivebox/config/constants.py 可以看到其定义链CUSTOM_PLUGINS_DIR_NAME: str custom_plugins ... USER_PLUGINS_DIR: Path DATA_DIR / CUSTOM_PLUGINS_DIR_NAME即默认位于 ArchiveBox 数据目录如data/custom_plugins/。在管理后台的Installed plugins列表视图中见 archivebox/plugins/views.py 的get_filesystem_plugins这两个目录分别被标记为builtin与user两种来源并且以source.plugin_name的形式如builtin.wget、user.myplugin作为插件唯一 ID用户目录下的插件user.*会渲染其源码路径为data/custom_plugins/{plugin_name}/config.json。get_filesystem_plugins()对这两个目录的扫描逻辑还揭示了插件目录的形态约定for base_dir, source in [(BUILTIN_PLUGINS_DIR, builtin), (USER_PLUGINS_DIR, user)]: if not base_dir.exists(): continue for plugin_dir in base_dir.iterdir(): if plugin_dir.is_dir() and not plugin_dir.name.startswith(_): ... for ext in (sh, py, js): hooks.extend(plugin_dir.glob(fon_*__*.{ext})) config_file plugin_dir / config.json即一个插件 一个以插件名命名的子目录内部可有on_事件__编号_名称.{sh,py,js}形式的钩子脚本和一个可选的config.json以_开头的目录会被跳过。统一目录get_plugin_catalog 与 iter_plugin_dirs所有发现逻辑最终都汇聚到一个被lru_cache(maxsize1)缓存的目录对象上lru_cache(maxsize1) def get_plugin_catalog() - PluginCatalog: return PluginCatalog.discover(extra_plugin_dirs[USER_PLUGINS_DIR], runtimearchivebox) lru_cache(maxsize1) def get_plugin_config_resolver() - PluginConfigResolver: return PluginConfigResolver(get_plugin_catalog())PluginCatalog.discover()在runtimearchivebox模式下把USER_PLUGINS_DIR作为额外插件目录合并进内置目录形成唯一的插件全集。整个进程生命周期内目录只构建一次之后所有查询共享同一份缓存。iter_plugin_dirs()是该目录的扁平化投影返回所有已发现插件的绝对路径列表def iter_plugin_dirs() - list[Path]: Return the exact plugin directories exposed by the shared catalog. return [plugin.path for plugin in get_plugin_catalog().values()]它在 archivebox/plugins/views.py 中被用于给定配置键反查它属于哪个插件的 config.jsonget_config_definition_link遍历插件目录按目录名匹配插件再根据路径落在内置还是用户根目录下决定跳转到插件仓库源码页还是本地管理页。插件枚举get_plugins 与数字前缀规范化get_plugins()返回当前环境中所有可用插件的目录名列表排序后的字符串列表文档注释明确了入选条件Returns plugin directory names for any plugin that exposes hooks, config.json, or a standardized templates/icon.html asset. This includes non-extractor plugins such as binary providers and shared base plugins.也就是说get_plugins()不只返回抽取器类插件还包括二进制提供者binary providers和共享基础插件如base。测试 archivebox/tests/test_hooks.py 的test_get_plugins_includes_config_only_plugin_dirs验证了这一点get_plugins()必须包含base而base目录只有config.json、没有任何on_*__*.*钩子文件。ArchiveBox 的传统插件目录名带有数字排序前缀例如10_title、26_readability、50_parse_html_urls。get_plugin_name()负责剥离前缀返回基础插件名def get_plugin_name(plugin: str) - str: parts plugin.split(_, 1) if len(parts) 2 and parts[0].isdigit(): return parts[1] return plugin其行为在测试中以get_extractor_name的内联实现被验证10_title - title、26_readability - readability、50_parse_html_urls - parse_html_urls无前缀时原样返回。按配置筛选get_enabled_plugins 与特殊配置键插件发现不只是有哪些插件更重要的是哪些插件在当前配置下启用。get_enabled_plugins()负责这一步过滤def get_enabled_plugins(config: ConfigLookup | None None, **config_kwargs: Any) - list[str]: if config is None: from archivebox.config.common import get_config config get_config(**config_kwargs) return get_plugin_config_resolver().enabled_plugin_names_from_flat(dict(config.items()))它接收一个配置查找对象把整个扁平化配置dict(config.items())交给PluginConfigResolver.enabled_plugin_names_from_flat()由 abx-dl 依据USE_/SAVE_系列开关、插件间的required_plugins依赖关系返回最终启用插件集合。调用方既可以直接传config对象也可以通过**config_kwargs覆盖某些配置值——这正是测试test_discover_hooks_skips_plugins_with_disabled_required_dependencies的做法传入{CHROME_ENABLED: False, WGET_ENABLED: True}后chrome与依赖它的accessibility都被剔除只保留wget。其中用到的ConfigLookup是一个typing.Protocol只要求实现两个方法class ConfigLookup(Protocol): def get(self, key: str, default: Any None) - Any: ... def items(self) - Iterable[tuple[str, Any]]: ...任何同时提供get()与items()的对象Django settings 包装器、字典、配置集合等都能作为插件发现时的配置来源体现了鸭子类型的解耦设计。get_plugin_special_config()则抽取每个插件的三组特殊配置键其签名与返回类型为def get_plugin_special_config(plugin_name: str, config: ConfigLookup, _visited: set[str] | None None) - PluginSpecialConfig: return get_plugin_config_resolver().runtime_settings(plugin_name, dict(config.items()))对应的返回类型PluginSpecialConfig是一个TypedDict包含三个字段_visited参数暗示旧实现曾用于处理配置继承环现实现已交由解析器处理字段类型含义enabledbool插件启用开关默认Truetimeoutint插件专属超时回退到全局TIMEOUT默认 300 秒binarystr插件主二进制的路径/名称默认取插件名本身按模块文档中的约定ArchiveBox 为每个插件识别三组形如{PLUGIN}_前缀的特殊配置键{PLUGIN}_ENABLED启用/禁用开关如WGET_ENABLED、HASHES_ENABLED{PLUGIN}_TIMEOUT插件专属超时时间{PLUGIN}_BINARY插件主二进制路径如WGET_BINARY默认值为wget。测试test_binary_env_var_empty_default印证了该约定内置 wget 插件的config.json中required_binaries[0][name]是模板字符串{WGET_BINARY}而properties[WGET_BINARY][default] wget。运行时这些XYZ_BINARY会被解析为宿主机的绝对路径或命令名再通过 abxpkg 投影进钩子子进程环境见 archivebox/tests/test_hooks.py 的TestRequiredBinaryConfigHandling。配置模式发现discover_plugin_configs 与 config.json每个插件可以用一个 JSONSchema 风格的config.json声明自己的配置项。discover_plugin_configs()一次性收集所有插件的 schemalru_cache(maxsize1) def discover_plugin_configs() - dict[str, dict[str, Any]]: Discover all plugin config.json schemas. Each plugin can define a config.json file with JSONSchema defining its configuration options. ... return get_plugin_config_resolver().schemas文档注释特别强调了缓存原因这些 schema 属于插件包的元数据而非用户的实时配置运行时的实际值仍然在每个调用点从 env/数据库配置中读取因此可以安全地整体缓存。该函数的消费方横跨多个模块archivebox/plugins/forms.py 的PluginConfigFormMixin依据 schema 动态生成 Django 表单_build_plugin_config_field会根据properties里每个键的 JSONSchema 类型选择控件boolean→ 开关、enum→ 下拉、integer/number→ 数字输入、array/object→ JSON 编辑器、带x-sensitive标记 → 密码框并通过_coerce_plugin_config_value做类型强制与范围校验clean_plugin_config_overrides则把用户改动整理成配置覆盖项若同一键在不同插件 schema 中被设置为不同值还会报错提示Set it once in Custom config overrides。archivebox/plugins/views.py 的find_plugin_for_config_key用它反查某个配置键由哪个插件定义从而把配置文件定义位置精确链接到对应插件的config.json管理后台的插件详情页也会用render_highlighted_json_block高亮展示整个config.json。plugin_config_keys()汇总所有插件声明的配置键集合供表单层判断哪些字段属于插件配置。搜索后端发现get_search_backendsArchiveBox 的搜索能力同样由插件提供。get_search_backends()只返回同时声明了独立搜索命令的插件def get_search_backends(): catalog get_plugin_catalog() return { plugin.name.removeprefix(search_backend_): plugin for plugin in catalog.values() if catalog.command(plugin.name, search) is not None and catalog.command(plugin.name, flush) is not None }判定条件是插件目录必须同时暴露search与flush两个命令分别对应搜索与清空/重建索引返回的字典键会去掉search_backend_前缀。这意味着一个插件只要声明了完整可用的独立搜索命令对就会自动成为 ArchiveBox 的可用搜索后端无需在核心代码中硬编码。这与 archivebox/search/config.py、archivebox/search/backends.py 中选择后端的逻辑相衔接可对照archivebox/tests/test_search.py验证后端枚举行为。模板与图标渲染DEFAULT_TEMPLATES、get_plugin_template、get_plugin_icon发现机制的最后一块拼图是面向展示层的模板能力。插件可以用templates/{icon,card,full}.html提供自定义渲染模板若未提供则回退到模块内置的DEFAULT_TEMPLATESDEFAULT_TEMPLATES { icon: span title{{ plugin }} styledisplay:inline-flex; width:20px; height:20px; ... {{ icon }} /span , card: iframe src{{ output_path }} classcard-img-top stylewidth: 100%; height: 100%; border: none; sandboxallow-same-origin allow-scripts allow-forms loadinglazy fetchprioritylow /iframe , full: iframe src{{ output_path }} classfull-page-iframe stylewidth: 100%; height: 100vh; border: none; sandboxallow-same-origin allow-scripts allow-forms /iframe , }三种模板的语义icon小尺寸图标20×20 内联元素用于插件卡片、选择框card缩略图卡片以沙箱化iframe加载插件输出页面懒加载、低优先级抓取full全页视图以铺满整个视口的iframe展示完整输出。get_plugin_template()是唯一的模板读取入口def get_plugin_template(plugin: str, template_name: str, fallback: bool True) - str | None: base_name get_plugin_name(plugin) if base_name in (yt-dlp, youtube-dl): base_name ytdlp catalog get_plugin_catalog() if base_name in catalog: template_path catalog.template_path(base_name, template_name) if template_path is not None: return template_path.read_text() if fallback: return DEFAULT_TEMPLATES.get(template_name, ) return None值得注意的细节先通过get_plugin_name剥掉数字前缀yt-dlp/youtube-dl两个名字会被归一化到ytdlp因为-/_会破坏目录名匹配template_name必须是icon/card/full三者之一fallbackFalse时插件若没有自定义模板则返回None。函数带lru_cache(maxsizeNone)缓存模板文件内容只读一次。get_plugin_icon()则是图标模板的便捷封装同样带无限缓存lru_cache(maxsizeNone) def get_plugin_icon(plugin: str) - str: icon_template get_plugin_template(plugin, icon, fallbackFalse) if icon_template: return mark_safe(icon_template.strip()) return mark_safe()它优先读取插件自定义的icon模板找不到时回退为 表情符号返回值通过 Django 的mark_safe标记为安全的 HTML 片段可直接嵌入模板。在 archivebox/plugins/forms.py 的get_plugin_choice_label中该图标被渲染进插件选择框的选项标签实现带图标的插件下拉列表管理后台插件页面的配置属性卡片render_config_properties_html也依赖同一套发现结果生成依赖、二进制、别名、回退值等链接。与钩子发现链路的衔接discovery.py的产物最终会流入 archivebox/plugins/hooks.py 的discover_hooks()——它把事件名 启用插件集合解析为按执行顺序排列的具体钩子文件路径def discover_hooks(event_name, filter_disabledTrue, configNone, **config_kwargs) - list[Path]: normalized normalize_hook_event_name(event_name) if not normalized or normalized BinaryRequest: return [] names None if filter_disabled: if config is None: from archivebox.config.common import get_config config get_config(**config_kwargs) names get_enabled_plugins(configconfig) return [hook.path for _plugin, hook in get_plugin_catalog().hooks(normalized, namesnames)]这里get_enabled_plugins()的返回值作为names传入catalog.hooks()直接决定某个事件如Snapshot、CrawlSetup上哪些钩子会被选中、哪些被禁用插件过滤掉。钩子文件名的排序规则数字前缀决定执行顺序在测试test_discover_hooks_sorted_by_name中被验证discover_hooks(Snapshot)返回的钩子名必须与排序后一致。整个链路可概括为BUILTIN_PLUGINS_DIR USER_PLUGINS_DIR │ PluginCatalog.discover(runtimearchivebox) ▼ get_plugin_catalog() ──► iter_plugin_dirs / get_plugins / get_search_backends / 模板查询 │ ▼ get_enabled_plugins(config) ──► discover_hooks(event, names) ──► 按序执行钩子子进程 │ ▼ discover_plugin_configs() ──► Django 表单生成 / 管理后台 schema 展示 / 配置键反查如何查看与调试已安装插件结合本文介绍的发现机制在真实部署中你可以用以下方式观察插件发现结果仓库为只读以下均为运行期查看操作管理后台以超级用户登录 Django admin访问Installed pluginsplugins_list_view见 archivebox/plugins/views.py可看到每个插件名称、来源builtin/user、绝对路径、钩子列表与 config.json 属性数量点击单个插件进入详情页可查看Summary、Hooks、Plugin Metadata标题、描述、依赖插件、必需二进制、输出 MIME 类型、完整config.json与逐条Config Properties含计算值链接、覆盖编辑入口、回退值、别名。命令行archivebox config子命令archivebox/cli/archivebox_config.py会列出当前生效的全部配置配合archivebox shell可在 Python REPL 中直接调用from archivebox.plugins.discovery import get_plugins, get_enabled_plugins, get_plugin_special_config等函数逐项核验发现结果。单元测试插件发现与钩子执行的行为均被 archivebox/tests/test_hooks.py 覆盖可运行python -m pytest archivebox/tests/test_hooks.py -v验证当前环境的发现链路是否正常该文件头部注释给出了建议的运行方式。自定义插件把符合规范的插件目录含config.json与on_*__*钩子放入data/custom_plugins/即USER_PLUGINS_DIR重启后get_plugins()会自动将其并入目录——发现逻辑无需任何注册步骤这是 ArchiveBox 插件体系零注册、纯目录约定设计的核心体现。小结archivebox.plugins.discovery是 ArchiveBox 插件体系的神经中枢它以PluginCatalog.discover(extra_plugin_dirs[USER_PLUGINS_DIR], runtimearchivebox)为唯一事实来源向上提供插件枚举get_plugins、名称规范化get_plugin_name、配置驱动过滤get_enabled_plugins、schema 发现discover_plugin_configs、特殊配置抽取get_plugin_special_config、搜索后端探测get_search_backends与展示模板渲染get_plugin_template/get_plugin_icon等一套完整 API同时被管理后台、Django 表单、钩子调度器与搜索模块复用。理解这一模块就等于掌握了 ArchiveBox 插件从目录里的一份 config.json到表单中的开关与图标、执行队列里的钩子、索引里的搜索后端的完整生命周期。赞分享后端数据工程【免费下载链接】ArchiveBox Open source self-hosted web archiving. Takes URLs/browser history/bookmarks/Pocket/Pinboard/etc., saves HTML, JS, PDFs, media, and more...项目地址https://gitcode.com/gh_mirrors/ar/ArchiveBox点击查看免费下载相关推荐ArchiveBox 插件系统剖析PluginsConfig Django 应用配置与插件发现机制ArchiveBox 插件系统剖析PluginsConfig Django 应用配置与插件发现机制 导读 ArchiveBox 作为一款开源自托管的网页归档工后端数据工程ArchiveBox 插件 Hook 系统源码级解析事件驱动、Hook 发现与 JSONL 输出协议ArchiveBox 插件 Hook 系统源码级解析事件驱动、Hook 发现与 JSONL 输出协议 本指南以 archivebox/plugins/hook后端数据工程ArchiveBox 版本识别机制深度解析archivebox.config.version 模块源码与实践ArchiveBox 版本识别机制深度解析archivebox.config.version 模块源码与实践 ArchiveBox 在启动、日志输出、HTTP后端数据工程上一篇如何三步在 Windows 上装好 IOPaint15 分钟跑通 AI 修图下一篇终极PS3管理神器webMAN-MOD如何让你的游戏主机变身全能控制中心创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考