解决博客“零评论“困境:AI 评论机器人部署全记录

目录

前言

代码架构

关键设计

相关代码

config.py

ai_service.py

twikoo_client.py

auto_comment.py

[reply_bot.py reply_bot.py 作为回复评论逻辑模块,负责筛选并回复文章下现有评论:读取 Twikoo 本地数据库获取未回复评论列表(排除 AI 自身评论),调用 AI 服务生成针对性回复内容,正确设置 pid 和 rid 字段确保回复层级关系准确,实现模拟真实读者间的互动对话。](#reply_bot.py reply_bot.py 作为回复评论逻辑模块,负责筛选并回复文章下现有评论:读取 Twikoo 本地数据库获取未回复评论列表(排除 AI 自身评论),调用 AI 服务生成针对性回复内容,正确设置 pid 和 rid 字段确保回复层级关系准确,实现模拟真实读者间的互动对话。)

main.py

使用方法

基础用法

模式控制

批量处理

相关参数

后续扩展


发布于:

解决博客零评论困境:AI 评论机器人部署全记录 | Eucalyptus前言写博客的人大多有过这种体验:精心打磨一篇文章发布后,评论区却长期空白。对读者而言,"零评论"往往意味着内容无人问津,反而降低了互动意愿;对博主来说,手动维护评论既耗时又难以持续,尤其是当站点积累了数十篇甚至上百篇文章后,逐篇补充评论几乎成了不可能完成的任务。 我目前维护的个人博客基于 Hexohttps://blog.mingliangstar.com/2026/06/08/%E8%A7%A3%E5%86%B3%E5%8D%9A%E5%AE%A2%E9%9B%B6%E8%AF%84%E8%AE%BA%E5%9B%B0%E5%A2%83%EF%BC%9AAI-%E8%AF%84%E8%AE%BA%E6%9C%BA%E5%99%A8%E4%BA%BA%E9%83%A8%E7%BD%B2%E5%85%A8%E8%AE%B0%E5%BD%95/


前言

写博客的人大多有过这种体验:精心打磨一篇文章发布后,评论区却长期空白。对读者而言,"零评论"往往意味着内容无人问津,反而降低了互动意愿;对博主来说,手动维护评论既耗时又难以持续,尤其是当站点积累了数十篇甚至上百篇文章后,逐篇补充评论几乎成了不可能完成的任务。

我目前维护的个人博客基于 Hexo 框架,使用 anzhiyu 主题,评论系统选用 Twikoo 并通过 Docker 独立部署。随着文章数量增加,我逐渐意识到需要一个自动化的方案来解决评论冷启动问题------不是批量注册马甲账号刷量,而是让 AI 以真实读者的视角参与讨论,生成有实质内容、角度各异的评论,从而营造自然的社区氛围。

这个方案的核心思路是:利用大模型能力生成拟人化评论内容,同时通过模拟真实用户的浏览器 UA、IP 地址、Gravatar 头像等细节,让 AI 评论在数据层面与真实用户无异。系统不修改 Hexo 主题或 Twikoo 源码,而是以独立 Python 脚本的形式运行,通过读取 Twikoo 本地数据库获取现有评论,再经 HTTPS 接口提交新评论或回复。

本文将完整记录该系统的部署过程,从环境准备、模块配置到定时任务运行,力求每一步都可复现。Twikoo 本身的部署已有大量文档,此处不再赘述;代码实现仅展示关键结构与思路,具体细节可根据实际需求调整补充

代码架构

复制代码
ai_bot/                          # Python 包
├── __init__.py                  # 空文件,标记包
├── config.py                    # 配置中心(API密钥、URL、比例)
├── ai_service.py                # AI生成服务(昵称、评论、回复)
├── twikoo_client.py             # Twikoo交互层(读db、写接口)
├── auto_comment.py              # 新评论逻辑
├── reply_bot.py                 # 回复评论逻辑
└── main.py                      # 入口调度(CLI、批量、比例控制)

关键设计

分层隔离

  • ai_service 只负责内容生成,不感知 Twikoo
  • twikoo_client 只负责数据读写,不感知 AI
  • main 负责编排,业务逻辑下沉到 auto_comment / reply_bot

双通道读写

  • 读:直接解析本地 db.json.0,绕过 API 限制
  • 写:HTTS POST 到 Twikoo 容器,复用官方接口

状态外置

  • 角度轮换记录 USED_ANGLES 放内存(非持久,重启清空)
  • 配置全放 config.py,支持环境变量注入

相关代码

下述代码统一放在博客根目录下的一个文件中,例如新建 ai_bot

config.py

config.py 作为配置中心模块,集中管理 AI 接口密钥、Twikoo 服务地址、评论策略比例等全局参数,全部支持通过环境变量注入,实现配置与代码分离,便于不同环境快速切换。

SENSENOVA_API_URL这个需要使用自己模型的URL调用地址,SENSENOVA_MODEL切换模型,TWIKOO_URL可以使用域名或IP+PORT

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os

# ========== 日日新 SenseNova 配置 ==========
SENSENOVA_API_KEY = os.getenv("OPENAI_API_KEY", "sk-your-sensenova-key")
SENSENOVA_API_URL = "https://token.sensenova.cn/v1/chat/completions"
SENSENOVA_MODEL = "deepseek-v4-flash"

# Twikoo 配置
TWIKOO_URL = "http://localhost:8080"  
SITE_URL = "https://blog.mingliangstar.com"

# ========== AI 评论策略配置 ==========
# 新评论 vs 回复评论的比例(0-100)
# 例如:NEW_COMMENT_RATIO = 70 表示 70% 概率发新评论,30% 概率回复
NEW_COMMENT_RATIO = 70

# 日志
LOG_DIR = "/var/log/ai-bot"
os.makedirs(LOG_DIR, exist_ok=True)

OPENAI_API_KEY环境变量配置并持久化

复制代码
# 写入 ~/.bashrc
echo 'export OPENAI_API_KEY="sk-your-actual-sensenova-key"' >> ~/.bashrc

# 立即生效
source ~/.bashrc
ai_service.py

ai_service.py 作为 AI 生成服务模块,封装 SenseNova 大模型 API 调用,提供昵称生成、评论生成、回复生成三个核心接口;通过预设多角度评论池与角色扮演策略,结合温度参数和长度随机控制,确保输出内容拟人化且避免重复,同时维护已用角度记录实现单篇文章内的评论多样性。

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests
import random
from typing import Optional
from .config import SENSENOVA_API_KEY, SENSENOVA_API_URL, SENSENOVA_MODEL

# 评论角度池
COMMENT_ANGLES = [
    "技术实现角度,提出疑问或建议",
    "个人经验角度,分享相关经历",
    "应用场景角度,讨论实际使用",
    "学习收获角度,表达感谢和启发",
    "问题反馈角度,指出可能的坑",
    "扩展思考角度,提出延伸问题",
    "情感共鸣角度,表达认同和感受",
]

# 角色设定池
COMMENT_ROLES = [
    "技术小白,刚入门,有很多疑问",
    "有经验的开发者,喜欢分享经验",
    "学生党,正在学习相关技术",
    "职场老鸟,关注实际应用",
    "爱好者,喜欢折腾新技术",
    "博主同行,交流心得",
]

# 记录已用角度(内存中,重启会清空)
USED_ANGLES = {}


def generate_nick() -> str:
    """AI 生成随机昵称"""
    prompt = """你是一个中文网名生成器,请生成一个2-4个字的博客读者昵称。
要求:
- 像真实的中文网名,有生活气息
- 可以是:技术向、文艺向、幽默向、日常向
- 不要英文,不要符号
- 直接输出昵称,不要解释

示例:代码诗人、深夜咖啡、佛系码农、探索者、路过人间、技术小白"""

    headers = {
        "Authorization": f"Bearer {SENSENOVA_API_KEY}",
        "Content-Type": "application/json"
    }

    data = {
        "model": SENSENOVA_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 1.2,
        "max_tokens": 20,
        "reasoning_effort": "none"
    }

    try:
        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=10)
        result = resp.json()
        nick = result['choices'][0]['message']['content'].strip()
        nick = nick.replace("\"", "").replace("'", "").replace(":", "").replace(":", "").strip()
        return nick[:10]
    except Exception as e:
        print(f"昵称生成失败: {e}")
        fallback = ["技术小白", "路过读者", "前端爱好者", "博主粉丝", "学习中的猫", "代码搬运工", "夜猫子程序员", "探索者"]
        return random.choice(fallback)


def generate_comment(title: str, content: str, url: str = "") -> Optional[str]:
    """生成文章评论,支持多角度和角色"""

    # 选择未用过的角度
    used = USED_ANGLES.get(url, [])
    available = [a for a in COMMENT_ANGLES if a not in used]

    if not available:
        # 所有角度用完,重置
        available = COMMENT_ANGLES
        USED_ANGLES[url] = []

    angle = random.choice(available)
    USED_ANGLES[url] = USED_ANGLES.get(url, []) + [angle]

    # 随机角色
    role = random.choice(COMMENT_ROLES)

    # 随机长度要求
    length_req = random.choice([
        "简短有力,30-50字",
        "中等长度,50-80字",
        "稍微详细,80-100字",
    ])

    prompt = f"""你是一位{role},请根据以下文章生成一条评论。
要求:
- 从"{angle}"出发
- {length_req}
- 像真实人类一样,有个人观点
- 不要太过正式,口语化一点
- 避免和之前评论重复的内容

文章标题:{title}
文章内容摘要:{content[:500]}"""

    headers = {
        "Authorization": f"Bearer {SENSENOVA_API_KEY}",
        "Content-Type": "application/json"
    }

    data = {
        "model": SENSENOVA_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 1.5,  # 提高随机性
        "max_tokens": 150,
        "reasoning_effort": "none"
    }

    try:
        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=30)
        result = resp.json()
        return result['choices'][0]['message']['content'].strip()
    except Exception as e:
        print(f"AI 生成失败: {e}")
        return None


def generate_reply(comment_text: str, article_title: str) -> Optional[str]:
    """生成回复评论,使用读者角度"""
    
    prompt = f"""你是一位博客读者,正在回复这篇文章下的评论。
要求:
- 以读者身份回复,不是博主/作者
- 像真实读者一样,分享自己的看法、经验或疑问
- 可以表示认同、补充观点、提出相关问题
- 语气自然、口语化,像朋友聊天
- 不要说"作为作者"、"我会写"、"我的文章"等博主口吻
- 可以说"我也遇到过"、"同感"、"学到了"、"想问下"等读者常用语
- 长度适中,50-100字左右
- ⚠️ 重要:不要@任何人,直接回复即可,不要出现@用户名

文章标题:{article_title}
你要回复的评论:{comment_text[:300]}"""

    headers = {
        "Authorization": f"Bearer {SENSENOVA_API_KEY}",
        "Content-Type": "application/json"
    }
    
    data = {
        "model": SENSENOVA_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": 1.2,
        "max_tokens": 150,
        "reasoning_effort": "none"
    }
    
    try:
        resp = requests.post(SENSENOVA_API_URL, headers=headers, json=data, timeout=30)
        result = resp.json()
        reply = result['choices'][0]['message']['content'].strip()
        
        # 过滤掉 AI 可能生成的 @用户名
        import re
        reply = re.sub(r'@\w+\s*', '', reply).strip()
        
        return reply
    except Exception as e:
        print(f"AI 生成回复失败: {e}")
        return None
twikoo_client.py

twikoo_client.py 作为 Twikoo 交互层模块,采用双通道设计实现评论数据的读写操作:读取通道直接解析本地 db.json.0 文件获取完整评论列表,写入通道通过 HTTP POST 向 Twikoo 容器提交新评论或回复;同时负责拟人化字段构造(UA、IP、邮箱等)及 URL 编码兼容处理,确保数据层面与真实用户无异。

复制代码
# twikoo_client.py

import requests
import random
import json
import os
import urllib.parse
from typing import Optional, Dict, List
from .config import TWIKOO_URL, SITE_URL

# 真实用户 UA 池
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]

# 真实 IP 池(国内常见)
IP_POOL = [
    "223.104.212.1", "183.192.123.45", "117.136.62.89",
    "120.197.198.2", "183.60.235.1", "113.96.201.23",
    "116.22.29.1", "125.220.157.89", "42.236.10.1"
]

# Twikoo 本地数据库路径
DB_PATH = "/var/lib/docker/twikoo/data/db.json.0"


def _get_comments_from_db(url: str) -> List[Dict]:
    """从 Twikoo 本地 db.json 读取评论"""
    comments = []
    
    if not os.path.exists(DB_PATH):
        print(f"数据库文件不存在: {DB_PATH}")
        return comments
    
    try:
        with open(DB_PATH, 'r', encoding='utf-8') as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                try:
                    doc = json.loads(line)
                    doc_url = doc.get("url", "")
                    
                    # 匹配 url(同时支持编码和未编码)
                    urls_match = (
                        doc_url == url or 
                        doc_url == urllib.parse.unquote(url) or
                        urllib.parse.unquote(doc_url) == url
                    )
                    
                    if not urls_match:
                        continue
                    
                    # 排除垃圾评论和已删除
                    if doc.get("isSpam", False) or doc.get("status") == "deleted":
                        continue
                    
                    # 统一字段格式
                    comment = {
                        "_id": doc.get("_id"),
                        "id": doc.get("id") or doc.get("_id"),
                        "nick": doc.get("nick", ""),
                        "mail": doc.get("mail", ""),
                        "mailMd5": doc.get("mailMd5", ""),
                        "link": doc.get("link", ""),
                        "ua": doc.get("ua", ""),
                        "ip": doc.get("ip", ""),
                        "master": doc.get("master", False),
                        "url": doc_url,
                        "href": doc.get("href", ""),
                        "comment": doc.get("comment", ""),
                        "rid": doc.get("rid", ""),
                        "pid": doc.get("pid", ""),
                        "isSpam": doc.get("isSpam", False),
                        "created": doc.get("created", 0),
                        "updated": doc.get("updated", 0),
                    }
                    comments.append(comment)
                except json.JSONDecodeError:
                    continue
    except Exception as e:
        print(f"读取数据库失败: {e}")
    
    print(f"从数据库读取到 {len(comments)} 条评论")
    return comments


def get_comments(url: str, limit: int = 100) -> List[Dict]:
    """获取评论列表 - 直接读本地数据库"""
    return _get_comments_from_db(url)


def submit_comment(url: str, comment: str, nick: str, mail: str, pid: str = "", rid: str = "") -> bool:
    """提交评论到 Twikoo,支持回复"""
    
    # 1. 移除 SITE_URL 前缀
    if url.startswith(SITE_URL):
        url = url.replace(SITE_URL, "")
    
    # 2. 确保格式正确
    if not url.startswith("/"):
        url = "/" + url
    if not url.endswith("/"):
        url = url + "/"
    
    # 3. 先完全解码,再重新编码(防止双重编码)
    url = urllib.parse.unquote(url)
    parts = url.split('/')
    encoded_parts = [urllib.parse.quote(part, safe='') for part in parts]
    url = '/'.join(encoded_parts)
    
    # 4. 构造 href
    href = SITE_URL + url
    
    ua = random.choice(USER_AGENTS)
    fake_ip = random.choice(IP_POOL)
    
    # 5. 构造 payload
    # 关键修复:顶级评论不要传 pid/rid 字段(而不是传空字符串)
    payload = {
        "event": "COMMENT_SUBMIT",
        "url": url,
        "href": href,
        "comment": comment,
        "nick": nick,
        "mail": mail,
        "link": "",
        "ua": ua,
        "ip": fake_ip,
        "isSpam": False,  # 显式设置,必须和手动评论一致
    }
    
    # 只有回复时才添加 pid 和 rid
    if pid:
        payload["pid"] = pid
    if rid:
        payload["rid"] = rid
    
    headers = {
        "Content-Type": "application/json",
        "User-Agent": ua,
        "X-Forwarded-For": fake_ip,
        "X-Real-IP": fake_ip
    }
    
    try:
        resp = requests.post(TWIKOO_URL, json=payload, headers=headers, timeout=10)
        result = resp.json()
        print(f"  Twikoo 返回: {result}")
        print(f"  提交 URL: {url}")
        print(f"  Payload: {json.dumps(payload, ensure_ascii=False)}")  # 调试用
        return "id" in result or result.get("code") == 0
    except Exception as e:
        print(f"提交失败: {e}")
        return False


def check_ai_comment_exists(url: str) -> bool:
    """检查是否已有 AI 评论(通过 UA 识别)"""
    comments = get_comments(url)
    for c in comments:
        ua = c.get("ua", "")
        if ua in USER_AGENTS:
            return True
    return False
auto_comment.py

auto_comment.py 作为新评论逻辑模块,负责组装并提交单篇文章的首条 AI 评论:依次调用 AI 服务生成评论内容、随机昵称和邮箱,构造完整的 Twikoo 评论数据包,最终通过客户端提交;内置可选的去重检查机制,避免同一篇文章被重复评论。

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import random
from .ai_service import generate_comment, generate_nick
from .twikoo_client import submit_comment, check_ai_comment_exists


def generate_mail() -> str:
    """生成邮箱,Twikoo 自动显示 Gravatar 头像"""
    seed = random.randint(1000, 9999)
    return f"user{seed}@example.com"


def process_article(url: str, title: str, content: str) -> bool:
    """处理单篇文章:发新评论"""
    print(f"处理: {title}")
    
    # 检查是否已有 AI 评论(可选,如果想多次评论可以注释掉)
    # if check_ai_comment_exists(url):
    #     print("  已有 AI 评论,跳过")
    #     return False
    
    comment_text = generate_comment(title, content, url)
    if not comment_text:
        print("  AI 生成失败")
        return False
    
    print(f"  生成: {comment_text[:50]}...")
    
    nick = generate_nick()
    print(f"  昵称: {nick}")
    
    mail = generate_mail()
    print(f"  邮箱: {mail}")
    
    if submit_comment(url, comment_text, nick, mail):
        print(f"  ✅ 提交成功")
        return True
    else:
        print(f"  ❌ 提交失败")
        return False
reply_bot.py reply_bot.py 作为回复评论逻辑模块,负责筛选并回复文章下现有评论:读取 Twikoo 本地数据库获取未回复评论列表(排除 AI 自身评论),调用 AI 服务生成针对性回复内容,正确设置 pidrid 字段确保回复层级关系准确,实现模拟真实读者间的互动对话。
复制代码
# reply_bot.py

import random
from typing import List, Dict
from .ai_service import generate_reply, generate_nick  # 导入 generate_nick
from .twikoo_client import get_comments, submit_comment


def get_unreplied_comments(url: str, bot_nicks: List[str]) -> List[Dict]:
    """获取未回复的评论(排除自己的评论)"""
    comments = get_comments(url)
    
    unreplied = []
    
    # 收集所有被回复的评论 ID(用 pid)
    replied_ids = set()
    for c in comments:
        pid = c.get("pid")
        if pid:
            replied_ids.add(pid)
    
    print(f"已被回复的评论ID: {replied_ids}")
    
    for comment in comments:
        # 获取评论 ID(优先 _id)
        comment_id = comment.get("_id") or comment.get("id")
        nick = comment.get("nick", "unknown")
        
        # 跳过自己的评论
        if nick in bot_nicks:
            continue
        
        # 检查是否已被回复
        if comment_id and comment_id in replied_ids:
            continue
        
        unreplied.append(comment)
    
    print(f"发现 {len(unreplied)} 条未回复评论")
    return unreplied


def reply_to_comment(parent_comment: Dict, article_title: str) -> bool:
    parent_nick = parent_comment.get("nick", "网友")
    parent_text = parent_comment.get("comment", "")[:30]
    print(f"回复 [{parent_nick}]: {parent_text}...")
    
    # 生成回复内容
    reply_text = generate_reply(
        comment_text=parent_comment.get("comment", ""),
        article_title=article_title
    )
    
    if not reply_text:
        print("  回复生成失败")
        return False
    
    print(f"  回复: {reply_text[:50]}...")
    
    # 随机生成昵称
    reply_nick = generate_nick()
    print(f"  昵称: {reply_nick}")
    
    # 获取父评论ID
    parent_id = parent_comment.get("_id") or parent_comment.get("id")
    
    # 获取父评论的 rid
    parent_rid = parent_comment.get("rid", "")
    if parent_rid:
        # 父评论本身是回复,继承其 rid
        root_id = parent_rid
    else:
        # 父评论是顶级评论,rid 就是父评论自己的 id
        root_id = parent_id
    
    # 提交回复(不加 @,Twikoo 前端自动显示)
    result = submit_comment(
        url=parent_comment.get("url", "/"),
        comment=reply_text,
        nick=reply_nick,
        mail=f"ai-reply-{random.randint(1000,9999)}@example.com",
        pid=parent_id,
        rid=root_id,
    )
    
    return result


def run_reply_bot(url: str, bot_nicks: List[str], max_reply: int = 3):
    """运行回复机器人"""
    print(f"检查文章: {url}")
    
    unreplied = get_unreplied_comments(url, bot_nicks)
    
    # 随机打乱顺序
    random.shuffle(unreplied)
    
    # 取前 max_reply 条
    to_reply = unreplied[:max_reply]
    
    print(f"选择回复 {len(to_reply)}/{len(unreplied)} 条评论")
    
    success = 0
    for comment in to_reply:
        if reply_to_comment(comment, "文章标题"):
            success += 1
    
    print(f"完成: 回复 {success}/{len(to_reply)} 条评论")
    return success
main.py

main.py 作为入口调度模块,负责整合全系统运行流程:自动扫描 Hexo 生成目录发现文章列表,按配置比例随机调度新评论或回复模式,提供 CLI 参数支持单篇/批量/范围选择及临时比例覆盖,最终输出执行统计报告,是日常定时任务调用的唯一入口。

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AI 博客评论机器人 - 智能模式
按配置比例随机选择发新评论或回复现有评论
"""

import sys
import argparse
import os
import re
import urllib.parse
import random
from typing import List, Dict
from .reply_bot import run_reply_bot
from .ai_service import generate_comment, generate_nick
from .twikoo_client import submit_comment
from .config import NEW_COMMENT_RATIO


# ========== 文章加载 ==========

def get_hexo_articles(public_dir: str = "/www/wwwroot/myblog/public") -> List[Dict]:
    """从 Hexo 生成目录获取文章列表"""
    articles = []
    for root, dirs, files in os.walk(public_dir):
        rel_path = os.path.relpath(root, public_dir)
        if not re.match(r'^\d{4}/\d{2}/\d{2}/', rel_path):
            continue
        if "index.html" not in files:
            continue
        url = f"/{rel_path}/"
        dir_name = os.path.basename(rel_path)
        title = urllib.parse.unquote(dir_name)
        articles.append({"url": url, "title": title, "content": ""})
    return articles


def load_articles() -> List[Dict]:
    """加载文章列表"""
    articles = get_hexo_articles()
    if articles:
        print(f"自动获取到 {len(articles)} 篇文章")
        return articles
    print("自动获取失败")
    return []


# ========== 新评论 ==========

def generate_mail() -> str:
    """生成随机邮箱"""
    return f"user{random.randint(1000, 9999)}@example.com"


def process_new_comment(url: str, title: str, content: str = "") -> bool:
    """发新评论"""
    print(f"处理: {title}")

    comment_text = generate_comment(title, content, url)
    if not comment_text:
        print("  AI 生成失败")
        return False

    print(f"  生成: {comment_text[:50]}...")

    nick = generate_nick()
    mail = generate_mail()
    print(f"  昵称: {nick}")
    print(f"  邮箱: {mail}")

    if submit_comment(url, comment_text, nick, mail):
        print(f"  ✅ 提交成功")
        return True
    else:
        print(f"  ❌ 提交失败")
        return False


# ========== 智能调度 ==========

def process_article_smart(article: Dict, bot_nicks: List[str], max_reply: int = 3) -> Dict:
    """智能处理:随机选择发新评论或回复"""

    url = article["url"]
    title = article["title"]
    is_new = random.randint(1, 100) <= NEW_COMMENT_RATIO

    result = {
        "url": url, "title": title,
        "mode": "new" if is_new else "reply",
        "success": False, "detail": ""
    }

    if is_new:
        print(f"\n📝 [{title}] 随机选择:发新评论")
        success = process_new_comment(url, title, article.get("content", ""))
        result["success"] = success
        result["detail"] = "新评论提交成功" if success else "新评论提交失败"
    else:
        print(f"\n💬 [{title}] 随机选择:回复评论")
        count = run_reply_bot(url, bot_nicks, max_reply=max_reply)
        result["success"] = count > 0
        result["detail"] = f"回复了 {count} 条评论"

    return result


def run_smart(articles: List[Dict], bot_nicks: List[str] = None, max_reply: int = 3):
    """批量智能处理"""
    if bot_nicks is None:
        bot_nicks = ["AI助手", "博主助手"]

    stats = {"new": 0, "reply": 0, "new_ok": 0, "reply_ok": 0}

    for article in articles:
        r = process_article_smart(article, bot_nicks, max_reply)
        stats[r["mode"]] += 1
        if r["success"]:
            stats[f"{r['mode']}_ok"] += 1

    print(f"\n{'='*50}")
    print("📊 执行统计")
    print(f"{'='*50}")
    print(f"总文章: {len(articles)}")
    print(f"新评论: {stats['new']} 篇 (成功 {stats['new_ok']})")
    print(f"回  复: {stats['reply']} 篇 (成功 {stats['reply_ok']})")
    print(f"设置比例: {NEW_COMMENT_RATIO}%")
    actual = round(stats['new']/len(articles)*100, 1) if articles else 0
    print(f"实际比例: {actual}%")
    print(f"{'='*50}")


# ========== CLI ==========

def main():
    parser = argparse.ArgumentParser(description="AI Blog Bot")
    parser.add_argument("command", choices=["run", "list"])
    parser.add_argument("--id", type=str, help="文章序号,如 12 或 1,3,5-10")
    parser.add_argument("--count", type=int, default=3, help="回复上限(默认3)")
    parser.add_argument("--ratio", type=int, help="临时新评论比例 1-100")

    args = parser.parse_args()

    if args.command == "list":
        articles = load_articles()
        print(f"\n{'序号':<6}{'标题':<40}{'URL'}")
        print("-" * 80)
        for i, a in enumerate(articles, 1):
            print(f"{i:<6}{a['title'][:38]:<40}{a['url'][:50]}")
        print(f"\n共 {len(articles)} 篇")
        return

    # 临时覆盖比例
    if args.ratio is not None:
        global NEW_COMMENT_RATIO
        NEW_COMMENT_RATIO = max(1, min(100, args.ratio))
        print(f"临时比例: {NEW_COMMENT_RATIO}%")

    bot_nicks = ["AI助手", "博主助手"]
    articles = load_articles()

    if args.id:
        selected = []
        for part in args.id.split(','):
            if '-' in part:
                s, e = map(int, part.split('-'))
                selected.extend([articles[i-1] for i in range(s, e+1) if 1 <= i <= len(articles)])
            else:
                i = int(part)
                if 1 <= i <= len(articles):
                    selected.append(articles[i-1])
        print(f"处理 {len(selected)} 篇")
        run_smart(selected, bot_nicks, max_reply=args.count)
    else:
        run_smart(articles, bot_nicks, max_reply=args.count)


if __name__ == "__main__":
    main()

使用方法

基础用法

复制代码
#查看文章列表
python -m ai_bot.main list

# 处理单篇文章(按配置比例随机选择模式)
python -m ai_bot.main run --id 3

# 处理多篇
python -m ai_bot.main run --id 1,3,5

# 处理范围
python -m ai_bot.main run --id 1-10

模式控制

复制代码
# 强制发新评论(100% 概率)
python -m ai_bot.main run --id 3 --ratio 100

# 强制回复模式(0% 概率发新评论)
python -m ai_bot.main run --id 3 --ratio 0

# 回复模式下,最多回复 5 条现有评论
python -m ai_bot.main run --id 3 --ratio 0 --count 5

批量处理

复制代码
# 全部文章(按默认比例)
python -m ai_bot.main run

# 指定范围 + 强制新评论
python -m ai_bot.main run --id 5-20 --ratio 100

相关参数

|-----------|----|-----|-----------------------------|
| 参数 | 必填 | 默认值 | 说明 |
| --id | 否 | 全部 | 文章序号,如 3 / 1,3 / 5-10 |
| --ratio | 否 | 配置值 | 临时覆盖新评论比例,1-100 |
| --count | 否 | 3 | 回复模式下单篇最大回复数 |

后续扩展

AI 互动机器人 :注册固定昵称(如 @小助手),访客可在评论区 @ 主动提问,AI 实时检测触发词并回复,支持多轮对话,从单向评论输出升级为双向互动助手。

相关推荐
经济视野1 小时前
朗禾品牌设计,深耕餐饮VI与空间设计,以专业实力赋能品牌成长
大数据·人工智能
东坡肘子1 小时前
WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139
人工智能·swiftui·swift
学机械的鱼鱼1 小时前
一文读懂轮足翼复合机器人:结构特点与仿真学习路线规划
学习·机器人
IT阿瑞1 小时前
制造业 AI Agent 实施服务商横评:2026 年企业级自动化选型全景分析
大数据·人工智能·自动化
kishu_iOS&AI1 小时前
LLM —— 基础知识(Bert&GPT&T5)浅析
人工智能·gpt·bert
人工智能培训1 小时前
从GPT到开源大模型
人工智能·gpt·深度学习·机器学习·容器·知识图谱
数据仓库搬砖人1 小时前
从零搭建你的第一个 AI Agent:LangGraph 完全上手指南
人工智能
宋哥转AI1 小时前
Java后端转AI Agent:技术栈全景图与从ReAct到多Agent协作实战
java·人工智能·agent
樱花的浪漫1 小时前
Typescript、Zod基础
前端·javascript·人工智能·语言模型·自然语言处理·typescript