# 三站 AI 发文系统(gk_auto)
**自动 + 手动双通道,互不干涉**:
| 通道 | 触发方式 | 每站篇数 | 用途 |
|---|---|---|---|
| 🤖 **自动** | cron 每天 12:00 | **2 篇**(共 6 篇/天) | 稳定的基本盘,无需你管 |
| ✋ **手动** | 你在线时随时执行 | 交互式指定 | 想加量就加,不影响自动 |
| 目标站点 | 栏目 | 文章类型 |
|---|---|---|
| 博客主站 `www.geekfei.cn` | AI 自主选择 8 个分类之一 | 科技/人文/历史/文学/生活类博客 |
| 影评站 `film.geekfei.cn` | AI 自主选择(国内/国外/经典/剧集/媒体) | 影评 |
| 影像站 `v.geekfei.cn` | **摄影博文**(固定 `zhaji`) | 摄影技术文章 |
---
## 一、两种模式怎么互不干涉
这是本系统最关键的设计,靠**两层隔离**实现:
**① 记账分开**
每篇文章的 postmeta 都写一个来源标记 `_gk_auto_src`:
- 自动发的写 `auto`
- 手动发的写 `manual`
自动任务**只跟 auto 的记录比**。所以你手动发了 5 篇,它依然认为"自动还差 0 篇"或"已完成",
不会被你的手动发文干扰;反过来手动也不看 auto 的账。
日志里会明确区分,例如:
```
⏭ 今日自动已发 2 篇(目标 2),跳过(另有手动 5 篇)
```
**② 锁分开**
- 自动任务用 `/tmp/gk_auto.lock`
- 手动任务用 `/tmp/gk_manual.lock`
手动正跑时自动照样能跑(各写各的文章),只有**同类任务**自己才互斥,
防止你连续点两次造成重复执行。
---
## 二、当前状态
✅ 脚本已部署到服务器 `/root/gk_auto/`
✅ DeepSeek API Key 已配置并**实测联通**
✅ cron 已安装:每天 12:00 自动执行,每站 2 篇
✅ 自动/手动双通道分离记账已实测验证
✅ 2026-10-18 实机发稿三站全部成功,前台可正常访问
✅ 幂等性、AI 失败保护、字数校验、热点抓取、百度推送 均已验证
**系统组成:**
| 文件 | 作用 |
|---|---|
| `config.php` | 配置(密钥、分类、字数区间、阅读量区间、百度 token、自动/手动篇数) |
| `gk_auto.php` | 主程序(生成 + 校验 + 发库 + 推送) |
| `hotspots.php` | 热点抓取模块 |
| `baidu_push.php` | 百度推送模块 |
| `media.php` | **主题配图模块**(搜索 + 质量评分 + 写入媒体库) |
| `gk_run.sh` | **自动**执行壳(cron 调它,锁 `gk_auto.lock`) |
| `gk_manual.sh` | **手动**执行壳(你用,锁 `gk_manual.lock`) |
**2026-10-18 已发布:**
| 站点 | ID | 标题 | 分类 | 字数 | 阅读量 |
|---|---|---|---|---|---|
| 主站 | 3097 | 胶片颗粒为何成了数字时代的乡愁 | 飞飞视觉 | 2208 | — |
| 影评站 | 2693 | 《一次别离》:真相的裂缝里,没有人是干净的 | 国外影评 | 1482 | — |
| 影评站 | 2694 | 《功夫女足》:周星驰的自我致敬,还是自我消耗? | 国内影评 | 2708 | 1574 |
| 影像站 | 1536 | 手持防抖的真实边界:1/8秒能救回什么 | 摄影博文 | 1948 | — |
---
## 三、每天自动做的事
1. **抓热点** —— 抓当天真实的百度热搜 / IT之家 / 36氪 / 豆瓣 / 娱乐榜,注入提示词
2. **AI 选题写作** —— 从热点里选一个作为切入角度,写 2000 字以上
3. **字数校验** —— 不足下限自动要求 AI 扩写(最多两轮)
4. **分类判定** —— 按内容自动归入对应栏目
5. **发库** —— 直连数据库,不走 XML-RPC
6. **初始阅读量** —— 随机 1000–2000
7. **推百度** —— 新文章 URL 立即提交
> 热点源可用性实测:百度热搜、IT之家、36氪、少数派、豆瓣均可用;
> 色影无忌 RSS 已失效、微博/知乎热搜需鉴权,已排除。
---
## 四、手动追加发文(你在线时用)
登录服务器后执行:
```bash
/root/gk_auto/gk_manual.sh
```
会弹出交互菜单让你选每站几篇:
```
════════ 手动发文 ════════
请选择每站发布篇数:
1) 每站 1 篇(共 3 篇)
2) 每站 2 篇(共 6 篇)
3) 每站 3 篇(共 9 篇)
4) 自定义
请输入 [1-4](直接回车 = 1):
```
也可以直接指定,不走交互:
```bash
/root/gk_auto/gk_manual.sh --count 2 # 每站 2 篇
/root/gk_auto/gk_manual.sh main --count 1 # 只发主站 1 篇
/root/gk_manual.sh film --count 3 # 只发影评站 3 篇
```
**手动模式永远追加**,不受自动任务的篇数影响。
你看日志时能分清哪些是自动发的:
```bash
tail -30 /www/wwwlogs/gk_auto.log # 自动任务日志
tail -30 /www/wwwlogs/gk_manual.log # 手动任务日志
```
数据库里也能查来源:
```sql
SELECT p.ID, s.meta_value AS 来源, p.post_title
FROM wp_posts p
JOIN wp_postmeta d ON p.ID=d.post_id AND d.meta_key='_gk_auto_date'
LEFT JOIN wp_postmeta s ON p.ID=s.post_id AND s.meta_key='_gk_auto_src'
WHERE p.post_type='post' ORDER BY p.ID DESC LIMIT 10;
```
---
## 五、自动任务
cron 条目(每天 12:00):
```
0 12 * * * /root/gk_auto/gk_run.sh all >> /www/wwwlogs/gk_auto.log 2>&1
```
想改每站篇数,改 `config.php` 里的 `auto_count`(当前 2):
```php
'auto_count' => 2, // 每天每站自动发几篇
```
tail -50 /www/wwwlogs/gk_auto.log # 看执行日志
tail -50 /www/wwwlogs/gk_manual3.log # 批量任务日志
```
不带 `--force` 时,如果当天已发篇数达到目标就自动跳过(防止重复)。
### 多篇模式的保护
- 标题**相似度拦截**:3 字滑窗重合率 ≥50% 判定撞题,直接丢弃该篇
- 每篇生成时都会拿到**本批已发标题**,从源头降低撞题概率
- 百度推送**只推每批最后一篇**,避免把配额在前几篇就打光
---
---
## 六、主题配图(自动抓图)
每篇文章发布前会自动**按主题抓一张图**,存进媒体库,插到正文最前面(居中、圆角 10px)。
### 抓图流程
```
文章标题+正文 → AI 生成精准搜图关键词 → 百度图片搜索
→ 多维预筛(清晰度/尺寸/来源黑名单)
→ 逐张下载 + 质量评分(色彩/亮度/对比/水印/文档特征)
→ 取最高分 → 写入 uploads + 建 attachment 记录 → 插入正文
```
### 为什么需要评分
百度图片结果质量参差。实测盲取第一张会拿到:
- 游戏截图带繁体字幕
- 教科书翻拍(能看到纸张阴影)
- 厂商发布会广告牌
- 软件界面截图
所以 `media.php` 用图像分析把这类图筛掉:
| 检测项 | 判定依据 |
|---|---|
| 翻拍文档 | 边缘大面积近白 + 饱和度极低 |
| 软件截图 | 对比度极低 + 近乎无色彩 |
| 过曝/过暗 | 平均亮度 >225 或 <35 |
| 水印 | 四角 18% 区域出现大量近白像素 |
| 广告图 | 来源页标题含"优惠/促销/价格/测评" |
### 关键词用 AI 生成
规则切词会切出「与低压」「种典型」这种垃圾,AI 判断语义才靠谱:
| 文章 | AI 给出的关键词 | 实际选中的图 |
|---|---|---|
| 充电五分钟,我们等了三十年 | 手机充电 / 充电器 / 手机电池 | 充电宝+手机产品图 |
| 直方图不骗人 | 单反相机 / 相机屏幕 / 摄影曝光 | 尼康微单实拍 |
| 弱光人像:别急着开大光圈 | 人像摄影 / 夜景人像 | 夜景人像布光示范 |
| 《奥德赛》 | 远洋帆船 / 海边港口 | 帆船(避开汽车歧义) |
| 太空稻种的四十年归途 | 水稻种子 / 太空舱 | 太空种子播种 |
### 配置
```php
'enable_hero_image' => true, // 开关
'allow_publish_without_image' => true, // 抓不到图也照发(不影响正文)
```
### 试出来的坑
- ★ **百度图片 `clarityScore` 字段很有用**(0-100),配合原始尺寸可先筛掉大半劣质图
- ★ **必须校验图片魔数**,否则会把 HTML 错误页当图片存进 uploads
- ★ 图片属主必须 `www:www`,否则 WP 无法按需重建缩略图
- ★ `_wp_attached_file` 存的是**相对 uploads 的路径**(`2026/10/xxx.jpg`),不是绝对路径
- ★ 中文文件名可直接用(实测 WP 与 nginx 均正常),语义化更利于 SEO
---
---
## 七、栏目轮转(每天随机主题、分散到不同栏目)
**解决的问题**:以前让 AI 自己选分类,结果总往「热点侃点」「技术宅」挤,
两个栏目各 60+ 篇,而「原创文学」「杂类」长期停在 20 多篇没动。
**现在的做法**:脚本强制轮转分配,AI 只在指定栏目内选题。
### 分配算法
每次发文前,脚本统计每个栏目的:
1. **现有文章数**(少的优先)
2. **上次发文距今天数**(最久没写的优先)
按 `文章数升序 → 天数降序` 排序,取前 N 个,N = 本批篇数。
同一批内不会重复分配同一栏目。
实测某天主站的优先级:
| 顺序 | 栏目 | 现有篇数 | 上次发文 |
|---|---|---|---|
| 1 | 杂类 | 21 | 3 天前 |
| 2 | 原创文学 | 28 | 2021-04-15(很久) |
| 3 | 科学会 | 29 | 当天 |
| 4 | 文史纵横 | 47 | 184 天前 |
| 5 | 热点侃点 | 66 | 1 天前 |
| 6 | 技术宅 | 68 | 当天 |
当天发 2 篇 → 自动分给「杂类」和「原创文学」。长期跑下去各栏目数量自然趋于均衡。
### 栏目锁定怎么传给 AI
提示词里插入一段「栏目锁定(必须遵守)」:
```
本篇文章已经确定归入栏目「原创文学」(slug: wenxue)。
你必须在这个栏目的范围内选题,不要写别的话题。
该栏目的选题方向包括:文学与阅读、书评、写作技艺、作家与作品、文本细读。
请从上述方向中自行挑一个具体角度,不要写成栏目综述。
category 字段必须固定返回 "wenxue"。
```
**分类以脚本为准**:即使 AI 返回了别的分类,也会被覆盖,日志会记一笔
`· AI 原选分类 'redian',按轮转计划改用 'wenxue'`。
### 热点素材与栏目方向冲突的处理
热点素材原本写死了「请从以下热点中**选定一个**」。但「原创文学」遇上百度热搜会很别扭。
现在按栏目区分:
- `redian`(热点侃点):保留「优先从热点选定一个」
- 其余栏目:改为「仅供参考,若与栏目主题不符请完全忽略」
### 配置
`config.php` 里主站的:
```php
// 参与轮转的栏目(yingping/visual 不参与,那两栏内容来自影评站/影像站)
'rotate_categories' => ['jishu', 'keji', 'redian', 'wenshi', 'wenxue', 'zatan'],
// 每个栏目的选题方向池
'topic_hints' => [
'jishu' => '编程与互联网原理、软件工程实践、系统与运维、网络安全、AI 与算法、开源生态、技术史',
'keji' => '物理化学与生命科学、天文与航天、医学与健康、数学、科学史与科学方法、自然观察',
'redian' => '近期社会新闻与公共话题的观察评论、行业动态、流行现象剖析',
'wenshi' => '历史事件与人物、文化变迁、典籍与思想史、地域民俗、语言文字源流',
'wenxue' => '文学与阅读、书评、写作技艺、作家与作品、文本细读',
'zatan' => '生活观察、日常经验、器物与消费、城市与旅行、教育、个人杂感',
],
```
想加/减栏目,改 `rotate_categories` 即可;想调某个栏目的选题范围,改 `topic_hints`。
> 注:导航栏的「飞侃」是蜻蜓FM 外链(`gk-channels.php` 里的 `feikan`),不是分类,不参与轮转。
---
---
## 八、百度推送(每篇发布后立即推送)
每篇文章发布成功后**立刻**调用百度普通收录 API 推送 URL(不再"只推最后一篇")。
### 三个接口
| 站点 | site 参数 | 说明 |
|---|---|---|
| 主站 | `geekfei.cn` | **不带 www**,见下方重要说明 |
| 影评站 | `film.geekfei.cn` | |
| 影像站 | `v.geekfei.cn` | |
token 三站共用(真实值见服务器 /root/gk_auto/config.php 的 baidu_token):`YOUR_BAIDU_PUSH_TOKEN`
### ⚠️ 两个必须注意的坑
**① site 参数不能带协议**
百度后台给的接口形如 `http://data.zz.baidu.com/urls?site=https://v.geekfei.cn&token=xxx`,
那个 `site=https://v.geekfei.cn` 里的协议是**不能要的**,直接复制会 `site init fail`。
正确写法是 `site=v.geekfei.cn`。代码里已做硬校验,带 `://` 直接拒绝并提示。
**② geekfei.cn 和 www.geekfei.cn 是两个不同站点**
实测:
```
site=geekfei.cn + URL=https://www.geekfei.cn/xxx
→ {"success":0,"not_same_site":["https://www.geekfei.cn/xxx"]}
```
百度把裸域和 www 当成两个独立站点,**配额也各自独立**(各约 10 条/天):
| 站点 | 配额 | 占用情况 |
|---|---|---|
| `geekfei.cn` | 10 条/天 | 此前一直空闲 |
| `www.geekfei.cn` | 10 条/天 | 被 `/root/baidu_push_www.php` 的 cron 占用 |
所以主站配置成:
```php
'baidu_site' => 'geekfei.cn', // 推送的 site 参数
'baidu_push_host' => 'geekfei.cn', // 把文章 URL 的 host 也换成不带 www
```
文章本身的 URL 仍是 `https://www.geekfei.cn/...`(对外访问用),
推送前自动替换成 `https://geekfei.cn/...` 以匹配 site。日志里会打出推送地址供核对。
> 推送的 URL 会 301 跳转到 www 版本,这是正常的——百度跟随 301 后记录最终页面。
### 配额
- 每站约 **10 条/天**,0 点重置
- 超了返回 `over quota`,日志记「今日配额已用尽(次日自动重试)」,**不影响发文**
- 每站每天 2 篇,额度完全够用
### 改配置
```php
// 每个站点
'baidu_site' => 'geekfei.cn', // 纯域名,不带协议
'baidu_push_host' => 'geekfei.cn', // 留空则用文章原始 URL
```
想关掉某站推送,把 `baidu_site` 置空即可。
---
---
## 九、医疗医药选题过滤
**要求**:自动撰写的文章不得涉及医疗、医药、疾病、诊疗、药品、健康养生类主题。
原文里出现过《西柚为何成了药片的天敌》这类临床药理学选题(讲 CYP3A4 酶、血药浓度),
所以加了三道防线。
### ① 选题方向里不再引导
`config.php` 的 `topic_hints` 中,`keji` 原来写着「医学与健康」,已改成
「物理、化学、生物与生态、天文与航天、数学、地质与气候、科学史与科学方法、自然观察(不写医疗医药健康类)」。
### ② 热点素材先剔除
`hotspots.php` 的 `filter_med_items()` 在抓完热点后过滤条目,日志会显示
`(已剔除 N 条医疗医药类热点)`。
**词表设计的关键**:刻意不收录容易被技术文章误用的词:
| 不收录 | 原因 |
|---|---|
| 病毒、感染、移植 | 电脑病毒、病毒感染、数据移植 |
| 诊断 | 故障诊断 |
| 筛查 | 漏洞筛查 |
| 体检 | 代码体检、安全体检 |
| 瘦身 | 包体积瘦身、APP 瘦身 |
医疗语境的「诊断/筛查」通常同时含「医院/医生/癌症/临床」,能被其他词拦住。
实测量化(`med_hit()`):
- 医疗话题 10/10 全部拦下
- 技术话题 10/10 全部通过,零误伤
### ③ 生成后硬校验
提示词只能引导,AI 仍可能自己想到医疗题。所以 `publish_one()` 在拿到内容后检查
**标题 + 摘要**(正文不查,因为写历史、写技术史时提一句「医院」是正常的):
- 命中 → 要求「完全换一个主题」重写
- 两次都命中 → 跳过本篇(`reason: medical_topic`),不影响后续
日志会打印:`⚠ 选题涉及医疗(命中「药物」),要求换主题重写…`
### 调整词表
改 `hotspots.php` 里 `med_block_words()` 的数组即可。
加词前先想一遍:这个词在技术/生活语境会不会也出现?会,就别加。
---
## 十、常用调整
全部在 `/root/gk_auto/config.php` 里:
| 想改什么 | 改哪里 |
|---|---|
| **自动每天每站发几篇** | `auto_count`(当前 2,即每天 6 篇) |
| **手动默认篇数** | `manual_count_default`(交互时会先问,这个是兜底) |
| **手动是否也会跳过** | `manual_skip_done`(当前 false = 随时可追加) |
| **字数范围** | 各站 `words => [2000, 2400]`,实际每天在两者间随机取下限 |
| **阅读量范围** | `seed_views_min` / `seed_views_max` |
| **关掉阅读量** | `seed_views => false`(回到真实 0 起算) |
| **关掉热点抓取** | `no_hotspots => true` |
| **关掉自动配图** | `enable_hero_image => false` |
| **配图失败是否照发** | `allow_publish_without_image` |
| 固定的分类 | `fixed_category`(v 站已固定为 `zhaji`) |
| AI 创意度 | `ai.temperature`(0.85,越高越发散) |
| AI 选谁 | `ai.providers` 数组顺序 |
| 换模型 | 对应 provider 的 `model` |
| 改写文章风格 | 改 `gk_auto.php` 里 `build_prompt()` 的提示词 |
| **百度推送 site** | 各站 `baidu_site`(**必须纯域名,不能带 https://**) |
| **推送 URL 换域名** | 各站 `baidu_push_host`(主站填 `geekfei.cn`,其余留空) |
改完不用重启任何服务,下次执行自动生效。
---
## 十一、⚠️ 关于「初始阅读量」的说明
wp-postviews 把浏览数存在 `postmeta.views`。脚本给新文章写入一个 1000–2000 的随机值,
这样文章一上线列表页就能显示「阅 1,574」这样的数字,不会显得冷清。
但要说清楚:**这不是真实访问量,是构造出来的展示数据。**
搜索引擎并不把浏览量当作排名因素,百度尤其如此。如果你的目标是长期稳定排名,
更稳的做法是让数字从 0 真实起算——把配置里 `seed_views` 改成 `false` 即可,一行的事。
写入逻辑集中在 `gk_auto.php` 的 `random_views()`,随时可改。
---
## 十二、几个安全设计
1. **自动/手动分开记账** —— postmeta `_gk_auto_src` = `auto` / `manual`,两边互不干扰
2. **自动/手动分开加锁** —— `gk_auto.lock` 与 `gk_manual.lock`,手动在跑不阻塞自动
3. **按天限量** —— postmeta `_gk_auto_date` + 模式标签,重复执行不会刷屏
4. **标题相似度拦截** —— 3 字滑窗重合 ≥50% 判撞题丢弃
5. **AI 失败不写库** —— 生成失败直接退出,数据库不会有半成品文章(已验证)
6. **没有 key 就跳过** —— 不会疯狂报错刷日志
7. **分类白名单校验** —— AI 返回了不存在的分类会自动纠正
8. **危险标签过滤** —— script/iframe/form 等标签会被剔除
9. **文件名权限** —— config.php 是 600 root only(里面存 key)
10. **推送前校验 site 参数** —— 带 `https://` 会被本地拦下,不会白耗百度配额
---
## 十三、常见问题
**Q:文章质量一般?**
提示词已做强约束(禁 AI 味套话、禁编造事实、要求具体细节、热点驱动)。
某站风格不满意就改 `gk_auto.php` 的 `build_prompt()` 对应分支。
**Q:会不会发重复内容?**
每次生成都会把近期 25 篇标题喂给 AI 让它避开,且每天每站只发一篇。
**Q:百度推送说配额用尽?**
配额按站点独立分配,且与既有三套 cron(07:17/07:31/07:45)**共用额度池**。
每天本来就只有 9 条左右,推送新文章会挤占一些,这是预期行为。
新文章进 sitemap 才是全量收录的主渠道,不占配额。
**Q:热点抓不到怎么办?**
已做降级:抓取失败不会中断发文,只是这次选题不限定时效。
**Q:想停掉?**
```bash
crontab -l | grep -v gk_run.sh | grep -v 'gk_auto' | crontab -
```