跳转至

内页跟随策略

类型:wiki(知识沉淀) 描述:网站抓取深度 ≥ 2 时,从首页找哪些站内链接进入再抓的规则 最后更新:2026-05-26(v0.10.14) 相关源码src/utils/dynamic-scraper.tsfindSubLinks + FollowOpts

是什么

抓商家官网时,深度=2 表示「首页 + 站内一层链接」,深度=3 表示「再深一层」。 哪些链接算"站内"、应该跟随,由 findSubLinks 用一组可配规则决定。

深度 1: ─→ [首页 acme.com]
深度 2: ─→ [首页] ──┬─→ [contact 页] ✅ 关键词命中
                    ├─→ [about 页]   ✅ 关键词命中
                    ├─→ [blog 列表]   ❌ blackKw 命中
                    └─→ [外部 fb]     ❌ 跨域

为什么这么设计

v0.10.13 之前(写死)

// 只跟 hostname 严格相等 + contact/about/imprint/company/联系/关于 等

用户场景:公司用多根域名(acme.com + acmecorp.io + acme-blog.io),抓不到。

v0.10.13 起(可配 4 项)

allowSubdomain        // 同根域子域是否算同站
keywordsWhite[]       // 优先关键词
keywordsBlack[]       // 排除关键词
skipAssets            // 跳过 jpg/css/pdf

v0.10.14 加跨根域白名单

allowedDomains[]      // 即使根域不同,命中此列表也算同站

关键代码

FollowOpts 接口

export interface FollowOpts {
  allowSubdomain?: boolean;       // false=严格 hostname
  keywordsWhite?: string[];       // 默认 contact/about/imprint/...
  keywordsBlack?: string[];       // 默认空
  skipAssets?: boolean;           // 默认 true
  allowedDomains?: string[];      // v0.10.14 跨根域白名单
}

同域判定逻辑(v0.10.14)

const linkHost = u.hostname.toLowerCase();
const linkRoot = getRootDomain(linkHost);

let sameSite = false;
if (allowSubdomain) sameSite = linkRoot === baseRoot;
else                sameSite = linkHost === base.hostname.toLowerCase();

// 白名单:完全相等 OR 用户填的是根域、链接是其子域
const inAllowList = allowedDomains.length > 0 &&
  allowedDomains.some((d) => linkHost === d || linkHost.endsWith('.' + d) || linkRoot === d);

if (!sameSite && !inAllowList) continue;  // 跳过此链接

关键词打分

// 白名单未命中 → 不跟(避免抓博客/产品页等)
// 命中靠前的关键词 → 分数更高 → 优先跟
for (let i = 0; i < whiteKws.length; i++) {
  if (lower.includes(whiteKws[i])) {
    score = whiteKws.length - i;
    break;
  }
}
if (score === 0) continue;

排序后取前 N 个(N = (depth - 1) * 3,depth=2 时 3 个,depth=3 时 9 个递归)。

三档同域策略

设置 行为 适用场景
全关 linkHost === base.hostname 严格相等 默认。最严,避免误跟
allowSubdomain: true 同根域子域算同站(shop.acme.comacme.com 公司用 CDN 子域 / 多语言子域
allowedDomains: [...] 用户填的域名也算同站(不论根域) 公司用多个完全不同的根域名
两者都开 最宽松 大公司多品牌矩阵

getRootDomain 局限

function getRootDomain(hostname: string): string {
  const parts = hostname.split('.');
  if (parts.length <= 2) return hostname;
  return parts.slice(-2).join('.');  // 简单取最后两段
}

⚠️ 不处理 ccTLD 复合后缀: - acme.co.uk → 返回 co.uk(错;应是 acme.co.uk) - acme.com.cn → 返回 com.cn(错)

够用判断:本项目用户主要是 .com / .net / .io / .cn 单后缀,目前不修。如果遇 .co.uk 案例,再引入 PSL(Public Suffix List)库。

易踩坑

⚠️ 坑 1:keywordsWhite 留空 = 不跟任何链接

设计是「白名单匹配制」 — 没命中关键词 = 不跟。用户清空想"跟全部"会反而什么都跟不到。

要"跟全部" → 改代码(目前不开放)。

⚠️ 坑 2:allowedDomains 与 followSubdomain 不冲突

二者叠加生效:满足任一条件即算同站。直觉上没问题,但 UI 文案要写明 — 用户可能以为是「3 选 1」。

⚠️ 坑 3:keywordsBlack 优先于 white

代码里先检查 black(命中即跳过),再算 white 分数。即使一个 URL 同时含 "blog" 和 "about",会被跳。

⚠️ 坑 4:资源后缀检测用正则不智能

只看后缀,不看 Content-Type。.json 之类自定义后缀不会被过滤。

默认值

字段 默认 理由
followSubdomain false 保守,严格 hostname
followKeywordsWhite contact,about,team,impressum,imprint,company,联系,关于,kontakt,contato,nosotros 多语种联系页关键词
followKeywordsBlack blog,news,login,cart,checkout,product,/p/,category,tag,archive 电商/博客噪音页
followSkipAssets true 显然
followAllowedDomains "" 老用户无感

修改这块时要 / 不要做什么

  • ✅ 加新 FollowOpts 字段 → storage-data 加 + UI 加 + 本 wiki 同步
  • ✅ 改默认关键词列表 → 改 storage-data 的 DefaultSettings.followKeywords*
  • ❌ 不要把"白名单匹配制"改成"白名单加分制"(用户已经习惯了)
  • ❌ 不要在 findSubLinks 里调 PSL 库(增加复杂度,目前用例不需要)

版本里程碑

版本 事件
v0.9.x 写死同域 + 5 关键词
v0.10.13 全部可配(4 个字段)
v0.10.14 followAllowedDomains 跨根域白名单