做内容站的站长都遇到过这个场景:辛辛苦苦写了两百篇文章,首页、分类页、文章页都提交了 sitemap,Google 只收了三十篇,百度更惨,收了十几篇还掉了几个。打开 GSC 一看,「已发现但未编入索引」的数字越堆越高。这时候你爬取诊断、改标题、换模板,折腾一个月毫无起色——因为真正的瓶颈往往不在页面本身,而在内链。
本文讲的是一套可以落地的内链体系:用标签、面包屑、相关推荐这三层,把权限集中在少数几个枢纽页上,再顺着枢纽把权重传导到深处页面。核心工具就是日志分析和几条 SQL。
为什么「已发现,但未编入索引」这么多
搜索引擎的抓取预算是有配额的。对于个人站点,这个配额通常低得可怜——几百到几千次每天的量级。GSC 里那个「已发现但未编入索引」的状态,翻译成人话就是:我知道这个 URL 存在,但我不觉得它值得占用我宝贵的抓取额度。
搜索引擎判断一个页面值不值得抓,主要看三件事:
- 这个 URL 有多少条来自站内的链接指向它(内链数量与质量)
- 这些链接所在页面本身的权重高不高
- 这个 URL 距离首页的点击深度(理想是 3 跳以内)
大多数个人站的现状是:文章平均只被链接到 0 到 1 次,全站权重几乎全堆在首页,深处文章无人问津。搜索引擎爬到第二层之后就找不到路了。
第一步:诊断——先看清现实
1. 用爬取日志统计各目录的抓取分布
假设你的站是 Typecho 或 WordPress,日志格式是 Nginx 默认的 combined。先用 GoAccess 跑一份总览,重点是看「哪些路径被爬得多」:
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
--ignore-crawlers=no \
--output=report.html \
--html-report-title="全站爬虫抓取概览"如果你想更精确地只看 Googlebot,先过滤出爬虫 UA 的日志:
awk -F'"' '/Googlebot/ {print $2}' /var/log/nginx/access.log \
| awk '{print $2}' | sort | uniq -c | sort -rn | head -40看结果里哪些目录占了大部分抓取量。如果是 /tag/ 或者 /page/ 分页占了大头,而正文页少得可怜,那说明你的分页和标签在做无意义的抓取消耗。
2. 用 SQL 统计每篇文章被内链的次数
如果你用的是 Typecho,文章正文存在 typecho_contents.text 里。可以写一条 SQL 统计每个 URL 在站内被引用的次数:
SELECT
cid,
title,
(LENGTH(text) - LENGTH(REPLACE(text, CONCAT('/', cid, '.html'), '')))
/ LENGTH(CONCAT('/', cid, '.html')) AS incoming_links
FROM typecho_contents
WHERE type = 'post' AND status = 'publish'
ORDER BY incoming_links ASC
LIMIT 50;这条 SQL 的原理是:把正文里某个 URL 字符串全部删掉,看长度减少了多少,除以单个 URL 的长度,就是出现次数。跑完之后你会看到一批 incoming_links = 0 的文章——这些就是「孤岛页面」,搜索引擎基本没有路径爬到它们。
(如果你用了缓存插件把正文存到了额外字段,记得把 text 换成实际字段名。)
第二步:用标签做枢纽(Hub & Spoke)
内链体系的核心不是「每篇文章互相链一遍」,那是蜘蛛网不是管道。真正有效的结构是 Hub & Spoke:设几个高权重枢纽页,让所有相关文章都链向它,再由它分发出去。
枢纽页怎么选
枢纽页要满足两个条件:一是能聚合足够多的文章(至少 15 篇),二是搜索需求明确。对于站长技术博客,天然的枢纽就是标签页,例如:
/tag/nginx/—— 聚合所有 Nginx 相关文章/tag/mysql/—— 聚合数据库相关/tag/security/—— 聚合安全加固类
问题在于,Typecho 默认的标签页是「文章列表 + 摘要」,权重分散,而且很多主题不输出完整的文章标题链。要做枢纽,需要改造标签页模板,让它至少输出:
- 标签的详细描述(一段 200 字左右的说明,讲清这个标签覆盖什么)
- 全部文章的标题链接,而不是分页只显示十篇
- 指向其他相关标签的链接(横向链接)
让正文自动链回标签
在文章页的固定位置(建议是正文结束后、相关推荐之前)插入一行标签链接。Typecho 主题里通常已经有 $this->tags() 的输出,关键是给它加上 rel="tag" 并保证是 dofollow 的真链接:
<div class="post-tags">
<span>本文标签:</span>
<?php $this->tags('<a href="%url%" rel="tag">%name%</a>', ' · '); ?>
</div>别小看这一行。如果你的每篇文章都链回标签页,那么一个覆盖 50 篇文章的标签页就会收到 50 条内链,它在站内的权重会迅速超过普通文章页,成为真正的枢纽。
标签别开太多
一个常见的错误是每篇文章打七八个标签。结果是每个标签都只有两三篇文章,全是低质量空壳页,反而稀释了权重。建议规则:
- 全站标签总数控制在文章数的 1/5 以内
- 单个标签至少要能聚合 10 篇以上文章,否则合并到上位标签
- 每篇文章打 3 到 5 个标签,且必须有至少两个是高频枢纽标签
清理标签可以直接用 SQL 先查出来,再决定合并:
SELECT m.mid, m.name, m.slug, COUNT(r.cid) AS cnt
FROM typecho_metas m
LEFT JOIN typecho_relationships r ON m.mid = r.mid
WHERE m.type = 'tag'
GROUP BY m.mid
HAVING cnt < 5
ORDER BY cnt ASC;跑出来全是 cnt < 5 的标签,就是该处理的对象——要么合并进大标签,要么直接删掉关系记录让标签页 404 而不是留着空页面。
第三步:面包屑导航不只是给用户看的
面包屑(Breadcrumb)是搜索引擎理解站点层级结构最直接的信号。但很多主题的面包屑是「首页 > 分类 > 文章标题」这种只有两级链接的死结构,对权重传导帮助有限。
优化思路是让面包屑变成多路径的。除了主分类路径,再加一条标签路径:
<nav class="breadcrumb">
<a href="/">首页</a>
<span>></span>
<a href="<?php $this->category($this->categories[0]['slug'] ? '/category/'.$this->categories[0]['slug'].'/' : '/'); ?>">
<?php echo $this->categories[0]['name']; ?>
</a>
<?php if (!empty($this->tags)): ?>
<span>></span>
<a href="<?php echo $this->tags[0]['permalink']; ?>"><?php echo $this->tags[0]['name']; ?></a>
<?php endif; ?>
</nav>同时,务必在页面头部输出 Breadcrumb 的结构化数据,让搜索引擎明确识别层级:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "首页", "item": "https://www.example.com/"},
{"@type": "ListItem", "position": 2, "name": "Nginx", "item": "https://www.example.com/tag/nginx/"},
{"@type": "ListItem", "position": 3, "name": "文章标题", "item": "https://www.example.com/1234.html"}
]
}
</script>注意 JSON-LD 里的 URL 必须和页面的 canonical 完全一致,包括结尾斜杠。不一致的话 Google 会认为你描述的是另一个页面。
第四步:相关推荐做「上下文内链」
「相关文章」是内链体系里性价比最高的一环,因为它能自动产生大量精准的上下文链接。但默认实现往往很差——按发布时间取最近五篇,跟当前文章毫无关系,纯属凑数。
按标签重合度算相关
最实用的算法是按标签重合度排序,一条 SQL 就能解决:
SELECT c.cid, c.title, COUNT(r2.mid) AS overlap
FROM typecho_relationships r1
JOIN typecho_relationships r2 ON r1.mid = r2.mid AND r2.cid != r1.cid
JOIN typecho_contents c ON c.cid = r2.cid
WHERE r1.cid = 1234
AND c.type = 'post' AND c.status = 'publish'
GROUP BY c.cid
ORDER BY overlap DESC, c.created DESC
LIMIT 6;把 1234 换成当前文章的 cid,就能拿到标签重合度最高的六篇文章。这比「最近发布」的相关性强得多。配合页面缓存,这个查询的开销完全可以接受。
在正文中做手动内链
自动推荐解决的是「底部链接」,但真正最有效的是正文里的上下文锚文本链接。搜索引擎对正文中自然出现的锚文本权重要远高于侧边栏或底部的链接块。
养成习惯:每写一篇新文章,回顾一遍相关内容,在正文合适的位置插入两到三个指向旧文章的链接,锚文本用具体的描述而不是「点击这里」。比如:
如果服务器是 Nginx,还需要额外配置
<a href="/1075.html">limit_req 与 limit_conn 限流规则</a>,
否则前面这层防护等于没有。反过来,也应该定期回头给老文章补上新文章的链接。新文章需要老文章导权,老文章也需要持续更新保持活跃度——这是双向的。
第五步:验证与迭代
改完内链结构之后,不要等一个月才看效果。可以这样验证:
- 在 GSC 里对几个关键 URL 点「请求编入索引」,观察 3 到 7 天的编入情况
- 在服务器上按天统计 Googlebot 对目标目录的抓取次数
- 看日志里爬虫的抓取深度分布——新出现的深层 URL 是结构改善的直接证据
统计抓取深度的命令:
grep Googlebot /var/log/nginx/access.log.1 \
| awk '{print $7}' \
| awk -F'/' '{print NF}' \
| sort | uniq -c \
| sort -k2 -n输出里如果 NF=2(即 /1234.html 这种浅层)占绝大多数,说明爬虫还是没往下走;当 NF 值较大的路径逐渐增多,说明枢纽页把它带下去了。
小结
内链体系不是「加个相关推荐插件」就能解决的。它的本质是把站内有限的权重,有意识地导向你想推的那些页面。执行的顺序建议是:
- 先用 SQL 找出零内链的孤岛页面(一小时)
- 把标签收敛成少数几个真正的枢纽(半天)
- 改造标签页模板,输出全量标题链(半天)
- 加面包屑结构化数据(一小时)
- 把相关推荐从「按时间」改成「按标签重合度」(半天)
- 然后长期维护:每篇新文手动链两三篇旧文
最后一步没有技术含量,但也是最重要的一步。工具能帮你把结构搭好,但真正决定内链质量的是你有没有在写文章的时候想着「这篇该链向谁」。