跳过内容

JSON 格式外链数据:$100 免费额度,无需绑卡,支持 Google 账号登录。

SEOMasters.dev
SEOMasters.dev注册

无间隙、无循环地浏览 URL 反向链接检查器

每次调用的行数、每次查询的页数以及 50,000 行的硬性上限。这里有一个尊重所有这三个条件并告知何时停止的循环。

横跨雾中河流的无尽石拱桥,无间隙
需要记住的三个数字

每次调用最多返回 1,000 行。每次调用仅计费一次,无论包含多少行。深度分页上限为 50,000 行,即 50 次调用,每次 1,000 行。

为什么排序在分页之前

对未排序列表进行分页是出现重复项的原因。始终将 sort 和 order 与 page 和 per 一起发送,以便第 3 页在每次请求中具有相同的含义。

refdomains 拉取每次调用花费 2 个积分,因此 50 次调用共需 100 个积分,即 1 美分。即使是最坏情况也很便宜;真正的成本是一个无法终止的循环。

选择排序列

按哪一列排序取决于问题。对于审计,按最大值排序通常是首选,因为这样上限会截断长尾,而不是截断你关心的部分。

如果按与问题无关的字段排序,截断可能会移除你需要的行。先按每个候选排序各拉取十行,看看哪些切片能保留下来。

将选择写入报告,以便下一个人知道截断点为何如此设置。

运行期间变动的记录

除非月度重建在运行期间发生,否则数据不会变动。这种情况罕见,但确实存在。跨越月初的夜间任务可能会从旧索引返回一页,从新索引返回下一页,因此请在日志中记录开始时间并进行比较。

在日志顶部记录开始时间,并在每一行记录页码。这样,输出中的不匹配可以指向时间问题,而不是解析错误。

如果任务已调度,避免在预计重建前的时间开始。你的日志会显示两次运行之间总数的变化作为重建的标志,因此请在每一行日志中保留总数。

跨越重建的运行默认并非错误。它只是不一致,因此在日志中标记这些运行,并在报告需要时重新运行受影响的切片。

一个能终止的分页循环

用于 Node 20 worker 的 TypeScript。它在遇到短页、达到上限或任何 ok 为 false 时停止。

ts
const BASE = "https://seomasters.dev/api.php";
const CAP = 50_000;
const PER = 1000;

export async function pullAll(domain: string, key: string) {
  const rows: unknown[] = [];
  for (let page = 1; rows.length < CAP; page++) {
    const u = new URL(BASE);
    u.search = new URLSearchParams({
      action: "refdomains", q: domain,
      page: String(page), per: String(PER),
      sort: "dr", order: "desc",
    }).toString();
    const res = await fetch(u, { headers: { Authorization: `Bearer ${key}` } });
    if (res.status === 401 || res.status === 402) throw new Error(`stop: HTTP ${res.status}`);
    const body = await res.json();
    if (!body.ok) throw new Error(JSON.stringify(body).slice(0, 300));
    const chunk = findRows(body);   // see note below
    rows.push(...chunk);
    if (chunk.length < PER) break;  // short page means we are done
  }
  return rows;
}

排查分页运行

你看到的现象可能的原因应对措施
第 51 页返回空或错误达到了 50,000 上限通过其他操作或不同的 sort 进行缩小,然后按 id 合并
同一域名出现在两页未排序的查询,或索引在运行期间重建发送 sort 和 order;按域名键去重
行数少于仪表板总数总数是针对整个图的,拉取已受限在 UI 中显示“前 50,000”
循环永不结束条件仅检查 rows.length在短页时中断,如上所述
账单高于预期对每个非 200 状态进行重试仅对网络故障进行重试,参见 errors
阳光温室中检查独特幼苗的园丁

无需猜测字段名即可读取行

上面的 findRows 助手故意保持开放。进行一次真实调用,查看 JSON,并根据你看到的内容编写访问器,而不是根据教程的承诺。

对于这个任务的更便宜版本,一次性对许多域名进行评分且无需分页,请阅读 bulk DR script。

冻结一个响应作为测试用例

然后通过测试冻结它。将一个响应保存为测试用例文件,并对行数和第一行的键进行断言,以便未来的更改在 CI 中失败,而不是在客户的图表中失败。

将测试用例保存在版本控制中,与测试文件放在一起,以便审查者无需运行任何代码就能看到代码期望的形状。

当 API 稍后添加字段时,测试用例差异会告诉你访问器是注意到了还是静默忽略了它。

根据查询来源命名测试用例,例如操作、域名和排序。名为 response1.json 的文件对下一个读者来说没有任何关于它证明了什么的提示。

统计存储的行数

将保存的行与循环报告的总数进行比较。差距意味着丢失了一页,重复意味着获取了两次页面并将其 upsert 为一行。

在每次测试拉取后运行此检查,而不仅仅是第一次。计数的漂移通常是排序更改或重建在运行期间落下的第一个症状。

将计数与页码保存在同一行日志中,以便一目了然地同时读取两者。

超过上限时拆分工作

一些问题超出了上限。对于超过上限的列表,拆分问题而不是更努力地分页。按一种排序拉取前 50,000 行,然后针对其他操作询问重负载域名,因为这些操作也花费 2 个积分,但回答不同的问题。更努力地分页永远无法突破截止点。

按一种排序拉取前 50,000 行

将上限视为 API 的事实。按一种排序拉取前 50,000 行,存储它们,并在任何报告中将结果标记为顶部切片。

将受限计数显示为完整总数的仪表板会误导阅读者。标签成本为零,带有它的 URL 反向链接检查器报告读起来更诚实。

在运行之前决定切片大小并将其写在报告标题中,这样数字看起来就不会比实际更大。标题行成本为零,稍后发现缺失行的读者会感谢你。

向重负载域名提出不同的问题

anchors 和 toppages 回答其他问题,例如哪个锚文本占主导地位或哪些页面吸引链接。对于超过上限的域名,这些答案可能比同一列表的下一页更重要。

将它们的结果保留在各自的表中,以域名为键,以便后续拉取同一域名时可以替换它们,而不影响顶部切片行。

每个额外操作都有其自己的积分成本,因此在运行之前定价,而不是在账单到达后。

每月拉取一次的最重域名短列表,通常比每晚拉取的长列表提供更有用的上下文。

按域名键合并

当两次拉取重叠时,按域名键合并它们并保留较新的时间戳。这样合并就不取决于哪次拉取先完成。

记录每行哪次拉取获胜。如果数字在两份报告之间发生变化,该日志会告诉你是指索引还是合并发生了变化。

将合并保留在一个带有测试的单个函数中,因为这是两次拉取相遇的唯一地方。

崩溃后恢复

崩溃会发生。部署重启 worker,笔记本电脑休眠,VPN 断开,循环在第 31 页(共 50 页)死亡。

每次调用后保存页码

每次调用后持久化最后完成的页码。从该数字加一开始重启,而不是从头开始,这样你只需为每页支付 2 个积分,而不是重新购买整个拉取。

在解析响应后保存,而不是之前。未能读取的页面应再次获取,保存的页码必须始终意味着你拥有的行。

每分钟保存一次而不是每次调用保存一次,会留下一个窗口,崩溃可能会重复积分支出。保持写入小巧,以便每次执行都很便宜。

记录每次保存的耗时以及每次调用的耗时。如果保存时间与调用时间相当,则指向存储层,这在循环增长之前值得修复。

按域名键 upsert

保持幂等写入。按域名键 upsert 每一行,这样获取两次的页面只留下一行,而不是两行。

在所有写入路径上使用相同的键,包括手动修复脚本。两个写入路径使用两个键是重复项在恢复后幸存的原因。

修复脚本通常在运行失败后匆忙编写。这时第二个键可能会混入,因此要像审查主循环一样仔细地审查它。

排序更改时拒绝恢复

将排序和顺序与页码一起记录。如果有人在崩溃和重启之间更改了 sort,保存的页码就不再代表相同的切片。拒绝恢复并重新开始。

存储一个包含排序、顺序、页码和时间的简短恢复记录。循环首先读取该记录,如果任何字段不再匹配即将发出的请求,它将拒绝继续。

拒绝恢复不是失败。这是循环在说保存的页码不再代表相同的行,重新开始是更便宜的错误。

在生产环境中运行循环

  1. 1

    从一个小域名开始

    针对拥有几百行的域名运行循环,以便短页停止触发,并观察退出路径是否正常工作。

  2. 2

    人工检查一页

    打开一个存储的响应并确认访问器仍然能读取行。不要信任你未查看过的解析。

  3. 3

    将限制保留在配置中

    将上限和页面大小放在一个配置文件中,以便更改在审查中显示,而不是隐藏在函数内部。

  4. 4

    记录循环结束的原因

    记录运行是因短页、上限还是错误结束。这三种结束方式对阅读日志的人来说意味着不同的事情。