SERP 数据清洗实战:字段标准化、日期解析与去重键

抓下来的原始 JSON 不能直接进库,也不能直接做对比:标题里混着 HTML 实体、日期是各种各样的文本、链接带着锚点和尾斜杠、同一条结果在不同查询里反复出现。清洗层要做的就四件事:文本标准化、日期归一、URL 规范化、稳定去重键。这篇把四个函数和一个映射模板给全。

先盘点字段

搜索端点的 organic 条目里,rank(必填,返回列表内 1 起算的位次)、titlelink 是必填;snippetdatepublished_aturldisplay_urlsitelinks 是可选字段,以实际返回为准。清洗前先对一批真实响应盘一遍:哪些字段有值、值长什么样,再决定清洗规则------比照着想象中的格式写解析器可靠。

四个清洗函数

python 复制代码
import hashlib, html, re
from datetime import datetime
from urllib.parse import urlsplit

TAG_RE = re.compile(r"<[^>]+>")
SPACE_RE = re.compile(r"\s+")

def clean_text(value: str) -> str:
    """去标签、反转义、压缩空白。"""
    if not value:
        return ""
    text = TAG_RE.sub("", value)
    return SPACE_RE.sub(" ", html.unescape(text)).strip()

def parse_date(raw: str):
    """把日期文本尽量解析成 ISO 日期;解析不出来返回 None,原文另存。"""
    if not raw:
        return None
    text = clean_text(raw)
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%b %d, %Y", "%d %b %Y", "%Y年%m月%d日"):
        try:
            return datetime.strptime(text, fmt).date().isoformat()
        except ValueError:
            continue
    return None

def normalize_url(url: str) -> str:
    """去掉锚点、统一尾斜杠,保留查询串。"""
    base = url.split("#", 1)[0]
    return base[:-1] if base.endswith("/") else base

def stable_id(link: str) -> str:
    """稳定去重键:注意不要用内置 hash()。"""
    return hashlib.md5(normalize_url(link).encode("utf-8")).hexdigest()

这里有个容易踩的坑:Python 内置的 hash() 每个进程都会加随机盐,同一个链接两次运行会得到不同的值,拿它做去重键,今天去过的第二天还会当新数据。用 hashlib 里的摘要函数才能跨进程稳定。

映射模板

python 复制代码
def normalize_item(item: dict) -> dict:
    link = item.get("link", "")
    return {
        "id": stable_id(link),
        "rank": item.get("rank"),
        "title": clean_text(item.get("title", "")),
        "link": normalize_url(link),
        "domain": urlsplit(link).netloc,
        "snippet": clean_text(item.get("snippet", "")),
        "published_on": parse_date(item.get("date", "")),
        "date_raw": item.get("date", ""),        # 解析失败时留证据
        "sitelinks": item.get("sitelinks", []),  # 列表,入库时序列化为 JSON 字符串
    }

清洗前后大致是这样(结构示例,不是实测值):

字段 原始值 清洗后
title Python &amp; asyncio 入门 <b>教程</b> Python & asyncio 入门 教程
date 3 days ago None(date_raw 留原文)
link https://example.com/post/#top https://example.com/post

字段的准确定义以 SerpBase 文档里的 search 端点字段表 为准------比如 date 的说明是从 snippet 里抽出的日期文本、published_at 是解析后的别名,所以格式不固定,解析器必须容错。

三个实践建议

  1. 边采边清,但原始 JSON 也留一份。 清洗规则会变;把原始响应存到单独的列或文件,重新清洗随时可重放。
  2. 解析失败不要静默成空串。 返回 None 并把原文放进 date_raw,否则你分不清"没有日期"和"没解析出来"。
  3. 去重键要包含参数上下文。 同一个链接在不同 hl/gl 下可能是两回事,如果一份表里混着多地区数据,stable_id 里应拼上地区和语言。

成本

清洗本身不花钱;数据来自 search 端点,每次成功请求 1 credit,失败自动退款。清洗逻辑写好之后,采集多少、清洗多少,成本只跟请求数有关。

FAQ

要用第三方清洗库吗? 上面四个函数用标准库就够了;真需要更复杂的 HTML 处理再引入解析库。

sitelinks 这种嵌套结构怎么入库? 单独一列存 JSON 字符串,别硬拆成多行------它是附属信息,拆表会让主表膨胀。

日期要不要统一到 UTC? date 是文本日期,通常没有时区信息;存 ISO 日期即可,不要凭空补时间。

normalize_item 挂到你现有的采集循环里,入库质量会立刻上一个台阶。

相关推荐
君顾11 小时前
本地城市低空经济管理系统定制:城市级低空运营平台架构与落地实战
java·开发语言·飞手
苏苏susuus1 小时前
核密度估计(KDE)与高斯核(概念分享)
人工智能·python·ocr
Databend1 小时前
Databend 原生数据血缘:追溯指标来源,检查变更影响
大数据·数据库·sql
镜舟科技1 小时前
Semantic View 技术解析(二):业务口径如何进入数据库执行路径
数据库·sql·agent
宸津-代码粉碎机2 小时前
微服务线上踩坑复盘:接口超时、负载倾斜隐形问题根治方案(生产级配置)
java·大数据·人工智能·python·spring
速易达网络2 小时前
Python + Tkinter 实现基于 TCP/IP 的局域网聊天工具
python·信息与通信
多多鼠2 小时前
System Prompt 的“版本漂移”问题:从变更管理到 A/B 测试体系
开发语言·网络·人工智能·python·langchain
TechWJ2 小时前
没有公网 IP 也想远程连 PostgreSQL?从本地数据库到固定 TCP 地址完整配置
大数据·数据库·网络安全·postgresql·内网穿透
昔我往昔2 小时前
pytest日志问题排查记录
python·pytest