Google 每天处理数十亿次搜索,每次查询背后的结果页面是某个词语排名情况的结构化快照:自然结果的标题、它们指向的链接,以及每条下方的摘要文本。这使得公开的 Google SERP 成为关键词研究、排名追踪和竞争对手分析的有用信号。数据就摆在结果页面上,关键在于如何稳定地获取它。
这正是一份聚焦、可运行的操作教程。你将设置 Python,通过 Crawling API 获取已渲染的 Google 结果页面,将每条自然结果解析为标题、链接和摘要,翻页获取更深层的结果,并导出为 JSON 和 CSV。本教程范围限定在任何人无需账号即可看到的公开搜索结果数据。若想全面了解 SERP 结构、多种方案和扩展策略,请阅读综合指南如何抓取 Google 搜索页面;本文专注于交付可用的爬虫。
你将构建什么
一个 Python 脚本,接收一个搜索查询词,通过 Crawling API 获取已渲染的 Google 结果页面,并为页面上的每条自然结果返回一条整洁的记录。我们将使用一个示例查询作为贯穿全文的例子,并从每条结果中提取以下字段:
- 排名位置:结果在页面上的排名,从顶部开始计数。
- 标题:结果的标题文字,与列表中显示的一致。
- 链接:结果指向的目标 URL。
- 摘要:标题下方显示的描述或摘要。
- 相关搜索:Google 在页面底部列出的建议延伸查询。
为什么普通请求在 Google 上会失败
从脚本向 google.com/search 发起一个裸 HTTP 请求,几乎不会得到你在浏览器中看到的页面。两个问题与你作对。首先,Google 现在依赖 JavaScript 来渲染结果页面:截至 2025 年,SERP 需要启用脚本才能加载,因此不执行 JavaScript 的普通请求只能得到一个空壳而非列表。其次,Google 会监控自动化流量。来自单个 IP 的过多请求、请求速度过快,或不像真实浏览器的请求,都会遭遇 CAPTCHA、限速或拦截。
因此,一个有效的 Google 爬虫需要在单次请求中同时具备两项能力:一个被搜索引擎视为普通访客的 IP,以及一个能渲染页面的浏览器。你可以自行组合无头浏览器加轮换住宅代理池,但维持这套设施健康运行才是绝大部分工作量。Crawling API 将两者集成到单次调用中。它还内置了 google-serp 爬虫,能从可信的轮换 IP 获取已渲染页面,并将自然结果解析为 JSON,让你无需针对 Google 频繁变化的标记编写脆弱的选择器。
前置条件
在编写任何代码之前,你需要准备几样东西。都不会花太长时间。
基础 Python。你应该能够编写和运行 Python 脚本,并使用 pip 安装包。如果你对网络爬取还比较陌生,我们的使用 Python 抓取网站指南涵盖了本教程所假设的基础知识。
Python 3.8 或更高版本。使用 python --version 确认你的版本。如果没有,可从 python.org 安装或通过 Anaconda 等发行版获取。任何编辑器都可以;VS Code、PyCharm 和 Jupyter 都适用。
Crawlbase 账号和 token。注册后打开控制台,复制你的请求 token。前 1,000 次请求免费,无需信用卡,内置的 google-serp 爬虫可与普通 token 配合使用。请像对待密码一样保管 token:它用于验证你的请求,因此不要将其提交到版本控制中。
设置项目
创建虚拟环境以隔离项目依赖,然后安装 Crawlbase Python 库,它将 Crawling API 封装在一个小型客户端中。
python --version python -m venv google_env source google_env/bin/activate pip install crawlbase
在 Windows 上,使用 google_env\Scripts\activate 代替 source 行来激活环境。crawlbase 包为你提供了一个 CrawlingAPI 客户端,无需手动构建请求 URL。由于 google-serp 爬虫返回的是解析后的 JSON,本教程中处理自然结果时甚至不需要 HTML 解析库。
步骤 1:通过 Crawling API 获取已渲染的 SERP
先获取数据。编写一个小的 scrape_google_results() 函数,它构建搜索 URL,启用 google-serp 爬虫后将其发送至 Crawling API,检查状态,并返回解析后的 body。Google 以 10 条为一组进行分页,因此该函数接受页码并将其转换为 Google 在 URL 中期望的 start 偏移量。
import json from urllib.parse import quote_plus from crawlbase import CrawlingAPI # Replace with your token from the Crawlbase dashboard crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def scrape_google_results(query, page=0): encoded = quote_plus(query) url = f"https://www.google.com/search?q={encoded}&start={page * 10}" options = {"scraper": "google-serp"} response = crawling_api.get(url, options) if response["headers"]["pc_status"] == "200": data = json.loads(response["body"].decode("latin1")) return data.get("body", {}) print(f"Failed to fetch results for '{query}' (page {page}).") return {} if __name__ == "__main__": results = scrape_google_results("web scraping tools", page=0) print(json.dumps(results.get("searchResults", [])[:2], indent=2))
options 字典是关键所在:传入 {"scraper": "google-serp"} 告诉 Crawling API 渲染页面、绕过机器人检测,并返回已解析的 SERP。客户端返回一个响应,其 headers["pc_status"] 是 Crawlbase 对本次爬取的状态,因此在解析前判断 "200" 可以让拦截或渲染失败明显显示,而不是将错误数据传入下游。body 以字节形式返回,用 latin1 解码后以 JSON 加载,然后从 body 键中读取解析后的 SERP。运行脚本,你应该会看到前两条自然结果以 JSON 形式打印出来,这确认了获取和解析在继续构建之前已正常工作。
pc_status 返回 200,正是因为请求以真实访客身份到达了 Google,页面已渲染且机器人检测已处理,之后 google-serp 爬虫才将其转换为你刚刚打印出的 JSON。Crawling API 在服务器端进行 JavaScript 渲染并轮换住宅 IP,让你无需自行运行无头浏览器集群和代理池。先在免费层将其指向公开结果 URL 试试看。
步骤 2:将自然结果解析为整洁记录
google-serp 爬虫返回已结构化的 SERP,因此解析只需读取你想要的字段,而无需编写 CSS 选择器。自然列表位于 searchResults 键下,每条包含 position、title、url 和 description。页面还提供 relatedSearches,爬虫在查询触发时还会暴露 ads、peopleAlsoAsk 和本地 snackPack。编写一个小函数,挑选你关心的字段并规范化摘要字段名。
def parse_results(serp): organic = [] for item in serp.get("searchResults", []): organic.append({ "position": item.get("position"), "title": item.get("title"), "link": item.get("url"), "snippet": item.get("description"), }) related = [r.get("title") for r in serp.get("relatedSearches", [])] return {"organic": organic, "relatedSearches": related}
这保留了大多数排名追踪和研究任务所需的四个字段:直接来自 Google 的 position、title、目标 link(从结果的 url 字段读取)以及 snippet(从 description 读取)。免费附带 relatedSearches 为关键词扩展提供了建议延伸查询。由于爬虫完成了字段提取,你可以免受 Google 标记变化的影响,只需始终访问这些稳定的 JSON 键。
你可以获取原始渲染 HTML 并用 BeautifulSoup 解析,但 Google 的结果容器类名是经过混淆的且频繁变化,手写选择器会频繁失效。google-serp 爬虫返回稳定的 JSON 键,如 searchResults、position 和 relatedSearches,维护成本低得多。如果你想了解选择器方法用于其他网站,我们的 XPath 和 CSS 选择器指南有详细介绍。
步骤 3:处理分页
一页是演示;真正的任务需要深入更多结果。Google 通过 start 参数以 10 条为一组分页,第二页是 start=10,第三页是 start=20,以此类推,scrape_google_results 中页码到偏移量的转换已经处理了这一点。遍历一个页码范围,当某页为空时提前停止,并将所有自然记录收集到一个列表中。在循环中加入短暂的 sleep 可以让长时间运行保持健康。
import time def scrape_all_pages(query, max_pages=3): all_organic = [] for page in range(max_pages): print(f"Scraping page {page + 1}...") serp = scrape_google_results(query, page) parsed = parse_results(serp) if not parsed["organic"]: print("No more results, stopping.") break all_organic.extend(parsed["organic"]) time.sleep(2) return all_organic
循环从第 0 页运行至 max_pages,获取每一页,解析并扩展运行列表。if not parsed["organic"]: break 判断会在某页没有自然结果时立即停止,防止你继续为列表末尾之后的空页付费。页面之间的 time.sleep(2) 将请求分散开来而不是密集发送,这是长时间运行保持不被封锁的最佳单一习惯。
步骤 4:组装完整脚本并导出
现在将获取、解析和分页整合为一个可运行脚本,并将输出写入 JSON 和 CSV。JSON 保留完整的嵌套结构;CSV 提供一个平铺表格,可在电子表格中打开进行快速排名检查。
import csv import json import time from urllib.parse import quote_plus from crawlbase import CrawlingAPI crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def scrape_google_results(query, page=0): encoded = quote_plus(query) url = f"https://www.google.com/search?q={encoded}&start={page * 10}" options = {"scraper": "google-serp"} response = crawling_api.get(url, options) if response["headers"]["pc_status"] == "200": data = json.loads(response["body"].decode("latin1")) return data.get("body", {}) print(f"Failed to fetch results for '{query}' (page {page}).") return {} def parse_results(serp): organic = [] for item in serp.get("searchResults", []): organic.append({ "position": item.get("position"), "title": item.get("title"), "link": item.get("url"), "snippet": item.get("description"), }) return organic def scrape_all_pages(query, max_pages=3): all_organic = [] for page in range(max_pages): print(f"Scraping page {page + 1}...") organic = parse_results(scrape_google_results(query, page)) if not organic: print("No more results, stopping.") break all_organic.extend(organic) time.sleep(2) return all_organic def save_json(rows, filename): with open(filename, "w", encoding="utf-8") as f: json.dump(rows, f, ensure_ascii=False, indent=2) def save_csv(rows, filename): fields = ["position", "title", "link", "snippet"] with open(filename, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(rows) if __name__ == "__main__": query = "web scraping tools" rows = scrape_all_pages(query, max_pages=2) save_json(rows, "google_results.json") save_csv(rows, "google_results.csv") print(f"Saved {len(rows)} results to JSON and CSV")
使用 python main.py 运行。它抓取 "web scraping tools" 的两页结果,将每条自然结果平铺到一个列表中,并同时写入 google_results.json 和 google_results.csv。要抓取不同词语,更改 query 字符串;要获取更深层的结果,增大 max_pages。设置 ensure_ascii=False 可使 JSON 文件中的非拉丁字符保持可读,而不是将其转义为 \u 序列。
输出示例
JSON 文件是一个有序的自然记录列表,每条包含排名位置、标题、链接和摘要,可直接导入数据库或排名追踪表格。
[ { "position": 1, "title": "Web Scraper - The #1 web scraping extension", "link": "https://webscraper.io/", "snippet": "The most popular web scraping extension. Start scraping in minutes." }, { "position": 2, "title": "ParseHub | Free web scraping - The most powerful web scraper", "link": "https://www.parsehub.com/", "snippet": "ParseHub is a free web scraping tool. Turn any site into a spreadsheet or API." } ]
CSV 文件以平铺表格的形式保存相同记录,每条结果一行,列为 position、title、link 和 snippet,这正是大多数电子表格式排名追踪所期望的格式。
跨查询扩展规模与保持不被封锁
上述脚本抓取单个查询的几个页面。生产任务通常需要运行大量查询,并可能随时间追踪,但结构不变:遍历你的查询列表,对每个查询调用 scrape_all_pages,并在保存前为每条记录打上来源查询的标签。Crawling API 在每次请求时处理渲染和 IP 轮换,因此扩展主要取决于你对自己的节奏控制和状态监控。以下几个习惯能让长时间运行保持健康。
- 控制请求节奏。在页面和查询之间保持 sleep,而不是密集循环发送。将流量分散开来是防止被挑战的最佳单一防御手段。
-
关注状态码。监控每个响应的
pc_status。一次开始失败的运行是在告诉你需要降速,而不是可以忽略的噪音。 - 对瞬时失败进行重试。来自 API 的任何 5XX 响应都不计费,因此重试失败的爬取不产生成本;在重试前进行短暂退避通常能解决问题。
- 依赖 IP 轮换。内置的住宅 IP 轮换将请求分散到众多真实用户地址。如果你想路由自己的流量,Smart AI Proxy 提供与之相同的轮换能力作为直接替换的代理端点。
更广泛的操作手册请参阅如何不被封锁地抓取网站。如果你还想拉取 Google 在页面中间显示的问题框,我们专注于抓取 Google 相关问题的指南专门介绍了该模块。
抓取 Google 搜索结果合法吗?
抓取 Google 是否被允许,取决于 Google 的服务条款、你所在的司法管辖区以及你对数据的使用方式。Google 的条款对自动化访问设有限制,因此无论你的工具多么谨慎,抓取行为都可能违反这些条款。这里的任何代码都不改变这一事实,它只是使技术部分可行。请阅读 Google 的条款和 robots.txt,并将两者视为你收集范围的边界。
以下几条值得坚守。只收集公开搜索结果数据:任何人在结果页面上无需账号即可看到的标题、链接、摘要和排名位置。将你的请求量控制在不会给 Google 服务器造成压力的范围内,控制爬取节奏而不是全速运行。不要抓取个人数据,不要获取登录墙后的内容,也不要重新发布你通过结果链接访问的受版权保护的媒体。这些是让工作保持合理性的界限。
如果你的项目需要经过授权的大规模访问,Google 为此提供了官方产品,包括 Custom Search JSON API 和通过 Google Cloud 进行的程序化搜索,这才是该规模下的正确路径,而不是更聪明的爬虫。本指南刻意将范围限定在公开 SERP 页面,因为这是保持工作干净的边界。有关 Google 在爬取者面前设置的具体障碍及如何负责任地处理它们的更多内容,请参阅我们关于抓取 Google 搜索结果的挑战的文章。
核心要点
- 普通请求在 Google 上会失败。2025 年的 SERP 需要 JavaScript 渲染,且 Google 会拦截看起来像爬虫的流量,因此你需要在一次请求中同时具备渲染能力和可信 IP。
-
google-serp 爬虫承担了繁重工作。传入
{"scraper": "google-serp"},Crawling API 就会渲染页面、处理机器人检测,并以解析后的 JSON 形式返回 SERP。 -
读取稳定的 JSON 键,而非选择器。从
searchResults中提取position、title、url和description,这样你就与 Google 的标记变化隔离开来。 -
用 start 偏移量分页。以 10 的倍数增加
start以深入结果,当某页返回空结果时停止,并在页面之间加入 sleep。 - 坚守公开数据。遵守 Google 的服务条款和 robots.txt,保持合理请求量,对高流量场景优先使用官方 Custom Search API,永远不要碰个人数据或登录后的数据。
常见问题
为什么普通请求从 Google 返回一个空页面?
截至 2025 年,Google 的结果页面需要 JavaScript 才能渲染,因此不执行脚本的裸 HTTP 请求只能得到一个空壳。Google 还会拦截看起来像自动化的流量。通过 Crawling API 使用 google-serp 爬虫进行获取,可以从可信的轮换 IP 渲染页面,并以解析后的 JSON 形式返回列表,这样你得到的是真实结果而非空页面。
我可以使用 Python 抓取 Google 搜索结果吗?
可以。使用 Crawlbase Python 库调用 Crawling API,启用内置的 google-serp 爬虫,就能直接从返回的 JSON 中读取标题、链接、摘要和排名位置。API 充当桥梁,从可信 IP 将你的请求送达 Google 并完成页面渲染,让请求顺利处理而不被拦截。若想全面了解 SERP 和其他方案,请参阅抓取 Google 搜索页面综合指南。
这与更广泛的 Google 抓取指南有何不同?
本文是聚焦的、可运行的 Python 教程:设置、获取、解析自然结果、分页和导出,附带可直接复制粘贴的代码。综合指南如何抓取 Google 搜索页面涵盖了完整的 SERP 结构(广告、相关问题、知识面板、本地信息包)、多种方案和扩展策略。如果你想立即获取代码,从这里开始;如果你想了解更广泛的全貌,则阅读综合指南。
如何分页获取更多 Google 结果?
Google 通过 start 查询参数以 10 条为一组分页:第二页是 start=10,第三页是 start=20,以此类推。scrape_google_results 函数将页码转换为该偏移量,scrape_all_pages 遍历页码范围,当某页没有自然结果时停止。在页面之间保持短暂 sleep 以控制爬取节奏。
抓取 Google 时需要自己处理 JavaScript 吗?
不需要。Google 的 SERP 需要 JavaScript 才能渲染,但 google-serp 爬虫在服务器端为你渲染页面,所以你不需要运行无头浏览器或管理渲染集群。只需发送附带爬虫参数的搜索 URL,就能获得完整渲染、已解析的结果。
如何存储抓取到的 Google 结果?
任何结构化格式都可以。本指南中的完整脚本同时写入 JSON 文件(保留嵌套结构)和 CSV 文件(提供包含排名位置、标题、链接和摘要的平铺表格,供电子表格使用)。从那里你可以将记录加载到数据库或排名追踪管道中。只收集公开搜索结果数据,避免任何登录墙后的内容。
大规模爬取任何站点,无需与基础设施对抗。
Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。
