ArchiveBox v1 Personas API 详解:Persona 列表与浏览器身份同步接口(v1_personas 模块)
2026/9/19 23:12:23 网站建设 项目流程

ArchiveBox v1 Personas API 详解:Persona 列表与浏览器身份同步接口(v1_personas 模块)

【免费下载链接】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 官方 API 文档 archivebox.api.v1_personas 及其对应源码 v1_personas.py 编写。读完你可以完整掌握 Personas 子 API 的两个端点(GET /api/v1/personas/personasPOST /api/v1/personas/sync)的请求/响应结构、四个 Schema 的全部字段与默认值、四个核心函数的底层行为(配置键映射、Persona 查找/创建规则、分页封装),并能直接复制可运行的调用示例。

1. 模块定位:v1 API 中的 Personas 路由

archivebox.api.v1_personas是 ArchiveBox REST API v1(ALPHA 阶段)中负责 Persona 管理的 Django Ninja 路由模块。Persona 是 ArchiveBox 中表示“浏览器身份/配置”的概念:每个 Persona 拥有独立的 Chrome 用户数据目录、cookies.txtauth.json,从而让归档请求以特定登录态、特定浏览器特征(User-Agent、分辨率、时区等)执行。该模块的文档定位与接口面如下(与 API 文档 的 Module Contents 完全一致):

  • ClassesPersonaBrowserSettingsSchemaPersonaSyncSchemaPersonaSchemaPersonaSyncResponseSchema
  • Functionsbrowser_settings_to_configfind_personaget_personassync_persona
  • Datarouterninja.Router实例,标签为Personas

路由挂载位置在 v1_api.py 的register_urls()中:

def register_urls(api: NinjaAPI) -> NinjaAPI: api.add_router("/auth/", "archivebox.api.v1_auth.router") api.add_router("/core/", "archivebox.api.v1_core.router") api.add_router("/crawls/", "archivebox.api.v1_crawls.router") api.add_router("/cli/", "archivebox.api.v1_cli.router") api.add_router("/machine/", "archivebox.api.v1_machine.router") api.add_router("/personas/", "archivebox.api.v1_personas.router") # 本模块挂载点 return api

因此本模块的两个端点完整路径为GET /api/v1/personas/personas(列表)与POST /api/v1/personas/sync(同步/创建),与测试用例 test_api_v1_personas_personas.py 中的实际请求路径一致。

整个 API 的认证由 auth.py 的API_AUTH_METHODS统一提供,从源码看支持三种 Token 方式:

认证方式实现类传递形式
请求头 TokenHeaderTokenAuthAPI 自定义请求头
Bearer TokenBearerTokenAuthAuthorization: Bearer <token>
查询参数 TokenQueryParamTokenAuth?api_key=<token>

并且这三种方式在实现中均经过_require_superuser校验,即从源码结构看,Token 访问要求对应账户具备超级用户权限。

2. 端点一:GET /api/v1/personas/personas—— Persona 列表

源码定义(v1_personas.py):

@router.get("/personas", response=list[PersonaSchema], url_name="get_personas") @paginate(CustomPagination) def get_personas(request: HttpRequest): """List personas available on this ArchiveBox server.""" return Persona.objects.all().order_by("name")

要点:

  • 返回按name升序排列的全部 Persona,序列化为PersonaSchema列表;
  • 通过@paginate(CustomPagination)包装,CustomPagination定义在 v1_core.py,分页参数默认值为limit=200offset=0page=0,其中limit最大被截断为 500(limit = min(pagination.limit, 500));
  • 响应体是统一的分页信封,字段固定为:counttotal_itemstotal_pagespagelimitoffsetnum_itemsitems

分页行为有测试佐证:test_api_v1_personas_personas.py 中先通过 sync 端点创建三个 Persona,再请求/api/v1/personas/personas?limit=1,断言响应是包含上述全部键的 dict,且count == total_itemslimit == 1num_items == 1items长度为 1。

3. 数据模型(Schemas)逐字段说明

以下四张字段表完整覆盖 API 文档 列出的全部类与属性,类型与默认值取自源码 v1_personas.py。

3.1 PersonaBrowserSettingsSchema(浏览器特征覆盖)

描述扩展/客户端希望为 Persona 设置的浏览器外观与行为特征,全部可省略:

字段类型默认值说明
user_agentstr""要使用的 User-Agent
viewport_sizestr""视口尺寸(如1920x1080
viewport_device_scale_factorfloat \| NoneNone设备像素比
languagestr""浏览器语言
timezonestr""时区
geolocationdict[str, Any] \| NoneNone地理定位信息

3.2 PersonaSyncSchema(同步请求体)

POST /api/v1/personas/sync的入参结构:

字段类型必填/默认说明
extension_persona_idstr必填浏览器扩展侧的 Persona 标识,作为 upsert 的匹配键之一
namestr必填Persona 名称(须通过名称校验,见 §5.3)
settingsPersonaBrowserSettingsSchemaField(default_factory=...),默认空设置浏览器特征覆盖,见 3.1
cookies_txtstr""Netscape 格式 cookies 文本,非空时写入 Persona 目录
auth_jsondict[str, Any]Field(default_factory=dict)完整认证状态(cookies/localStorage 等),非空时写入auth.json

3.3 PersonaSchema(Persona 响应结构)

字段类型说明
TYPEstr固定值"personas.models.Persona",用于类型标识
iduuid.UUIDPersona 主键(模型层使用 UUIDv7,见 models.py)
namestrPersona 名称,数据库层唯一约束
created_atdatetime创建时间
created_by_idstr创建者 ID,由静态方法resolve_created_by_id序列化为str(obj.created_by.pk)
created_by_usernamestr由静态方法resolve_created_by_username解析为obj.created_by.username
configdict[str, Any] \| NonePersona 配置,返回前经过脱敏

resolve_config是安全关键点(v1_personas.py):

@staticmethod def resolve_config(obj): # Redact credential values so REST responses don't leak the raw # token/secret/api-key the operator stored in Persona.config. from archivebox.config.common import redact_sensitive_config return redact_sensitive_config(obj.config)

即任何 GET 响应中,Persona.config里的 token/secret/api-key 类凭据值都会被redact_sensitive_config脱敏,防止 REST 响应泄露原始凭据。

3.4 PersonaSyncResponseSchema(同步响应结构)

字段类型说明
successbool是否成功
createdbool本次调用是否新建了 Persona(False 表示命中已有记录并更新)
personaPersonaSchema落库后的 Persona 完整结构(config 已脱敏)
cookies_file_writtenbool是否写入了cookies.txt
auth_file_writtenbool是否写入了auth.json

4. 核心函数逐一剖析

4.1browser_settings_to_config:设置项到配置键的映射

该函数(v1_personas.py)把扩展侧的浏览器设置翻译为 ArchiveBox 配置键,完整映射关系如下:

输入(settings 字段)生成的配置键
extension_persona_id(入参)BROWSER_EXTENSION_PERSONA_ID(恒写入)
—(服务端时间)BROWSER_EXTENSION_SYNCED_AT(恒写入,UTC ISO8601 +Z
user_agent(非空时)USER_AGENTCHROME_USER_AGENTWGET_USER_AGENTCURL_USER_AGENT四个键同值
viewport_size(非空时)RESOLUTIONCHROME_RESOLUTION两键同值
viewport_device_scale_factor(非 None 时)BROWSER_DEVICE_SCALE_FACTOR
language(非空时)BROWSER_LANGUAGE
timezone(非空时)BROWSER_TIMEZONE
geolocation(非 None 时)BROWSER_GEOLOCATION(原样保留 dict)

注意 User-Agent 被同时写入四个键,说明这些覆盖对 Chrome、wget、curl 等不同抓取器都生效;而RESOLUTION的键名也表明视口设置同时面向通用配置与 Chrome 专用配置。

4.2find_persona:upsert 的匹配规则

def find_persona(extension_persona_id: str, name: str) -> Persona | None: return ( Persona.objects.filter( Q(config__BROWSER_EXTENSION_PERSONA_ID=extension_persona_id) | Q(name=name), ) .order_by("created_at") .first() )

匹配规则为“或”关系:configJSON 中的BROWSER_EXTENSION_PERSONA_ID等于请求的扩展标识,name等于请求名称,二者命中其一即视为同一 Persona;多个命中时取created_at最早的一条。这保证了同一浏览器扩展反复同步不会无限创建新记录,而是幂等更新既有 Persona。

4.3get_personas:见 §2,列表 +CustomPagination分页。

4.4sync_persona:完整的同步流程

sync_persona(v1_personas.py)是模块中最复杂的函数,其 docstring 明确了职责边界:扩展发送浏览器设置与可移植认证产物,服务端把浏览器覆盖设置保存在Persona.config,并把cookies.txt/auth.json写入 Persona 目录供抓取器消费。执行流程拆解如下:

  1. 名称校验name = payload.name.strip()后调用 validate_persona_name,不合法则直接raise ValueError(error_message)。该校验函数与 CLI/Admin 侧共用,防路径穿越规则为:空名、含/\、含..、以.开头、含 NUL/换行/回车,均会被拒绝;
  2. 查找或创建:调用find_persona(payload.extension_persona_id, name);未命中则Persona(name=name)新建,且若request.user已认证则绑定persona.created_by = request.user
  3. 配置合并persona.config = {**(persona.config or {}), **browser_settings_to_config(...)},即保留既有 config 键、仅覆盖扩展同步写入的键,然后persona.save()persona.ensure_dirs()
  4. 写认证产物cookies_txt去除首尾空白后非空才写入persona.path / "cookies.txt"auth_json非空才以json.dumps(..., indent=2, sort_keys=True) + "\n"写入persona.path / "auth.json"
  5. 返回{success, created, persona, cookies_file_written, auth_file_written}

其中ensure_dirs()与 Persona 目录布局见 §6。

5. 调用示例(可复制)

5.1 创建/更新 Persona(sync)

最小请求体(与 test_api_v1_personas_sync.py 一致):

curl -X POST 'http://localhost:8000/api/v1/personas/sync' \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <API_TOKEN>' \ -d '{ "extension_persona_id": "extension-api-persona-basic", "name": "api-persona-basic", "settings": {}, "cookies_txt": "", "auth_json": {} }'

带完整浏览器设置与认证产物的请求体:

{ "extension_persona_id": "ext-0001", "name": "work-profile", "settings": { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "viewport_size": "1920x1080", "viewport_device_scale_factor": 1.0, "language": "en-US", "timezone": "America/New_York", "geolocation": {"latitude": 40.7128, "longitude": -74.006} }, "cookies_txt": "# Netscape HTTP Cookie File\n...", "auth_json": {"cookies": [], "localStorage": {}} }

成功时(测试断言为200)返回PersonaSyncResponseSchema结构,首次创建时createdtrue;同一extension_persona_idname再次调用时createdfalse,仅更新。

5.2 列出 Persona

curl 'http://localhost:8000/api/v1/personas/personas?limit=50&offset=0' \ -H 'Authorization: Bearer <API_TOKEN>'

返回{"count": N, "total_items": N, "total_pages": P, "page": 1, "limit": 50, "offset": 0, "num_items": K, "items": [PersonaSchema, ...]}itemsname排序,且config已脱敏。

5.3 错误行为

  • 名称非法sync_persona抛出ValueError(如"Persona name cannot contain path separators (/ or \\)")。由于它不属于ObjectDoesNotExist/EmptyResultSet/PermissionDenied,会落入 v1_api.py 的通用异常处理器,返回503,响应体含succeeded: falsemessage(异常类名 + 信息)与errors(完整 traceback 文本);
  • 认证失败/权限不足:由 API 层认证逻辑处理;
  • 所有 API 响应还带有Cache-Control: no-storeX-ArchiveBox-Stdout/X-ArchiveBox-Stderr/X-ArchiveBox-Auth-*调试头(见 NinjaAPIWithIOCapture),便于排查后端输出。

6. 落盘结构与 Persona 模型的关系

sync 端点写入的目录由 Persona 模型决定。每个 Persona 对应PERSONAS_DIR/<name>/一个目录,ensure_dirs()(models.py)保证其中存在chrome_profile/chrome_downloads/两个子目录;Persona.pathCHROME_USER_DATA_DIRCOOKIES_FILEAUTH_STORAGE_FILE等属性均派生自该目录:

PERSONAS_DIR/ └── <persona_name>/ ├── chrome_profile/ # Chrome 用户数据目录(模板) ├── chrome_downloads/ # 下载目录 ├── cookies.txt # sync 端点写入,供 wget/curl 使用 └── auth.json # sync 端点写入,完整认证状态

两个值得注意的实现细节:

  1. 默认权限注入Persona.save()(models.py)在configPERMISSIONS缺失或不合法时,会从全局配置注入normalize_permissions(get_config(include_machine=True).PERMISSIONS),因此 sync 端点创建的 Persona 不会缺省权限策略;
  2. 运行时隔离:Persona 目录是“模板”,实际爬取时prepare_runtime_for_crawl(models.py)会把模板 profile 复制到该 Crawl 输出目录下的.persona/<name>/,并清理 Chrome 易变状态(CacheSessionsSingletonLock等,见VOLATILE_PROFILE_DIR_NAMES),从而避免多个 Crawl 共享同一 Chrome 用户数据目录。sync 端点写入的cookies.txt/auth.json同样会被复制到运行时根目录并映射为COOKIES_FILE/AUTH_STORAGE_FILE配置键。

7. 小结

archivebox.api.v1_personas用两个端点 + 四个 Schema 构成了 Persona 的完整 REST 面:GET /api/v1/personas/personas提供带分页信封、config 脱敏的 Persona 列表;POST /api/v1/personas/sync提供以extension_persona_idname幂等匹配的 upsert 语义,把浏览器特征翻译成USER_AGENT/RESOLUTION/BROWSER_*等配置键并持久化,同时把cookies.txt/auth.json落盘到 Persona 目录供抓取器消费。围绕它的名称校验(importers.py)、认证(auth.py)、分页(v1_core.py)与 Persona 目录模型(models.py)共同保证了该接口在多用户、多 Crawl 场景下的安全性与隔离性。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询