logo
0
0
WeChat Login
obaby<root@obaby.org.cn>
添加项目官网地址

Baby Anti-Spam

项目官网: https://anti-spam.zhongxiaojie.cn

作者: obaby,博客署名 baby 𝐢‍𝐧⃝ void

面向 中英混合 评论的 WordPress 垃圾识别方案:PHP 插件在评论入库前调用 本机 Python 服务,由小型多语种向量模型 + 分类器(或演示用规则)给出垃圾概率。

适合评论量不大、单机部署(例如 4 核 / 8GB RAM 的 Ubuntu),服务与 WordPress 同机时使用 127.0.0.1 即可。

仓库结构

baby_anti_spam/ ├── README.md ├── screenshots/ # 文档:服务启动与 curl 自测示意 │ ├── service.png │ └── test.png ├── service/ # Python FastAPI 侧车服务 │ ├── .env.example │ ├── requirements.txt │ ├── requirements-ml.txt │ ├── run.py │ ├── app/ │ │ └── stats_backends/ # 统计存储:sqlite / mysql │ └── scripts/ │ ├── init_stats_mysql.sql │ └── init_stats_mysql.py │ ├── train_sklearn.py │ ├── download_embedding_model.py │ └── download_embedding_model.sh └── wordpress/baby-anti-spam/ └── baby-anti-spam.php # WordPress 插件

Python 服务

环境

  • Python 3.10+(建议)
  • 核心依赖见 service/requirements.txt多语种向量service/requirements-ml.txt(含 PyTorch 等,单独安装可规避部分 pip 解析 bug)
  • *.joblibscikit-learn 版本需一致:训练机与服务机的 sklearn 应同一大版本(本仓库约束 >=1.8)。混用 1.7 与 1.8 易出现反序列化警告或推理报错(例如 LogisticRegression 缺少 multi_class);请在服务器上 pip install -U "scikit-learn>=1.8" 与训练环境对齐,或改用与线上一致的 sklearn 重新训练后再部署。

安装与启动

务必先升级 pip,再继续(可减少 AssertionError: assert len(weights) == expected_node_count 一类错误):

cd service python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate python -m pip install -U "pip>=24.2" setuptools wheel pip install -r requirements.txt pip install -r requirements-ml.txt # 若仅开演示规则、不用 .joblib 模型,可跳过本行 cp .env.example .env # 编辑 .env:至少配置 SPAM_API_SECRET,或使用 SPAM_API_KEYS / SPAM_API_KEYS_FILE(见下文) python run.py

默认监听:http://127.0.0.1:8765(由 SPAM_HOST / SPAM_PORT 决定,run.py 可用 --port 覆盖端口)。

run.py 本机 / 对外开放(命令行覆盖 SPAM_HOST):

参数说明
(默认)使用 .env 中的 SPAM_HOST,未设置时为 127.0.0.1
--local强制只监听 127.0.0.1
--remote监听 0.0.0.0(供局域网 / 公网访问,务必配合防火墙与强密钥)
--port N覆盖端口(否则用 SPAM_PORT 或默认 8765

启动时会在终端打印作者与博客信息、监听地址、模型与统计库等摘要(便于确认当前配置)。

安装报错排查(pip AssertionError / 拓扑排序)

若出现 get_topological_weights / assert len(weights) == expected_node_count(常见于 未先升级 pip 就装 requirements-ml.txt):

  1. 在同一 venv 内先升级,再单独重试 ML 依赖:
    python -m pip install -U "pip>=24.2" setuptools wheel
    pip install -r requirements-ml.txt
  2. 保持分步安装:先 requirements.txt,再 requirements-ml.txt,不要合并成一条长命令。
  3. 仍失败时再启用旧版解析器(仅作兜底):
    • Linux / macOS:PIP_USE_DEPRECATED=legacy-resolver pip install -r requirements-ml.txt
    • Windows PowerShell:
      $env:PIP_USE_DEPRECATED = "legacy-resolver" pip install -r requirements-ml.txt
  4. CPU 机器可先装 CPU 版 PyTorch 再装 sentence-transformers(减轻冲突),例如:
    pip install torch --index-url https://download.pytorch.org/whl/cpu
    然后再:pip install -r requirements-ml.txt

环境变量(.env

变量说明
SPAM_HOST监听地址,同机建议 127.0.0.1
SPAM_PORT端口,默认 8765
SPAM_API_SECRET单密钥模式(兼容旧版):未配置 SPAM_API_KEYS 且未配置 SPAM_API_KEYS_FILE 时,仅此密钥有效,等价于 name=default、不限流(max_rpm=0)。与 WP 插件里填写的密钥一致
SPAM_API_KEYS多密钥:JSON 数组。每项为 name(唯一,用于统计与限流分组)、keysecret(与请求头一致)、max_rpmrpm(每分钟最大请求数,0 表示不限制)。与 SPAM_API_KEYS_FILE 合并时:先读文件条目,再追加本变量
SPAM_API_KEYS_FILE可选,指向 JSON 文件,根节点为与上表相同结构的数组。文件必须存在,否则进程启动失败
SPAM_MODEL_PATH训练得到的 *.joblib 路径;留空则取决于 SPAM_FALLBACK_RULES
SPAM_FALLBACK_RULES无模型文件时是否启用内置极简规则(演示用);生产训练后应设为 false 并配置 SPAM_MODEL_PATH
SPAM_LABEL_THRESHOLD可选,默认 0.8spam_score ≥ 此值时 JSON 中 labelspam,否则为 normal
SPAM_DFA_ENABLED默认 true。为 true 时使用 dfa-python-filter/keywords 做敏感词检测;命中则直接 spam_score=1detail=dfa_sensitive(早于 sklearn)
SPAM_DFA_KEYWORDS_PATH可选,自定义敏感词文件路径;留空则用 service/dfa-python-filter/keywords
SPAM_NON_CHINESE_FLOOR_ENABLED默认 true。为 true 时若合并后的 author/email/url/text 中无任何 CJK 表意字符(主要针对中文训练语料),则将 spam_score 至少抬到 SPAM_NON_CHINESE_SPAM_FLOOR
SPAM_NON_CHINESE_SPAM_FLOOR默认 0.9。与上项配合,在「无中文」评论上与 sklearn / 规则分取 max
SPAM_STATS_ENABLED默认 true。为 true 时记录每次成功返回的 /v1/classify 请求与响应(失败 / 401 不落库),并允许 /v1/mark-spam 写入 spam_marks
SPAM_STATS_BACKENDsqlite(默认)或 mysql。选 mysql 时需安装 pymysql(已在 requirements.txt)并配置下方 MySQL 变量
SPAM_STATS_DB_PATHsqlite:数据库文件路径;留空则为 service/data/stats.sqlite(已加入 .gitignore
SPAM_STATS_MYSQL_HOST / SPAM_STATS_MYSQL_PORTmysql:默认 127.0.0.1 / 3306
SPAM_STATS_MYSQL_USER / SPAM_STATS_MYSQL_PASSWORDmysql:连接账号(user 必填)
SPAM_STATS_MYSQL_DATABASEmysql:库名(必填),默认示例 baby_spam_stats
SPAM_STATS_MYSQL_CHARSETmysql:默认 utf8mb4

MySQL 统计库初始化(配置步骤)

将统计后端从默认 SQLite 迁到 MySQL 8+MariaDB 10.5+ 时,按下面顺序配置即可(脚本与表结构与运行时一致,见 service/scripts/init_stats_mysql.sqlservice/scripts/init_stats_mysql.py)。

  1. service 目录准备 .env
    在已有 SPAM_API_SECRET(或 SPAM_API_KEYS)基础上增加例如:

    SPAM_STATS_ENABLED=true SPAM_STATS_BACKEND=mysql SPAM_STATS_MYSQL_HOST=127.0.0.1 SPAM_STATS_MYSQL_PORT=3306 SPAM_STATS_MYSQL_USER=baby_spam SPAM_STATS_MYSQL_PASSWORD=请改为强密码 SPAM_STATS_MYSQL_DATABASE=baby_spam_stats SPAM_STATS_MYSQL_CHARSET=utf8mb4

    SPAM_STATS_MYSQL_USERSPAM_STATS_MYSQL_DATABASE 在使用 MySQL 后端时必填;库名可与示例不同,但须与后续建库一致。

  2. 安装客户端库
    requirements.txt 已包含 pymysql,正常执行 pip install -r requirements.txt 即可。

  3. 创建数据库与表(任选其一;建议生产由 DBA 或脚本预置)

    • Python 初始化脚本(推荐):在已激活 venv、service 为当前目录、且上一步 MySQL 变量已写入 .env 后执行:

      cd service python scripts/init_stats_mysql.py

      该账号需能首次执行 CREATE DATABASE IF NOT EXISTS(或你事先建好库则仅需对该库有建表权限);脚本会创建库(若不存在)并调用与应用相同的 CREATE TABLE / 索引逻辑。不要求事先把 SPAM_STATS_BACKEND 设为 mysql,脚本只读取 SPAM_STATS_MYSQL_*

    • 手工执行 SQL:用高权限账号执行 init_stats_mysql.sql(与 service/scripts/init_stats_mysql.py 建库建表语义一致),例如当前目录为仓库根时:

      mysql -h 127.0.0.1 -P 3306 -u root -p < service/scripts/init_stats_mysql.sql

      若已在 service 目录下,则改为 < scripts/init_stats_mysql.sql

      脚本内含有注释掉的「创建专用用户 + GRANT」示例,可按环境取消注释并替换密码后再执行,再让应用使用受限账号连接。

    • 仅依赖服务启动须先人工创建好目标库(应用连接时使用 database=…不会在运行时 CREATE DATABASE)。对该库授予 CREATE TABLECREATE INDEXALTER(用于旧表补列)等权限后,设置 SPAM_STATS_BACKEND=mysql 并启动服务, lifespan 中的 init_stats_db() 会创建 classify_callsspam_marks 及索引。

  4. 启动并自检
    运行 python run.py,终端不应出现统计库初始化失败日志;可用带 X-Baby-Anti-Spam-SecretGET /v1/stats/calls 验证,或在库中查询上述两张表是否有新写入。

多密钥示例(单行 JSON,注意引号转义):

SPAM_API_KEYS=[{"name":"blog_a","key":"第一个长随机串","max_rpm":120},{"name":"blog_b","secret":"第二个密钥","rpm":30}]

WordPress 只需填写当前站点使用的那一个 key/secret,与服务端列表中任一项匹配即可。

限流:对每个 name 独立计数,60 秒滑动窗口;超限返回 429detailrate_limit_exceeded/v1/stats/calls/v1/classify/v1/mark-spam 共用同一组密钥,且都会计入该密钥的 max_rpm

HTTP 接口

  • GET /health — 健康检查,无需认证
  • GET / — 粉色风格首页:系统简介、随机背景图(zhongxiaojie.cn/baby_images/)、链向测试页与 /docs
  • GET /test/spam — 浏览器内粉色测试页(无需单独密钥即可打开页面;检测时请在页内填写与后端一致的 API 密钥)。同源访问可避免 CORS,例如 http://127.0.0.1:8765/test/spam
  • GET /v1/stats/calls — 调用统计(需密钥)。返回全库汇总次数、是否启用统计,以及按密钥 name 分组的已落库次数(by_key)。仅统计已成功写入统计库 classify_calls 的分类请求
  • POST /v1/mark-spam — 人工标记评论(需密钥)。text 必填labelspam(标记为垃圾,默认)或 normal(标记为不是垃圾)。其余字段与分类对齐;可选 sourcenote。写入表 spam_marksmarked_label 列,并记录 api_key_nameSPAM_STATS_ENABLED=false 时返回 503 stats_disabled
  • POST /v1/classify — 分类,需在请求头携带 X-Baby-Anti-Spam-Secret: <与任一已配置密钥相同>

请求体(JSON):

{ "text": "评论正文", "author": "昵称", "email": "邮箱", "url": "网址" }

响应示例:

{ "spam_score": 0.12, "label": "normal", "detail": "sklearn" }

label 为二分类:normalspam(由 SPAM_LABEL_THRESHOLDspam_score 决定)。WordPress 插件可用 spam_score + 阈值,也可按 label 分项阈值 再判垃圾 / 待审 / 放行。

统计接口响应示例GET /v1/stats/calls):

{ "call_count": 42, "stats_enabled": true, "by_key": { "default": 30, "blog_a": 12 } }

在已配置 SPAM_MODEL_PATH 时,返回的 spam_scoresklearn 概率与内置规则分的较大值(常见英文 SEO、click here+链接等会抬高分数),detail 可能为 sklearn+rules(...);纯模型主导时仍为 sklearn

敏感词检测(dfa_sensitive)在匹配前会 去掉 http(s)://…www.… 整段 URL,避免长链接里的 Base64/参数偶然拼出词表短串(例如 wyd)误命中。

若启用 SPAM_NON_CHINESE_FLOOR_ENABLED,且整条评论(含昵称、邮箱、网址、正文)里检测不到 CJK 表意字符,最终分数会与 SPAM_NON_CHINESE_SPAM_FLOOR(默认 0.9)取较大值,detail 中会出现 no_cjk_floor

用 curl 自测

将下面地址、端口与密钥换成你 .env 里的 SPAM_HOST / SPAM_PORT / 当前配置的 SPAM_API_SECRETSPAM_API_KEYS 中任一的 key

健康检查(无需密钥):

curl -sS "http://127.0.0.1:8765/health"

正常中文评论(期望 spam_score 偏低):

curl -sS -X POST "http://127.0.0.1:8765/v1/classify" \ -H "Content-Type: application/json" \ -H "X-Baby-Anti-Spam-Secret: change-me-long-random" \ -d '{"text":"写得很清楚,感谢分享!","author":"读者","email":"","url":""}'

中英混合、含链接或推广话术(演示规则或模型下通常分数更高):

curl -sS -X POST "http://127.0.0.1:8765/v1/classify" \ -H "Content-Type: application/json" \ -H "X-Baby-Anti-Spam-Secret: change-me-long-random" \ -d '{"text":"SEO optimization click here https://example.com/junk","author":"Agent","email":"spammer@example.com","url":"https://example.com"}'

错误密钥(响应头里应为 401 Unauthorized):

curl -sS -i -X POST "http://127.0.0.1:8765/v1/classify" \ -H "Content-Type: application/json" \ -H "X-Baby-Anti-Spam-Secret: wrong-secret" \ -d '{"text":"test","author":"","email":"","url":""}'

调用次数(需与上面相同的有效密钥头):

curl -sS "http://127.0.0.1:8765/v1/stats/calls" \ -H "X-Baby-Anti-Spam-Secret: change-me-long-random"

标记垃圾 / 标记正常(写入 spam_marksmarked_labellabel 一致,api_key_name 为当前密钥的 name):

# 标记为垃圾(label 可省略,默认 spam) curl -sS -X POST "http://127.0.0.1:8765/v1/mark-spam" \ -H "Content-Type: application/json" \ -H "X-Baby-Anti-Spam-Secret: change-me-long-random" \ -d '{"text":"明显广告","author":"spammer","source":"manual","note":"后台标记"}' # 标记为不是垃圾 curl -sS -X POST "http://127.0.0.1:8765/v1/mark-spam" \ -H "Content-Type: application/json" \ -H "X-Baby-Anti-Spam-Secret: change-me-long-random" \ -d '{"text":"认真讨论","label":"normal","source":"manual","note":"误杀纠正"}'

运行截图

服务已启动(本机 python run.py / Uvicorn 监听示例):

Python 服务运行

curl 自测分类接口/v1/classify 请求与 JSON 响应示例):

curl 分类接口测试

模型从哪里来(怎么「下载」)

本项目里实际上是两件东西,不要混在一起:

  1. 句向量模型(Hugging Face 上的 sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

    官方模型页(境外,若超时可用下方镜像链接):
    https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

    国内常用镜像(同一条 repo_id,仅域名不同,便于浏览说明页):
    https://hf-mirror.com/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2

    拉到指定目录的脚本(便于离线拷贝;仅需 pip install huggingface_hub,不必先有 PyTorch):

    cd service pip install huggingface_hub # 默认走 huggingface.co;若超时请加 --mirror 或先 export HF_ENDPOINT=https://hf-mirror.com python scripts/download_embedding_model.py --mirror --out-dir models/paraphrase-multilingual-MiniLM-L12-v2 # 或: bash scripts/download_embedding_model.sh models/paraphrase-multilingual-MiniLM-L12-v2

    huggingface.co 拉取仍失败时,可在一台能访问外网的机器上下载整个 models/paraphrase-multilingual-MiniLM-L12-v2 目录,再打包拷贝到服务器(repo_id 不变,仍是同一模型)。

    训练时改为指向该目录(同一 repo 内相对路径即可):

    python scripts/train_sklearn.py --data /path/to/comments.csv --out models/spam_pipeline.joblib \ --model-name models/paraphrase-multilingual-MiniLM-L12-v2

    默认行为(不写脚本): 在已安装 sentence-transformers 的前提下,下面任一操作会自动从 Hub 下载到缓存~/.cache/huggingface/,约几百 MB):

    • 第一次运行 scripts/train_sklearn.py 训练;或
    • 服务已配置 SPAM_MODEL_PATH 指向训练好的 *.joblib第一次对评论做推理、管线里要加载 SentenceTransformer 时。

    只想预下载向量模型到默认缓存(可选):service 的 venv 里执行:

    Linux / macOS(bash):

    export HF_ENDPOINT=https://hf-mirror.com # 可选,再执行下行 python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')"

    Windows PowerShell(当前会话生效):

    $env:HF_ENDPOINT = "https://hf-mirror.com" # 可选 python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')"

    Windows CMD:

    set HF_ENDPOINT=https://hf-mirror.com python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')"

    缓存默认在 ~/.cache/huggingface/(也可用环境变量 HF_HOME 指定目录;无网机器可把缓存目录整体拷过去)。

    huggingface_hub / sentence-transformers 走镜像下载时,一般设置:export HF_ENDPOINT=https://hf-mirror.com(与 hf-mirror 文档一致)。Windows 用 PowerShell 的 $env:HF_ENDPOINT = "https://hf-mirror.com",或 CMD 的 set HF_ENDPOINT=https://hf-mirror.com。其它第三方镜像若有提供等价 Hub API 地址,可用下载脚本的 --endpoint URL

  2. 「是不是垃圾」的分类头(spam / normal)

    • 本仓库默认可直接加载的,只有你本地训练出来的 spam_pipeline.joblibtrain_sklearn.py + 你的 CSV)。本仓库不提供一份「官方预训练 joblib」供下载。
    • 互联网上当然有大量「已训练好的文本分类模型」(例如 Hugging Face Hub 里搜索 spamsms spam 等),但它们多半是 英文、任务定义(标签含义)和 推理框架 与当前服务不一致;不能把某个 .bin / safetensors 直接填进 SPAM_MODEL_PATH——当前服务只认 joblib.dump 的 sklearn 流水线格式。
    • 若你希望「只下载、少标注」,更现实的做法仍是:用 少量 本站评论(几十~几百条)微调一层分类头,生成 joblib;或自行 fork 后在服务里增加对 Transformers pipeline("text-classification") 的调用路径,再指定 Hub 上的模型 ID(需自行评估语种、误杀率和许可协议)。

只用内置演示规则时(detailfallback_rules不会去下载上述向量模型。

训练模型

准备 CSV,两列:textlabel,其中 labelnormalspam(旧数据里 ham 在训练脚本中会映射为 normal):

text,label 这是一条正常评论,normal 点击领取红包...,spam

方式 A(推荐):本站已通过评论 + fixed 仅垃圾

  • normalsources/private/所有 *.csv(WordPress 导出格式:comment_approved=1comment_type=comment)都会并入;例如 mars_comments.csvzhong.wp_comments.csv。正文会做简单去 HTML。该目录已在 .gitignore 中忽略,请自行放入导出文件。
  • spam:仅使用 sources/fixed/data/spam.txt(一行一条)。fixed 下的 normal 数据不参与训练。 若语料里游戏公会 / COC「几本」类内容过多,可在 service 下执行 python scripts/filter_spam_remove_game_lines.py(会先备份 spam.txt.bak 再剔除匹配行;--dry-run 仅统计)。
    若还要去掉相对中性、像正常聊天的短句、只保留广告/脏词/链接/联系方式等明显垃圾行,可执行 python scripts/filter_spam_obvious_only.py(备份 spam.txt.bak_obvious--dry-run 仅统计)。
cd service source .venv/bin/activate python scripts/sources_to_csv.py \ --spam-file sources/fixed/data/spam.txt \ --private-dir sources/private \ --out data/comments.csv

还可追加单个导出文件:--normal-wp-export path/to/extra.csv(可多次写)。

方式 B:两个纯文本整文件(一行一条;会用到 fixed 的 normal 时选此项):

python scripts/sources_to_csv.py \ --normal-file sources/fixed/data/normal.txt \ --spam-file sources/fixed/data/spam.txt \ --out data/comments.csv

方式 C:目录 sources/normal/*.txtsources/spam/*.txt

python scripts/sources_to_csv.py --out data/comments.csv
  • 目录模式可用 --sources /path/to/root 指定含 normal/spam/ 的根路径。
  • 需要按「文本+标签」去重时加 --dedupe
  • 全量数据训练耗时长、占用内存大;试跑可先 --dedupe 或裁切子集。

Windows PowerShell(方式 A):

python scripts/sources_to_csv.py ` --spam-file sources/fixed/data/spam.txt ` --private-dir sources/private ` --out data/comments.csv

训练(默认会从 Hub 拉取 paraphrase-multilingual-MiniLM-L12-v2,约几百 MB;若已下载或拷贝到本地目录,请加 --model-name 指向该目录):

cd service source .venv/bin/activate python scripts/train_sklearn.py --data data/comments.csv --out models/spam_pipeline.joblib # 已本地化向量模型时,例如: # python scripts/train_sklearn.py --data data/comments.csv --out models/spam_pipeline.joblib \ # --model-name models/paraphrase-multilingual-MiniLM-L12-v2 python scripts\train_sklearn.py --data data\comments_blog_fixed_spam.csv --out models\spam_obaby_pipeline.joblib

Windows(--data 换成你的 CSV 绝对路径或 .\data\comments.csv):

cd service .\.venv\Scripts\Activate.ps1 python scripts/train_sklearn.py --data .\data\comments.csv --out models\spam_pipeline.joblib # 若需本地向量模型: 再加 --model-name models\paraphrase-multilingual-MiniLM-L12-v2

.env 中设置:

SPAM_MODEL_PATH=/绝对路径/models/spam_pipeline.joblib SPAM_FALLBACK_RULES=false

重启 Python 服务后生效。

若加载模型时出现 InconsistentVersionWarning( pickle 时 sklearn 比运行环境新)或推理时报 LogisticRegression/multi_class 相关错误:在服务的 venv 内执行 pip install -U "scikit-learn>=1.8"(与 requirements.txt 一致),重启服务;不要在低版本 sklearn 上加载高版本导出的 joblib

WordPress 插件

  1. 将目录 wordpress/baby-anti-spam 复制到 wp-content/plugins/baby-anti-spam/,或打包为 zip 在后台上传安装。
  2. 在后台启用 Baby Anti-Spam
  3. 进入 设置 → Baby Anti-Spam
    • 服务地址:例如 http://127.0.0.1:8765
    • API 密钥:与服务端配置的 SPAM_API_SECRET(单密钥模式)或 SPAM_API_KEYS 中该站点对应条目的 key 一致
    • 判定依据:仅用 spam_score,或按 API 的 normal / spam 分项阈值再结合分数
    • 超时:默认约 2.5 秒,可按机器情况调整
    • 阈值:垃圾线与待审线(可针对 normal / spam 两档分别设置)
    • 服务失败时:放行或待审(避免服务宕机导致整站评论不可用)

插件使用过滤器 pre_comment_approvedspam_score 达垃圾线则 spam,达待审线则 hold,否则保持原状态。

安全建议

  • 默认只监听本回环地址(127.0.0.1);若使用 run.py --remoteSPAM_HOST=0.0.0.0,应对公网暴露有明确需求并配合防火墙、反代与强密钥。
  • 使用强随机密钥;多站点可用 SPAM_API_KEYS 分密钥并设置 max_rpm,降低单 key 泄露后的滥用面。
  • 统计库(SQLite 文件或 MySQL 中的 classify_calls)含评论正文等数据:SQLite 注意文件权限;MySQL 注意账号最小权限、网络隔离与备份留存策略。
  • 定期用本站真实垃圾/正常样本更新训练数据,再重新导出 joblib

许可证

插件头注明 GPLv2 or later(与 WordPress 生态常见约定一致);若你单独发布服务代码,可自行选择与服务侧一致的许可证。