云计算百科
云计算领域专业知识百科平台

新闻采集字段坑:source/time 是解析文本,不是结构化字段

新闻采集字段坑:source/time 是解析文本,不是结构化字段

新闻端点有个和其他端点不一样的坑:source(来源媒体)和 time(发布时间)不是 Google 直接给的结构化字段,而是解析器从一段紧凑的元数据文本里"抠"出来的。文档原话:“Source and time are parsed from compact Google metadata text, so clients should treat both as optional.”

翻译成工程语言:你脚本里 item["source"] 和 item["time"] 直取,某天就会 KeyError;更隐蔽的是你用 .get() 兜底拿到了空字符串,然后在"来源媒体数量"“发布时效分布"这类统计里,把"解析失败"算成了"没有来源”。

这篇把这个坑讲透,并给一个带填充率监控的采集脚本。字段口径以 SerpBase 官方文档的 news 端点字段表 为准;每次成功请求 1 credit。

先说路径:是 /google/news/search

端点路径是 POST /google/news/search,不是 /google/news。和 image/search、video/search 一个路子——单数资源名 + /search。如果你从旧教程里抄的路径,先改过来。

入参四个:q 必填,hl/gl/page 可选(默认 en/us/1)。没有 device 参数,那是搜索端点独有的。返回信封里 search_type 会解析成 news,可以拿来断言路由对不对。

字段口径:三个必填,其余全靠解析

NewsResult 里必填的只有 rank、title、link 三个。其余全是可选,而且可选的理由分两类:

字段可选的原因能不能指望
position rank 的别名,“when available” 能用 rank 就别用
url 归一化后的规范链接 一般有,没有就用 link
source_url 原始 Google 跳转 URL,调试/溯源用 别依赖
display_url 从展示文本或域名推导 别依赖
source “when publisher text is parsed” 不能
time “when Google exposes time text” 不能
published_at 解析到 time 时的归一化别名 跟随 time
snippet “when a snippet is parsed” 新闻卡常常没有
thumbnail_url/thumbnail Google 暴露缩略图时 别依赖

顶层 news 数组本身也是可选的——"解析到新闻结果时"才有。零结果时可能整个键缺失,所以必须 data.get("news", [])。

source 和 time 为什么不可靠

新闻卡片在 Google 里的呈现是一行紧凑文本,大概长这样:“媒体名 · 3 小时前 · 来源域名”。解析器要从这一行里切出媒体名和时间文本。这里每一环都可能断:

  • Google 对某些结果不渲染这行元数据(比如聚合页、视频新闻)
  • 元数据行的格式随布局变化,解析规则没覆盖到就整条丢
  • 时间文本是给人看的相对时间(“3小时前”“昨天”),不是机器时间;published_at 只是它的归一化别名,解析不出 time 就没有 published_at

所以这两个字段的正确用法是:有就用,没有就记空,统计时单独看填充率。把它们当"一定有"来写代码,等于把解析器的上游依赖藏进了你的数据管道——出问题那天,你看到的现象是"媒体数量突然少了",而真实原因是"解析失败率突然高了"。

带填充率监控的采集脚本

import csv, json, pathlib, requests
from datetime import date

API = "https://api.serpbase.dev/google/news/search" # 注意路径
KEY = "你的 API Key"
HEADERS = {"X-API-Key": KEY, "Content-Type": "application/json"}

TERMS = ["人工智能", "新能源汽车", "半导体"]

def news(q: str, hl: str = "zh-CN", gl: str = "cn", page: int = 1) –> list:
resp = requests.post(API, headers=HEADERS,
json={"q": q, "hl": hl, "gl": gl, "page": page}, timeout=30)
data = resp.json()
if data.get("status") != 0:
print(f"{q} p{page}: status={data.get('status')} rid={data.get('request_id')}")
return []
return data.get("news", []) # 数组本身可选,必须兜底

def collect(terms: list) –> list:
rows = []
for q in terms:
for page in (1, 2):
items = news(q, page=page)
if not items:
break # 空页即停,别继续翻
for it in items:
rows.append({
"词组": q,
"页": page,
"位次": it["rank"], # 必填,直取
"标题": it["title"], # 必填,直取
"链接": it["link"], # 必填,直取
"来源": it.get("source", ""), # 解析字段,兜底
"时间文本": it.get("time", ""), # 解析字段,兜底
"发布时间": it.get("published_at", ""), # 别名,跟随 time
"摘要": it.get("snippet", ""),
})
return rows

def fill_report(rows: list) –> dict:
"""来源/时间的填充率,和数据一起落盘。"""
n = len(rows) or 1
return {
"总条数": len(rows),
"来源填充率": round(sum(1 for r in rows if r["来源"]) / n, 3),
"时间填充率": round(sum(1 for r in rows if r["时间文本"]) / n, 3),
"去重来源数": len({r["来源"] for r in rows if r["来源"]}),
}

if __name__ == "__main__":
rows = collect(TERMS)
day = date.today().isoformat()
with open(f"news_{day}.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0].keys()) if rows else
["词组", "页", "位次", "标题", "链接", "来源", "时间文本", "发布时间", "摘要"])
w.writeheader()
w.writerows(rows)
report = fill_report(rows)
pathlib.Path(f"news_fill_{day}.json").write_text(
json.dumps(report, ensure_ascii=False, indent=1), encoding="utf-8")
print(f"{len(rows)} 条 → news_{day}.csv")
print(f"填充率: {report['来源填充率']:.0%} 来源 / {report['时间填充率']:.0%} 时间")

关键点是 fill_report:来源/时间填充率必须和数据分开落一份。下次看到"去重来源数从 30 掉到 12",先翻填充率文件——如果是 95% 掉到 60%,那是解析退化,不是行业变天。

三条实战经验

  • 时间字段只做粗聚合,别做时间窗过滤。 time 是"3小时前"这种文本,published_at 是它的归一化别名但同样依赖解析成功。做周报按"天"粒度聚合可以用;要精确到"最近 24 小时",得用你自己记录请求那一刻的时间戳做上界,再用 time 文本做人工判断。别写 WHERE published_at > … 这种查询,底层数据撑不住。
  • 来源去重前先确认来源字段解析成功了。 把"来源为空"当独立一类统计出来,别让它混进"未知来源"。前者是数据质量问题,后者是真实的来源分布。
  • 翻页到空页就停。 news 数组可选,空页可能就是"这页没解析到结果",继续翻只是白烧 credits。1 credit 一页,10 个词组翻 5 页就是 50 credits,够跑半个月日报。
  • FAQ

    路径到底是 /google/news 还是 /google/news/search? 文档写的是 /google/news/search。如果旧代码用的 /google/news 还能跑,也别依赖——以文档为准改过来。

    snippet 为空正常吗? 正常。它是可选字段,"解析到摘要时"才有;新闻卡的标题+来源行往往就是全部内容,没有摘要。

    thumbnail 能直接当封面用吗? 和图片端点一样有防盗链和可达性问题,thumbnail_url 只是 Google 暴露的地址。落库记着,展示层先校验再决定是否代理。

    为什么搜索端点有 device 参数新闻没有? 文档里 news 的入参就是 q/hl/gl/page 四个,device 属于搜索端点。别把搜索端点的参数习惯带到新闻端点。

    把 fill_report 加进你现有的新闻采集流程——它不改变你采到什么,只让你知道采到的东西里有多少是真的。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 新闻采集字段坑:source/time 是解析文本,不是结构化字段
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!