Skip to content
搜索背后:从查询到结果的一趟旅程
搜索引擎并非在用户提交查询后才实时扫描互联网。当用户在搜索框输入“vue3 响应式原理”并按下回车时,系统查询的是预先构建好的索引库,而不是现场遍历页面。
整个过程分为三个阶段:抓取 → 索引 → 排序。Google 的官方文档将这三个阶段称为抓取、编入索引和呈现搜索结果[1]。
- 抓取阶段:爬虫沿着链接发现新页面,将 HTML、图片、视频等资源拉取到搜索引擎的服务器。
- 索引阶段:解析页面内容,提取词项并构建倒排索引——一种将“关键词→页面”查询变为常数或对数级别查找的数据结构。
- 排序阶段:用户输入查询后,系统在索引中检索匹配页面,依据数百个因素决定展示顺序。
从 SEO 视角来看,这三个阶段各有侧重:抓取阶段关注页面能否被发现、抓取预算是否被浪费在低价值页面上;索引阶段关注内容是否被正确解析、关键信息是否被提取;排序阶段关注页面的相关性、权威性和用户体验能否支撑高排名。
爬虫的抓取逻辑:发现、排队与限制
链接发现与 URL 队列
爬虫的启动入口通常是若干已知的高质量种子页面,例如新闻门户首页或 Wikipedia 分类页。程序下载页面后提取所有 <a href> 链接,将新 URL 加入待抓取队列,并重复这一过程。
这一过程并不是简单的 BFS 或 DFS。搜索引擎会将 URL 队列分层:重要页面的 URL 优先级高,新发现的页面优先级低。优先级的判断依据包括页面 PageRank 近似值、更新频率、域名权威度等。
在工程实现上,URL 队列通常是一个分布式消息队列。爬虫集群的各个节点从队列中取任务、下载页面、提取链接、将新链接写回队列。去重是必须的——同一 URL 不应被重复抓取,否则会陷入无限循环。
抓取预算与抓取策略
搜索引擎不会无限抓取一个网站。每个网站在一定时间内可被抓取的页面数量是有限的,这个限制常被称为抓取预算(crawl budget)。
决定抓取预算的两个核心因素是抓取速率限制和抓取需求。速率限制防止爬虫对服务器造成压力,也避免搜索引擎自身资源被浪费。抓取需求则取决于页面的流行度和陈旧度——热门页面需要更频繁地重新抓取,冷门页面可能数周才爬一次。
对于百万级页面的大型网站,抓取预算的分配直接影响 SEO 效果。如果爬虫将大量预算消耗在无意义的参数组合页面(例如过滤排序产生的 URL 变体),重要的内容页面就可能得不到及时抓取。URL 规范化、避免无限 URL 空间正是因此成为 SEO 中的关键约束。
robots.txt 与限速
爬虫在访问一个网站之前,通常会先请求网站根目录下的 /robots.txt 文件。该文件声明了爬虫的访问规则,例如哪些目录禁止抓取、哪些爬虫有特殊限制。
User-agent: *
Disallow: /admin/
Disallow: /api/
Crawl-delay: 10上述规则表示:所有爬虫不得访问 /admin/ 和 /api/ 路径,每次请求之间至少间隔 10 秒。需要注意的是,Crawl-delay 并非所有搜索引擎都遵循——Googlebot 不理会该指令,而是通过 Search Console 控制抓取速率。
robots.txt 属于“君子协定”——它请求爬虫遵守,但不具备强制力。搜索引擎的爬虫一般会遵守该协议,但通过外部链接指向的禁止页面仍可能被收录(尽管内容不会被抓取)。robots.txt 的详细配置和 sitemap 编写策略将在下一篇展开,此处仅作为抓取流程的边界说明。
倒排索引:让关键词瞬间定位页面
从网页文本到词项
爬虫下载 HTML 后,系统会进行文本提取与预处理。大致流程如下:
- 移除 HTML 标签,提取可见文本及关键 meta 信息(
<title>、<meta description>)。 - 对文本进行分词——英文按空格拆分,中文需要专门的分词算法。
- 去除停用词(如“的”“是”“the”“a”等高频但无区分度的词)。
- 对剩余词项进行词干提取或词形还原,将“running”映射回“run”。
处理后的结果是:每个页面被表示为一个词项集合,并记录每个词项的出现位置和频率。
倒排列表的构建
正排索引是“页面 → 词项列表”,查询时需要遍历所有页面。倒排索引与之相反:词项 → 包含该词项的页面列表。
假设有 3 个文档:
- 文档A:
vue reactive system - 文档B:
vue component lifecycle - 文档C:
react hooks system
构建出的倒排索引结构可简化为:
vue → [A, B]
reactive → [A]
system → [A, C]
component → [B]
lifecycle → [B]
react → [C]
hooks → [C]查询“vue system”时,系统分别查找“vue”和“system”的倒排列表,取交集即可得到文档A。在实际搜索系统中,倒排列表不仅记录文档 ID,还会记录词在文档中的位置、词频、是否出现在标题中等信息,这些数据都是后续排序的输入。
索引模型简化示例
用 TypeScript 类型结构来表达这层关系:
typescript
// 每个词项对应的倒排记录
interface Posting {
docId: number;
positions: number[]; // 词在文档中的位置
termFrequency: number; // 词频
inTitle: boolean; // 是否出现在标题中
}
// 倒排索引
type InvertedIndex = Map<string, Posting[]>;
// 文档存储
const documents = new Map<number, string>();该模型省略了大量工程细节——实际系统会使用压缩数据结构、分片存储和多层索引来应对万亿级别的页面与词项。但从概念上说,倒排索引就是将“搜索关键词”映射为“查找哈希表键”的过程。
并非所有被抓取的页面都会进入索引。Google 明确说明,是否将页面编入索引取决于网页内容与元数据[2]——页面质量低、内容重复或 meta 标签明确禁止索引的页面会被跳过。
排名的秘密:相关性、权威与体验的三角博弈
排名的本质是搜索引擎对“某个页面能否满足用户查询需求”的预测,这一预测由三大类信号共同决定。
相关性信号:关键词与内容匹配
相关性解决“这个页面在讲什么”的问题。最基本的相关性判断是关键词匹配——查询词是否出现在页面的标题、正文或 URL 中。
现代搜索引擎远不止字符串匹配。通过 TF-IDF、BM25 等算法,系统可以评估页面对某一主题的覆盖深度;借助语义模型(如 BERT),能够理解“苹果手机”和“iPhone”是同一意图的不同表达;再结合用户点击后的停留时间反馈,可以判断“用户搜索 X 后打开页面是否真的找到了答案”。
在 SEO 操作中,相关性优化的核心不是堆砌关键词,而是确保标题与正文准确覆盖用户的查询意图。搜索“vue3 响应式原理”的用户想要的是机制解释与源码分析,而非 Vue3 的介绍页面或安装教程。
权威信号:链接与站点信任
权威解决“这个页面值不值得信任”的问题。经典 PageRank 算法的核心思想是:一个页面被其他重要页面链接的次数越多,它本身越重要。
实际系统中,权威评估远比 PageRank 复杂。链接来源、锚文本、链接出现的位置(正文、页脚或侧栏)以及链接的新鲜度都会被纳入考量。站点级别的信任度也很关键——developer.mozilla.org 上的页面天然比随机博客上的同名文章拥有更高的权威权重。
体验信号:用户行为与性能
体验解决“用户打开页面后是否满意”的问题。页面加载速度、移动端适配、HTTPS 启用和页面布局稳定性(CLS)都会影响排名。Google 明确表示,用户位置、语言和设备等因素均会影响搜索结果[3]。
用户体验信号还包含用户行为的间接反馈:如果大量用户点击某个结果后迅速返回搜索结果页(pogosticking),可能意味着该页面未能满足需求,这类信号会被搜索引擎纳入质量评估。
三大类信号的权重并非固定不变。对于医疗、金融类查询,权威信号会被大幅提升;对于导航类查询(如“vue 官网”),相关性和权威性几乎决定一切;对于信息类查询,体验信号的权重则相对更高。
动手实践:Node.js 模拟爬虫抓取页面链接
下面给出一个简化版的链接抓取器。它从起始 URL 开始,下载 HTML,提取其中的 <a href> 链接,然后将新发现的链接加入队列继续抓取。该实现用于演示爬虫的核心逻辑,并非生产级工具。
前置准备与依赖
创建项目目录并安装依赖:
bash
mkdir crawler-demo && cd crawler-demo
npm init -y
npm install cheerio
npm install --save-dev typescript @types/nodecheerio 用于在 Node.js 中解析 HTML 并提取元素,API 与 jQuery 类似。
tsconfig.json 配置:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}抓取 HTML 并解析链接
核心抓取逻辑封装为两个函数:一个负责下载 HTML,一个负责提取链接。
typescript
// src/crawler.ts
import * as https from 'https';
import * as http from 'http';
import { URL } from 'url';
import * as cheerio from 'cheerio';
/**
* 下载指定 URL 的 HTML 内容
* 返回 HTML 字符串,网络错误时返回 null
*/
function fetchHTML(url: string): Promise<string | null> {
return new Promise((resolve) => {
const client = url.startsWith('https') ? https : http;
const req = client.get(url, { timeout: 5000 }, (res) => {
// 只处理 2xx 状态码
if (res.statusCode && res.statusCode >= 300) {
resolve(null);
return;
}
// 只处理 text/html 类型
const contentType = res.headers['content-type'] || '';
if (!contentType.includes('text/html')) {
resolve(null);
return;
}
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => resolve(data));
});
req.on('error', () => resolve(null));
req.on('timeout', () => {
req.destroy();
resolve(null);
});
});
}
/**
* 从 HTML 中提取所有绝对链接
* baseURL 用于将相对路径转为绝对路径
*/
function extractLinks(html: string, baseURL: string): string[] {
const $ = cheerio.load(html);
const links: string[] = [];
$('a[href]').each((_, el) => {
const href = $(el).attr('href');
if (!href) return;
try {
// 解析相对路径
const absolute = new URL(href, baseURL).href;
links.push(absolute);
} catch {
// 忽略无效 URL
}
});
return links;
}fetchHTML 返回 HTML 字符串或 null。返回 null 的情况包括:非 2xx 状态码、非 text/html 响应类型、网络超时或错误。现实中的爬虫会在此基础上实现更精细的重试策略与指数退避逻辑。
extractLinks 使用 cheerio 选中所有带 href 属性的 <a> 元素,通过 new URL(href, baseURL) 将相对路径转换为绝对路径,baseURL 为当前页面的完整 URL。
维护待抓取队列与去重
typescript
async function crawl(startURL: string, maxPages: number = 10): Promise<void> {
const visited = new Set<string>(); // 已抓取的 URL
const queue: string[] = [startURL]; // 待抓取队列
const results: Map<string, string[]> = new Map(); // 结果记录
while (queue.length > 0 && visited.size < maxPages) {
const currentURL = queue.shift()!;
// 去重:已经抓取过的跳过
if (visited.has(currentURL)) continue;
visited.add(currentURL);
console.log(`[抓取] ${currentURL}`);
const html = await fetchHTML(currentURL);
if (!html) {
console.log(`[跳过] 无法获取或非 HTML: ${currentURL}`);
continue;
}
const links = extractLinks(html, currentURL);
results.set(currentURL, links);
console.log(`[发现] ${links.length} 个链接`);
// 将新链接加入队列(此处可按需添加域名过滤)
for (const link of links) {
if (!visited.has(link) && !queue.includes(link)) {
queue.push(link);
}
}
}
console.log(`\n抓取结束,共处理 ${visited.size} 个页面`);
// 输出抓取结果
for (const [page, pageLinks] of results) {
console.log(`\n${page}`);
pageLinks.forEach(link => console.log(` └─ ${link}`));
}
}
// 入口
const targetURL = process.argv[2];
if (!targetURL) {
console.log('用法: node dist/crawler.js <URL> [最大页数]');
process.exit(1);
}
const maxPages = parseInt(process.argv[3]) || 5;
crawl(targetURL, maxPages).catch(console.error);运行结果与边界说明
编译并运行:
bash
npx tsc
node dist/crawler.js https://example.com 10输出示例:
[抓取] https://example.com
[发现] 12 个链接
[抓取] https://www.iana.org/domains/example
[发现] 8 个链接
...
抓取结束,共处理 10 个页面该实现省略了多项现实组件:
- 无域名过滤:抓取器会顺着链接爬到任何域名,生产系统会限定目标域或域名集合。
- 无请求间隔控制:连续请求同一域名可能被服务器封禁,真正的爬虫需要在请求间隔和并发数上施加控制。
- 无 robots.txt 解析:生产系统在抓取前会检查 robots.txt 规则。
- 无链接优先级:队列采用简单的 FIFO,所有链接同等对待。
此外,fetchHTML 仅处理 text/html 类型,跳过了 CSS、JS、图片等资源——对于现代搜索引擎爬虫,JavaScript 渲染的页面需要额外的渲染步骤(动态抓取),实现复杂度很高。
这个示例的核心价值在于展示基本循环:下载 → 解析 → 提取链接 → 入队 → 循环。将该循环扩展到分布式环境,配合优先级队列、布隆过滤器去重和 robots.txt 解析器,就能得到真正的爬虫系统。
关键排名因素速览与分类
排名因素数量庞大,可以归入四个层面。下表列出的是被广泛讨论且搜索引擎文档中明确提及的因素分类,具体权重取决于查询类型和搜索引擎版本。
内容层面
| 因素 | 说明 |
|---|---|
| 标题与描述匹配度 | <title> 和 meta description 是否包含与查询意图相关的词 |
| 内容深度与全面性 | 页面是否充分覆盖主题的核心子话题 |
| 关键词自然分布 | TF-IDF 信号,非堆砌式出现 |
| 内容新鲜度 | 发布时间和更新频率,对新闻类查询尤其关键 |
| 结构化数据标记 | 通过 JSON-LD 等格式提供实体信息,帮助搜索引擎理解内容含义 |
技术层面
| 因素 | 说明 |
|---|---|
| 可索引性 | 页面未被 noindex 标记阻止,能被爬虫正常抓取和索引 |
| URL 规范性 | 同一内容只有一个规范 URL,避免重复索引分散权重 |
| HTTPS 启用 | HTTPS 是排名提升信号 |
| 移动端兼容 | 响应式设计或独立移动版页面,影响移动搜索排名 |
| 页面加载速度 | 首字节时间、总下载量等,慢页面在排名中受损 |
链接层面
| 因素 | 说明 |
|---|---|
| 外链数量与质量 | 来自高权威域名的链接是强信号 |
| 锚文本相关性 | 链接文字与目标页面内容的相关程度 |
| 内链结构 | 站内链接的层次和锚文本分布,影响权重在站内的流动 |
| 链接位置 | 正文中的链接比页脚、侧栏的链接权重更高 |
体验层面
| 因素 | 说明 |
|---|---|
| Core Web Vitals | LCP、INP、CLS 三个指标分别衡量加载、交互和视觉稳定性 |
| 用户停留时间 | 点击后用户是否持续浏览,短期跳出可能降低质量评估 |
| 页面布局与可读性 | 字体大小、行间距、广告占比等影响阅读体验 |
| 安全浏览 | 无恶意软件、无钓鱼内容 |
本篇小结与下一步
搜索引擎的工作流程决定了 SEO 工作的三个阶段:保证页面能被发现和抓取,保证内容能被正确解析和索引,保证页面在相关性、权威和体验上能获得足够好的排序位置。
SEO 涉及的三个层面——抓取与索引控制、内容相关性与权威信号、页面加载体验——是后续操作的基础。本篇围绕搜索引擎内部机制展开:爬虫如何发现和抓取页面,索引如何组织成倒排结构,排名如何综合多维信号做出决策。Node.js 抓取示例提供了一个可运行的“缩小版爬虫”以帮助理解核心循环。
下一篇将进入具体的配置层面:robots.txt 的编写方式,sitemap 如何辅助搜索引擎发现更多页面,以及二者的协作和常见配置错误。
