SEO 全生命周期管理平台。平台托管内容并执行 SEO 规则检测,第三方通过 API 提交内容、 获取不合格项、自行优化后重新提交,达标才允许发布。
平台不介入 AI,不生成内容——只提供接口与判定,优化由第三方自行完成。
第三方 POST 内容
→ 平台跑 30 条 SEO 规则
→ 返回不合格项(规则编码 + 证据 + 修复建议 + 预估耗时 + 可挽回分数)
→ 第三方自行优化后 PUT 更新
→ 重新检测(可多轮迭代)
→ 达标 → publish(存在 critical 问题时返回 422 拒绝)
→ 按 Host 渲染上线 + sitemap 自动包含
→ 自动通知搜索引擎(IndexNow / Search Console 提交 sitemap)
→ 每日回读 Search Console:点击、曝光、排名
→ 面板显示效果,发现新问题继续优化
修订已发布的内容后可以再次发布——修复版本必须能替换线上旧版本, 否则「检测→修复→发布」会断在最后一步。
| 方式 | 成本 | 域名 | 适用 |
|---|---|---|---|
| A. Cloudflare Pages(推荐) | 免费(500 构建/月) | 可绑根域名 | 内容站、博客、文档 |
| B. GitHub Pages | 免费 | 子路径 /仓库名/ |
同上,无自有域名时 |
| C. Docker 自托管 | 需服务器 | 自定义 | 多租户、API 动态提交 |
A 和 B 都不需要 Docker 与数据库。A 更推荐:可绑根域名, 避免子路径带来的相对链接问题,且 Cloudflare CDN 对 SEO 更友好。
CLOUDFLARE_API_TOKEN=xxx CLOUDFLARE_ACCOUNT_ID=yyy \
./infra/scripts/cloudflare-pages-setup.sh rankloop https://你的域名API Token 在 Cloudflare 控制台 创建,权限选 Cloudflare Pages:Edit。
配置自动部署:仓库 Settings → Secrets 添加 CLOUDFLARE_API_TOKEN、
CLOUDFLARE_ACCOUNT_ID;Variables 添加 SITE_URL。
之后修改 content/ 推送到 main 即自动构建部署。
# 1. 在 content/ 下新增 Markdown
# 2. 提交 PR → 自动跑 30 条 SEO 检测,不合格无法合并
# 3. 合并 → 自动构建、部署 Pages、提交搜索引擎本地预览:
npm ci
SITE_URL=https://<用户名>.github.io/<仓库名> npm run site首次使用需在仓库 Settings → Pages → Source 选择 GitHub Actions。
自动化能力见 docs/Google-SEO-自动化说明.md。
cp .env.example .env
# 必须替换为随机值,否则生产模式启动会直接失败
openssl rand -base64 32 # 填入 SESSION_SECRET
openssl rand -base64 32 # 填入 ENCRYPTION_KEY
openssl rand -hex 16 # 填入 POSTGRES_PASSWORD
docker compose up -d --build
docker compose exec api node /app/seed.mjs # 输出 API Key,只显示一次打开 http://127.0.0.1:8080/console,粘贴 API Key 即可使用管理控制台。
两个界面均由 API 进程直接提供(单容器、单域名,无需额外构建):
/console 管理控制台(Alpine.js + 自写样式,本地内置无 CDN)
管理员用邮箱密码登录;租户无需登录,凭签发的 API Key 直接调接口。
- 总览:健康分环形图、分数分布、发布漏斗(内容→通过检测→已发布→已提交搜索引擎)、 「下一步该修什么」按性价比排序(影响面 + 耗时 + 每页可挽回分数)
- 发布之后:跨站点的点击、曝光、点击率、平均排名(近 28 天,含环比)
- 内容:提交(HTML/Markdown)、预览渲染效果、打开线上页、检测详情、版本历史、发布
- 站点:添加、自有域名绑定与验证
- 搜索表现:按站点的关键词榜、趋势、发布事件标注
- 密钥与租户:创建、查看、吊销
/ 可视化面板(只读大屏)
- 平均健康分、趋势折线、问题级别分布
数据全部来自 /api/v1 真实接口,无任何演示数据。
已实现:
- 内容托管 CRUD,支持 HTML 与 Markdown 两种格式
- 30 条 SEO 规则,分 critical / warning / notice 三级
- 可解释健康分(按规则权重扣分,可追溯每一分的来源)
- 发布门槛:critical 问题阻断发布
- 无状态预检接口(不落库,供发布前反复试算)
- Sitemap 与 robots.txt 自动生成
- Google Search Console 全自动化:验证所有权 → 添加站点 → 提交 sitemap → 回读确认
- 静态站生成器(GitHub Pages / Cloudflare Pages 免费部署)
- 配置驱动的站点自定义(导航、首页、配色,零代码)
- IndexNow 提交与后台实际投递(含幂等、跨站 URL 拦截、可重试/不可重试区分)
- Search Console 搜索表现同步:每日自动回读点击 / 曝光 / 排名, 发布事件标注在流量曲线上(平台独占发布时间戳,外部工具做不到)
- Webhook 投递:HMAC 签名、指数退避重试、4xx 不重试 5xx 重试、SSRF 防护
- 发布即自动通知搜索引擎(无需客户再调一次提交接口)
- API Key 认证与 scope 授权、多租户隔离
- 多租户按 Host 渲染:
<slug>.rankloop.<平台域名>, 自有域名验证归属后优先生效(配置见 DEPLOY.md) - 首次启动自动创建平台管理员(幂等,已存在时不覆盖)
- 管理控制台 + 可视化面板
- 站点与 API Key 管理接口
- OpenAPI 3.1 文档
- Docker 一键部署 + GitHub Actions 自动构建镜像
明确不做(第一版):
- 不接入生成式 AI,不生成文章与 SEO 文案
- 不承诺"保证收录"或"强制排名"
- 不实现黑帽 SEO
尚未实现(二期):
- 外部站点抓取(抓取他人站点做审计;当前只检测平台托管的内容)
- 网站所有权验证(针对用户自有站点;平台托管站点已支持 Google 自动验证)
- 关键词研究与竞品分析
必须如实说明:没有任何工具可以强制 Google 收录,更不能保证排名。 Google 官方文档写明「不保证抓取、索引或提供你的页面」, 也「不接受付费提高抓取频率或排名」。
本平台能做的是把技术层面的障碍降到零:
- 检测并阻止会导致不被收录的问题(noindex、缺 title、canonical 跨域、空内容等);
- 自动生成 sitemap 并在 robots.txt 中声明;
- 通过 Search Console API 提交 sitemap —— Google 侧唯一的主动手段;
- 通过 IndexNow 通知 Bing / Yandex / Seznam / Naver —— Google 不支持该协议;
- 发布后每日回读真实搜索表现,让优化效果可被验证。
收录与排名仍取决于 Google 的抓取策略、内容质量、站点年龄与竞争程度。 新域名通常需要数周到数月才会获得正常的收录速度,这段时间做什么都无法加速。
零依赖(只用标准库),把「发布被拦截」做成可直接读取的异常:
pip install "git+https://github.com/LordFoxFairy/RankLoop.git#subdirectory=sdk/python"from rankloop import Client, PublishBlockedError
client = Client(api_key="rkl_live_xxx")
content = client.submit(site_id, path="/posts/hello", body=html)
try:
client.publish(content.id)
except PublishBlockedError as e:
print(f"当前 {e.score} 分,必须先修:{e.blocking}")批量场景不想写 try/except:
ok, todo = client.publish_when_ready(content.id)
for r in todo:
print(r.message, r.recommendation, f"约 {r.minutes} 分钟")统一前缀 /api/v1,认证:Authorization: Bearer rkl_live_xxx
| 方法 | 路径 | scope | 说明 |
|---|---|---|---|
| GET | /rules |
公开 | 30 条规则清单与权重 |
| GET | /openapi.json |
公开 | OpenAPI 3.1 文档 |
| POST | /sites/:siteId/contents |
contents:write |
提交内容并检测 |
| GET | /sites/:siteId/contents |
contents:read |
列出站点内容 |
| GET | /contents/:contentId |
contents:read |
查看内容与检测结果 |
| PUT | /contents/:contentId |
contents:write |
更新内容,产生新版本 |
| POST | /contents/check |
contents:write |
无状态预检,不落库 |
| POST | /contents/:contentId/publish |
contents:publish |
发布,critical 时 422 |
| GET | /contents/:contentId/recommendations |
contents:read |
按性价比排序的修复建议 |
| GET | /stats/overview |
contents:read |
面板总览统计 |
| GET | /stats/trend |
contents:read |
健康分趋势 |
| GET | /stats/search |
analytics:read |
跨站点搜索表现汇总 |
| GET | /sites/:siteId/search-performance |
analytics:read |
站点点击 / 曝光 / 排名 |
| GET | /sites/:siteId/keywords |
analytics:read |
关键词榜 |
| GET | /sites/:siteId/search-trend |
analytics:read |
每日趋势 + 发布事件标注 |
| POST | /webhooks |
sites:write |
登记回调地址 |
| GET | /webhooks/:id/deliveries |
sites:read |
投递记录(排查未收到通知) |
| GET | /sites/:siteId/sitemap.xml |
indexing:read |
已发布内容的 sitemap |
| GET | /sites/:siteId/robots.txt |
indexing:read |
robots.txt |
| POST | /sites/:siteId/indexnow/key |
indexing:write |
配置 IndexNow Key |
| POST | /sites/:siteId/indexnow/submit |
indexing:write |
提交 URL(幂等) |
| GET | /sites/:siteId/indexnow/submissions |
indexing:read |
提交记录 |
| GET | /sites |
sites:read |
列出站点 |
| POST | /sites |
sites:write |
添加站点 |
| DELETE | /sites/:siteId |
sites:write |
归档站点(软删除) |
| GET | /api-keys |
sites:read |
列出 API Key(不含明文) |
| POST | /api-keys |
sites:write |
创建 Key(明文仅返回一次) |
| DELETE | /api-keys/:keyId |
sites:write |
吊销 Key |
KEY=rkl_live_xxx
SITE=<site_id>
# 提交(缺 title,会被判 critical)
curl -X POST "http://127.0.0.1:8080/api/v1/sites/$SITE/contents" \
-H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"path":"/article","format":"markdown","body":"# 标题\n\n短"}'
# → score 13,critical: MISSING_TITLE / EMPTY_CONTENT
# 发布被拦截
curl -X POST "http://127.0.0.1:8080/api/v1/contents/<id>/publish" -H "Authorization: Bearer $KEY"
# → 422 SEO_GATE_FAILED,details.blocking 列出阻塞规则
# 修复后更新 → score 90,publishable: true → 再次发布返回 200Markdown 的 title / description / canonical 通过 frontmatter 提供:
---
title: 如何优化网站的搜索引擎排名完整指南
description: 本文详细介绍网站 SEO 优化的核心方法……
canonical: https://example.com/article
lang: zh-CN
og:
title: SEO 指南
description: 完整指南
image: https://example.com/og.png
---
# 正文标题平台负责渲染并生成 <head>,因此 HTML 与 Markdown 跑同一套规则、结论一致
(有等价性测试保证,第三方无法通过切换格式绕过门槛)。
按 DDD 分层,依赖方向由外向内:
apps/api/src/
domain/ 领域层:聚合根、值对象、领域异常、仓储接口(不依赖框架与数据库)
application/ 应用层:用例编排、事务边界
infrastructure/ 基础设施层:Prisma 仓储实现、规则引擎适配、IndexNow 投递
interfaces/ 接口层:HTTP 路由、错误映射、管理控制台与面板
shared/ 跨层工具:URL 规范化、API Key、Webhook 签名
packages/
seo-rules/ 规则引擎(输入无关,二期爬虫可直接复用)
db/ Prisma schema 与迁移
发布门槛等业务规则住在 domain/content/content.ts 聚合根中,
任何调用方(HTTP、队列、脚本)都无法绕过。
npm ci
npm run generate # 生成 Prisma Client,typecheck 依赖它
npm test # 160 个测试
npm run typecheck
npm run build| 变量 | 默认值 | 说明 |
|---|---|---|
NODE_ENV |
development |
生产模式下使用示例密钥会拒绝启动 |
PORT |
8080 |
API 端口 |
APP_URL |
http://localhost:3000 |
对外地址 |
DATABASE_URL |
— | PostgreSQL 连接串,必填 |
REDIS_URL |
redis://redis:6379 |
Redis 连接串 |
SESSION_SECRET |
— | 至少 32 字节随机值,必填 |
ENCRYPTION_KEY |
— | 至少 32 字节随机值,必填 |
LOG_LEVEL |
info |
日志级别 |
DEFAULT_MAX_SITES |
5 |
每工作区站点数配额 |
DEFAULT_MAX_CONTENTS |
1000 |
每工作区内容数配额 |
GitHub Actions 自动构建并推送到 GHCR,支持 linux/amd64 与 linux/arm64:
ghcr.io/lordfoxfairy/rankloop:main # main 分支
ghcr.io/lordfoxfairy/rankloop:v1.2.3 # 版本标签
ghcr.io/lordfoxfairy/rankloop:latest # 正式 tag
ghcr.io/lordfoxfairy/rankloop:sha-abc1234 # 单次提交
部署与升级见 DEPLOY.md。
- API Key 只存 SHA-256 哈希,明文仅创建时返回一次
- 跨租户访问返回 404 而非 403,避免泄露资源是否存在
- 内容路径阻断
..与%2e%2e编码绕过 - IndexNow 只接受属于本站点的 URL
- Webhook 签名覆盖时间戳,防重放
- 容器以非 root 用户运行,数据库与 Redis 不映射公网
- 生产模式使用示例密钥直接拒绝启动
发现安全问题请通过 GitHub Issue 私下联系维护者。