搜索全站
插件 13 分钟阅读

不用插件拦 WordPress 垃圾评论:时间戳 + 蜜罐字段代码方案

不想为反垃圾再装插件?本文用不到 60 行代码实现蜜罐字段 + 提交时间戳 + 关闭 pingback 三重防护,讲清每个钩子的作用与误伤规避方法。

U
UFO_PF 2023-07-31 · 阅读 13 分钟
不用 插件
阅读提示
本文约 4,951 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:插件之外的第三条路

装 Akismet 或 Antispam Bee,是拦垃圾评论最省事的办法。但有些场景并不适合:站点在内网或数据不能出境、希望减少插件数量以降低维护成本、或者垃圾评论量大到每次提交都要等一次云端接口。

这时候就该考虑代码方案了。它的思路与云端模型完全不同:不去判断内容像不像垃圾,而是判断提交行为像不像人。真实用户打开页面、阅读、打字、提交,至少要花十几秒;而机器人是拿到页面后立刻 POST。「蜜罐字段」和「提交时间戳」就是把这两点差异变成可判定的条件,用极小的代价过滤掉绝大部分自动化垃圾。

原理分析:三个可判定的行为特征

特征 正常用户 机器人 判定方式
页面停留时间 十几秒以上 通常小于 3 秒 隐藏字段记录打开时间,提交时比较
是否填写隐藏字段 看不到也填不了 会填满所有字段 蜜罐字段非空即为垃圾
是否具备完整浏览器环境 执行 JS、带 Cookie 直发 POST 结合 JS 写入的令牌判断

蜜罐字段(honeypot)的做法是:在表单里放一个用 CSS 隐藏、但对机器人可见的输入框。真实用户看不到它,自然不会填;机器人为了「提高成功率」会把所有字段都填上。于是「该字段非空」就成了高置信度的垃圾信号。

时间戳的做法是:在渲染表单时写入当前时间戳(并用 nonce 防止伪造),提交时检查间隔。间隔太短(<5 秒)说明不是人;间隔太长(>6 小时)说明是过期页面或被缓存的旧表单,也应拒绝。

第三个要点是关掉不需要的通道:pingback / trackback 是垃圾的重灾区,而且几乎无人使用,直接在代码层关闭即可。

保姆级实操:一个 mu-plugin 搞定

步骤1:创建必用插件。 在 wp-content/mu-plugins/ 下新建文件(mu-plugin 自动加载,不依赖后台启用,也不会被误停用)。

步骤2:输出蜜罐字段与时间戳。 用 comment_form_after_fields 在表单末尾追加隐藏字段,并用 CSS 把蜜罐输入框移出可视区域(注意:不要用 display:none,部分机器人会跳过被隐藏的字段;用定位偏移更安全)。

步骤3:拦截校验。 用 preprocess_comment 在评论入库前判断,命中条件直接终止。

<?php
/**
 * Plugin Name: MF Anti Spam Comment
 * Description: 蜜罐字段 + 提交时间戳 + 关闭 pingback
 */

// 输出蜜罐与时间戳字段
add_action( 'comment_form_after_fields', function () {
    $ts  = time();
    $hp  = wp_create_nonce( 'mf_hp_' . $ts );
    printf(
        '<input type="hidden" name="mf_ts" value="%d">' .
        '<input type="hidden" name="mf_hp_token" value="%s">' .
        '<p style="position:absolute;left:-9999px;" aria-hidden="true">' .
        '<label for="mf_website">Website</label>' .
        '<input id="mf_website" name="mf_website" type="text" size="30" autocomplete="off" tabindex="-1">' .
        '</p>',
        esc_attr( $ts ), esc_attr( $hp )
    );
} );

// 提交时校验
add_filter( 'preprocess_comment', function ( $commentdata ) {
    // 蜜罐被填写 → 判定为机器人
    if ( ! empty( $_POST['mf_website'] ) ) {
        wp_die( '评论未通过验证。', '评论被拒绝', array( 'response' => 403 ) );
    }

    $ts    = isset( $_POST['mf_ts'] ) ? (int) $_POST['mf_ts'] : 0;
    $token = isset( $_POST['mf_hp_token'] ) ? sanitize_text_field( wp_unslash( $_POST['mf_hp_token'] ) ) : '';

    // 时间戳缺失或伪造
    if ( ! $ts || ! wp_verify_nonce( $token, 'mf_hp_' . $ts ) ) {
        wp_die( '表单已过期,请刷新页面后重试。', '评论被拒绝', array( 'response' => 403 ) );
    }

    $elapsed = time() - $ts;
    if ( $elapsed < 5 ) {           // 提交过快
        wp_die( '提交过快,请稍后再试。', '评论被拒绝', array( 'response' => 403 ) );
    }
    if ( $elapsed > 6 * HOUR_IN_SECONDS ) {  // 页面停留过久,可能是被缓存的旧表单
        wp_die( '页面停留过久,请刷新后重试。', '评论被拒绝', array( 'response' => 403 ) );
    }
    return $commentdata;
} );

步骤4:关闭 pingback / trackback。 这两个通道几乎不产生有价值的内容,却贡献了大量垃圾。

<?php
// 关闭文章的 pingback/trackback
add_filter( 'pings_open', '__return_false' );
// 从 XML-RPC 中移除 pingback 方法
add_filter( 'xmlrpc_methods', function ( $methods ) {
    unset( $methods['pingback.ping'], $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );

步骤5:给老文章自动关闭评论。 老文章的评论区是重灾区,可以按需自动关闭。该过滤器只影响前台判定,不改数据库里的开关状态。

<?php
// 发布超过 90 天的文章自动关闭评论
add_filter( 'comments_open', function ( $open, $post_id ) {
    $post = get_post( $post_id );
    if ( $post && 'post' === $post->post_type
        && ( time() - strtotime( $post->post_date_gmt ) ) > 90 * DAY_IN_SECONDS ) {
        return false;
    }
    return $open;
}, 10, 2 );

步骤6:配合关键词黑名单。 后台「设置 → 讨论 → 评论黑名单」里加入高频垃圾词,WordPress 的 wp_blacklist_check() 会自动拦截。这是原生能力,不需要插件。

步骤7:为蜜罐字段补充样式兜底。 若主题自带评论表单样式冲突,可以在 CSS 里把蜜罐容器固定移出视口:

/* 蜜罐字段容器移出可视区域,且不吃焦点 */
.comment-form p[aria-hidden="true"] {
    position: absolute !important;
    left: -9999px;
    height: 0;
    overflow: hidden;
}

步骤8:验证效果。 用 curl 模拟机器人直接 POST 到 wp-comments-post.php,应当被拒绝;再用浏览器正常提交一次,应当通过。

# 模拟机器人:不带时间戳与蜜罐保护字段,直接 POST
curl -s -o /dev/null -w '%{http_code}\n' \
  -d 'comment=test+spam&author=bot&email=bot@example.com&comment_post_ID=1' \
  https://example.com/wp-comments-post.php

常见踩坑

  1. 页面缓存导致时间戳过期被误伤。整页缓存把带有旧时间戳的表单缓存住了,真实用户提交时被判为「停留过久」。要么对评论表单所在页面关闭缓存,要么把时间戳改成由 JS 在提交前写入。
  2. display:none 隐藏蜜罐反而失效。部分机器人会跳过隐藏字段,导致蜜罐从不触发。用定位偏移而不是隐藏。
  3. 蜜罐字段名太明显。叫 honeypot、trap 之类,聪明的机器人会绕过。用 website、url 这类正常字段名。
  4. wp_die() 影响用户体验。命中规则时直接 403 会让真实用户困惑,文案要写清楚「刷新页面后重试」。
  5. 忘了登录用户的情况。登录用户的评论表单字段不同,若你的站点允许登录用户评论,需要单独处理或跳过校验。
  6. 案例:某站点上线蜜罐后垃圾评论反而变多,排查发现蜜罐字段名用了 spam_check,并且 CSS 用了 display:none,机器人直接跳过该字段;同时整页缓存让时间戳字段长期是同一份,机器人拿到了合法令牌。整改后把字段改为 website、用 position:absolute 偏移、对含评论表单的文章关闭整页缓存,垃圾评论从每天 200 多条降到个位数。
  7. 同时装了云端插件导致重复拦截。代码方案与插件可以共存,但要注意两者的判定记录会同时写入 wp_commentmeta,表体积增长更快。
  8. 关闭 pingback 后旧文章仍收到通知。已存在的 pingback 状态需单独清理,过滤器只对新提交生效。

总结

代码方案的价值在于零依赖、零出境、零成本,对中文站点尤其有效,因为大量垃圾评论是针对英文站点的通用话术,靠行为特征就能过滤掉九成以上。落地时按「蜜罐 + 时间戳 + 关闭 pingback + 关键词黑名单」四件套来做,注意避开整页缓存与字段命名的坑。它的边界也要认清:能拦自动化机器人,拦不住人工发布的广告评论——后者仍需要人工审核或云端模型兜底,可参考Akismet 与 Antispam Bee 拦垃圾评论对比。评论数据积累后的清理,见Advanced Database Cleaner 清理冗余数据。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。