搜索全站
插件 12 分钟阅读

Broken Link Checker 的替代方案:为什么它拖慢站点与更轻的检测方式

Broken Link Checker 靠 WordPress 定时任务逐条发起 HTTP 请求,文章一多就会拖慢站点。本文分析它的性能成因,给出外部工具扫描、轻量 SQL 自查、按需检测脚本三种更合理的替代方案。

U
UFO_PF 2024-05-18 · 阅读 12 分钟
Br 插件
阅读提示
本文约 4,623 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:装了死链检测插件,网站反而更慢了

Broken Link Checker 是很多站长的第一款死链插件:装上就自动扫描文章里的外链,后台红红绿绿列出一堆失效链接,看着很有安全感。但用上半年,很多人发现站点越来越慢——后台打开要等几秒、发布文章时卡顿、服务器 CPU 偶尔飙高。卸载之后立刻流畅。

这不是错觉。这类插件的工作方式和它的名字一样朴素:把每篇文章里的每个链接都发起一次 HTTP 请求,看返回码是多少。一个有一千篇文章、每篇五个外链的站点,一轮扫描就是五千次外部请求。这些请求由 WordPress 的定时任务(WP-Cron)驱动,和前台访问抢同一份 PHP 进程和数据库连接。更麻烦的是,WP-Cron 是”访问触发”的,流量越大触发越频繁,扫描也越频繁。

原理分析:慢在哪里,慢得有多具体

拆解一下开销来源:

开销项 说明
数据库表 插件会建 wp_blc_links、wp_blc_instances、wp_blc_synch 等表,链接量大时行数可达数十万
HTTP 请求 每次扫描对每个唯一 URL 发起请求,第三方站点响应慢时会阻塞
WP-Cron 串行执行 扫描任务占用同一个请求生命周期,前台用户可能撞上正在跑的扫描
后台页面渲染 打开插件页面要 join 多张表,未加索引时全表扫描

关键点在于:死链检测本质上是一个低频、批量、可以离线的任务,而它被实现成了一个高频、常驻、在线的任务。这是设计上的错配。

正确的思路是把检测”移出”WordPress 的请求生命周期,有三个方向:

  1. 外部工具扫描:用桌面爬虫(Screaming Frog SEO Spider、Sitebulb)或在线服务(Ahrefs Site Audit、Semrush Site Audit)爬取整站,导出失效链接清单。完全不消耗服务器资源,适合一次性大扫除。
  2. 轻量自查:不发 HTTP 请求,只在数据库层面做”站内链接指向的 URL 是否存在”的检查。零外部请求,秒级完成。
  3. 按需检测脚本:用 WP-CLI 写一个脚本,手动触发时才跑,跑完就结束,不进 WP-Cron。

保姆级实操

方案一:用 SQL 自查站内死链(零开销)

-- 找出正文里指向本站、但目标已不存在的链接
SELECT p.ID, p.post_title, p.post_type
FROM wp_posts p
WHERE p.post_status = 'publish'
  AND p.post_content REGEXP 'href="https?://mimangfei\\.com/[^"]+"'
  AND p.post_content REGEXP 'href="https?://mimangfei\\.com/[a-z0-9-]+/"'
LIMIT 50;

更实用的做法是先把所有站内链接抽出来,再和现有 URL 做差集。可以用 WP-CLI 一次性导出:

# 导出所有正文中的站内链接
wp db query "SELECT ID, post_title FROM wp_posts WHERE post_status='publish' AND post_type IN ('post','page');" --skip-column-names > posts.txt

# 配合 grep 抽出 href,再去重
wp db query "SELECT post_content FROM wp_posts WHERE post_status='publish';" --skip-column-names \
  | grep -oE 'href="https://mimangfei\.com/[^"]+"' \
  | sed 's/href="//; s/"$//' | sort -u > internal_links.txt

wc -l internal_links.txt

拿到链接清单后,用批量请求判断哪些返回非 200:

while read -r url; do
  code=$(curl -o /dev/null -s -w '%{http_code}' -m 8 "$url")
  [ "$code" != "200" ] && echo "$code $url"
done < internal_links.txt > broken.txt

方案二:写一个按需运行的 WP-CLI 检测脚本

把脚本放到 wp-content/mu-plugins/link-audit.php,只有手动执行时才工作:

<?php
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
    return;
}

WP_CLI::add_command( 'link-audit', function ( $args, $assoc ) {
    global $wpdb;
    $limit  = isset( $assoc['limit'] ) ? (int) $assoc['limit'] : 200;
    $urls   = $wpdb->get_col(
        "SELECT DISTINCT post_content FROM {$wpdb->posts}
         WHERE post_status = 'publish' AND post_type IN ('post','page') LIMIT {$limit}"
    );

    $found = array();
    foreach ( $urls as $content ) {
        if ( preg_match_all( '/href="(https?:\/\/[^"]+)"/i', $content, $m ) ) {
            foreach ( array_unique( $m[1] ) as $url ) {
                $res = wp_remote_head( $url, array( 'timeout' => 5, 'redirection' => 3 ) );
                if ( is_wp_error( $res ) ) {
                    $found[] = 'ERR  ' . $url;
                } elseif ( (int) wp_remote_retrieve_response_code( $res ) >= 400 ) {
                    $found[] = wp_remote_retrieve_response_code( $res ) . ' ' . $url;
                }
            }
        }
    }
    WP_CLI::log( implode( "\n", array_unique( $found ) ) );
    WP_CLI::success( '检测完成,共 ' . count( array_unique( $found ) ) . ' 条异常。' );
} );
wp link-audit --limit=100

方案三:外部工具定期大扫除

台式机上跑 Screaming Frog SEO Spider(免费版限 500 个 URL),配置里勾上 “Response Codes → Client Error (4XX)”,爬完导出 CSV,再用数据库批量替换:

# 全站批量替换失效域名(务必先备份)
wp db export ~/backup/before-link-fix.sql
wp search-replace 'https://old-example.com/path' 'https://new-example.com/path' --dry-run

方案四:卸载旧插件后清理残留表

wp db query "SHOW TABLES LIKE '%blc%';"
wp db export ~/backup/before-blc-cleanup.sql
wp db query "DROP TABLE IF EXISTS wp_blc_links, wp_blc_instances, wp_blc_synch, wp_blc_filters, wp_blc_modules;"
wp option delete wsblc_options 2>/dev/null

常见踩坑

  1. 直接删插件不删表。残留表会一直占空间,还可能被后续插件误读。
  2. 检测脚本跑在生产环境的高峰期。wp_remote_head 是同步阻塞的,几百个 URL 会卡住 CLI,但不会影响前台——这正是用 WP-CLI 而不是 WP-Cron 的好处。
  3. 把 403 当成死链。很多站点对无 UA 的请求返回 403,实际上浏览器能正常打开。检测时要带上 UA 或人工复核。
  4. wp search-replace 直接序列化数据出错。一定要先 --dry-run,且确认替换串不含会破坏 PHP 序列化的内容。
  5. 案例:一个内容站装了 Broken Link Checker 两年,wp_blc_links 表涨到 38 万行。某次插件升级触发了全表重建,前台所有页面超时,站点挂了 40 分钟。事后统计,真正修复过的死链不超过 200 条——用 38 万行的表换来 200 次修复,性价比极低。最终方案是卸载插件、删表,改为每季度用桌面爬虫扫一次。
  6. 外链失效不该盲目删除。有些外链指向的站点只是临时挂了,建议先标记再观察。
  7. robots/防火墙把检测请求当攻击。自家站点开了 WAF 时,批量请求容易触发封 IP,检测前应加白名单或调低并发。

总结

判断标准很明确:死链检测是低频离线任务,不该常驻在线。文章数在几百以内、每月只更新几篇的站点,用桌面爬虫或在线审计工具季度扫一次即可;需要自动化的站点,写一个 WP-CLI 脚本手动触发,比常驻插件安全得多;只想查站内链接,用 SQL 差集几秒就能出结果。无论哪种方式,卸载旧插件后务必清理残留数据表,并先备份再操作。

清理完死链,别忘了同步处理 Redirection 插件做 301 与 404 监控 里的失效入口;数据库瘦身可以配合 WordPress 数据库优化与清理 一起做。

U
UFO_PF作者

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

留下你的想法

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