)
ArchiveBox v1 Core API 深度解析快照、归档结果与标签的 REST 接口实现archivebox.api.v1_core【免费下载链接】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/api/v1_core.py模块及其 API 文档docs/apidocs/archivebox/archivebox.api.v1_core.md为核心系统讲解/api/v1/core/端点下 Snapshot、ArchiveResult、Tag 三类核心资源的 REST 接口设计、请求/响应 Schema、过滤与分页机制以及浏览器扩展上传归档产物时的分块上传与幂等合并实现。读完后你可以直接依据源码调用这些接口完成快照查询、RSS 订阅、归档产物上传、标签管理与导出等自动化操作。模块定位v1 API 的 Core 路由ArchiveBox 的 REST API 基于 django-ninja 构建统一挂载在/api/v1/前缀下。在 archivebox/api/v1_api.py 中可以看到各子路由的注册方式api.add_router(/core/, archivebox.api.v1_core.router)v1_core模块定义了自己的路由实例router Router(tags[Core Models])见 archivebox/api/v1_core.py负责三组资源Snapshot快照一个被归档的 URL核心模型定义在 archivebox/core/models.pyArchiveResult归档结果快照上单个归档插件的运行产物记录包含输出文件、输出文本与状态Tag标签快照的分类元数据附带完整的标签编辑/导出端点。API 文档首页/api/v1/docs由 archivebox/api/urls.py 中的重定向规则提供访问/api/或/api/v1都会跳转到 Swagger 文档页。所有 API 响应均设置Cache-Control: no-store并附带X-ArchiveBox-Auth-*调试响应头见 v1_api.py 中NinjaAPIWithIOCapture。分页机制CustomPagination文档中的CustomPagination类继承自ninja.pagination.PaginationBase所有列表接口/archiveresults、/snapshots、/tags都通过paginate(CustomPagination)装饰器接入v1_core.py#L66-L98输入参数limit默认 200实际会被min(pagination.limit, 500)截断到最大 500、offset默认 0、page默认 0offset缺省时用page * limit推算输出结构count、total_items、total_pages、page、limit、offset、num_items、items。实现细节值得注意总数统计时对DISTINCT查询会改用queryset.values(pk).distinct().count()避免重复计数当前页码由math.ceil(offset / (limit 1))反推保证与偏移量语义自洽。ArchiveResult 接口与 Schema响应 Schema 的两层结构文档列出的MinimalArchiveResultSchema与ArchiveResultSchema对应源码 v1_core.py#L104-L178MinimalArchiveResultSchema基础字段包括idUUID、created_at/modified_at、created_by_id/created_by_username、status、retry_at、plugin、hook_name、process_id、cmd_version、cmd、pwd、output_str、output_json、output_files、output_size、output_mimetypes、start_ts/end_tsTYPE固定为core.models.ArchiveResultArchiveResultSchema在其上追加快照上下文snapshot_id、snapshot_timestamp、snapshot_url、snapshot_tags通过resolve_snapshot_*静态方法从 ORM 对象解析。resolve_output_mimetypes的实现有实用细节它会遍历output_file_map()按各输出文件的mimetype累加size按体积降序拼出 MIME 摘要串只有当文件中没有任何带大小的 MIME 元数据时才回退到模型上的output_mimetypes字段v1_core.py#L139-L154。查询接口GET /api/v1/core/archiveresultsget_archiveresults接收ArchiveResultFilterSchema查询参数并分页返回。ArchiveResultFilterSchemav1_core.py#L181-L211基于 django-ninja 的FilterLookup声明支持参数查找逻辑id同时匹配id__startswith、snapshot__id__startswith、snapshot__timestamp__startswithsearch快照 URL/标题/标签名、插件名、output_str及各级 ID 前缀的宽泛模糊匹配snapshot_id/snapshot_url/snapshot_tag关联快照的 ID 前缀、URL、标签模糊匹配status/plugin/hook_name/process_id/cmd/pwd/cmd_version精确或模糊匹配对应字段created_at/created_at__gte/created_at__lt时间过滤当传入search或snapshot_tag时返回查询集会加.distinct()以避免多对多关联造成重复行。GET /api/v1/core/archiveresult/{archiveresult_id}get_archiveresult通过_uuid_ref_query辅助函数定位对象。该函数体现了宽松 ID 引用策略——先做前缀匹配再追加忽略大小写的包含匹配如果参数是合法 UUID则同时接受标准带连字符形式和去掉连字符的 32 位 hex 形式v1_core.py#L224-L239。这意味着你可以用 ID 前几位直接查。上传接口POST /archiveresults 与 PATCH /archiveresult/{id}这是 v1_core 中最复杂的两个端点服务于浏览器扩展Save Page Now与外部工具直接上传归档产物。关键常量对应文档 Data 部分ARCHIVERESULT_UPLOAD_HOOK_NAME Snapshot.BROWSER_EXTENSION_UPLOAD_HOOK_NAME # on_Snapshot__archivebox_browser_extension_upload ARCHIVERESULT_UPLOAD_PLUGIN_RE re.compile(r^[A-Za-z0-9_.-]{1,32}$)hook_name的默认值即浏览器扩展上传钩子模型定义见 core/models.py#L539插件名必须匹配 1–32 位[A-Za-z0-9_.-]的白名单正则否则返回 400。POST/archiveresultscreate_archiveresult以 multipart 表单接收表单项snapshot_id必填、plugin必填、output_str默认空、hook_name默认浏览器扩展钩子、status默认succeeded、output_json必须是合法 JSON 对象否则 400文件项files/file可配output_paths、mime_types指定每个文件的落盘相对路径与 MIME 类型输出文件写入快照目录下的output_dir/plugin_name/子目录文件路径经过_normalize_uploaded_archiveresult_output_path校验统一 POSIX 分隔符拒绝绝对路径以及含./..的路径段防止路径穿越。落盘采用按 (snapshot, plugin, hook_name) 定位已有记录存在则safe_update合并、不存在则get_or_create_by_hook创建的幂等策略合并逻辑用最多 3 次 CAS 重试防止并发写冲突失败返回 409v1_core.py#L531-L566。分块上传当表单中带chunk_output_path时进入分块模式必须恰好上传 1 个文件并额外提供chunk_index、chunk_count、chunk_offset、chunk_total_size。服务端按以下规则校验v1_core.py#L402-L473必须是单文件chunk_index chunk_countchunk_offset不得超过chunk_total_sizechunk_index 0 chunk_offset 0时先删除已有目标文件重新开始磁盘上已有文件大小必须恰好等于chunk_offset否则 409offset mismatch追加写入后若chunk_index 1 chunk_count最后一个分块落盘总大小必须等于chunk_total_size否则 409output_files元数据中记录upload.chunked/chunk_index/chunk_count/chunks_received/complete上传进度。PATCH/archiveresult/{archiveresult_id}patch_archiveresult用于向已有归档结果追加/替换文件。注意其语义细节若上传的output_files仍处于分块未完成状态ArchiveResult.output_files_upload_complete判定为 False只合并文件与大小统计、不触碰status完整上传后若未显式提供status会把queued状态推进为succeededv1_core.py#L590-L618。维护队列上传类接口不直接封存快照而是调用_queue_archiveresult_snapshot_maintenance——它只把非sealed行的retry_at抬升到当前时间并刷新modified_at/downloaded_at将 symlink、索引重写等副作用留给 runner 统一处理v1_core.py#L359-L378。这是理解上传接口为什么不会立刻产出完整归档页的关键。对应测试可参考 test_api_v1_core_archiveresults.py 与 test_api_v1_core_archiveresult_archiveresult_id.py。Snapshot 接口与 SchemaSnapshotSchema 字段全集文档中的SnapshotSchema字段v1_core.py#L629-L678id、created_by_id/created_by_username、created_at/modified_at、status、retry_at、bookmarked_at、downloaded_at、url、tags排序后的名称列表、title、timestamp、archive_path、archive_size即output_size、num_archiveresults、archiveresults内嵌MinimalArchiveResultSchema列表。性能上有个巧妙处理resolve_archiveresults只有在请求对象上设置了with_archiveresults为真时才加载归档结果集否则返回ArchiveResult.objects.none()避免列表页 N1 查询。列表与过滤GET /api/v1/core/snapshotsget_snapshots接收SnapshotFilterSchema与with_archiveresults布尔参数。SnapshotFilterSchemav1_core.py#L821-L848声明的过滤字段参数查找逻辑idid__istartswith/id__iendswith/timestamp__startswithcreated_by_id/created_by_username经crawl关联到创建者created_at[__gte/__lt]、modified_at[__gte/__lt]时间范围url/title/tag/timestamp精确、模糊icontains、标签名、时间戳前缀bookmarked_at[__gte/__lt]收藏时间范围searchsearch_mode委托搜索引擎见下status由filter_snapshots_by_status解析search与search_mode字段的filter_*钩子都返回空Q()真实检索在视图函数内完成先按search_mode取配置默认取SEARCH_BACKEND配置调用apply_snapshot_search做全文检索当搜索后端不可用异常路径时有外部后端则直接返回空集否则回退到meta模式做本地元数据匹配v1_core.py#L856-L878。状态过滤走 snapshot_status.py 的filter_snapshots_by_status非法状态值抛 400。单条读写与删除GET/snapshot/{snapshot_id}with_archiveresults默认为TrueID 定位同样宽松_get_snapshot_by_ref会先尝试 UUID 前缀/包含匹配 timestamp__startswith再退化为纯 UUID 查询v1_core.py#L351-L356。POST/snapshotscreate_snapshot请求体为SnapshotCreateSchemaurl必填、crawl_id可选、depth默认 0仅允许 0–4、title、tags、status。行为要点URL 长度校验validate_url_lengthdepth超范围返回 400指定crawl_id时复用现有 Crawl未指定则自动创建一个新的QUEUED状态 Crawl同 URL 同 Crawl 已有快照时不重复插入IntegrityError兜底仅用带modified_at条件CAS的safe_update同步title/status冲突时 409提供的tags经normalize_tag_list去空白后保存无标签且复用 Crawl 时继承 Crawl 的tags_str。PATCH/snapshot/{snapshot_id}patch_snapshot请求体SnapshotUpdateSchemaaction、status、retry_at、tags。action支持pause/resume或unpause/cancel三种生命周期操作其余字段更新包裹在crawl_lifecycle_lock(crawl_id)锁内进行statussealed会清空retry_at并调用snapshot.cancel()用于取消排队中的归档工作v1_core.py#L982-L1034。DELETE/snapshot/{snapshot_id}先cancel()快照再在 Crawl 生命周期锁内执行run_pending_crawlsdaemonFalse确保 runner 不再触碰该 Crawl最后调用snapshot.delete()删除输出文件与数据库行返回SnapshotDeleteResponseSchemasuccess、snapshot_id、crawl_id、deleted_count。RSS 订阅端点GET /api/v1/core/snapshots.rss把最近归档的快照输出为 RSS 2.0.1 源参数为crawl_id支持 UUID 子串最多匹配前 100 个 Crawl、created_by用户名或用户 ID、limit1–500默认 50、before支持 ISO 日期时间、YYYY-MM-DD、YYYYMMDD三种格式默认当前时间见_parse_rss_before。实现上只选取快照元数据列.only(...)并按bookmarked_at倒序生成的每个 item 的link指向归档页/web/archive_pathdescription中同时包含原 URL 与归档 URLcategories为该快照的标签pubdate取bookmarked_at回退created_atv1_core.py#L780-L818。响应Content-Type为application/rssxml; charsetutf-8且 feed URL 会自动剥离api_key/token/password等敏感查询参数避免凭据泄露到订阅源地址中。相关行为由 test_api_v1_core_snapshots_rss.py 覆盖。Tag 接口查询端点GET/tags分页返回TagSchema列表id、name、created_by_*、num_snapshots、snapshots列表页强制with_snapshotsFalse不加载快照明细。GET/tag/{tag_id}with_snapshots默认True按 ID 或名称引用get_tag_by_ref定位未找到返回 404。GET/any/{id}get_any万能对象端点依次尝试按快照、归档结果、标签、Crawl 解析 ID命中后 302 重定向到该对象的规范端点如/api/v1/core/snapshot/pk全部未命中才 404。适合自动化脚本只拿到一个裸 ID 时做探测v1_core.py#L1112-L1142。标签编辑与搜索 API这一组端点支撑后台的标签管理界面对应文档中search_tags、tags_autocomplete等函数GET/tags/search/参数q、sort默认created_desc、created_by、year、has_snapshots默认all参数经normalize_tag_sort等归一化后返回TagSearchResponseSchema。其中TagSearchCardSchema内嵌每个标签的filter_url/edit_url/export_urls_url/export_jsonl_url/rename_url/delete_url操作链接与TagSearchSnapshotSchema预览快照id/title/url/favicon_url/admin_url/archive_url/downloaded_at。GET/tags/autocomplete/?q...唯一声明authNone的端点访问控制改在函数内判断——已认证用户直接放行匿名请求则要求PUBLIC_INDEX配置开启_public_tag_listing_enabled否则 401。匿名且无 API token 时只返回与公开快照关联的标签无查询词返回最多 50 条有查询词最多 20 条v1_core.py#L1299-L1315。POST/tags/create/请求体TagCreateSchemanameget-or-create 语义响应TagCreateResponseSchema中的created字段区分新建与复用。POST/tag/{tag_id}/rename与DELETE/tag/{tag_id}分别返回TagUpdateResponseSchema、TagDeleteResponseSchemasuccess/tag_id/tag_name或deleted_count。GET/tag/{tag_id}/urls.txt与GET/tag/{tag_id}/snapshots.jsonl整标签导出前者为纯文本 URL 列表text/plain后者为 NDJSON 快照行application/x-ndjson均设置Content-Disposition: attachment触发下载文件名分别为tag-slug-urls.txt与tag-slug-snapshots.jsonl。POST/tags/add-to-snapshot/与POST/tags/remove-from-snapshot/请求体TagSnapshotRequestSchemasnapshot_idtag_name或tag_id二选一。快照引用解析由_get_snapshot_for_tag_edit完成兼容完整 UUID、14 位以上时间戳前缀及 ID 前缀v1_core.py#L1223-L1254添加时标签不存在会自动创建。该组端点的端到端测试见 test_api_v1_core_tags.py、test_api_v1_core_tag_tag_id.py、test_api_v1_core_tags_autocomplete.py、test_api_v1_core_tags_add_to_snapshot.py 等。从源码结构看的设计要点ID 宽松引用是贯穿性约定_uuid_ref_query、_get_snapshot_by_ref、_get_snapshot_for_tag_edit三处都支持 UUID 前缀/无连字符/时间戳前缀匹配脚本无需保存完整 ID 即可引用对象。并发写保护统一为 CAS 有限重试safe_update(..., extra_filter{modified_at: ...})配合 3 次重试冲突升级 409而不是锁表删除快照则显式持有crawl_lifecycle_lockarchivebox/crawls/locks.py。职责边界清晰上传 API 只负责落盘 记 ArchiveResult 抬升retry_atsealing/symlink/索引等收尾全部交给 runner这与文档 docstring 中 Upload API handlers are allowed to persist files... side effects belong to the runner 的表述一致。安全细节插件名白名单正则、输出路径防穿越校验、RSS feed URL 剥离敏感参数、匿名标签自动补全仅限公开快照均体现了对匿名与半匿名访问面的一致收紧。快速参考端点一览方法路径说明GET/api/v1/core/archiveresults过滤 分页列出归档结果GET/api/v1/core/archiveresult/{id}按 ID 前缀查单条归档结果POST/api/v1/core/archiveresults上传输出文件创建/合并归档结果支持分块PATCH/api/v1/core/archiveresult/{id}向已有归档结果追加/替换文件GET/api/v1/core/snapshots过滤 搜索 分页列出快照GET/api/v1/core/snapshots.rss快照 RSS 订阅源GET/api/v1/core/snapshot/{id}查单条快照默认含归档结果POST/api/v1/core/snapshots新建快照/入队归档PATCH/api/v1/core/snapshot/{id}pause/resume/cancel、改状态与标签DELETE/api/v1/core/snapshot/{id}取消并删除快照及其产物GET/api/v1/core/tags//tag/{id}标签列表 / 单标签GET/api/v1/core/any/{id}万能 ID 探测重定向到规范端点GET/api/v1/core/tags/search/标签卡片搜索GET/api/v1/core/tags/autocomplete/标签自动补全支持匿名PUBLIC_INDEXPOST/api/v1/core/tags/create/新建或复用标签POST/api/v1/core/tag/{id}/rename重命名标签DELETE/api/v1/core/tag/{id}删除标签GET/api/v1/core/tag/{id}/urls.txt导出标签下全部 URLGET/api/v1/core/tag/{id}/snapshots.jsonl导出标签下快照 NDJSONPOST/api/v1/core/tags/add-to-snapshot/给快照加标签POST/api/v1/core/tags/remove-from-snapshot/从快照移除标签以上所有端点默认要求 API 认证API_AUTH_METHODS含 API Token 与浏览器会话两种方式见 archivebox/api/auth.py认证方式与 Token 管理可参考 test_api_v1_auth_check_api_token.py 等测试。整体而言v1_core把读查询/过滤/导出、写上传/入队、管标签生命周期三类操作收拢在同一路由命名空间下并以宽松 ID、幂等合并与 CAS 重试作为三大底层约定是编写 ArchiveBox 自动化集成脚本时最值得精读的一个 API 模块。【免费下载链接】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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考