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 存成视图,渲染脚本挂到周末的任务计划里,下周开始就能自动收到报告。

相关推荐
Data-Miner7 小时前
离线AI制表:模型适配与脚本固化实操
大数据·数据库·人工智能·excel
xixiaoyunya7 小时前
数据库三种备份方式怎么组合?从原理到落地的完整方案
数据库
2501_933670797 小时前
2027届数学与应用数学转经营分析:SQL、财务指标与业务指标补齐路线
数据库·sql·数学建模
我科绝伦(Huanhuan Zhou)7 小时前
oracle坏块导致备份不了
数据库·oracle·ffmpeg
IT大白鼠7 小时前
图数据库系列 · 第 08 篇——面试收官:高频题与全景总结
数据库·面试·职场和发展·nosql·图数据库
xixiaoyunya8 小时前
定时备份数据库怎么规划周期?一套“少打扰业务、真能恢复“的策略
数据库
liangshanbo12158 小时前
主系统登录后,子系统怎么实现自动登录?
java·网络·数据库
许彰午8 小时前
05-静默安装常见报错与解决
数据库
ly76898 小时前
Elasticsearch 写入链路的 refresh、flush 与 translog 边界:为什么 bulk 吞吐会突然塌陷
大数据·elasticsearch·搜索引擎·translog·写入链路·bulk调优
害人终害己8 小时前
redis修改密码的地方在哪里
数据库·redis·缓存