Baby Anti-Spam 自建反垃圾评论系统

很久之前,就经常收到akismet的授权提醒,对应一个错误码10010。

刚开始还以为是多域名访问导致的授权校验出问题了。后来换了n个key,同时添加了插件hook掉所有的垃圾评论检测逻辑,让全部走统一的域名,结果前几天又收到这个提醒了。

插件代码:

<?php
/**
 * Plugin Name: Akismet 单一主域名(多域名站点)
 * Description: 当站点配置了多个域名时,强制发往 Akismet 的请求只使用一个主域名,避免被计为多站点触发 10010。
 * Version: 1.0
 * Author: obaby
 *
 * 使用:在下方设置 AKISMET_CANONICAL_HOME 为主域名(或留空则用 WordPress「设置」里的站点地址)。
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * 主域名(规范 URL,不要末尾斜杠)。留空则使用 get_option( 'home' )。
 * 例如: https://www.example.com
 */
if ( ! defined( 'AKISMET_CANONICAL_HOME' ) ) {
    define( 'AKISMET_CANONICAL_HOME', 'https://zhongxiaojie.cn' );
}

/**
 * 获取发往 Akismet 时使用的唯一主域名 URL。
 */
function akismet_single_domain_get_canonical_home() {
    $home = AKISMET_CANONICAL_HOME;
    if ( $home === '' || $home === null ) {
        $home = get_option( 'home' );
    }
    return untrailingslashit( $home );
}

/**
 * 将任意 URL 替换为使用主域名的版本(只改 host,保留 path/query)。
 */
function akismet_single_domain_normalize_url( $url, $canonical_home ) {
    if ( empty( $url ) || ! is_string( $url ) ) {
        return $url;
    }
    $parsed = wp_parse_url( $url );
    $canon  = wp_parse_url( $canonical_home );
    if ( empty( $canon['scheme'] ) || empty( $canon['host'] ) ) {
        return $url;
    }
    $scheme = isset( $parsed['scheme'] ) ? $parsed['scheme'] : $canon['scheme'];
    $host   = $canon['host'];
    $path   = isset( $parsed['path'] ) ? $parsed['path'] : '/';
    $query  = isset( $parsed['query'] ) ? '?' . $parsed['query'] : '';
    $frag   = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : '';
    return $scheme . '://' . $host . $path . $query . $frag;
}

/**
 * 统一 verify-key / get-subscription / get-stats 的 blog 为主域名。
 */
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
    $paths = array( 'verify-key', 'get-subscription', 'get-stats' );
    if ( ! in_array( $path, $paths, true ) ) {
        return $request_args;
    }
    $canon = akismet_single_domain_get_canonical_home();
    if ( ! empty( $request_args['blog'] ) ) {
        $request_args['blog'] = $canon;
    }
    return $request_args;
}, 10, 2 );

/**
 * 统一 comment-check(以及 recheck)的 blog、permalink,并把请求里的 HTTP_HOST 等改为主域名。
 */
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
    if ( $path !== 'comment-check' ) {
        return $request_args;
    }
    $canon = akismet_single_domain_get_canonical_home();
    $parsed = wp_parse_url( $canon );
    if ( empty( $parsed['host'] ) ) {
        return $request_args;
    }
    $canon_host = $parsed['host'];

    $request_args['blog'] = $canon;
    if ( ! empty( $request_args['permalink'] ) ) {
        $request_args['permalink'] = akismet_single_domain_normalize_url( $request_args['permalink'], $canon );
    }

    // 让服务端看到的“当前请求”也统一为主域名,减少被计为多站点
    if ( isset( $request_args['HTTP_HOST'] ) ) {
        $request_args['HTTP_HOST'] = $canon_host;
    }
    if ( isset( $request_args['REQUEST_URI'] ) ) {
        $uri = $request_args['REQUEST_URI'];
        $request_args['REQUEST_URI'] = ( is_string( $uri ) && ( $p = wp_parse_url( $uri, PHP_URL_PATH ) ) !== null ) ? $p : '/';
    }
    if ( isset( $request_args['DOCUMENT_URI'] ) ) {
        $uri = $request_args['DOCUMENT_URI'];
        $request_args['DOCUMENT_URI'] = ( is_string( $uri ) && ( $p = wp_parse_url( $uri, PHP_URL_PATH ) ) !== null ) ? $p : '/';
    }
    return $request_args;
}, 10, 2 );

/**
 * 统一 submit-spam / submit-ham 的 blog、permalink。
 */
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
    if ( ! in_array( $path, array( 'submit-spam', 'submit-ham' ), true ) ) {
        return $request_args;
    }
    $canon = akismet_single_domain_get_canonical_home();
    $request_args['blog'] = $canon;
    if ( ! empty( $request_args['permalink'] ) ) {
        $request_args['permalink'] = akismet_single_domain_normalize_url( $request_args['permalink'], $canon );
    }
    return $request_args;
}, 10, 2 );

这次授权的密钥撑得时间稍微长了点,但是最终还是收到了这个提醒,意思是需要订购商业版授权。我这个人站点为了发垃圾评论订购一个商业版授权,确实有些难以接受。

于是,我决定自建反垃圾评论系统,基于scikit-learn实现了现在的这套垃圾评论检测系统,训练数据一部分来源于github的开源数据,另外一个就是我自己博客的评论数据。为了保证样本正例和负例数量差别不至于过大,经过各种方式进行了多轮数据清洗。

如果想要评论识别更加准确,可以提供自己的博客评论数据,如果能提供垃圾评论更好。现在欠缺的主要是垃圾评论数据,正常的评论数据我已经提供几千条数据。

效果测试:

测试地址:https://anti-spam.zhongxiaojie.cn/test/spam

简介:

面向 中英混合 评论的 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 插件

关键配置:

| 变量 | 说明 |
|------|------|
| `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`(唯一,用于统计与限流分组)、`key` 或 `secret`(与请求头一致)、`max_rpm` 或 `rpm`(每分钟最大请求数,`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.8`。`spam_score` ≥ 此值时 JSON 中 `label` 为 `spam`,否则为 `normal` |
| `SPAM_DFA_ENABLED` | 默认 `true`。为 `true` 时使用 `dfa-python-filter/keywords` 做敏感词检测;命中则直接 `spam_score=1`、`detail=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_BACKEND` | `sqlite`(默认)或 `mysql`。选 `mysql` 时需安装 `pymysql`(已在 `requirements.txt`)并配置下方 MySQL 变量 |
| `SPAM_STATS_DB_PATH` | 仅 `sqlite`:数据库文件路径;留空则为 `service/data/stats.sqlite`(已加入 `.gitignore`) |
| `SPAM_STATS_MYSQL_HOST` / `SPAM_STATS_MYSQL_PORT` | 仅 `mysql`:默认 `127.0.0.1` / `3306` |
| `SPAM_STATS_MYSQL_USER` / `SPAM_STATS_MYSQL_PASSWORD` | 仅 `mysql`:连接账号(`user` 必填) |
| `SPAM_STATS_MYSQL_DATABASE` | 仅 `mysql`:库名(必填),默认示例 `baby_spam_stats` |
| `SPAM_STATS_MYSQL_CHARSET` | 仅 `mysql`:默认 `utf8mb4` |

 

系统服务启动截图:

wp插件配置:

项目地址:https://anti-spam.zhongxiaojie.cn

代码地址:https://cnb.cool/oba.by/baby-wp-anti-spam

说明:如果自己不想训练数据,下载发布版的spam_pipeline.joblib 放入指定目录下配置服务启动即可,baby-anti-spam.zip 为wp插件。

训练耗时大约11分钟:



闺蜜圈APP

You may also like

60 comments

  1.  Level 3
    Google Chrome 131.0.6778.200 Google Chrome 131.0.6778.200 Android 16 Android 16 cn中国–湖南省–永州市–道县–中国电信–公众宽带 IPv6

    😬看不懂看不懂,我只看头图。多发点!!! smile

  2. Level 4
    Microsoft Edge 148.0.0.0 Microsoft Edge 148.0.0.0 Android 10 Android 10 cn中国–上海–上海–长宁区 电信/普陀区电信 IPv4

    你们都是高手,我就是个普通用户,啥也不懂🤦‍♂️

        1.  公主 Queen Queen 
          Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

          很有可能
          不过如果是认定广告(我有商业行为),难道还来看网页内容了?

  3. Level 1
    Microsoft Edge 146.0.0.0 Microsoft Edge 146.0.0.0 Mac OS X  26.4 Mac OS X 26.4 cn中国–河南–郑州 电信 IPv4

    这可太有实力了……
    我的小站连垃圾评论都看不上…… cry

    1.  公主 Queen Queen 
      Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–山东–青岛 联通 IPv4

      这个都不需要实力,wp的垃圾评论插件无差别攻击。哈哈哈

  4. Level 3
    Microsoft Edge 146.0.0.0 Microsoft Edge 146.0.0.0 Android 10 Android 10 cn中国–安徽省–合肥市–蜀山区–中国联通–3GNET网络 IPv6

    有垃圾评论的说明人家看你那有价值,不像俺这小垃圾站,都某得垃圾评论

    1.  公主 Queen Queen 
      Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–山东–青岛 联通 IPv4

      这玩意儿都是批量扫描的,和价值无关

  5. Level 3
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Mac OS X  26.4 Mac OS X 26.4 cn中国–山东–临沂 移动 IPv4

    我最近在使用 LiMhy,这些个大佬动不动就手搓插件,弄个主题,太厉害了

  6. Level 2
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 11 x64 Edition Windows 11 x64 Edition cn中国–陕西省–咸阳市–杨陵区–中国电信–公众宽带 IPv6

    设置个评论验证码不是也能拦住部分吗?

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–潍坊市–潍城区–中国联通–3GNET网络 IPv6

      php搞算法太麻烦了,直接起个服务最简单

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–潍坊市–潍城区–中国联通–3GNET网络 IPv6

      算不上,充其量是个算法,哈哈

      1.  Level 2
        Microsoft Edge 146.0.0.0 Microsoft Edge 146.0.0.0 Android 10 Android 10 cn中国–江西省–九江市–德安县–中国联通–3GNET网络 IPv6

        哈哈哈哈我现在就是搞注册加验证码,基本上就没垃圾评论了。不过搞个插件也不错 laugh

        1.  公主 Queen Queen 
          Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–潍坊市–潍城区–中国联通–3GNET网络 IPv6

          你这又是注册,登录,又是验证码的。太复杂了。
          如果不是真爱,没人会费那么大功夫评论。

          1.  Level 2
            Microsoft Edge 146.0.0.0 Microsoft Edge 146.0.0.0 Android 10 Android 10 cn中国–江西省–九江市–德安县–中国联通–3GNET网络 IPv6

            哈哈哈哈其实也还好,邮箱注册是有点麻烦,不过我最近开发了一个QQ第三方登录插件,注册就方便了很多。
            其实一切都是为了让博客干净一些 kiss

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–潍坊市–潍城区–中国联通–3GNET网络 IPv6

      没办法,受限制了就不如自己弄一个。

  7.  Level 4
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 GNU/Linux x64 GNU/Linux x64 cn中国–辽宁–朝阳–北票市 电信 IPv4

    现在写代码不是问题,只要有想法,天天都可以用大模型写脚本和插件,甚至出项目。不过有的时候想法挺好,实现起来总是达不到预想的效果,可能是细节考虑的还是不到位。

    好处是,看不顺眼的,可以自己写,坏处是代码不在值钱了。

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      代码不值钱这个石趋势,必然如此

  8. Level 2
    Google Chrome 132.0.0.0 Google Chrome 132.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–湖南 移动/全省通用 IPv4

    100多个违禁词、不允许纯数字/字母、不允许网址、必须4个中文以上。差不多了吧?

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      其实禁止纯英文,网址,就能k掉一大部分了

  9. Level 4
    Google Chrome 109.0.0.0 Google Chrome 109.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–上海–上海 腾讯云 IPv4

    灵妹妹的露背装第一次见哦

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      这件秀禾服就是这么设计的。

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      越多越好,可以用来训练模型 rofl

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      是叭,我也觉得。

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      嗯嗯,所以一直用的这个,不过最近批发给我封序列号,我是真的麻了。

  10. Level 4
    Firefox 148.0 Firefox 148.0 GNU/Linux x64 GNU/Linux x64 cn中国–广东–中山 电信 IPv4

    scikit-learn 库我也用过,研究机器学习那时。这篇日志里,主要用他哪一个 api 做的训练和预测呀,线型回归么

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6


      return LogisticRegression(
      max_iter=max_iter,
      class_weight="balanced",
      random_state=42,
      solver="lbfgs",
      C=1.0,
      )

  11. Level 2
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–四川省–中国电信–公众宽带 IPv6

    mark/你这个专业,我只弄了关键词ip或者cf

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      关键词的我也弄了个插件,不过我觉得垃圾评论还是得识别一下。
      习惯了~~

  12.  Level 3
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–广东–深圳 电信 IPv4

    这可太有实力了……
    我的小站连垃圾评论都看不上…… cry

    wp众矢之的,tp的貌似少些。
    用这个自动识别垃圾,访客体验上来说比搞各种验证码的要好的多 👍

    1.  公主 Queen Queen 
      Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 10 x64 Edition Windows 10 x64 Edition cn中国–山东–青岛 联通 IPv4

      wp的确是众矢之的,谁都能扫扫,谁都想发点垃圾东西

  13.  Level 3
    Microsoft Edge 143.0.0.0 Microsoft Edge 143.0.0.0 Windows 11 x64 Edition Windows 11 x64 Edition cn中国–河南省–漯河市–源汇区–中国联通–公众宽带 IPv6

    官方这个WP自带的插件一直没有用过,安装完WP第一件事情就是卸载了。妹子这个还能训练啊。

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      我一直用的官方的,不过现在天天被封key,实在是换不动了。
      自己训练个模型,勉强先用着。开源、安全。

  14. Level 2
    Google Chrome 146.0.0.0 Google Chrome 146.0.0.0 Windows 11 x64 Edition Windows 11 x64 Edition cn中国–陕西省–西安市–莲湖区–中国电信–公众宽带 IPv6

    来学习了!

    1.  公主 Queen Queen 
      Google Chrome 142.0.0.0 Google Chrome 142.0.0.0 Mac OS X  10.15.7 Mac OS X 10.15.7 cn中国–山东省–青岛市–城阳区–中国联通–3GNET网络 IPv6

      国内的也发,不但是国外的。嘎嘎

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注