**这是本文档旧的修订版!**
README
项目简介
本项目实现了一个 无人值守的电子硬件资讯采集与发布系统,基于 GPT/Agent 自动完成: - 采集:从 hackster.io、Kickstarter、Seeed Blog 等源站抓取最新文章; - 分析:调用 GPT 自动提取标题、摘要、参数表、标签; - 翻译:中英文双语输出; - 去重与合规:SimHash 检查、引用标注、版权检测; - 发布:每日自动向 eetree.cn 发布 10–20 篇文章。
系统完全无人值守,依赖 Agent/MCP 工具链完成全流程任务。支持自动回滚与黑名单机制,保障质量与合规。
目录结构
├── README.md # 项目说明 ├── openapi.yaml # API 契约(抓取/分析/发布接口) ├── sources.yaml # 源站配置文件 ├── orchestrator.py # 调度与流水线控制 ├── workers/ # 各阶段 Worker 实现 │ ├── fetcher.py # 抓取 │ ├── parser.py # 解析 │ ├── deduper.py # 去重 │ ├── nlp_agent.py # GPT 调用 │ ├── guard.py # 风控合规 │ └── publisher.py # 发布 └── storage/ # 数据存储定义(DB schema / S3 等)
工作流
1. Source Poller:按 sources.yaml 配置轮询 RSS/API/HTML。
2. Fetcher:抓取 HTML/JSON,写入队列。
3. Parser:抽取正文、图片、作者、时间。
4. Deduper:SimHash 比对,重复则丢弃。
5. NLP Agent:调用 GPT,输出 JSON(标题、摘要、specs、标签、风险标记、置信度)。
6. Guard:合规检查,低置信或高风险任务丢回队列。
7. Publisher:写入 CMS API,附带引用与参考。
8. Monitor:监控成功率/成本/延迟,支持撤稿与黑名单。
OpenAPI (openapi.yaml)
- snippet.yaml
openapi: 3.0.3 info: title: Eetree Auto Publisher API version: 1.0.0 servers: - url: https://api.eetree.cn paths: /articles: post: summary: 发布文章 requestBody: required: true content: application/json: schema: type: object properties: title: type: string body: type: string tags: type: array items: { type: string } source_url: type: string lang: type: string enum: [zh, en] specs: type: object attribution: type: string responses: '200': description: 发布成功 content: application/json: schema: type: object properties: id: { type: string } status: { type: string } /rollback/{id}: post: summary: 撤回已发布文章 parameters: - in: path name: id schema: { type: string } required: true responses: '200': { description: 撤回成功 }
源站配置 (sources.yaml)
- snippet.yaml
sources: - name: hackster type: rss url: https://www.hackster.io/feed rate_limit: 30/m strategy: rss-parse-summarize - name: kickstarter type: api url: https://api.kickstarter.com/v1/discover rate_limit: 10/m strategy: api-parse-summarize - name: seeed type: html url: https://www.seeedstudio.com/blog/ rate_limit: 20/m strategy: html-parse-translate - name: sparkfun type: rss url: https://www.sparkfun.com/news.xml rate_limit: 15/m strategy: rss-parse-summarize
Agent/MCP 工具定义
- fetch_url(url) → { html, finalurl, status }
- extract_content(html) → { title, text, images[], publishedat, author }
- dedupe_check(fingerprint) → { isduplicate, matchedids[] }
- llm_summarize_translate(payload) → { summaryzh, summaryen, specs, tags, confidence }
- copyright_guard(payload) → { riskflags, attribution }
- publish_to_cms(article) → { id, status }
- rollback(id) → { status }
—
提示词(NLP Agent)
```
你是电子硬件资讯编辑机器人。输入为【原文】,输出 JSON:
- title_zh, title_en
- summary_zh, summary_en
- bullets[3-6]
- specs{k:v}
- tags[3-6]
- risk_flags[]
- confidence [0-1]
要求:不杜撰,所有数据必须能在原文找到依据。
```
---
发布策略
- 每日发布 10–20 篇,按来源/专题配额分配。
- CRON 定时 + 随机抖动,避免集中发布。
- 自动在正文末尾生成“引用与参考”区块。
—
监控与合规
- 指标:抓取成功率、NLP 成功率、Token 成本、发布成功率。
- 风控:Robots 合规、版权检查、敏感词检测。
- 回滚:支持文章撤稿与黑名单机制。
---
部署建议
- 抓取与处理:K8s CronJob + Worker Pod。
- 队列:Redis/RabbitMQ。
- 数据库:Postgres + pgvector。
- 对象存储:S3/OSS。
- 模型调用:OpenAI GPT-4o-mini 或私有化轻量模型。
—
# Docker Compose 部署示例
下面提供 docker-compose.yml、.env.example 与 init/pgvector.sql。把三段分别存成对应文件后,执行 docker compose up -d 即可起一套最小可用的无人值守流水线(含:Redis 队列、Postgres+pgvector、MinIO 对象存储、Orchestrator + 5 个 Workers)。
docker-compose.yml
```yaml
version: "3.9"
x-app-env: &app_env
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
OPENAI_API_KEY: ${OPENAI_API_KEY}
EETREE_API_URL: ${EETREE_API_URL}
EETREE_API_TOKEN: ${EETREE_API_TOKEN}
MINIO_ENDPOINT: ${MINIO_ENDPOINT}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_BUCKET: ${MINIO_BUCKET}
SOURCES_FILE: /app/config/sources.yaml
PROMPT_FILE: /app/config/prompt.md
services:
# 队列 & 存储
redis:
image: redis:7-alpine
restart: unless-stopped
ports: ["6379:6379"]
command: ["redis-server", "--save", "60", "1000", "--loglevel", "warning"]
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports: ["5432:5432"]
volumes:
- pgdata:/var/lib/postgresql/data
- ./init/pgvector.sql:/docker-entrypoint-initdb.d/pgvector.sql:ro
minio:
image: quay.io/minio/minio:RELEASE.2025-01-11T00-00-00Z
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY}
command: ["server", "/data", "--console-address", ":9001"]
ports: ["9000:9000", "9001:9001"]
volumes:
- minio:/data
createbuckets:
image: quay.io/minio/mc:latest
depends_on:
- minio
entrypoint: ["/bin/sh","-c"]
command: >
"mc alias set local http://minio:9000 ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY} &&
mc mb -p local/${MINIO_BUCKET} || true &&
mc anonymous set download local/${MINIO_BUCKET} || true"
# 代码镜像(可替换为你自己的 CI 构建镜像)
app-base:
image: python:3.11-slim
working_dir: /app
volumes:
- ./:/app
command: bash -lc "pip install -r requirements.txt && tail -f /dev/null"
orchestrator:
extends: { service: app-base }
environment: *app_env
depends_on: [redis, postgres, minio, createbuckets]
command: bash -lc "pip install -r requirements.txt && python orchestrator.py"
restart: unless-stopped
worker-fetch:
extends: { service: app-base }
environment: *app_env
depends_on: [redis]
command: bash -lc "python workers/fetcher.py"
restart: unless-stopped
worker-parse:
extends: { service: app-base }
environment: *app_env
depends_on: [redis]
command: bash -lc "python workers/parser.py"
restart: unless-stopped
worker-dedupe:
extends: { service: app-base }
environment: *app_env
depends_on: [redis, postgres]
command: bash -lc "python workers/deduper.py"
restart: unless-stopped
worker-nlp:
extends: { service: app-base }
environment: *app_env
depends_on: [redis]
command: bash -lc "python workers/nlp_agent.py"
restart: unless-stopped
worker-guard-publish:
extends: { service: app-base }
environment: *app_env
depends_on: [redis]
command: bash -lc "python workers/guard.py && python workers/publisher.py"
restart: unless-stopped
volumes:
pgdata: {}
minio: {}
```
.env.example
env
# === 数据库 / 队列 ===
POSTGRES_DB=eetree
POSTGRES_USER=eetree
POSTGRES_PASSWORD=secret
DATABASE_URL=postgresql+psycopg://eetree:secret@postgres:5432/eetree
REDIS_URL=redis://redis:6379/0
# === MinIO 对象存储 ===
MINIO_ENDPOINT=http://minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadminsecret
MINIO_BUCKET=eetree-media
# === 外部服务 ===
OPENAI_API_KEY=sk-xxxx
EETREE_API_URL=https://api.eetree.cn
EETREE_API_TOKEN=your_eetree_token
# === 配置文件路径(容器内) ===
SOURCES_FILE=/app/config/sources.yaml
PROMPT_FILE=/app/config/prompt.md
init/pgvector.sql
```sql
-- 在首次初始化数据库时自动启用 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;
-- 示例:为 articles 表添加向量列(若你使用 pgvector 存 embeddings)
-- ALTER TABLE articles ADD COLUMN IF NOT EXISTS embedding vector(1536);
```
目录与启动
bash
# 1) 准备文件
mkdir -p init config
# 将上面的 pgvector.sql 保存到 init/pgvector.sql
# 将你的 sources.yaml、prompt.md 放到 config/
# 2) 本地测试
cp .env.example .env
docker compose up -d
# 3) 查看日志
docker compose logs -f orchestrator worker-nlp
# 4) 停止与清理
# docker compose down -v
## 说明
- app-base 会挂载当前目录,默认安装 requirements.txt 后常驻;各 worker 继承它并执行相应脚本。
- 如你已将代码构建为镜像(例如 ghcr.io/eetree/auto-publisher:latest),可将 app-base 与各 worker 的 image 替换为你的镜像,并去掉 volumes/pip install。
- pgvector 用于存储文章向量,用来驱动“相关文章/AI 深解读”。若改用 Milvus/Weaviate,请在 Compose 中添加对应服务并调整 DATABASE_URL/索引层逻辑。
- MinIO 提供与 S3 兼容的对象存储,createbuckets 服务在首次启动时自动创建桶并设置匿名下载(可按需修改权限)。
- 生产环境建议:
- 打开 Postgres 持久化备份、设置密码管理(Vault/Secrets Manager)。
- 使用 Traefik/Nginx 暴露 Publisher API 或内部管理面板。
- 将 OpenAI Key 等敏感信息改用 Docker Secrets 或 K8s Secret。