如何用 Python 建立可標記 AI Overviews 的關鍵字排名追蹤器
Building a Keyword Rank Tracker That Also Flags AI Overviews (Python)
SteadyScrape 説明如何用 Python 設計關鍵字排名追蹤器,記錄排名 URL、位置變化及 AI Overview 等 SERP 功能。追蹤記錄按關鍵字、地區、語言及裝置區分,並可比較多次執行以識別排名頁面更換和新增的 SERP 功能。
Rank tracking sounds simple: search a keyword, find your site, write down the position. The useful part is everything after that. Did it move? Did a different page of yours start ranking (cannibalization)? Did Google add an AI Overview that pushed your "position 2" below the fold?
This post walks through the design of a small rank tracker: what to store per keyword, how to diff runs, and which SERP features to record. The code works with any source of SERP data. At the end there's a hosted option.
Where the SERP data comes from
Don't scrape Google results yourself. It's against Google's terms, gets blocked quickly, and results vary by IP location. In practice everyone uses a SERP API: you send keyword + location + language + device and get back parsed results. Whatever provider you choose, make sure it returns:
- organic results with position and URL,
- the absolute position (counting ads, AI Overview, Local Pack and other blocks),
- a list of SERP features on the page.
The snapshot
Store one record per keyword, keyed by everything that changes the SERP:
def key(keyword, location, language, device):
return f"{keyword.lower()}|{location}|{language}|{device}"
def snapshot_row(keyword, serp, target_domain):
mine = [r for r in serp["organic"] if r["domain"] == target_domain
or r["domain"].endswith("." + target_domain)]
best = mine[0] if mine else None
return {
"keyword": keyword,
"position": best["position"] if best else None,
"url": best["url"] if best else None,
"otherUrls": [r["url"] for r in mine[1:]],
"features": sorted(serp.get("features", [])),
"top3": [r["domain"] for r in serp["organic"][:3]],
}
Count subdomains as you (make it a setting), and keep the URL, not just the position: most rank-tracking insights come from URL changes.
The diff
def compare(prev, cur):
if prev is None:
return {"changeStatus": "first_check", "positionChange": None}
p, c = prev["position"], cur["position"]
if p is None and c is None:
status, change = "not_ranked", None
elif p is None:
status, change = "new", None
elif c is None:
status, change = "lost", None
else:
change = p - c # positive = moved up
status = "up" if change > 0 else "down" if change < 0 else "same"
return {
"changeStatus": status,
"positionChange": change,
"rankingUrlChanged": bool(p and c and prev["url"] != cur["url"]),
"newFeatures": sorted(set(cur["features"]) - set(prev["features"])),
}
rankingUrlChanged is your cannibalization alert: two of your pages are competing for one keyword, and Google is swapping between them. newFeatures tells you when, say, ai_overview or local_pack appears on a SERP where it wasn't before. Also, mark a run not_comparable if the depth changed (top 10 vs top 100) instead of reporting false "lost" rankings.
Why SERP features matter now
An organic position doesn't tell you how visible the result is. Position 2 under an AI Overview, a Local Pack and four ads sits far lower on the page than position 2 on a plain SERP. Record the absolute position too, and flag the features that matter to you: AI Overview, People Also Ask, featured snippet, Local Pack, shopping, video and forums.
The hosted version
Disclosure: we (SteadyScrape) built the tool below and get paid when it's used.
Google Rank Tracker API: Daily Keyword Positions (steady-scrape/google-rank-tracker) is an Apify Actor that implements this design on top of a licensed SERP data API. You give it a target (a domain, or an exact URL if only that page should count), up to 2,000 keywords, and a location, language and device. Each keyword comes back with its organic and absolute position, the ranking URL and any other URLs of yours on the page, the SERP features (and whether your site appears inside them), the top 3 competing domains, positions for up to 10 competitors you name, and, if you reuse the same trackerId, the change since the last run.
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("steady-scrape/google-rank-tracker").call(run_input={
"target": "apify.com",
"keywords": ["scrape amazon prices", "instagram scraper", "web scraping api"],
"location": "United States",
"language": "English",
"device": "desktop",
"depth": "10",
"queueMode": "standard",
"trackerId": "apify-us-desktop",
})
for r in client.dataset(run["defaultDatasetId"]).iterate_items():
print(r["keyword"], r["position"], r["changeStatus"], r["serpFeatures"], r["aiOverviewCited"])
That's the Actor's example input. Here's what our test run of it returned on October 31, 2026:
- 1 of 3 keywords ranked in the top 10, at position 7 (absolute position 8, because an AI Overview sat above the organic results).
- All 3 SERPs had an AI Overview. The site was cited in 2 of those 3 AI Overviews, including one for a keyword where it wasn't in the organic top 10 at all. That's exactly the kind of thing a position-only tracker misses.
- The ranking URL was an individual page on apify.com, not the homepage. That's how a domain target works: any page on the site counts. Use a full URL as the target to track one page only.
- The run took 43 seconds in total. The SERP checks waited 11 to 27 seconds in the standard queue.
Run it again with the same trackerId, or put it on an Apify schedule, and every row gets positionChange, changeStatus (up, down, same, new, lost and so on), previousRankingUrl and rankingUrlChanged. Set loadAiOverview: true to also load AI Overviews that Google shows asynchronously and check whether they cite you.
A few practical notes:
- Depth costs more because Google serves 10 results per page. A top-100 check loads 10 pages per keyword. Most teams track the top 10 or top 30 and only go deeper for a few keywords.
- Queue modes: Standard is the cheapest. In our tests top-10 checks came back in under a minute, top 30 in about 4 minutes and top 100 in about 15 minutes. Priority is faster for a small surcharge, and Live returns in seconds.
What it costs
You pay per keyword that was checked successfully. Errors, invalid keywords and timeouts are free.
| Event | Price | Apify Bronze / Silver / Gold plans |
|---|---|---|
| Keyword checked, top 10 | $0.003 ($3 per 1,000) | $0.0027 / $0.0024 / $0.00225 |
| Keyword checked, top 30 | $0.007 | $0.0063 / $0.0056 / $0.00525 |
| Keyword checked, top 100 | $0.025 | $0.0225 / $0.02 / $0.01875 |
| Priority queue surcharge | $0.0015 per 10 results checked | same |
| Live mode surcharge | $0.003 per 10 results checked | same |
| AI Overview citations (only when one was loaded) | $0.002 per keyword | same |
| Run start (only after the first successful check) | $0.015 | same |
Apify also adds its own $0.00005 start fee per run.
Examples at list price:
- The 3-keyword test above: $0.015 + 3 × $0.003 = $0.024.
- 100 keywords every day at top 10: $0.315 per run, about $9.45 a month.
- 200 keywords weekly at top 10: about 860 checks a month × $0.003 + 4.3 run starts × $0.015 ≈ $2.64 a month.
- The same 200 keywords at top 100: 860 × $0.025 ≈ $21.50 a month plus run starts, which is why top 10 or top 30 is the usual choice.
You can set a maximum cost per run. The Actor works out how many keywords fit before it starts and marks the rest as skipped, free of charge.
Wrapping up
Whatever data source you use, the design is what makes a tracker useful: key by keyword + location + language + device, store the URL as well as the position, diff run over run, and record SERP features. AI Overviews are changing what "ranking #2" is worth, and a tracker that doesn't record them will report rankings that look better than they are.
來源:DEV Community · dev.to