搜索结果转JSON就这么简单!字段映射与清洗入库教程
做SEO分析、竞品监控或者舆情跟踪的朋友,几乎都遇到过同一个问题:搜索结果页抓下来是一大坨HTML,标题、链接、摘要混在各种标签里,想统计个排名变化……
为什么搜索结果要转成JSON?
做SEO分析、竞品监控或者舆情跟踪的朋友,几乎都遇到过同一个问题:搜索结果页抓下来是一大坨HTML,标题、链接、摘要混在各种标签里,想统计个排名变化都要人工翻半天。而把搜索结果转成JSON之后,情况就完全不一样了——每条结果变成字段清晰的记录,程序可以直接读取、筛选、统计,后续入库和分析都顺理成章。
| 对比项 | 原始HTML | JSON |
|---|---|---|
| 数据结构 | 混杂在标签中 | 字段一目了然 |
| 读取方式 | 需要解析器逐层提取 | 程序直接加载 |
| 存储体积 | 冗余代码多 | 轻量精简 |
| 后续处理 | 二次清洗成本高 | 映射清洗即可入库 |
一条标准的搜索结果,转成JSON后大概长这样:
{
"keyword": "代理IP 推荐",
"results": [
{
"title": "如何挑选稳定的代理IP服务",
"url": "https://example.com/article/101",
"snippet": "挑选代理IP时要关注匿名度、延迟和IP池规模……",
"rank": 1
}
],
"crawl_time": "2025-06-01 10:30:00"
}
五步流程总览:从网页到数据库
整个流程可以拆成五步:抓取结果 → 解析内容 → 字段映射 → 清洗去重 → 写入数据库。抓取和解析负责把页面变成原始数据,字段映射负责统一命名,清洗负责保证质量,最后一步才是落库。下面我们重点讲最容易踩坑的中间三步。
字段映射:给每个字段起一个标准的名字
不同网站的搜索结果,字段命名五花八门:有的叫 result_title,有的叫 item_name。如果直接入库,表结构会乱成一团。字段映射就是在这中间加一张“翻译表”,把所有来源的字段统一成一套命名规范:
| 原始字段 | 目标字段 | 含义 |
|---|---|---|
| result_title | title | 结果标题 |
| result_link | url | 跳转链接 |
| result_abstract | snippet | 摘要描述 |
| result_position | rank | 排名位置 |
用Python实现只需要一个字典:
FIELD_MAP = {
"result_title": "title", # 结果标题
"result_link": "url", # 结果链接
"result_abstract": "snippet", # 摘要描述
"result_position": "rank", # 排名位置
}
def map_fields(raw_item):
"""把原始字段名替换成标准字段名"""
return {
FIELD_MAP[key]: value
for key, value in raw_item.items()
if key in FIELD_MAP
}
三条设计原则记住即可:字段名统一小写加下划线;一个字段只表达一个含义;映射表单独维护成配置文件,目标网站改版时只改配置、不动主流程。
数据清洗:把脏数据挡在数据库之外
刚解析出来的数据往往“看着能用,实则带毒”:标题里残留HTML标签、摘要混着全角空格和 、排名字段有时是字符串有时是空值。这些脏数据一旦入库,统计结果就会失真。常用的清洗动作有五类:去标签、空值兜底、格式统一、去重合并、异常筛查。
import re
from datetime import datetime
def clean_text(text):
"""去标签、合并空白、去首尾空格"""
if not text:
return ""
text = re.sub(r"<[^>]+>", "", text) # 去掉残留的HTML标签
text = re.sub(r"\s+", " ", text) # 多个空白合并成一个
return text.strip()
def clean_item(item):
item["title"] = clean_text(item.get("title"))
item["snippet"] = clean_text(item.get("snippet"))
# 排名统一成整数,缺失或异常补 0
try:
item["rank"] = int(item.get("rank", 0))
except (TypeError, ValueError):
item["rank"] = 0
# 补齐抓取时间
item["crawl_time"] = item.get("crawl_time") or \
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return item
去重建议以 url 为唯一键,保留首次出现的记录:
def dedup(items):
"""按 url 去重,保留第一条"""
seen, result = set(), []
for item in items:
if item["url"] and item["url"] not in seen:
seen.add(item["url"])
result.append(item)
return result
写入数据库:让JSON顺利落库
清洗完成后,数据已经是干净的JSON记录,落库只需几行代码。下面以轻量的SQLite为例,其他数据库只需替换连接方式:
import sqlite3
items = dedup([clean_item(map_fields(r)) for r in raw_results])
conn = sqlite3.connect("search_results.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
url TEXT UNIQUE,
snippet TEXT,
rank INTEGER,
crawl_time TEXT
)
""")
conn.executemany(
"INSERT OR IGNORE INTO results (title, url, snippet, rank, crawl_time) "
"VALUES (:title, :url, :snippet, :rank, :crawl_time)",
items,
)
conn.commit()
conn.close()
print(f"成功入库 {len(items)} 条记录")
两个实用细节:给 url 加 UNIQUE 约束配合 INSERT OR IGNORE,重复数据在数据库层面也会被自动挡住;每次入库后抽样 SELECT 几条看看,确认字段没有串位。
采集稳定,清洗入库才有意义
很多朋友流程写得没问题,却卡在第一步:搜索结果采集量大、频率高,同一个IP反复请求,很快就会被目标站点限流甚至拦截,后面映射清洗做得再好也无米下锅。想让数据持续稳定地进来,IP层面必须做好准备。
这方面可以了解光络云的动态住宅代理:基于真实住宅IP资源,支持高匿名访问与IP自动轮换,让每次请求来自不同的出口,大幅降低被识别和拦截的概率,配合上面的采集脚本就能跑得又稳又久。光络云定位为全球网络基础设施及数据服务商,产品覆盖国内和海外多种代理类型。需要提醒的是,光络云的代理IP需要你自身具备海外网络环境才能使用(TikTok专线除外),在此基础上它为你提供可靠的IP资源支撑。
常见问题
Q: 不会写代码,也能把搜索结果转成JSON吗?
可以。不少采集工具都支持把抓取结果直接导出为JSON文件,在导出设置里选择JSON格式即可。想长期自动化入库,建议掌握一点Python基础,本文示例代码可以直接套用。
Q: 字段映射表应该怎么设计?
记住三条:字段名统一小写加下划线;一个字段只表达一个含义;映射表独立成配置文件维护,目标网站改版时只改配置、不动主流程。
Q: 清洗时最容易被忽略的问题是什么?
编码和空值。搜索结果里常混有全角空格、不可见字符和HTML实体,肉眼难以察觉,入库后却会污染统计结果。建议清洗后抽样打印检查,再用去重和长度校验兜底。
Q: 采集搜索结果时频繁被拦截怎么办?
先降低请求频率、完善请求头,再从IP层面解决:使用光络云动态住宅代理这类真实住宅IP资源,配合高匿名与自动轮换,让请求更接近真实用户行为。注意使用前需自身具备海外网络环境(TikTok专线除外)。
Q: JSON和CSV该怎么选?
需要保留层级结构(比如一组关键词下嵌套多条结果)选JSON;只是简单表格、主要用Excel查看,CSV更方便。做搜索结果入库,通常JSON更合适,字段扩展也更灵活。
