手把手教你用Python+SerpApi搭建实时Google关键词排名查询工具
做 SEO 的朋友都知道,掌握自己网站在 Google 搜索中的关键词排名是日常工作的核心。市面上的排名追踪工具(如 Ahrefs、SEMrush)动辄上百美元/月,并且批量实时排名查询很难用,而实际上,我们完全可以借助 SerpApi 提供的搜索结果 API,用 Python 自己动手搭建一个轻量、灵活且免费(有额度)的排名查询工具。
本文将从零开始,手把手带你完成一个支持批量关键词查询、实时排名展示、CSV 导出的完整工具,并提供命令行和 Web 可视化两种使用方式。
1. 背景与工具选型
为什么要自己造轮子?
商业化的 SEO 排名追踪工具功能强大,但存在几个痛点:
- 价格高昂:Ahrefs Lite 每月 $99,SEMrush Pro 每月 $129.95,对个人站长和小团队来说成本不低。
- 灵活性不足:无法按自己的需求定制查询逻辑、输出格式和触发时机。
- 数据封闭:数据被锁在第三方平台,难以与自己的分析流程打通。
而 SerpApi 提供了一种"原子级"的方案——它直接返回 Google 搜索的结构化结果(JSON 格式),我们拿到数据后可以自由加工处理。
为什么选 SerpApi?
- 结构化数据:直接返回 JSON,包含自然搜索结果的标题、链接、排名位置、摘要等字段,无需自己解析 HTML。
- 稳定可靠:由 SerpApi 负责处理 Google 的反爬机制(代理、验证码等),开发者只需调接口。
- 免费额度:新注册账户每月提供 100 次免费搜索,足够个人使用或测试。
- 支持丰富参数:可以指定国家/地区、语言、设备类型、返回结果数量等。
2. SerpApi 账号准备
2.1 注册并获取 API Key
- 访问 serpapi.com 并注册账号。
- 注册成功后进入 Dashboard,在页面中可以看到你的 API Key,复制备用。
- 免费套餐每月提供 100 次搜索,付费套餐起步 $50/月,提供 5000 次搜索。
2.2 API 基本用法
SerpApi 的调用非常简单,只需向 https://serpapi.com/search.json 发送一个 GET 请求,带上参数即可:
GET https://serpapi.com/search.json?api_key=YOUR_KEY&engine=google&q=best+crm+software&gl=us&hl=en&num=100核心参数说明:
| 参数 | 说明 | 示例值 |
|---|---|---|
api_key | 你的 API Key | abc123... |
engine | 搜索引擎 | google |
q | 搜索关键词 | best crm software |
gl | 地理位置(国家代码) | us(美国) |
hl | 界面语言 | en(英语) |
num | 返回结果数量 | 100(最多100) |
device | 设备类型 | desktop / mobile |
2.3 返回数据结构
API 返回的 JSON 中,我们最关心的是 organic_results 字段,它是一个数组,每个元素代表一条自然搜索结果:
{
"organic_results": [
{
"position": 1,
"title": "Best CRM Software 2025 - Forbes Advisor",
"link": "https://www.forbes.com/advisor/business/best-crm-software/",
"displayed_link": "https://www.forbes.com › advisor › business",
"snippet": "Compare the best CRM software of 2025..."
},
{
"position": 2,
"title": "...",
"link": "...",
...
}
]
}我们的核心逻辑就是:遍历 organic_results,检查每条结果的 link 字段是否包含我们的目标域名,如果匹配,则该条目的 position 就是我们的排名。
3. 核心原理:如何通过 API 获取排名
整个查询流程可以用三步概括:
1. 发送请求 → SerpApi(关键词 + 地区 + 语言)
2. 解析返回 → 遍历 organic_results
3. 域名匹配 → 找到目标域名的 position域名匹配的关键逻辑
域名匹配看似简单,但有几个细节需要处理:
from urllib.parse import urlparse
def normalize_domain(domain: str) -> str:
"""标准化域名:去掉协议前缀、www 和尾部斜杠"""
domain = domain.lower().strip()
if domain.startswith(("http://", "https://")):
domain = urlparse(domain).hostname or domain
domain = domain.removeprefix("www.")
return domain.rstrip("/")
def extract_domain(url: str) -> str:
"""从完整 URL 中提取裸域名"""
try:
return urlparse(url).hostname.removeprefix("www.").lower()
except Exception:
return ""为什么需要标准化? 因为用户输入的域名可能是 example.com、www.example.com、https://example.com/ 等各种形式,而 Google 返回的链接也可能带或不带 www。统一标准化后才能准确匹配。
子域名匹配:我们还支持子域名匹配——如果目标是 example.com,那么 blog.example.com、docs.example.com 也会被识别为匹配结果。实现方式是检查 item_domain.endswith("." + target)。
4. 第一部分:命令行版本
命令行版本适合批量查询、定时任务和脚本集成。
4.1 环境准备
pip install requests richrequests:发送 HTTP 请求。rich:在终端中渲染美观的表格、进度条和彩色输出。
4.2 核心代码实现
单个关键词查询函数
import requests
SERPAPI_URL = "https://serpapi.com/search.json"
def query_keyword(api_key: str, keyword: str, num: int = 100) -> dict:
"""查询单个关键词,返回 SerpApi 完整响应"""
params = {
"api_key": api_key,
"engine": "google",
"q": keyword,
"gl": "us", # 固定美国地区
"hl": "en", # 英文界面
"num": num, # 返回结果数(查询深度)
"device": "desktop",
}
resp = requests.get(SERPAPI_URL, params=params, timeout=30)
resp.raise_for_status()
return resp.json()在结果中查找目标域名
def find_domain_in_results(data: dict, target_domain: str) -> dict | None:
"""遍历自然搜索结果,查找目标域名的排名"""
target = normalize_domain(target_domain)
organic = data.get("organic_results", [])
for item in organic:
link = item.get("link", "")
item_domain = extract_domain(link)
# 精确匹配 + 子域名匹配
if item_domain == target or item_domain.endswith("." + target):
return {
"position": item.get("position"),
"title": item.get("title", ""),
"link": link,
"snippet": item.get("snippet", ""),
}
return None # 在前 N 条结果中未找到批量查询主循环
import time
def run_check(api_key, domain, keywords, num=100, delay=1.0):
"""逐个查询关键词,收集排名结果"""
results = []
for i, kw in enumerate(keywords):
record = {"keyword": kw, "status": "error", "position": None}
try:
data = query_keyword(api_key, kw, num)
match = find_domain_in_results(data, domain)
if match:
record.update({"status": "found", **match})
else:
record["status"] = "notfound"
except requests.exceptions.HTTPError as e:
record["error"] = str(e)
except Exception as e:
record["error"] = str(e)
results.append(record)
# 请求间隔,避免触发 API 限流
if i < len(keywords) - 1:
time.sleep(delay)
return results注意 time.sleep(delay):每次请求之间加入 1 秒延迟。虽然 SerpApi 本身允许较高频率的请求,但加入间隔是一个好习惯,既保护 API 配额,也降低被限流的风险。
4.3 美化终端输出
使用 rich 库渲染彩色表格和实时进度条:
from rich.console import Console
from rich.table import Table
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich import box
console = Console()
def display_results(results, domain):
"""渲染结果表格"""
found = [r for r in results if r["status"] == "found"]
top10 = [r for r in found if r["position"] <= 10]
table = Table(box=box.ROUNDED, header_style="bold cyan")
table.add_column("#", width=4, justify="right")
table.add_column("关键词", min_width=20)
table.add_column("排名", width=6, justify="center")
table.add_column("状态", width=8, justify="center")
for i, r in enumerate(results, 1):
if r["status"] == "found":
pos = r["position"]
style = "bold green" if pos <= 3 else "cyan" if pos <= 10 else "yellow"
table.add_row(str(i), r["keyword"], f"[{style}]{pos}[/]", "[green]✓[/]")
elif r["status"] == "notfound":
table.add_row(str(i), r["keyword"], "—", "[yellow]✗[/]")
else:
table.add_row(str(i), r["keyword"], "!", "[red]ERR[/]")
console.print(table)4.4 导出功能
支持 CSV 和 JSON 两种导出格式:
import csv
from datetime import datetime
def export_csv(results, domain, filepath=None):
"""导出为 CSV 文件,支持 Excel 直接打开(UTF-8 BOM)"""
if not filepath:
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filepath = f"rank_{normalize_domain(domain)}_{ts}.csv"
with open(filepath, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["关键词", "排名", "状态", "标题", "链接", "摘要"])
for r in results:
writer.writerow([
r["keyword"],
r.get("position", "—"),
{"found": "已找到", "notfound": "未收录"}.get(r["status"], "出错"),
r.get("title", ""),
r.get("link", ""),
r.get("snippet", ""),
])
return filepath这里使用 utf-8-sig 编码写入 BOM(字节顺序标记),这样用 Excel 打开 CSV 文件时中文不会乱码。
4.5 使用方式
# 从文件读取关键词(每行一个)
python rank_checker.py \
--api-key YOUR_SERPAPI_KEY \
--domain example.com \
--keywords keywords.txt
# 直接指定关键词
python rank_checker.py \
--api-key YOUR_SERPAPI_KEY \
--domain example.com \
-k "best crm software" \
-k "free crm tools" \
-k "crm for small business"
# 指定查询深度和导出格式
python rank_checker.py \
--api-key YOUR_SERPAPI_KEY \
--domain example.com \
--keywords keywords.txt \
--num 50 \
--export csv终端输出效果示例:
╭──────── 查询配置 ────────╮
│ 域名: example.com │
│ 地区: Google US │
│ 深度: 前 100 条结果 │
│ 关键词: 5 个 │
╰──────────────────────────╯
查询中 ━━━━━━━━━━━━━━━━━ 5/5 · best crm software
╭──────── 查询统计 ────────╮
│ 已收录: 3/5 | Top 10: 2 │
╰──────────────────────────╯
╭────┬─────────────────────┬──────┬──────╮
│ # │ 关键词 │ 排名 │ 状态 │
├────┼─────────────────────┼──────┼──────┤
│ 1 │ best crm software │ 7 │ ✓ │
│ 2 │ free crm tools │ 3 │ ✓ │
│ 3 │ crm for small biz │ 23 │ ✓ │
│ 4 │ crm pricing │ — │ ✗ │
│ 5 │ top crm 2025 │ 12 │ ✓ │
╰────┴─────────────────────┴──────┴──────╯5. 第二部分:Web 可视化版本
命令行虽好,但视觉体验有限。我们再来做一个 Web 版本,提供更直观的操作界面。
5.1 为什么不能直接在浏览器中调 SerpApi?
这是一个很常见的坑:SerpApi 的接口不支持浏览器直接调用。原因是浏览器的同源策略(CORS)——从你的网页域名(如 localhost:3000)向 serpapi.com 发 AJAX 请求时,浏览器会检查响应头中是否包含 Access-Control-Allow-Origin,而 SerpApi 并未设置这个头,所以请求会被浏览器拦截。
解决方案:在本地搭建一个代理服务器。前端请求发到本地服务器,由服务器转发给 SerpApi,再将结果返回给前端。Python 的服务端请求不受 CORS 限制。
浏览器 → localhost:8765/api/search → Python 服务器 → serpapi.com → 返回 JSON5.2 架构设计
整个 Web 版本只有一个 Python 文件,它同时充当:
- 静态文件服务器:返回 HTML/CSS/JS 页面(内嵌在 Python 代码中)。
- API 代理:将前端的查询请求转发到 SerpApi。
import http.server
import urllib.request
import urllib.parse
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path.startswith("/api/search"):
self.handle_search() # 代理转发
else:
self.serve_html() # 返回页面
def handle_search(self):
"""将请求代理到 SerpApi"""
query_string = self.path.split("?", 1)[1]
params = urllib.parse.parse_qs(query_string)
serpapi_params = {
"api_key": params["api_key"][0],
"engine": "google",
"q": params["q"][0],
"gl": "us",
"hl": "en",
"num": params.get("num", ["100"])[0],
"device": "desktop",
}
url = f"https://serpapi.com/search.json?{urllib.parse.urlencode(serpapi_params)}"
req = urllib.request.Request(url)
with urllib.request.urlopen(req, timeout=30) as resp:
data = resp.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(data)5.3 前端核心逻辑
前端使用原生 JavaScript(零依赖),通过 fetch API 逐个查询关键词,每完成一个就立即刷新界面:
async function startQuery() {
const kwList = keywords.split('\n').filter(k => k.trim());
for (let i = 0; i < kwList.length; i++) {
results[i].status = 'loading';
renderResults(); // 立即刷新:显示"查询中"
const params = new URLSearchParams({
api_key: apiKey,
q: kwList[i],
num: String(num)
});
const resp = await fetch('/api/search?' + params);
const data = await resp.json();
const match = findDomain(data, domain);
results[i] = match
? { ...results[i], status: 'found', ...match }
: { ...results[i], status: 'notfound' };
renderResults(); // 再次刷新:显示结果
await sleep(800); // 间隔 800ms
}
}5.4 前端展示模块
页面顶部有 5 个统计卡片:
- 总查询数:已完成的查询数 / 总关键词数
- 已收录:找到排名的关键词数及其百分比
- Top 3:排在前 3 名的关键词数
- Top 10:排在首页的关键词数
- 平均排名:所有找到排名的关键词的平均位置
排名数字使用颜色编码来直观区分:
- 绿色:1-3 名(Top 3)
- 蓝色:4-10 名(首页)
- 橙色:11-30 名
- 白色:30 名以后
5.5 启动和使用
python rank_checker_web.py终端会显示:
╔══════════════════════════════════════════════╗
║ Google 排名追踪器 - Web 版 ║
║ SerpApi · US Region · Local Server ║
╠══════════════════════════════════════════════╣
║ 🌐 http://localhost:8765 ║
║ 按 Ctrl+C 停止服务器 ║
╚══════════════════════════════════════════════╝在浏览器中打开 http://localhost:8765,即可看到完整的可视化界面。
6. 关键技术细节解析
6.1 查询深度(num 参数)
num 参数控制 Google 返回的结果数量。设为 100 意味着查看前 100 条结果中是否有你的域名。
- num=10:只查首页,省配额但可能遗漏排在 11-100 的情况。
- num=100:覆盖前 100 名,最全面但消耗的搜索量也是 1 次(SerpApi 按请求次数计费,不按 num 计费)。
建议默认用 100,反正都是一次 API 调用的费用。
6.2 地区与语言
本工具固定查询 Google 美国地区(gl=us, hl=en),如果需要查询其他地区,只需修改参数:
| 地区 | gl | hl |
|---|---|---|
| 美国 | us | en |
| 英国 | uk | en |
| 中国(Google) | cn | zh-cn |
| 日本 | jp | ja |
| 德国 | de | de |
6.3 请求间隔与限流
代码中在每次请求之间加入了间隔(命令行版 1 秒,Web 版 0.8 秒)。这么做有两个原因:
- 保护 API 配额:避免短时间内大量请求被 SerpApi 判定为滥用。
- 稳定性:网络波动时给足缓冲时间。
如果你使用付费套餐且需要更快速度,可以将 delay 调低到 0.3-0.5 秒。
6.4 错误处理
工具对以下异常做了处理:
- HTTP 错误(401 认证失败、429 限流、500 服务器错误):记录错误信息并继续查询下一个关键词。
- 网络超时:默认 30 秒超时,超时后标记为出错。
- 中途取消(Web 版):使用
AbortController实现,点击"停止"后立即中断当前请求。
6.5 CSV 导出编码
导出 CSV 时使用 utf-8-sig 编码(带 BOM 的 UTF-8)。这是因为 Excel 默认用系统编码打开 CSV 文件,不带 BOM 的 UTF-8 文件中的中文会显示乱码。加上 BOM 后 Excel 会自动识别为 UTF-8 编码。
7. 进阶优化思路
如果你想在此基础上进一步扩展,以下是一些实用的优化方向:
7.1 定时自动查询
使用 cron(Linux/macOS)或 Task Scheduler(Windows)定时执行命令行版本,自动追踪排名变化:
# 每天早上 8 点执行查询并导出
0 8 * * * cd /path/to/tool && python rank_checker.py \
--api-key YOUR_KEY --domain example.com \
--keywords keywords.txt --export csv7.2 历史趋势记录
将每次查询结果追加到数据库(如 SQLite)或追加写入 CSV,然后用 matplotlib 或 Plotly 绘制排名趋势图:
import sqlite3
def save_to_db(results, domain):
conn = sqlite3.connect("rankings.db")
c = conn.cursor()
c.execute("""CREATE TABLE IF NOT EXISTS rankings (
date TEXT, domain TEXT, keyword TEXT,
position INTEGER, status TEXT
)""")
for r in results:
c.execute("INSERT INTO rankings VALUES (?, ?, ?, ?, ?)",
(datetime.now().isoformat(), domain,
r["keyword"], r.get("position"), r["status"]))
conn.commit()
conn.close()7.3 多地区对比
同一个关键词在不同地区的排名可能差异巨大。可以扩展为同时查询多个地区(us、uk、de 等),对比排名差异。
7.4 移动端排名
Google 的移动端和桌面端搜索结果排名不同。将 device 参数从 desktop 改为 mobile,即可查询移动端排名。可以同时查询两者进行对比。
7.5 竞品监控
不仅查自己的域名,还可以同时查竞争对手的域名。修改代码使其接受多个域名参数,一次查询就能看到你和竞品的排名对比。
8. 完整源码
本项目包含以下文件:
| 文件 | 说明 |
|---|---|
rank_checker.py | 命令行版本,支持批量查询、结果表格展示、CSV/JSON 导出 |
rank_checker_web.py | Web 可视化版本,内置本地服务器和前端页面 |
keywords_example.txt | 示例关键词文件 |
快速开始
# 安装依赖(Web 版无需额外依赖)
pip install requests rich
# 命令行版
python rank_checker.py --api-key YOUR_KEY --domain example.com --keywords keywords.txt --export csv
# Web 版
python rank_checker_web.py
# 浏览器打开 http://localhost:8765完整代码已在文末附上。你也可以基于这些代码,根据自己的需求做二次开发。
9. 总结
通过这篇教程,我们从零搭建了一个完整的 Google 关键词排名查询工具。回顾一下核心知识点:
- SerpApi 提供了结构化的 Google 搜索结果 API,免去了自己处理反爬的麻烦。
- 域名匹配需要做标准化处理,支持子域名匹配才能不遗漏结果。
- 浏览器 CORS 限制导致无法直接在前端调 SerpApi,解决方案是搭建本地代理服务器。
- 命令行版本适合自动化和脚本集成,Web 版本适合日常手动查询和团队使用。
- 通过定时任务 + 数据库可以实现排名趋势追踪。
这个工具虽然简单,但它的核心逻辑和商业 SEO 工具的排名查询模块是一致的。掌握了这些基础,你可以根据自己的需求不断扩展——无论是多地区对比、竞品监控还是自动化报告,都是在这个框架上的自然延伸。
希望这篇教程对你有帮助,祝你的网站排名节节攀升!