← 返回列表 README.md 19.2 KB · 2026-11-06 10:21
# 三站 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 -
```