搜索数据定时批量采集的工程化:从脚本到能跑一个月的任务
后台接第三方搜索数据接口这件事,写通一条请求很容易,难的是让它稳定地、按计划地、批量地跑起来,还不烧冤枉钱。这篇记录我把一个「每天批量查几百个关键词」的采集任务做成工程化的过程:调度、批量、去重、失败恢复、成本控制,几个要点一次讲清。
先定位:这篇解决什么问题
- 输入:一个关键词清单(几百到几千个)
- 任务:每天按计划批量查这些词的谷歌搜索结果
- 约束:成本可控、断点能续、不重复扣费、结果能审计
它和「写个 SDK 封装」是两个层面的问题:封装解决「怎么发请求」,这篇解决「怎么把请求拼成一个可靠的生产任务」。
一、批量:并发与控制节奏
几百个关键词串行跑太慢,全并发又容易撞 QPS 限制。我用的服务是 SerpBase(官网 serpbase.dev,/google/search 端点,POST JSON),它的 QPS 有限流设计,所以批量要「有节制的并发」。
用 Python 的 ThreadPoolExecutor 控制并发数:
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
API_KEY = "你的Key"
URL = "https://api.serpbase.dev/google/search"
HEADERS = {"Content-Type": "application/json", "X-API-Key": API_KEY}
KEYWORDS = ["serp api", "google search api", "cheap serp api", ...] # 几百个
def fetch(kw):
resp = requests.post(
URL, headers=HEADERS,
json={"q": kw, "hl": "en", "gl": "us"},
timeout=30,
)
data = resp.json()
return {
"keyword": kw,
"status": data.get("status"),
"elapsed_ms": data.get("elapsed_ms"),
"credits_charged": data.get("credits_charged"),
"organic": data.get("organic", []),
}
results = []
with ThreadPoolExecutor(max_workers=5) as ex: # 控制并发,别打满 QPS
futures = {ex.submit(fetch, kw): kw for kw in KEYWORDS}
for fut in as_completed(futures):
kw = futures[fut]
try:
results.append(fut.result())
except Exception as e:
results.append({"keyword": kw, "status": "error", "error": str(e)})
print(f"完成 {len(results)} 条,成功 {sum(1 for r in results if r.get('status') == 0)} 条")
max_workers=5 是起步值,实际根据你的 QPS 额度调整——这个值决定了「快」和「稳」的平衡点。
二、去重与幂等:跑挂了不重复
批量任务最怕「跑一半断了,重跑一遍,已成功的又扣一次费」。解决办法是把「任务状态」和「结果」分开存,用关键词做主键做幂等:
import sqlite3
con = sqlite3.connect("crawl.db")
con.execute("""CREATE TABLE IF NOT EXISTS tasks (
keyword TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'pending', -- pending/running/done/failed
result TEXT,
updated_at TEXT
)""")
con.execute("""CREATE TABLE IF NOT EXISTS billing (
keyword TEXT, request_id TEXT, credits REAL, elapsed_ms INT, ts TEXT
)""")
def mark_done(con, kw, result):
con.execute("UPDATE tasks SET status='done', result=? WHERE keyword=?",
(result, kw))
con.commit()
关键点:
- 任务表用
keyword做主键,天然去重 - 每条响应里的
request_id和credits_charged写进billing表——这就是你的审计账本 - 重跑时跳过
status='done'的词,只补跑failed/pending的
这样断点续跑,已成功的不会再发请求,也就不会重复扣费。
三、失败处理:重试与续跑
SerpBase 请求失败和上游超时会自动退 credits,所以「重试」本身不会额外烧钱——这是能放心做重试的前提。失败处理我分两层:
单条重试(API 层,最多 3 次指数退避):
def fetch_with_retry(kw, retries=3):
for attempt in range(retries):
try:
return fetch(kw)
except Exception:
if attempt < retries - 1:
time.sleep(0.5 * (2 ** attempt)) # 0.5s, 1s, 2s
raise RuntimeError(f"{kw} failed")
任务级续跑(调度层):把失败的词留在 failed 状态,下次定时任务只补跑这批。
两层配合,单条失败不会拖垮整批,整批失败能安全续跑。
四、调度:谁在什么时间跑
任务无状态(读写库 + 调接口),用系统调度最省心,不用额外引入框架。
- Linux:cron
- Windows:任务计划程序
# 每天早上 8 点跑一次全量
0 8 * * * cd /opt/crawl && python3 run_full.py >> /var/log/crawl.log 2>&1
# 每小时补跑失败项
15 * * * * cd /opt/crawl && python3 rerun_failed.py >> /var/log/crawl.log 2>&1
两个任务:一个做全量,一个做失败补跑。日志统一落文件,配合 request_id 排查。
五、成本:批量任务最容易被忽略的两点
- 端点费率不同。SerpBase 的
/google/search、/google/news、/google/videos是 1 credits/次,/google/images和两个 Maps 端点是 2 credits/次。批量前确认你调的是哪个端点,别按 1 credits 估预算。 - 记账靠响应字段。每条响应都带
credits_charged,写进billing表,月底对账直接查表,不靠猜。
一个量级参考:300 个关键词每天一次,一个月 9000 次,按 $0.50/千次($10 标准包,2 万次、永不过期)算,月成本约 $4.5——这个量级下成本几乎可以忽略,重点是别浪费在重复请求上。
六、这套方案的沉淀
跑了一个月,最值得分享的经验就三条:
- 并发别贪多。5 路并发的稳定性和成本都比 50 路好,QPS 限制是有道理的。
- 幂等是省钱核心。没有去重表,一次断线重跑就是双倍扣费。
- 把计费当数据对待。
request_id+credits_charged进表,排查和审计都有据可查。
如果你也想把「批量查谷歌数据」做成能长期跑的任务,接口细节在 SerpBase 官方文档。先把批量、去重、失败续跑三块搭起来,这个任务就从「脚本」升级成「能跑一个月的工程」了。
有在批量采集上踩过坑的同学,欢迎交流你的并发和重试策略。

浙公网安备 33010602011771号