跳转至

限频

限速策略里的每个数字都服务于同一个目标:归档流量必须看起来像普通浏览。 单线程导出只有 1–2 次请求——约等于一次页面浏览——batch 运行再把这些请求 摊到随机间隔上,且无并发。这是明确要求的反风控纪律 (pplx_export/core/throttle.py:1-2),不是可调的性能参数。

具体数字

位置 节奏 代码
batch:线程之间 10–20 s 随机均匀间隔(--delay-min / --delay-max pplx_export/cli.py:126-129pplx_export/core/throttle.py:33-36
sync-deleted --online:候选之间 10–20 s 随机均匀间隔 pplx_export/cli.py:164-167pplx_export/commands/sync_deleted_cmd.py:368-369
search-mode-backfill:联网兜底 10–20 s 随机均匀间隔 pplx_export/cli.py:144-147
线程内翻页 / 空间列表翻页 每页 ≥3 s pplx_export/sites/perplexity/rest.py:39,56pplx_export/sites/perplexity/adapter.py:285-309
schematized 块补抓(computer / deep-research / council / study) 第二次抓取前等 ≥4 s pplx_export/sites/perplexity/adapter.py:27-32,88
spaces --fetch-meta 每空间 3 s pplx_export/commands/spaces_cmd.py:328
usage-backfill 每线程 3 s pplx_export/commands/usage_backfill_cmd.py:77
assets-backfill 在线阶段 每线程 3 s pplx_export/commands/assets_backfill_cmd.py:215,456
线程内资产下载 0.5 s pplx_export/sites/perplexity/assets.py:28,103
assets-backfill CDN 阶段 6 路并行下载,无间隔 pplx_export/commands/assets_backfill_cmd.py:460-475
API 并发 无——任何时候都没有

为什么是这个数

  • 单次导出 = 1–2 次请求 ≈ 一次页面浏览。 search 线程只需一次 GET /rest/thread/<uuid>;computer / deep-research / council / study 恰好再加一次 schematized 块抓取 (pplx_export/sites/perplexity/adapter.py:87-89)。这与浏览器打开一次 页面的开销相当——归档不会在日常使用之上增加有意义的负载。
  • 10–20 s 随机间隔、无并发。 贴近人的阅读节奏,随机化避免节拍器式的 规律请求;串行让请求速率低于普通浏览本身。
  • 翻页 ≥3 s。 长线程内的翻页模拟滚动与阅读时间。
  • 块补抓前 ≥4 s。 否则 schematized 重抓会与 plain 抓取背靠背打到 API; 这段停顿模拟重页面加载完整负载前的延迟。
  • 资产下载 0.5 s。 静态小文件,开销远低于 API 调用——但仍然有节奏。
  • CDN 阶段是唯一的放宽。 签名 URL 下载打到的是内容分发网络而非 Perplexity API,因此仅在此处允许 6 路并行。

错误处理与退避

所有分类都在 CookieTransport._requestpplx_export/core/http/cookie_transport.py:63-126)完成;每个请求最多 max_retries=3 次尝试(cookie_transport.py:48)。

flowchart TD
    R{响应} -->|"2xx"| OK["退避计数清零"]
    R -->|"429"| BO["退避重试(≤3 次)"]
    R -->|"5xx / 网络错误"| BO
    R -->|"401 / 403"| AF["立即抛出 →<br/>连续 3 次即中止"]
    R -->|"ENTRY_EXPIRED / ENTRY_DELETED"| TERM["终态标记<br/>永不重试"]
响应 分类 处理
2xx 成功 退避计数清零(cookie_transport.py:77)——计数不跨请求累积
429 限流 退避重试(cookie_transport.py:86-92
500 / 502 / 503 / 504 服务器瞬态错误(504 常为 Cloudflare 抖动) 至少退避重试一次再放弃(cookie_transport.py:99-107
网络错误 瞬态 退避重试(cookie_transport.py:117-125
401 / 403 鉴权失败 立即抛 AuthTransportError——不退避(cookie_transport.py:82-85
400 + ENTRY_EXPIRED 平台清除 EntryExpiredError——终态,永不重试(cookie_transport.py:96-98
400 + ENTRY_DELETED 用户/远端删除 EntryDeletedError——终态,永不重试(cookie_transport.py:93-95
404 / 其他状态码 普通错误 传输层不重试;绝不映射为终态(cookie_transport.py:108-116

退避公式pplx_export/core/throttle.py:38-50): delay_max × 3^NN 为连续失败次数(指数钳制到 8),±20% 抖动防同步, 封顶 300 s。最后一次失败不再白睡;首次成功即 throttle.reset() 清零 (throttle.py:52)。

每条规则的理由:

  • 429 退避 —— 服务器明确要求减速,指数式地照办。
  • 5xx 重试 —— 一次网关抖动不该让一个线程失败。
  • 401/403 不退避 —— cookie 失效时等待无法自愈。
  • ENTRY_EXPIRED 不重试 —— 平台清除(约 3 个月窗口)是永久的,重试只会 白费请求与退避预算。
  • 404 永不进终态 —— pplx-ask 新建的线程可能因传播延迟瞬态 404;打上 终态会把只是暂时不可见的活线程误葬。

鉴权 fail-fast

batch 层对连续鉴权失败计数(_AUTH_FAIL_FAST = 3pplx_export/commands/batch_cmd.py:43)。任何成功到达服务器的响应——包括 ENTRY_DELETED / ENTRY_EXPIRED——都证明 cookie 有效并将计数清零 (batch_cmd.py:170-182)。连续 3 次 401/403:保存状态文件后中止运行 (batch_cmd.py:190-194)——cookie 失效还继续空转,只会让数百个线程各失败 一遍,浪费数小时。sync-deleted 遵循同一纪律 (pplx_export/commands/sync_deleted_cmd.py:111,333-337)。处理办法:更新 cookie 后重跑,已导出的部分全部跳过。

batch 与传输层共享同一个 Throttle 实例(pplx_export/cli.py:280-282batch_cmd.py:101-105),退避计数不在层间分裂——且该共享实例在账户自动 切换后依然保留。

定时同步

pplx-export schedule 计算当前增量计划(新增/更新数量),并把 cron 片段 写入 <out>/index/cron_snippet.txtpplx_export/commands/misc_cmd.py:86-96pplx_export/hooks/scheduler.py:48-77):

pplx-export schedule --account alice
17 3 * * * cd '<out-parent>' && '/abs/path/to/pplx-export' batch --account 'alice' --out '<out>'
  • 周期运行只跑增量(早停)——不做全量重抓(scheduler.py:4-9)。
  • 片段使用加引号的绝对路径,因为 cron 的工作目录与 PATH 不可预期 (scheduler.py:63-75)。
  • crontab -e 安装后按需调整时间;多账户错开时段。
  • 可选兜底:每周或每月手动跑一次 pplx-export batch --account alice --full(见 incremental-sync.md)。

另见