SEO 周报自动化:从采集数据到一页报告

周报的价值在于"同一组指标、每周同一时间、一眼看出变化"。数据你已经在存了(前面写过定时采集入库),这篇补上后半段:三个 SQL 加一个 Markdown 模板,把库里的原始 JSON 变成一页能发给团队的报告。

先定指标,再写 SQL

周报只放五个数字,多了没人看:

指标 定义 变了说明什么
平均最好位次 每个词取你站最好位次,再求平均 整体盘子的移动方向
Top3 / Top10 占比 最好位次 ≤3 / ≤10 的词占比 头部阵地的得失
本周新进榜 上周没有、本周进前 10 的词 新内容的起量
本周跌出 上周在前 10、本周掉出去的词 需要排查的对象
采集成功率 成功词数 / 总词数 数据本身可不可信

口径说明写在报告末尾:位次取 rank(接口文档定义为"本条响应内的 1 起算位次"),同一关键词多个页面命中时取最好的一条;采集失败的词单列,不参与均值。

三个 SQL

假设沿用前面的表结构:serp_daily(day, keyword, organic JSON, credits, elapsed_ms),organic 是结果数组。

sql 复制代码
-- 1) 每天的每词最好位次
CREATE VIEW IF NOT EXISTS daily_best AS
SELECT day, keyword,
       MIN(CAST(json_extract(value, '$.rank') AS INTEGER)) AS best_rank
FROM serp_daily, json_each(serp_daily.organic)
GROUP BY day, keyword;

-- 2) 本周 vs 上周同一天(懒得管自然周就用 -7 天)
WITH latest AS (SELECT MAX(day) AS d FROM daily_best)
SELECT a.keyword,
       a.best_rank AS this_week,
       b.best_rank AS last_week,
       b.best_rank - a.best_rank AS delta
FROM daily_best a
JOIN daily_best b ON a.keyword = b.keyword
WHERE a.day = (SELECT d FROM latest)
  AND b.day = date((SELECT d FROM latest), '-7 day')
ORDER BY delta DESC;

-- 3) 采集成功率
SELECT day,
       COUNT(*) AS total,
       SUM(CASE WHEN organic IS NOT NULL AND organic != '[]' THEN 1 ELSE 0 END) AS ok
FROM serp_daily GROUP BY day ORDER BY day DESC LIMIT 7;

如果你的采集只跑工作日,把 -7 day 换成 -5 day,或者干脆按周聚合------关键是同一个对比口径每周保持一致

渲染成报告

python 复制代码
import pathlib, sqlite3
from datetime import date

db = sqlite3.connect("serp.db")
day = db.execute("SELECT MAX(day) FROM daily_best").fetchone()[0]

summary = db.execute("""
    SELECT COUNT(*), AVG(best_rank),
           SUM(CASE WHEN best_rank <= 3 THEN 1 ELSE 0 END),
           SUM(CASE WHEN best_rank <= 10 THEN 1 ELSE 0 END)
    FROM daily_best WHERE day = ?
""", (day,)).fetchone()

movers = db.execute("""
    WITH latest AS (SELECT MAX(day) AS d FROM daily_best)
    SELECT a.keyword, a.best_rank, b.best_rank, b.best_rank - a.best_rank AS delta
    FROM daily_best a JOIN daily_best b ON a.keyword = b.keyword
    WHERE a.day = (SELECT d FROM latest)
      AND b.day = date((SELECT d FROM latest), '-7 day')
    ORDER BY ABS(b.best_rank - a.best_rank) DESC LIMIT 10
""").fetchall()

total, avg_rank, top3, top10 = summary
lines = [
    f"# SEO 周报 · {day}",
    "",
    f"- 跟踪词数:{total}",
    f"- 平均最好位次:{avg_rank:.1f}",
    f"- Top3 占比:{top3 / total:.0%} · Top10 占比:{top10 / total:.0%}",
    "",
    "## 位次变化 Top 10",
    "",
    "| 关键词 | 上周 | 本周 | 变化 |",
    "|---|---|---|---|",
]
for kw, now, before, delta in movers:
    lines.append(f"| {kw} | {before} | {now} | {delta:+d} |")
lines += ["", "## 异常与待办", "", "- (例)词 X 连续两周下滑,检查页面是否被改版影响", ""]

pathlib.Path(f"report_{day}.md").write_text("\n".join(lines), encoding="utf-8")
print(f"报告已生成 report_{day}.md")

跑完就是一份结构化的报告(结构示例):

段落 内容
概览 三个数字:平均位次、Top3 占比、Top10 占比
变化 Top10 位次变动最大的十个词,带方向
异常与待办 人工补,只写需要动作的条目
数据口径 位次定义、失败词处理、对比周期

周报里的位次口径和计费说明,分别以接口文档和 SerpBase 官方文档的计费与 credits 说明 为准------报告是给人看的,口径写清楚才不会每周被问一遍。

和采集层的分工

采集脚本负责"每天把数据拿回来、幂等入库"(那是另一篇的主题);周报脚本只读库、不发起请求,所以生成报告本身零成本,成本都发生在采集环节(search 端点每次成功请求 1 credit,失败自动退款)。

FAQ

为什么取"最好位次"而不是平均位次? 同一关键词下你可能有多个页面进榜,取最好的那条最直观;要更严格,可以改成只看指定 URL 的位次。

周报没人看怎么办? 砍到三个数字、五条变化、三条待办。一屏放得下,才会有人看。

数据对不上怎么排查? 先看采集成功率,再看当天 credits 合计是否等于成功请求数;失败词会在表里缺失,周报里单列说明。

把两个 SQL 存成视图,渲染脚本挂到周末的任务计划里,下周开始就能自动收到报告。

相关推荐
jaysee-sjc1 小时前
【苍穹外卖】Day01:从零认识企业级项目开发
java·开发语言·数据库·mysql·spring·intellij-idea·mybatis
智购科技自动贩卖机1 小时前
自动售货机嵌入式系统时钟同步与时间管理实战:从RTC校准到断网时间保持的工程实践
大数据·linux·数据库·人工智能·yolo
雨辰AI1 小时前
多租户数据库资源配额管控|避免租户资源抢占雪崩(金仓 / 达梦 / 高斯 /openGauss 全库原生适配)
java·大数据·数据库·后端
念何架构之路1 小时前
beego ORM 源码(下):SQL 生成与方言
数据库·sql·beego
livemetee2 小时前
MySQL联合唯一索引(code,deleted)与逻辑删除问题
java·数据库
Doris__HE2 小时前
【元脑服务器NF8480G7-NF8480M7技术规格分享】
运维·服务器·数据库·缓存·性能优化
实心儿儿2 小时前
MySQL — 库的操作
数据库·mysql·oracle
bksczm2 小时前
MySQL基础篇之索引
数据库·sql·mysql
Lethehong2 小时前
让数据库真正“长“在 Kubernetes 里:KES-Operator 正式发布,K8s 环境下的 KES 集群管理有了标准答案
数据库