← 返回专栏
· 2 分钟阅读

用 Python 抓 Google SERP:真实失败日志与真正可行的方案

每个 SERP 爬虫的故事都一样:第一天能跑,两周后数据悄悄归零,因为请求依然返回 200。本文复盘一次真实的 Google 抓取失败——302 区域重定向、一个装着零条结果的 200 页面——再讲为什么 Bing 好抓但换个方式骗你,以及从裸请求到免费额度 SERP API 的三级阶梯。真实命令、真实输出、真实局限。

TL;DR

抓 Google SERP 失败得比你想象的安静:请求成功了(HTTP 200),返回的页面里却一条结果都没有。本文复盘真实日志——一次 302 区域重定向,落进一个 92 KB 的 JS 空壳——然后给出能用的三级阶梯:裸请求(脆)、无头浏览器(重)、免费额度 SERP API(结构化、按量计费)。附真实耗时、数据中心 IP 下 Bing 与 Google 的真实行为差异,以及可直接粘贴的限速 + 退避代码。

每个 SERP 爬虫的传记都一样。第一天能跑。你写好解析器,觉得自己很聪明。两周后关键词计数悄悄归零,没人发现——因为请求依然返回 200。

这篇文章是尸检报告。真实命令、真实响应、来自真实数据中心 IP,不是「Google 可能会封你」式的假设性警告。然后是真正能用的阶梯,连同成本。

实验:一次 curl 打向 Google

最小可行爬虫,连 Python 都不用——curl 加一个正常桌面 UA:

curl -s -o /tmp/google.html \
  -w "HTTP %{http_code}, %{size_download} bytes, ip=%{remote_ip}\n" \
  -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" \
  "https://www.google.com/search?q=serp+scraping+python&num=10"

响应:

HTTP 302, 420 bytes, ip=198.18.0.63

没有 CAPTCHA。没有 429。没有「异常流量」页。一个 302 重定向——420 字节的「请去别处」。去哪?看一眼:

curl -s -o /dev/null -w "%{redirect_url}\n" \
  -A "Mozilla/5.0 ..." \
  "https://www.google.com/search?q=serp+scraping+python&num=10"
https://www.google.com.hk/url?sa=p&hl=zh-CN&pref=hkredirect&pval=yes&q=https://www.google.com.hk/search%3Fq%3Dserp%2Bscraping%2Bpython...

这次请求的出口 IP 落在香港,于是 Google 重定向到 google.com.hk,还硬塞了 hl=zh-CN。不跟重定向的爬虫看到 302 就放弃了。跟重定向的爬虫,会掉进真正的陷阱。

陷阱:装着空气的 200 OK

跟着重定向走:

curl -sL -o /tmp/google2.html -w "HTTP %{http_code}, %{size_download} bytes\n" \
  -A "Mozilla/5.0 ..." \
  "https://www.google.com/search?q=serp+scraping+python&num=10"
HTTP 200, 92366 bytes

成功了吧?92 KB 的 HTML。看看里面实际有什么:

grep -o '<title>[^<]*</title>' /tmp/google2.html
# <title>Google Search</title>

grep -o -c 'href="/url?' /tmp/google2.html
# 0

grep -o -iE 'enablejs|jserror' /tmp/google2.html | sort | uniq -c
#    2 enablejs
#    1 jserror

**零条自然结果链接。**页面标题是通用的「Google Search」,正文带 enablejs 标记——这是「需要 JavaScript」的空壳。Google 返回了一个尺寸正常、完全合法的 HTML 页面,里面没有结果,以后也不会有,因为它要的是真浏览器。

这是抓取里最坏的失败模式:静默成功。你的 requests.get() 返回 200。.status_code == 200 检查通过。解析器找到零个 h3,管线记下「该关键词无结果」,然后你的关键词数据悄悄腐烂。几周后,你正在基于空数据做决策。

修复不是更好的解析器,是断言:

resp = session.get(url, headers=HEADERS, timeout=15)
resp.raise_for_status()
results = parse_organic(resp.text)
if len(results) < 3:
    raise SerpShellError(
        f"got 200 but only {len(results)} results — "
        f"likely a JS shell or consent page, not a real SERP"
    )

对结果条数断言,不对状态码断言。我们发布的每个 SERP 拉取器都这么做。

同一时刻,Bing 那边

同样的请求、同样的 IP、同样的 UA:

curl -s -o /tmp/bing.html \
  -w "HTTP %{http_code}, %{size_download} bytes\n" \
  -A "Mozilla/5.0 ..." \
  "https://www.bing.com/search?q=serp+scraping+python"
HTTP 200, 124893 bytes

完整页面、真实结果、b_algo 结果块就在 HTML 里。从数据中心 IP 抓 Bing 比 Google 容易一个量级。

那直接抓 Bing?我们在自己的关键词难度工具里撞过的坑在这:**从数据中心 IP 抓到的排名,不是目标市场真实用户看到的排名。**我们拿 Bing 位置对过目标市场真实用户的结果——同关键词、不同顺序、有的域名整个消失。位置和 IP 信誉都是排名输入,而你的数据中心出口是个非常反常的「用户」。

好抓但悄悄错,比难抓更糟。至少 Google 的封锁是响的。

真正能用的阶梯

三级,可靠性递增:

**第一级:有礼貌的裸请求。**钉死 locale(glhl 参数)、复用 session、请求间 sleep、非 200 指数退避。每天几十个查询没问题。上量或 IP 信誉差的日子就死。做实验行,做管线不行。

**第二级:无头浏览器。**Playwright 用真实指纹能过 JS 空壳,但单查询成本乘 10-50 倍(浏览器启动、页面体积),而且你进了指纹军备竞赛——无头 Chrome 有破绽,Google 会找。要渲染后的页面内容时用它,要 SERP 结构时别用。

**第三级:免费额度 SERP API。**别人运营 IP 池、浏览器和解析,你拿结构化 JSON:

curl -s -X POST "https://google.serper.dev/search" \
  -H "X-API-KEY: $SERPER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"serp scraping python","num":10}'
{
  "searchParameters": {"q": "serp scraping python", "gl": "us", "hl": "en"},
  "organic": [
    {"title": "Web Scraping in Python (Complete Tutorial 2026)", "link": "https://serpapi.com/blog/python-web-scraping-tutorial/", "position": 1},
    {"title": "How to Scrape Google Search Results: Python Tutorial", "link": "https://github.com/oxylabs/scrape-google-python", "position": 2}
  ],
  "peopleAlsoAsk": [...],
  "relatedSearches": [...],
  "credits": 1
}

连续三次调用的真实耗时:**2.04s、2.34s、3.06s。**免费额度每月 2,500 次——$0 跑完一整条关键词研究管线。而且响应已解析,「静默空页」这个失败模式在结构上不可能存在:organic 数组为空是 API 在告诉你某件事,不是在藏。

我们整条关键词研究栈——难度评分、SERP 意图检查、竞品差距——都跑在这级。

非抓不可时的模式

有时免费额度不够,还是得裸抓。最低限度的纪律:

import time, random, requests

def fetch_with_backoff(url, session, max_retries=4):
    for attempt in range(max_retries):
        resp = session.get(url, timeout=15)
        if resp.status_code == 200 and parse_organic(resp.text):
            return resp
        if resp.status_code in (429, 503) or resp.status_code == 200:
            # 200 但零结果,按软封锁处理
            sleep = (2 ** attempt) + random.uniform(0, 1)
            time.sleep(sleep)
            continue
        resp.raise_for_status()
    raise RuntimeError("soft-blocked after retries")

# 关键词之间也要限速,不只是重试之间
time.sleep(random.uniform(4, 9))

比代码更重要的三条规则:把 200-零结果当封锁处理(上面整课的重点)、在关键词之间限速而不只是重试之间、缓存每个响应绝不重复问同一个问题——本地缓存层能把 2,500 次免费额度撑出好几倍

这套认知哪里不完美

  1. **你的 IP 表现会不一样。**这些日志来自一条代理路由的一个数据中心出口。住宅 IP 从 Google 拿到的待遇更友好;别的数据中心段拿到的可能直接是 CAPTCHA 而不是礼貌重定向。你的失败模式会不同——但教训(对结果断言,不对状态码断言)无条件迁移。

  2. **SERP API 是个依赖。**第三级意味着别人的可用性、别人的定价、一个硬计数器。管线长过 2,500/月的那天,你开始付费。依然比自建浏览器集群便宜,但不会永远免费。

  3. **API 的 SERP 是归一化的 SERP。**SERP API 返回的是确定性的、钉死位置的快照——做评分正合适,但不等于某个具体真实用户看到的。同一个 API 内不同市场差多少,我们量过:见本地化排名差异测试

放进工作流

抓取是管线的数据获取层,不是管线本身:

  1. 获取——本文的阶梯:裸请求、浏览器或 API
  2. 撑预算——花下一个 credit 之前先缓存每个 SERP 响应
  3. 评分——喂给难度评分SERP 结构分析
  4. 追踪——每周同一套钉死的 gl/hl 重跑,配合知道自己边界的排名追踪

整条栈一行装好,跑在免费额度上:

pip install git+https://github.com/ZensInk/zens-ink-seo-package.git
export SERPER_API_KEY="your_key"

要点

SERP 抓取的难点从来不是拿到 HTML,而是知道你拿到的 HTML 是不是在撒谎。Google 最有效的反爬武器不是 CAPTCHA,是那个装着空气的 200 OK。对你解析出的东西断言,别对服务器说的话断言——剩下的阶梯自然会就位。

FAQ

为什么我的 Google 爬虫返回 HTTP 200 却抓不到结果?

因为 Google 经常不发封禁页,而是发一个「需要 JavaScript」的空壳页或区域重定向。本文复盘的测试里,google.com/search 返回 302 跳到 google.com.hk,跟过去之后拿到一个 200 OK、约 92 KB 的页面——标题只是「Google Search」,正文里零条自然结果链接。只检查状态码的爬虫会记录一次「成功」,然后解析出空气——除非你对结果条数做断言,否则这个失败是静默的。

抓取 Google 搜索结果合法吗?

本文不给法律意见,实操答案因司法辖区和规模而异。可以确定的是:Google 服务条款禁止自动化抓取,执法以限速为主——在有人找你谈话之前,你会先撞上 consent 墙、CAPTCHA 或 IP 封锁。SERP API 的存在正是为了把这块灰色地带做成服务:你付费(或用免费额度),不用自己做 IP 信誉生意。

Bing 比 Google 好抓吗?

从数据中心 IP 看,好抓得多。同一个被 Google 重定向清空的请求,打到 Bing 返回了完整的 124 KB 结果页,自然结果就在 HTML 里。但坑在于:Bing 的排名本身随 IP 和位置变化——你从数据中心抓到的,不是目标市场真实用户看到的结果。好抓但悄悄错,比难抓但响亮地失败更糟。

拿结构化 Google SERP 数据最便宜的方式是什么?

用免费额度的 SERP API。Serper.dev 每月免费 2,500 次查询,返回已解析的 JSON——自然结果、People Also Ask、相关搜索——单次约 2-3 秒。对一个每月几千关键词的研究管线来说,成本是 $0。再加一层本地缓存,同样的预算还能再翻几倍。

想给自己的站点做同样的分析?

ZensInk Pro 把这个流程自动化了。一条命令,从种子词到内容计划。

查看 Pro →