评论区被灌了几百条「555」,我把过滤函数重写了一遍

作者: admin 分类: wordpress主题制作教程 发布时间: 2026-10-21 22:14

最近后台的评论列表没法看了。一屏十几个,全是同一个模子:

  • 作者:1,或者直接就是 example.com
  • 邮箱:testing@example.com
  • 内容:555
  • 网址:http://www.example.com

IP 还都不一样,209.50.163.107、45.3.35.45、65.111.20.170 换着来。典型的脚本批量灌。

让我想不通的是:我这个主题本来就有评论过滤,写在 functions.php 里,还是当年从网上抄的。为什么一条都没拦住?

先看原来那段代码

原来也写过文章描述过:限制wordpress评论文章字数(垃圾评论)的方法

原样贴出来,就这么几行:

add_filter( 'preprocess_comment', 'wpb_preprocess_comment' );

function wpb_preprocess_comment( $comment ) {

    if ( strlen( $comment['comment_content'] ) > 350 ) {
        wp_die( '评论内容过长,请精简到350字以内' );
    }

    $has_http  = stristr( $comment['comment_content'], "http" )  !== false;
    $has_https = stristr( $comment['comment_content'], "https" ) !== false;
    $has_www   = stristr( $comment['comment_content'], "www" )   !== false;

    if ( $has_http || $has_https || $has_www ) {
        wp_die( '评论内容包含不适当的链接信息,请移除后重新提交' );
    }

    return $comment;
}

逻辑很朴素:正文里出现 http 或者 www,就拒绝。看着没毛病。

但垃圾评论根本不往正文里写链接。它把链接塞在「网址」那一栏,正文只写三个字符「555」。

我盯着这段代码看了半天才反应过来——我检查的是 comment_content,垃圾填的是 comment_author_url,压根不是同一个字段。

三个 stristr 全都在查正文,网址栏从头到尾没人管。

该挂哪个钩子

WordPress 存一条评论的流程是这样的:

访客点提交
  → 各字段打包成 $comment 数组
  → 触发 preprocess_comment 钩子   ← 只能在这里拦
  → 写入数据库
  → 前台显示

有几个教程会让人用 comment_post,但那个钩子是评论已经进库之后才触发的。等于门已经关了才去抓人,抓到也只能删,属于先污染后治理。

preprocess_comment 是唯一能在落库前拦住的位置。它拿到的参数是一个数组,里面装着这次提交的全部字段:

键名 内容
comment_author 昵称
comment_author_email 邮箱
comment_author_url 网址 ← 灌水的重灾区
comment_content 评论正文
comment_post_ID 评论的是哪篇文章

重写成五道闸

核心思路:不光查正文,把网址栏、长度、邮箱一起检查。完整函数是这样:

add_filter( 'preprocess_comment', 'wpb_preprocess_comment' );

function wpb_preprocess_comment( $comment ) {

    // 管理员自己不受限制,方便回复时贴链接
    if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
        return $comment;
    }

    $content = isset( $comment['comment_content'] )      ? (string) $comment['comment_content']      : '';
    $url     = isset( $comment['comment_author_url'] )   ? trim( (string) $comment['comment_author_url'] ) : '';
    $email   = isset( $comment['comment_author_email'] ) ? (string) $comment['comment_author_email'] : '';

    // 闸门 1:网址栏不许填
    if ( '' !== $url && 'http://' !== $url && 'https://' !== $url ) {
        wpb_comment_die( '请不要在「网址」一栏填写链接。' );
    }

    // 闸门 2:正文里不许出现网址
    if ( preg_match( '/https?:\/\/|www\./i', $content ) ) {
        wpb_comment_die( '评论内容中不能包含网址链接。' );
    }

    // 闸门 3:正文太短
    $plain = trim( wp_strip_all_tags( $content ) );
    if ( mb_strlen( $plain, 'UTF-8' ) < 5 ) {
        wpb_comment_die( '内容太短,请至少写下 5 个字。' );
    }

    // 闸门 4:正文过长(原来的规则)
    if ( strlen( $content ) > 350 ) {
        wpb_comment_die( '内容过长,请精简到 350 字以内。' );
    }

    // 闸门 5:占位邮箱
    if ( '' !== $email && preg_match( '/@(example|test|localhost|sample)\./i', $email ) ) {
        wpb_comment_die( '请填写真实的邮箱地址。' );
    }

    return $comment;
}

拒绝的时候别只 wp_die( '失败' ) 一行,访客看到白屏会以为网站坏了。给它一个能返回的提示:

function wpb_comment_die( $msg ) {
    wp_die(
        '<p>' . esc_html( $msg ) . '</p>'
        . '<p><a href="javascript:history.back()">&larr; 返回上一页修改</a></p>',
        '评论未通过校验',
        array( 'response' => 403 )
    );
}

第三个参数 'response' => 403 是有讲究的。默认 wp_die 返回 500,那是「服务器内部错误」的意思;403 才是「禁止访问」。有些自动提交脚本认这个状态码,看到 403 就知道是被规则挡了,不会傻乎乎地重试。

三个我踩过的地方

一、网址栏不能只判断「非空」

第一版我写的是 if ( ! empty( $url ) ),结果误伤了一批正常访客。

原因是浏览器的表单自动填充。很多人(包括我自己测试时)明明没填网址,但浏览器会预塞一个 http:// 进去,值不为空,于是被判成垃圾。

所以判断要放过三个值:空字符串、http://、https://。这三个都算「没填」。

二、判断长度要用 mb_strlen,不是 strlen

一个汉字在 UTF-8 里占 3 个字节。

strlen( '你好' )                    // 6
mb_strlen( '你好', 'UTF-8' )        // 2

如果用 strlen 去判断「少于 5」,两个汉字就算合格了,垃圾照样能过。只有 mb_strlen 才是真的在数字。

(下面那条「超过 350」我保留了原来的 strlen,因为那是字节语义,350 字节约等于 116 个汉字,跟原来的行为一致,没动。)

三、函数重名会让整站白屏

这个不是理论,是我自己翻车了。

新代码沿用了旧函数的名字 wpb_preprocess_comment,好处是 add_filter 那行不用改。但如果你只把新代码贴上去、旧函数没删干净,PHP 会直接抛:

Fatal error: Cannot redeclare wpb_preprocess_comment()

整个网站瞬间白屏,后台也进不去。我当时的报错在第 96 行——不过那是另一次事故,等下说。

所以替换之前,一定先把 functions.php 备份一份,再把旧函数整段删掉,不要只删一半。

顺带说一下那次白屏

同一天我还踩了另一个坑,值得单独讲,因为太反直觉了。

我在改一个截取摘要的函数时,判断函数从哪一行结束,写的是「往下找第一行只有 } 的」。结果原函数里有个嵌套的 if { },脚本在内层那个 } 就停了,把旧函数的尾巴留在了原地,多出一个 }。

报错就一句:Unmatched '}' in functions.php on line 96。整站 500。

后来才明白,找函数结尾不能数第一个右括号,要数括号的层级:遇到 { 加一,遇到 } 减一,减到 0 才是函数真正的结尾。用正则或者行匹配去找,遇到嵌套必错。

最后,我还是把评论关了

上面这些做完,垃圾确实进不来了。但想了几天,我把评论功能整个关掉了。

原因很实在:我这是个手机卡套餐站,一千多篇文章讲的是「19 元 130G 怎么办理」。读者看完就去办卡了,不会有人在这儿讨论「这个套餐值不值」。建站到现在,真实评论一只手数得过来,剩下全在灌水。

既然如此,留着评论区图什么呢?维护过滤规则要成本,规则还可能被新的脚本绕过去。真要有人联系我,页脚有 QQ 群和联系方式,比评论区靠谱。

所以最后的选择是:后台「设置 → 讨论」里取消勾选「允许他人在新文章上发表评论」,一劳永逸。

如果你的站评论有真实互动、舍不得关,那就把上面那套五道闸挂上去,同时务必在同一个设置页勾上「评论必须经人工批准」。别让它自动发布——自动发布等于把前台交给脚本。

相关文章

搜索文章:
联系方式:
Powered by 草根站长笔记 © 2015-2020 草根站长笔记 Inc.版权所有,禁止转载