返回博客

如何在 Python 中使用 Google Search API 构建排名跟踪器

Isabella Garcia
Isabella Garcia

Web Data Collection Specialist

16-Sep-2026

TL;DR:

  • 一个排名跟踪器是一个时间序列管道,而不是一个SERP请求。 它必须保留查询、位置、语言、设备、搜索域、观察时间、排名URL和位置。
  • 当您需要原始SERP观察时使用搜索API。 当您还需要关键词发现、报告、警报和客户工作流时使用完整的SEO平台。
  • 在匹配目标域之前规范化主机名。 根据明确的策略处理 www.example.com、方案差异、路径和子域。
  • 将“未排名”记录为数据。 不要将缺失转换为零位置,也不要将昨天的位置向前推。
  • 存储排名URL以及排名。 当着陆页面更改时,域可以保持其位置。
  • 无抓取Google搜索API返回结构化搜索数据。 下面的Python实现请求SERP,解析自然结果,并写入仅附加的CSV快照。

排名跟踪器实际测量的内容

排名跟踪器观察一个目标域在特定时间的特定搜索结果集中出现的位置。

这个定义故意很狭窄。位置取决于查询、国家或位置、语言、Google域、设备、结果类型和分页深度。改变一个维度,观察就属于不同的系列。

一个值得信赖的记录应至少包含:

  • 关键词
  • 目标域
  • 观察的位置或明确的未排名状态
  • 排名URL
  • 结果标题
  • 国家或位置设置
  • 语言
  • 设备
  • Google域
  • 观察时间戳

跟踪器绝不应暗示采样的SERP是一个通用排名。个性化、实验、索引变化和区域差异是搜索的一部分。

搜索API与排名跟踪平台

搜索API和排名跟踪平台解决的是相关但不同的任务。

需求 搜索API 排名跟踪平台
原始自然结果记录 适用性强 通常通过平台模型可用
自定义匹配逻辑 完全控制 依赖于平台
自有数据库和仪表盘 由您构建 通常包含
关键词发现 单独工作流 通常包含
白标报告 由您构建 通常包含
不寻常的采样时间表 完全控制 计划依赖
与内部数据的集成 直接 导出或API依赖

当排名观察是内部产品、实验或数据仓库的一个输入时,选择API。当分析师需要现成的界面和报告工作流时,选择平台。

Google搜索API请求模型

无抓取Google搜索API接受搜索参数并返回结构化数据。当前的Google搜索API文档记录了常见参数,包括查询、国家、语言、Google域、结果类型、偏移量和结果计数。

在产品文案中使用当前的客户-facing名称Google搜索API。稳定的参与者名称和API路由可以保留旧的内部命名。

本指南使用的请求是一个 POST/api/v1/scraper/request 的请求,附加参与者 scraper.google.search。身份验证应在 x-api-token 头中。输入将查询和搜索设置结合在一起,以便每个响应都可以追踪到其采样配置。

在Python中构建跟踪器

下面的脚本完成四个任务:

  1. 每个关键词发送一个Google搜索API请求;
  2. 找到第一个与目标策略匹配的自然结果的主机名;
  3. 写入仅追加的CSV快照;
  4. 当目标未找到时保留空的位置信息和URL。

先决条件:实时请求需要在 SCRAPELESS_API_KEY 中提供无抓取API密钥。在进行身份验证请求之前,匹配、规范化和CSV逻辑可以使用保存的响应固定件在本地进行测试。

python Copy
import csv
import os
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urlparse, urlunsplit

import requests

API_URL = "https://api.scrapeless.com/api/v1/scraper/request"


def normalized_host(value: str) -> str:
    candidate = value if "://" in value else urlunsplit(("https", value, "", "", ""))
    host = (urlparse(candidate).hostname or "").lower().rstrip(".")
    return host.removeprefix("www.")


def host_matches(result_url: str, target_domain: str, include_subdomains=True) -> bool:
    result_host = normalized_host(result_url)
    target_host = normalized_host(target_domain)
    if not result_host or not target_host:
        return False
    return result_host == target_host or (
        include_subdomains and result_host.endswith(f".{target_host}")
    )


def organic_results(payload: dict) -> list[dict]:
    if isinstance(payload.get("organic_results"), list):
        return payload["organic_results"]
    data = payload.get("data", {})
    if isinstance(data, dict) and isinstance(data.get("organic_results"), list):
        return data["organic_results"]
    return []


def find_rank(payload: dict, target_domain: str) -> dict:
    for fallback_position, item in enumerate(organic_results(payload), start=1):
        url = item.get("link") or item.get("url") or ""
        if host_matches(url, target_domain):
            return {
                "position": item.get("position", fallback_position),
                "ranking_url": url,
                "title": item.get("title", ""),
            }
    return {"position": None, "ranking_url": "", "title": ""}


def fetch_serp(keyword: str, *, gl="us", hl="en", device="desktop") -> dict:
    api_key = os.environ["SCRAPELESS_API_KEY"]
    response = requests.post(
        API_URL,
        headers={"x-api-token": api_key, "Content-Type": "application/json"},
        json={
            "actor": "scraper.google.search",
            "input": {
                "q": keyword,
                "gl": gl,
                "hl": hl,
                "google_domain": "google.com",
                "device": device,
                "start": 0,
            },
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


def append_snapshot(path: Path, row: dict) -> None:
    fields = [
        "observed_at", "keyword", "target_domain", "position",
        "ranking_url", "title", "gl", "hl", "device", "google_domain",
    ]
    exists = path.exists()
    with path.open("a", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=fields)
        if not exists:
            writer.writeheader()
        writer.writerow(row)


def track(keyword: str, target_domain: str, output="rank_history.csv") -> dict:
    settings = {"gl": "us", "hl": "en", "device": "desktop"}
    payload = fetch_serp(keyword, **settings)
    match = find_rank(payload, target_domain)
    row = {
        "observed_at": datetime.now(timezone.utc).isoformat(),
        "keyword": keyword,
        "target_domain": normalized_host(target_domain),
        "position": match["position"] or "",
        "ranking_url": match["ranking_url"],
        "title": match["title"],
        **settings,
        "google_domain": "google.com",
    }
    append_snapshot(Path(output), row)
    return row


if __name__ == "__main__":
    print(track("web scraping api", "scrapeless.com"))

标准库 urlparse 文档 解释了为什么主机名解析应该使用URL解析器而不是字符串切片。该脚本仅移除前导 www.,并可选择接受子域;在跟踪多品牌域名资产之前,调整该策略。

验证位置解析器

在使用实时信用之前,保存一个来自账户的真实API响应,并对其运行解析器。至少要包含以下这些固定件:

固定件 预期结果
精确的顶级域 匹配
www. 版本 匹配
允许的子域 匹配
example.com.attacker.test 的类似域 不匹配
格式错误或缺失的结果URL 不匹配
从采样页面缺失的目标 位置为空;状态未排名

在API提供明确的 position 字段且未先理解分页之前,请勿根据列表顺序计算位置。在后续结果页面中,列表索引一并不是全局位置一。保留提供的位置信息或故意添加页面偏移量。

不重写地存储历史记录

只追加快照比一个可变的“当前排名”表更容易审计。稍后的转化可以为每个关键字和市场选择最新的一行。

CSV适用于个人跟踪器。生产服务应使用一个数据库键,以区分关键字、域名、国家或位置、语言、设备、谷歌域和观察时间。 SQLite表文档 足以满足一个紧凑的本地服务;当系列供稿仪表盘和警报时,仓库变得有用。有关超出此处使用的主机名策略的URL身份规则,请参阅URI通用语法标准

保持 positionranking_url。这些变化意味着不同的事项:

  • 位置变化,URL不变:相同的着陆页移动;
  • 位置不变,URL变化:谷歌选择了不同的页面;
  • 位置为空:在采样结果深度内未找到该域;
  • 来自该域的多个URL出现:存储最佳位置并可选择在详细表中保留每个匹配项。

处理地理、语言、设备和时间

将搜索设置视为维度,而非后添加的可选标签。

  • gl 表示国家上下文。
  • hl 控制界面语言。
  • google_domain 选择谷歌属性。
  • device 在支持时区分桌面和移动观察。
  • 精确的位置设置可以比国家更狭窄地建模市场。
  • 时间戳在存储时应使用UTC,仅为显示进行转换。

请勿将城市级系列与同一图表线下的国家级系列混合。同样,移动结果不应无声替换桌面观察。

抽样时间也很重要。在一个有限的窗口内运行可比较的关键字组。如果一批跨越多个小时,应存储每个请求的时间戳,而不是整个工作的一个日期。

在不发布过时价格的情况下计算成本

稳定的计算比复制的计划金额更有用:

monthly requests = keywords × markets × devices × pages sampled × runs per month

然后应用当前账户费率和故障处理政策。将计划请求与重复和失败的尝试分开,以便运营团队可以解释发票。在实施时检查 Scrapeless定价,而不是嵌入一个可能在代码之前就老化的数字。

开始使用Scrapeless抓取

利用Scrapeless提升您的网页抓取和自动化工作流程!
今天注册并获得**$5的免费信用**— 无需信用卡

现在在Scrapeless仪表盘中领取您的免费信用。

生产检查清单

  • 为解析器使用的字段固定一个模式合同。
  • 将API密钥保存在秘密管理器或环境变量中。
  • 仅在临时服务故障后重复有限请求;不要无限循环。
  • 在每个观察旁边存储请求设置。
  • 将未排名与请求失败区分开。
  • 跟踪排名URL,而不仅仅是数值位置。
  • 为解析器回归测试保留一个被编辑的响应样本。
  • 遵循适用的条款、隐私要求和当地法律。
  • 在缺失批次和模式漂移之前发出警报,而不是在SEO变动时发出警报。

结论

一个有用的排名跟踪器是一个有纪律的观察系统。Scrapeless谷歌搜索API提供结构化的SERP记录;其价值来自于明确匹配、完整的搜索维度、只追加的历史记录和对缺失结果的诚实处理。

从一个关键字、一个市场、一个设备和一个经过验证的样本开始。系列稳定后,扩大批次并将CSV或数据库连接到仪表盘。有关相关工作流程,请参阅 谷歌搜索API指南


构建您的第一个SERP快照

加入Scrapeless社区以获取实施帮助和数据管道模式: Discord · Telegram
app.scrapeless.com 创建一个免费账户,运行一个限制性查询,并在调度跟踪器之前验证保存的响应。


常见问题解答

问:什么是排名跟踪 API?

排名跟踪 API 提供搜索结果或排名观察,软件可以存储和分析。SERP API 返回原始结果记录;专用的排名跟踪 API 还可能提供项目、历史记录、警报和报告。

问:我如何找到我的域名在自然结果中的位置?

使用 URL 解析器解析每个自然结果的 URL,规范化主机名,应用明确的顶级域/子域策略,并返回第一个匹配结果的提供位置。避免子字符串匹配。

问:当域名缺失时,我应该存储什么位置?

存储一个空或空白的位置,外加一个明确的未排名状态,针对采样深度。不要使用零,也不要延续之前的观察。

问:排名跟踪器应该多长时间运行一次?

根据数据支持的决策选择一个频率。日常采样对于积极的 SEO 监测是常见的,而较慢的战略报告可能需要更少。一致性和可比较的设置比最大频率更重要。

问:这个脚本证明了一个普遍的 Google 排名吗?

不。它记录一个针对定义的查询、市场、语言、设备、域名、深度和时间设置的结构化观察。搜索结果可能会在该采样配置之外有所不同。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录