翻页听起来就是把page从 1 往上加,真跑起来决定成败的是三个细节:什么时候停、怎么去重、翻到第几页还值得。这篇把这三件事讲清楚,附可直接跑的脚本和成本对照表。
先明确参数事实
文档写得清楚:搜索端点的page参数是1 起算的页码,默认值 1;每次成功请求 1 credit。没有"每页多少条"的硬承诺,所以脚本不要假设一页固定条数,判断"到底了"以返回空数组为准更稳。
停的三条线
- 空页停:返回的
organic是空数组(或压根没有该键),说明没有更多结果了,立即停止。 - 预算停:给
max_pages一个上限。绝大多数场景 2~3 页封顶,除非你在做很深的覆盖分析。 - 成本停:翻页成本是线性叠加的,跑之前先看下一节的对照表,别让一个词吃掉一天的额度。
去重:跨页重复是常态
翻页时同一个链接在第 2 页再出现一次很常见。脚本按link去重,重复的直接跳过——注意是"跳过不记",不是"覆盖",否则你会丢掉它在第一页的真实位次。
还有个容易踩的坑:rank的文档定义是"本条响应内的 1 起算位次"。也就是说每一页的rank都从 1 开始,它不是全局名次。我把每页的rank当成rank_in_page存下来,配合page列一起看;要换算全局位次,得先自己记录每页的实际条数再累加,别拍脑袋乘 10。
脚本
importcsv,timeimportrequests API_URL="https://api.serpbase.dev/google/search"API_KEY="你的 API Key"deffetch_page(query:str,page:int)->list:resp=requests.post(API_URL,headers={"X-API-Key":API_KEY,"Content-Type":"application/json"},json={"q":query,"page":page},timeout=30,)resp.raise_for_status()returnresp.json().get("organic",[])defcollect(query:str,max_pages:int=3)->list:rows,seen=[],set()forpageinrange(1,max_pages+1):items=fetch_page(query,page)ifnotitems:break# 空页:到底了foriteminitems:link=item.get("link","")iflinkinseen:continue# 跨页重复,跳过seen.add(link)rows.append({"page":page,"rank_in_page":item.get("rank"),"title":item.get("title",""),"link":link,})time.sleep(1.5)# 串行 + 间隔,避开 1029returnrows rows=collect("python 异步编程")withopen("paged_results.csv","w",newline="",encoding="utf-8-sig")asf:w=csv.DictWriter(f,fieldnames=["page","rank_in_page","title","link"])w.writeheader()w.writerows(rows)print(f"{len(rows)}条(已去重)")请求参数和计费以 SerpBase 官方文档的 search 端点参数 为准;organic条目还带date、sitelinks等可选字段,按需取用。
成本对照表
| 翻页深度 | 每个词成本 | 100 个词跑一轮 |
|---|---|---|
| 1 页 | 1 credit | 100 credits |
| 2 页 | 2 credits | 200 credits |
| 3 页 | 3 credits | 300 credits |
| 5 页 | 5 credits | 500 credits |
按这个表反推:max_pages从 3 降到 2,成本直接省三分之一,而多数关键词在第 2 页之后的信息增量很小。先跑 2 页看数据够不够,不够再加。
三个常见坑
- 把
rank当全局名次:如前所述,它是页内位次;跨页分析时要么分开存,要么自己换算。 - 忘了一致性:翻页时
hl/gl等参数要和第一页保持一致,否则你拿到的"第 2 页"其实来自另一个市场/语言的结果。 - 重复计数:不做去重就统计条数,结果会虚高;按
link去重后再计数。
FAQ
到底该翻几页?排名监控类 2 页够用;内容调研 1~2 页;深覆盖分析最多 5 页,再多性价比急剧下降。
翻页会不会更容易触发限流?只要串行请求、页间留 1.5 秒以上,常规量级不会;真收到 1029 就退避重试,错误码含义以官方文档为准。
每页条数固定吗?文档没有承诺固定条数,所以脚本用"空数组停"而不是"条数不足停";如果要统计每页条数,直接把每次返回的长度记下来即可。
把脚本里的max_pages设成 2,先拿你自己的词表跑一轮,再决定要不要加深。