Google 是大多数互联网公开信息的入口,它为一次查询返回的页面远不止一组蓝色链接。单一搜索结果页面可以承载自然列表、付费广告、"相关问题"框、知识面板、带地图的本地信息包以及一行相关搜索,每个模块都是一组独立的结构化数据。对于做关键词研究、排名追踪、竞争对手分析或市场情报的人来说,这个页面是互联网上最丰富的公开数据集之一。

本指南是关于如何抓取 Google 搜索页面的综合性文章:SERP 的实际构成、为什么普通 HTTP 请求会被拦截、收集数据的可行方法,以及一个可以解析各功能模块并实现分页的可运行示例。如果你更需要一份简洁、可直接复制粘贴的 Python 教程,我们专注于如何使用 Python 抓取 Google 搜索结果的配套文章正是你需要的。本文的内容范围限定在任何人无需账号即可看到的公开 SERP 数据,末尾的法律部分并非套话,请在对任何真实流量执行操作之前先行阅读。

你将构建什么

完成本指南后,你将拥有一个小型 Python 脚本,它能将公开的 Google 搜索 URL 发送至具备 SERP 解析能力的 API,接收解析后的 JSON,并将每个功能模块读取为整洁的记录。我们以 "data science" 为贯穿全文的示例查询,并从响应中提取以下字段:

  • 搜索结果。自然列表,每条包含标题、目标 URL、显示的摘要以及在页面上的排名位置。
  • 广告。当查询具有商业意图时,显示在自然结果上方或下方的赞助列表。
  • 相关问题(People Also Ask)。可展开的相关问题框,每条包含问题本身、一段简短答案以及来源链接。
  • 相关搜索。Google 在页面底部显示的建议延伸查询。
  • 本地信息包。当查询具有本地意图时,带地图的附近地点模块。
  • 结果数量。报告的结果总数,用于驱动分页。

Google 搜索结果页面的结构剖析

要想抓好 SERP,首先需要对其组装方式有一个清晰的认识。Google 返回的并非一个统一列表,它将页面组合自多个独立模块,哪些模块出现取决于查询的意图。同一个解析器必须能识别每一个模块。以下是值得关注的各个部分。

  • 自然结果。已排名网页的核心列表。每条包含标题、目标 URL、摘要,有时还有日期或站内链接。这是大多数 SEO 工作关注的模块,因为在这里的排名位置就是"排名"的含义。
  • 广告。标有"赞助商"的付费列表,在商业性查询中显示于页面顶部和底部。它们外观与自然结果相似,但属于购买所得,因此竞争情报工作将其视为独立数据流。
  • 相关问题(PAA)。可展开的相关问题列表。每条展开后会显示一段简短答案及其来源页面的链接。PAA 是了解搜索者围绕某主题所提问题的有用地图。
  • 知识面板。右侧的信息框(针对人物、公司、地点、事物),由 Google 知识图谱构建。它在不需要用户点击的情况下呈现关键事实、图片和相关实体。
  • 本地信息包。当查询具有本地意图时(如"附近的咖啡店"),显示的地图加上附近商家的简短列表。包含名称、评分和地址,与完整的 Google 地图结果不同。
  • 相关搜索。页面底部的建议查询行,用于关键词扩展。
  • 分页。用于深入结果集的控件,通过 URL 中的 start 偏移量而非页码来驱动。

实际要点是,"抓取 Google"实质上是抓取恰好共享一个页面的若干独立模块。一个好的提取器将每个模块作为独立列表返回,以便在下游分别使用。如果你想深入了解问题框这一模块,可以参阅如何抓取 Google 相关问题

为什么普通请求在 Google 上会失败

如果你从脚本向 https://www.google.com/search?q=... 发起一个裸 HTTP 请求,几乎不会得到你在浏览器中看到的页面。Google 是网络上防御最为严密的目标之一,原因有两点且相互叠加。

首先,页面是动态的。现代 SERP 的大部分内容,尤其是各功能模块,是在初始 HTML 加载完成后通过 JavaScript 组装的。原始的 requests.get 只能看到一个骨架,而非渲染后的结果,因此你想要的数据根本不在你收到的字节里。其次,Google 会严密监控自动化操作。一个缺少真实浏览器请求头、来自数据中心 IP,或请求速度快于人工输入的请求,会遭遇同意声明页、CAPTCHA 或彻底拦截。持续从单一地址抓取会导致该地址被快速限速。

因此,一个有效的 Google 抓取器需要在单次请求中同时具备两项能力:一个让平台视为普通访客的 IP,以及在页面依赖脚本时能够渲染它的浏览器。你可以自行组合无头浏览器加轮换住宅代理池,但维持这套设施正常运行才是绝大部分工作量和成本所在。另一个选择是使用托管式 SERP API,将渲染、IP 轮换和 CAPTCHA 处理集成到单次调用中,并将解析好的数据直接返回给你。我们接下来会分析这两种方式的取舍。

抓取 Google 的方法:托管 API 与自建方案

在任何真实规模下,有两种可行的方式,都值得客观评价。

自建方案。你运行一个无头浏览器(Playwright 或 Selenium)来渲染页面,通过轮换住宅代理池进行路由以防止单个 IP 耗尽,添加请求头和指纹管理使流量看起来像人工访问,并为仍然受到挑战的请求构建重试和 CAPTCHA 破解逻辑。这个方案可行,对于一次性的学术项目可能是正确选择。问题在于维护成本:代理失效、指纹过时、Google 更新页面布局、CAPTCHA 破解器需要持续维护。抓取器本身是简单的部分;保持不被封锁才是真正的工作。我们关于不被封锁地抓取网站绕过 CAPTCHA 的指南对这套技术栈有深入介绍。

托管 API。你将 URL 发送到一个已拥有住宅 IP 池、渲染层和 CAPTCHA 处理能力的接口,它返回处理完毕的 HTML,或者更好的是,返回将 SERP 解析成 JSON 的结果。你完全跳过基础设施,按成功请求付费。代价是你依赖某个提供商及其解析器。对于大多数生产环境下的排名追踪和研究工作,这是务实的选择,因为你本来用于维护代理池的时间,价值远超每次请求的费用。以下示例使用的是Crawlbase Crawling API,它内置了 Google SERP 解析器。

本指南其余部分采用托管 API 方案,因为这样可以展示真实的解析输出,而无需 200 行代理配置代码。解析概念(哪个模块是哪个、字段如何映射)在你选择自建方案时同样适用,无需修改。

前置条件

在编写任何代码之前,你需要准备几样东西。都不会花太长时间。

基础 Python。你应该能够编写和运行 Python 脚本,并使用 pip 安装包。如果你想复习自建方案的解析部分,我们的 Python BeautifulSoup 使用指南涵盖了基础知识。

Python 3.8 或更高版本。使用 python --version 确认你的版本。如果没有,可从 python.org 安装或通过 Anaconda 等发行版获取。

Crawlbase 账号和 token。注册后打开控制台,复制你的请求 token。前 1,000 次请求免费,无需信用卡,且只对成功的请求计费。请像对待密码一样保管 token:它用于验证你的请求,因此不要将其提交到版本控制中。

设置项目

创建虚拟环境以隔离项目依赖,然后安装轻量级的 Crawlbase Python 封装包,它负责向 API 发送请求并读取响应。

bash
python --version

python -m venv serp_env
source serp_env/bin/activate

pip install crawlbase

在 Windows 上,使用 serp_env\Scripts\activate 代替 source 行来激活环境。crawlbase 包是 API 的轻量、无依赖封装:使用 token 初始化后,传入 URL 调用 get 方法即可。我们后续用于存储结果的 SQLite 随 Python 一同附带,无需额外安装。

步骤 1:通过 Crawling API 获取 SERP

Google 搜索页面可以通过 Crawling API 的普通 token 正常获取,因为解析器读取的是服务器渲染的标记。使用 token 初始化客户端,将其指向搜索 URL,并请求 JSON 响应格式,从而得到整洁的响应信封而非原始 HTML。

python
from crawlbase import CrawlingAPI
import json

# Initialize the Crawling API with your Crawlbase token
api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

# URL of the Google search page you want to scrape
google_search_url = "https://www.google.com/search?q=data+science"

# Ask the API for a JSON response envelope
options = {"format": "json"}

response = api.get(google_search_url, options)

if response["headers"]["pc_status"] == "200":
    body = json.loads(response["body"].decode("latin1"))
    print(json.dumps(body, indent=4, sort_keys=True))
else:
    print("Failed to retrieve the page. Status:", response["status_code"])

API 返回一个带有两个状态字段和一个 body 的 JSON 信封。pc_status 是 Crawlbase 状态,original_status 是 Google 本身返回的状态;对 pc_status == "200" 进行判断,意味着拦截或中间页会作为失败暴露出来,而不是将错误数据传递至下游。body 以字节形式返回,因此在 json.loads 之前需要用 latin1 解码。运行此脚本会打印原始页面信封,在你添加解析逻辑之前,这一步可确认获取是否成功。

Crawlbase Google Scraper

pc_status 返回 200,正是因为请求以普通访客身份到达了 Google。Crawling API 从轮换住宅 IP 获取 SERP,在功能模块需要浏览器时进行渲染,并处理否则会阻断原始请求的 CAPTCHA,让你无需自行运行无头浏览器集群和住宅代理池。先在免费层将其指向公开搜索 URL 试试看。

步骤 2:解析 SERP 功能模块

手动读取原始 HTML 并为每个功能模块编写选择器在技术上可行,但 Google 频繁更新布局且类名经过混淆,手写选择器会不断失效。Crawling API 内置了 google-serp 爬虫,可以替你完成解析并将每个模块作为整洁列表返回。只需一个选项即可启用。

python
from crawlbase import CrawlingAPI
import json

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

google_search_url = "https://www.google.com/search?q=data+science"

# The google-serp scraper parses the page into structured JSON
options = {"scraper": "google-serp"}

response = api.get(google_search_url, options)

if response["headers"]["pc_status"] == "200":
    parsed = json.loads(response["body"].decode("latin1"))
    serp = parsed["body"]

    # Each feature block comes back as its own key
    organic = serp.get("searchResults", [])
    ads = serp.get("ads", [])
    paa = serp.get("peopleAlsoAsk", [])
    related = serp.get("relatedSearches", [])
    local = serp.get("snackPack", {})
    total = serp.get("numberOfResults", 0)

    print(f"Organic results: {len(organic)}")
    print(f"Ads: {len(ads)}")
    print(f"People Also Ask: {len(paa)}")
    print(f"Related searches: {len(related)}")
    print(f"Total results reported: {total}")
else:
    print("Failed to retrieve the page. Status:", response["status_code"])

google-serp 爬虫返回一个 body 对象,其键直接对应结构剖析部分的功能模块。searchResults 是自然列表,ads 是赞助列表,peopleAlsoAsk 是问题框,relatedSearches 是建议行,snackPack 是带地图链接和地点结果的本地信息包。numberOfResults 是报告的总数,你将用它来驱动分页。searchResults 中的每条包含 titleurldescriptionposition,无需任何选择器工作即可直接使用。

出现哪些模块取决于查询

商业性查询会显示广告;信息性查询会填充相关问题框;本地性查询会填充本地信息包。始终使用 .get(key, default) 读取每个键,这样缺失的模块会返回空列表,而不是抛出 KeyError。同一查询在不同日期也可能随 Google 调整布局而返回不同的模块。

步骤 3:组装完整脚本

现在将获取和解析整合为一个可运行脚本,将自然结果和问题框提取为平铺记录并写入 JSON。这就是你在真实任务中会扩展的基础形态。

python
from crawlbase import CrawlingAPI
import json

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
options = {"scraper": "google-serp"}

def fetch_serp(url):
    response = api.get(url, options)
    if response["headers"]["pc_status"] != "200":
        raise Exception(f"Unable to crawl '{url}'")
    parsed = json.loads(response["body"].decode("latin1"))
    return parsed["body"]

def extract(serp):
    results = []
    for item in serp.get("searchResults", []):
        results.append({
            "position": item.get("position"),
            "title": item.get("title"),
            "url": item.get("url"),
            "description": item.get("description"),
        })
    questions = [q.get("title") for q in serp.get("peopleAlsoAsk", [])]
    related = [r.get("title") for r in serp.get("relatedSearches", [])]
    return {
        "searchResults": results,
        "peopleAlsoAsk": questions,
        "relatedSearches": related,
        "numberOfResults": serp.get("numberOfResults", 0),
    }

def main():
    url = "https://www.google.com/search?q=data+science"
    serp = fetch_serp(url)
    data = extract(serp)
    with open("google_results.json", "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    print(f"Saved {len(data['searchResults'])} organic results")

if __name__ == "__main__":
    main()

使用 python main.py 运行。fetch_serp 函数获取解析后的 SERP,任何非 200 状态都会引发异常,确保失败时明显可见;extract 将你关心的模块平铺为单个对象。更换 URL 中的查询词,同样的两个函数就能处理返回的所有内容。要追踪每个相关问题的完整答案和来源,响应中每条记录的 descriptiondestination 字段已经包含了这些信息,你可以在不发起额外请求的情况下扩展 extract

输出示例

google-serp 爬虫返回一个以功能模块为键的结构化对象。以下是 "data science" 查询返回内容的精简示例,让你在编写代码之前了解字段名称。

json
{
  "numberOfResults": 2520000000,
  "ads": [],
  "peopleAlsoAsk": [
    {
      "position": 1,
      "title": "What exactly does a data scientist do?",
      "description": "A data scientist uses data to understand and explain the phenomena around them...",
      "destination": { "text": "Coursera", "url": "https://www.coursera.org/articles/what-is-a-data-scientist" }
    }
  ],
  "relatedSearches": [
    { "title": "data science jobs", "url": "https://google.com/search?q=Data+science+jobs" }
  ],
  "searchResults": [
    {
      "position": 1,
      "title": "What is Data Science?",
      "url": "https://www.ibm.com/topics/data-science",
      "description": "Data science combines math and statistics, specialized programming..."
    }
  ],
  "snackPack": { "mapLink": "", "results": [] }
}

每个模块都有自己的键,你可以只取 searchResults 用于排名追踪,只取 peopleAlsoAsk 用于内容研究,或只取 ads 用于竞争分析。由于对于 "data science" 这样的信息性查询,广告和本地模块均为空,你的代码应始终以默认方式读取每个键,而不是假定它们一定存在。

跨页面分页

一页只是演示;真正的 SERP 工作需要深入多页。Google 使用 start 查询参数进行分页,这是结果列表的偏移量,而非页码。每页约有九到十条自然结果,start=10 是第二页,start=20 是第三页,以此类推。解析器返回的 numberOfResults 字段是你的上界,但实际上 Google 对单次查询很少提供超过几百条结果。

python
import time

base = "https://www.google.com/search?q=data+science"
all_results = []

for page in range(3):
    start = page * 10
    url = f"{base}&start={start}"
    serp = fetch_serp(url)
    all_results.extend(extract(serp)["searchResults"])
    time.sleep(2)  # pace the crawl

print(f"Collected {len(all_results)} results across 3 pages")

用偏移量构建每个页面 URL,通过 API 获取并用同一函数解析。保持长时间运行健康的一个好习惯是控制节奏:在请求之间加入短暂的 sleep,使流量分散而不是密集发送。要持久化收集的内容,可将每条记录的标题、URL、描述和排名位置写入数据库;SQLite 是最简单的选项,它随 Python 附带,无需服务器。你还可以将这些行保存为 CSV 供电子表格使用,或加载到你使用的任何分析工具中。

扩展规模与保持不被封锁

从少量查询扩展到数千个,问题就从"解析页面"变成了"长期保持不被封锁"。以下几个习惯能让长时间运行的任务保持稳定。

  • 控制节奏并保持变化。将请求分散开来,并在多个查询间轮换,而不是以最快速度翻页。密集、重复的流量是最快触发挑战的方式。
  • 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,使任何一个都不会触发限制。Crawling API 为你处理这些;如果你自建方案,这是需要重点投入的部分。我们关于轮换代理抓取 Google 搜索结果的指南有深入介绍。
  • 失败重试免费。Crawlbase 不对失败的请求收费,因此被封锁或不可用的 URL 重试不产生成本。构建带退避的小型重试机制,而不是直接丢弃页面。
  • 关注布局变化。Google 频繁更新 SERP。托管解析器能为你吸收大部分变化,但如果某个字段开始返回空值,布局很可能已经移动,在认为代码有问题之前先检查响应结构。

如果你更愿意通过轮换池路由自己的流量而不使用托管 API,Smart AI Proxy 为你提供与住宅 IP 轮换相同的能力,作为直接替换的代理端点。对于需要同时展开大量查询的任务,异步爬虫允许你将 URL 排入队列并在完成时收集结果,而不必对每个请求串行等待。对于同意声明和验证页面这一具体问题,我们关于抓取 Google 时绕过 CAPTCHA 的教程比本文所能介绍的更为深入。

抓取 Google 搜索结果合法吗?

抓取 Google 是否被允许,取决于 Google 的服务条款、你所在的司法管辖区以及你对数据的使用方式。抓取公开搜索结果页面,即任何人无需登录即可看到的标题、链接、摘要和排名,在若干司法管辖区已被法院视为可收集的公开数据。然而,Google 的服务条款限制对其服务的自动化访问,因此无论你的工具多么谨慎,抓取行为都可能违反这些条款。这里的任何代码都不改变这一事实,它只是使技术部分可行。请阅读 Google 的服务条款及其 robots.txt,并将两者视为你收集什么以及收集速度的边界。

以下几条值得坚守。只收集公开的 SERP 数据,远离任何需要登录的内容、个人隐私数据,以及你打算从链接目标重新发布的受版权保护的媒体。将你的请求量控制在不会对 Google 基础设施造成压力的范围内,控制爬取节奏而不是全速运行。目标是用于研究、SEO 和市场分析的结构化公开数据,而不是批量转载 Google 的索引。

在存在官方 API 的情况下,优先使用它。Google 为特定需求提供了经过授权的产品:用于在定义范围内进行程序化搜索的 Custom Search JSON API,以及用于自有网站性能数据的 Search Console。这些产品在明确的条款内提供结构化访问。它们无法覆盖 SERP 爬虫的所有使用场景,这正是公开数据抓取持续存在的原因,但当你的项目适合使用官方接口时,那才是更干净的路径。如果你在权衡更广泛的取舍,我们关于抓取 Google 搜索结果的挑战的文章值得一读。

回顾

核心要点

  • SERP 由多个模块组成,而非单一列表。自然结果、广告、相关问题、知识面板和本地信息包是独立的模块,请分别解析每个模块。
  • 普通请求会被拦截。Google 使用 JavaScript 渲染功能模块,并对数据中心流量发起挑战,因此你需要来自可信住宅 IP 的已渲染页面。
  • 托管 API 或自建方案。具备 SERP 能力的 API 将渲染、IP 轮换和 CAPTCHA 处理集成到一次调用中;自建方案可行,但维护才是真正的成本。
  • 用 start 偏移量分页。以 10 为步长增加 start 以深入结果,以 numberOfResults 作为上界,并在请求之间加入 sleep 控制节奏。
  • 坚守公开数据。遵守 Google 的服务条款和 robots.txt,避免登录和个人数据,保持合理请求量,并在适合时优先使用官方 API。

常见问题

Google SERP 由哪些内容组成?

搜索引擎结果页面由多个独立模块组合而成:自然结果(已排名的网页列表)、广告(商业性查询中的付费列表)、相关问题(可展开的问题框)、知识面板(来自 Google 知识图谱的实体信息框)、本地信息包(地图加附近商家)以及相关搜索。哪些模块出现取决于查询意图,因此一个好的爬虫会独立读取每个模块,并能容忍缺失的模块。

为什么普通请求在 Google 上会失败?

两个原因叠加。现代 SERP 的大部分内容,尤其是功能模块,使用 JavaScript 渲染,因此不执行脚本的原始 HTTP 请求只能得到骨架而非完整页面。Google 还会严密监控自动化操作,对来自数据中心 IP 或缺少真实浏览器请求头的请求,会返回同意声明页或 CAPTCHA。通过使用轮换住宅 IP 和渲染能力的 SERP API 进行获取,可以让请求看起来像普通访客。

我应该使用托管 API 还是自建爬虫?

对于一次性项目,自建方案(无头浏览器加轮换代理)可以胜任。对于生产环境的排名追踪或规模化研究,托管 API 通常是务实选择,因为持续工作在于保持爬虫不被封锁,而非编写它:代理失效、指纹过时、Google 更新布局。解析概念在两者之间可以迁移,因此选择主要取决于谁来维护基础设施。

如何专门使用 Python 抓取 Google 搜索结果?

本文示例使用 Crawlbase Python 封装包配合 google-serp 爬虫,无需编写选择器即可获取解析后的 JSON。如需一份从头到尾逐步构建请求和解析的 Python 教程,请参阅我们专注于如何使用 Python 抓取 Google 搜索结果的配套文章,它是本综合指南的配套教程。

如何分页获取更多 Google 结果?

使用 start 查询参数,它是结果的偏移量而非页码。每页约有十条自然结果,start=10 是第二页,start=20 是第三页,以此类推。用偏移量构建每个 URL,通过 API 获取,用同一函数解析,并在请求之间暂停几秒以控制爬取节奏而不是猛烈抓取。

抓取 Google 搜索结果合法吗?

公开 SERP 数据(任何人无需账号即可看到的标题、链接、摘要和排名)通常被视为可收集的数据,但 Google 的服务条款限制自动化访问,因此抓取可能与这些条款相冲突。请阅读 Google 的条款和 robots.txt,避免登录和个人数据,保持合理请求量,并在适合你的使用场景时优先使用 Custom Search JSON API 或 Search Console 等官方接口。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量