不花一分钱优化抓取预算,让谷歌多抓多收录
抓取预算是隐形瓶颈,正在悄悄拖垮你的收录。本文手把手教你用 Google Search Console、服务器日志、robots.txt 和自写 Python 分析器来测量、诊断、优化它——完全不用付费买 Ahrefs、Botify 或 OnCrawl,就能让谷歌多抓多收录。
我有个网站,340 个页面。Google 只索引了 89 个。三个月过去,74% 的内容在搜索里完全不可见。
我第一反应是内容质量问题。也许页面太单薄,也许内链太弱,也许我需要更多外链。我花了两周重写 meta description、加内链、用 Google Search Console 的 URL Inspection 工具提交 URL。什么都没变。页面依旧卡在 “Crawled - currently not indexed” 的炼狱里。
然后我看了下抓取统计。Googlebot 每天发 400-500 个抓取请求。听起来很多,直到我意识到其中 280 个请求打向的是我几个月前就删掉的 URL。过期的参数 URL、旧的 tag 页面、已经不存在的 faceted navigation 组合。Googlebot 把 60% 的 crawl budget 花在垃圾上,剩下的 40% 根本不够发现和处理我的新页面。
这不是内容质量问题。这是 crawl budget 问题。一旦我搞清楚是什么在吃预算,一个下午就修好了。两周之内,索引页数从 89 跳到了 241。
这篇指南会讲完我学到的关于 crawl budget 优化的所有东西,包括我写的用来分析抓取模式、识别浪费的 Python 脚本。不需要 500 刀/月的 Botify,也不需要 200 刀/月的 OnCrawl。只要免费工具和一套清晰的方法论。
Crawl Budget 到底是什么(以及不是什么)
Crawl budget 就是 Googlebot 在给定时间内抓取你网站的页面数量。它由两个因素决定:crawl rate limit(Googlebot 在不过载你服务器的前提下能爬多快)和 crawl demand(Google 根据内容热度和新鲜度想要爬多少)。
Crawl Rate Limit(抓取速率上限)
Googlebot 根据三个信号调整抓取速度:
-
服务器响应时间。 页面 200ms 响应,Googlebot 就爬得快。3 秒才响应,它就会降速以免压垮服务器。这里看的是滚动平均值,单个慢页面不会拖垮抓取速率,但长期慢响应会。
-
错误率。 如果 Googlebot 遇到大量 5xx 错误(服务器错误),它会退避。这是保护机制。服务器已经在挣扎了,Googlebot 不想火上浇油。
-
并发连接数。 Google 限制了对你的服务器的并发连接数。对大多数小网站这不是瓶颈。对几千个 URL 的大网站,可能是。
你可以在 Google Search Console 的 Settings > Crawl Stats 里看到当前抓取速率。报告显示 90 天内的每日请求数、下载大小和响应时间。
Crawl Demand(抓取需求)
就算服务器扛得住高速抓取,Googlebot 也不会超出它认为需要的量。需求由下面几个因素驱动:
-
热度。 在搜索结果中获得更多点击、更多内链、更多外链的页面,抓取频率更高。Google 知道用户在乎这些页面。
-
新鲜度。 经常变化的页面(新闻站、商品列表)比几年不更新的页面爬得更勤。Google 试图保持索引新鲜。
-
新 URL。 Google 发现新 URL(通过 sitemap、内链或外链)后,会优先抓取它们来扩展索引。
你控制不了的预算 vs 你控制得了的浪费
这里有个大多数 SEO 指南都忽略的关键洞察:你没办法增加 crawl budget。 Google 根据服务器容量和内容的感知价值来设定。求 Googlebot 多爬一点是没用的。
你能做的是消除浪费。每个花在低价值 URL 上的抓取请求,都是从你真正想要索引的页面上偷走的。Crawl budget 优化的重点不是拿到更多预算,而是更高效地花掉现有的。
这就是为什么小网站也要关注 crawl budget。如果你有 50 个页面,Googlebot 每天爬 50 个,没有一点余量。10 个请求浪费在垃圾 URL 上,就是 20% 的预算没了。500 个页面的网站,问题会滚雪球。
怎么衡量你的 Crawl Budget
你没法优化一个衡量不了的东西。下面是用免费工具评估 crawl budget 的方法。
方法一:Google Search Console Crawl Stats
GSC 的 Crawl Stats 报告是最容易获取的抓取数据。进入 Settings > Crawl Stats(这替代了 Search Console 里旧的 “Crawl” 部分)。
报告显示过去 90 天的三个关键指标:
-
每日总抓取请求数。 这就是你的有效 crawl budget。50 个页面的网站可能每天 30-80 个请求。500 个页面的网站可能每天 200-500 个。单独看没有”好”的数字,重要的是抓取请求数跟总 URL 数的比例。
-
每日总下载大小。 Googlebot 在消耗多少带宽。如果相对于你的页面大小来说这个值偏高,说明 Googlebot 可能在下载不需要索引的大文件(图片、PDF)。
-
平均响应时间。 Googlebot 视角下你的服务器速度。500ms 以下都是好的。超过 1 秒是警告信号。超过 3 秒意味着 Googlebot 在主动降速。
报告还按响应类型拆分了抓取请求:2xx(成功)、3xx(重定向)、4xx(客户端错误)、5xx(服务器错误)。这个拆分就是找到浪费的地方。
浪费公式: 算一下返回 3xx、4xx 或 5xx 的抓取请求占比。这些是非生产性抓取。健康的网站上,90% 以上的抓取请求应该返回 2xx。如果你看到 30% 以上落在错误类别,就是有大量浪费。
在我那个出问题的网站上,拆分是这样的:
- 2xx 响应:42% 的抓取请求
- 3xx 重定向:18%
- 404 错误:28%
- 5xx 错误:2%
- 其他:10%
58% 的抓取请求是非生产性的。难怪新页面一直没被索引。
方法二:主机级响应分析
GSC Crawl Stats 报告还按响应类型展示了 Googlebot 请求的具体 URL。点进 “Not Found (404)” 或 “Redirected” 部分,就能看到哪些 URL 在浪费预算。
你通常会看到这些模式:
- 像
?sort=price&order=desc这样产生无限组合的参数 URL - 迁移产生的旧 URL 结构,301 到新 URL
- 已删除的页面,因为别处还有链接,所以继续收到抓取请求
- CMS 自动生成的 tag 和 category 页面
- 重复内容的翻页 URL
导出这些 URL 做分类。模式会明确告诉你该修什么。
方法三:服务器日志分析(如果有的话)
服务器访问日志是抓取分析的金标准。它们记录 Googlebot 的每个请求,包括精确 URL、时间戳、响应码、响应大小和 user agent。GSC 展示聚合数据,日志展示原始细节。
如果你的主机提供访问日志(大多数 VPS 提供商都有,Cloudflare Workers 也可以记录到 R2),你可以用一个简单的过滤器提取 Googlebot 请求。
下面是我用来分析服务器日志中 crawl budget 浪费的 Python 脚本:
#!/usr/bin/env python3
"""Analyze server logs for Googlebot crawl patterns."""
import re
import sys
from collections import Counter
from urllib.parse import urlparse, parse_qs
# Googlebot user agents (simplified pattern)
GOOGLEBOT_PATTERN = re.compile(
r'(?:Googlebot|Googlebot-Image|Googlebot-News|AdsBot-Google)', re.I
)
def parse_log_line(line):
"""Parse a common log format line."""
# Common Log Format: host ident authuser date request status bytes
# Nginx Combined: ... "request" status bytes "referer" "user-agent"
match = re.match(
r'\S+ \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+) (\d+) "[^"]*" "([^"]*)"',
line
)
if not match:
return None
return {
'timestamp': match.group(1),
'method': match.group(2),
'path': match.group(3),
'protocol': match.group(4),
'status': int(match.group(5)),
'size': int(match.group(6)),
'user_agent': match.group(7),
}
def categorize_url(url):
"""Categorize URL for waste analysis."""
parsed = urlparse(url)
path = parsed.path
qs = parse_qs(parsed.query)
categories = []
# Parameter URLs
if qs:
categories.append('parameter-url')
# Trailing slash inconsistency
if path != '/' and not path.endswith('/'):
categories.append('no-trailing-slash')
# Pagination
if '/page/' in path or 'page=' in parsed.query:
categories.append('pagination')
# Tag/category pages
if path.startswith('/tag/') or path.startswith('/category/'):
categories.append('taxonomy')
# Faceted navigation
if any(k in parsed.query for k in ['sort', 'filter', 'facets', 'color', 'size', 'brand']):
categories.append('facet')
# File types that shouldn't be crawled
if any(path.endswith(ext) for ext in ['.css', '.js', '.png', '.jpg', '.gif', '.svg', '.ico']):
categories.append('static-asset')
return categories if categories else ['content']
def analyze_logs(filepath):
"""Analyze log file for crawl budget waste."""
googlebot_requests = []
with open(filepath) as f:
for line in f:
if not GOOGLEBOT_PATTERN.search(line):
continue
parsed = parse_log_line(line)
if parsed and parsed['method'] == 'GET':
googlebot_requests.append(parsed)
if not googlebot_requests:
print("No Googlebot requests found.")
return
# Response code distribution
status_counts = Counter(r['status'] for r in googlebot_requests)
total = len(googlebot_requests)
print(f"=== CRAWL BUDGET ANALYSIS ===")
print(f"Total Googlebot requests: {total}")
print(f"\n--- Response Code Distribution ---")
for status in sorted(status_counts.keys()):
count = status_counts[status]
pct = count / total * 100
print(f" {status}: {count} ({pct:.1f}%)")
productive = sum(c for s, c in status_counts.items() if 200 <= s < 300)
waste = total - productive
print(f"\n--- Budget Efficiency ---")
print(f" Productive crawls (2xx): {productive} ({productive/total*100:.1f}%)")
print(f" Wasted crawls (3xx/4xx/5xx): {waste} ({waste/total*100:.1f}%)")
# URL category distribution for non-2xx responses
waste_requests = [r for r in googlebot_requests if r['status'] >= 300]
if waste_requests:
waste_categories = Counter()
for r in waste_requests:
cats = categorize_url(r['path'])
for cat in cats:
waste_categories[cat] += 1
print(f"\n--- Waste Sources ---")
for cat, count in waste_categories.most_common(15):
print(f" {cat}: {count}")
# Most-crawled paths
path_counts = Counter(r['path'] for r in googlebot_requests)
print(f"\n--- Top 20 Most-Crawled Paths ---")
for path, count in path_counts.most_common(20):
status = next(
(r['status'] for r in googlebot_requests
if r['path'] == path), '?'
)
print(f" [{status}] {path}: {count} requests")
# Unique vs total ratio
unique_paths = len(set(r['path'] for r in googlebot_requests))
print(f"\n--- URL Discovery ---")
print(f" Unique URLs crawled: {unique_paths}")
print(f" Total requests: {total}")
print(f" Recrawl ratio: {total/unique_paths:.1f}x "
f"(each URL crawled {total/unique_paths:.1f} times on average)")
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <access-log-file>")
sys.exit(1)
analyze_logs(sys.argv[1])
这个脚本会精确告诉你 crawl budget 花在了哪里。在服务器日志上跑一下,你会得到类似这样的拆分:
=== CRAWL BUDGET ANALYSIS ===
Total Googlebot requests: 12,847
--- Response Code Distribution ---
200: 5,395 (42.0%)
301: 2,312 (18.0%)
404: 3,597 (28.0%)
500: 257 (2.0%)
--- Budget Efficiency ---
Productive crawls (2xx): 5,395 (42.0%)
Wasted crawls (3xx/4xx/5xx): 7,452 (58.0%)
--- Waste Sources ---
parameter-url: 2,847
no-trailing-slash: 1,923
pagination: 1,102
taxonomy: 847
facet: 578
--- URL Discovery ---
Unique URLs crawled: 487
Total requests: 12,847
Recrawl ratio: 26.4x (each URL crawled 26.4 times on average)
这个 recrawl ratio 才是杀手。Googlebot 在 90 天内对同样的 487 个 URL 平均抓取了 26 次,也就是每个 URL 每 3.4 天被爬一次。与此同时,新页面要等好几周才迎来第一次抓取。
方法四:Cloudflare Workers 日志(Serverless 替代方案)
如果你用的是 Cloudflare Pages 或 Workers,没有传统服务器日志,可以用一个简单的 middleware 模式记录 Googlebot 请求。加到你的 worker 或 Astro middleware 里:
// Log Googlebot requests to R2 or KV
const ua = request.headers.get('user-agent') || '';
if (/Googlebot/i.test(ua)) {
const url = new URL(request.url);
const logEntry = JSON.stringify({
ts: Date.now(),
path: url.pathname + url.search,
method: request.method,
status: response.status,
});
// Option 1: Store in KV (simpler, shorter retention)
await env.CRAWL_LOG.put(
`crawl:${Date.now()}:${Math.random().toString(36).slice(2)}`,
logEntry,
{ expirationTtl: 7776000 } // 90 days
);
// Option 2: Store in R2 (no size limit)
await env.CRAWL_BUCKET.put(
`logs/${new Date().toISOString().slice(0,10)}/${Date.now()}.json`,
logEntry
);
}
这样不需要服务器访问日志也能拿到抓取数据。收集 30 天数据,导出成 JSON 文件,稍微改一下就能跑同一个分析脚本。
Crawl Budget 浪费的五大来源
通过分析多个网站的抓取数据,我总结出了五个反复出现的 crawl budget 消耗模式。每个网站至少有其中两个。
来源一:参数污染
URL 参数是 crawl budget 的头号杀手。每个唯一的参数组合都会产生一个唯一 URL。Googlebot 把每个都当成独立页面。看参数是怎么翻倍的:
?sort=price— 1 个变体?sort=price&order=desc— 2 个变体