排名监控做久了会发现光看「我的排名」不够。真正有价值的问题是——今天谁新进了前 10谁掉出去了精选摘要换人了吗这些答案都不在单次采集里而在「两次快照的对比」里。这篇教程教你用 Python 做搜索结果快照对比存全量、做 diff、输出人能看的日报。第一步每天存一份全量快照对比的前提是「存全量」不是只存你自己。每次采集把前 10 名的完整信息存下来importos,sqlite3,datetime,requests consqlite3.connect(serp.db)con.execute( CREATE TABLE IF NOT EXISTS snapshots ( keyword TEXT NOT NULL, market TEXT NOT NULL, -- 例如 cn/zh-cn day TEXT NOT NULL, rank INTEGER NOT NULL, title TEXT, link TEXT NOT NULL, PRIMARY KEY (keyword, market, day, link) ) )defcollect_snapshot(keyword,marketcn/zh-cn):hl,glmarket.split(/)datarequests.post(https://api.serpbase.dev/google/search,headers{Content-Type:application/json,X-API-Key:os.environ[SERPBASE_API_KEY]},json{q:keyword,hl:hl,gl:gl},timeout30,).json()daydatetime.date.today().isoformat()forrindata.get(organic,[])[:10]:con.execute(INSERT OR IGNORE INTO snapshots VALUES (?, ?, ?, ?, ?, ?),(keyword,market,day,r.get(rank),r.get(title),r.get(link)),)con.commit()主键里带market和day——市场维度千万别省否则中文和英文的结果混在一张表里对比全是错的。第二步写对比函数对比的逻辑就三组新进、掉出、名次变化。defload_day(keyword,market,day):rowscon.execute(SELECT link, rank, title FROM snapshots WHERE keyword? AND market? AND day?,(keyword,market,day),).fetchall()return{link:{rank:rank,title:title}forlink,rank,titleinrows}defdiff(keyword,market,day_a,day_b):aload_day(keyword,market,day_a)bload_day(keyword,market,day_b)entrants[(link,b[link][rank])forlinkinb.keys()-a.keys()]exits[(link,a[link][rank])forlinkina.keys()-b.keys()]moves[(link,a[link][rank],b[link][rank])forlinkina.keys()b.keys()ifa[link][rank]!b[link][rank]]returnentrants,exits,moves第三步输出「人话版」日报原始 diff 是给机器看的日报要给人看。加一个重点域名标记比如你自己的站MY_DOMAINyour-site.comdefreport(keyword,market,day_a,day_b):entrants,exits,movesdiff(keyword,market,day_a,day_b)lines[f【{keyword}】{day_a}→{day_b}]forlink,rankinsorted(entrants,keylambdax:x[1]):mark ★ifMY_DOMAINinlinkelselines.append(f 新进 #{rank}{link[:60]}{mark})forlink,rankinsorted(exits):mark ★ifMY_DOMAINinlinkelselines.append(f 掉出原 #{rank}{link[:60]}{mark})forlink,r1,r2insorted(moves,keylambdax:abs(x[1]-x[2]),reverseTrue):mark ★ifMY_DOMAINinlinkelsearrow↑ifr2r1else↓lines.append(f{r1}→{r2}{arrow}{link[:60]}{mark})return\n.join(lines)★标记的意义日报扫一眼就知道「变化跟我有没有关系」。第四步模块变化也别放过除了排名进出SERP 的「结构变化」同样值得盯——精选摘要换人、AI 概览出现、PAA 数量变化。存一份「模块指纹」defmodule_fingerprint(data):fp{}fsdata.get(featured_snippet)or{}fp[snippet_domain]fs.get(link,)# 摘要归属fp[has_ai]bool(data.get(ai_overview))fp[paa_count]len(data.get(people_also_ask)or[])returnfp每天每个词存一行指纹第二天对比 key 级别的变化——「摘要换人了」这种消息对内容团队的提醒价值不比排名下降低。第五步自动化的两个细节定时跑任务计划程序 / cron 每天固定时间执行采集 对比定时采集的做法不展开固定hl/gl口径是铁律阈值过滤别把 1 名的波动都推给运营——只报「新进/掉出」和「≥3 名的移动」或者连续两天同向才报噪声能降一大半踩坑记录坑 1直接比两个列表的下标。列表顺序可能因解析细节抖动导致「全员位移」的假 diff。用 link 集合 rank 映射对比别比数组位置。坑 2链接没规范化。同一页面今天带?utm_source...明天不带会被当成「掉出 新进」。入库前统一去掉查询参数、域名小写。坑 3单日波动当信号。一次小更新就能让前 10 洗牌。阈值≥3 名 双日确认两个都值得加。坑 4只存解析结果不存原始 JSON。哪天想换解析口径比如加 PAA 分析历史数据全废。原始响应和解析结果都存。坑 5市场/语言维度缺失。混市场对比是新手最隐蔽的错——数字看着对结论全错。小结快照对比的完整链路每日全量入库带市场和日期→ 集合求差进出/位移→ 模块指纹对比 → 阈值过滤后出日报。做完这套你的监控从「我的排名是几」升级成「结果页发生了什么」运营价值完全不一样。快照用到的字段rank、link、模块结构在 SerpBase 官方文档 里有完整说明。建议今晚就把今天的快照存下来——明天的对比才有得比。
