搜索全站
性能优化 14 分钟阅读

WordPress 站内搜索太弱怎么办:默认搜索优化与插件方案

访客搜"缓存"搜不到标题里没有这个词的文章,是 WordPress 默认搜索的硬伤。本文讲清默认搜索的查询逻辑、四条改进路线(扩展字段、相关性排序、拼音容错、替换为插件),并附可直接用的代码片段。

U
UFO_PF 2018-10-23 · 阅读 14 分钟
Wo 性能优化
阅读提示
本文约 5,377 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:搜什么都搜不到

站点文章超过 200 篇后,站内搜索就会开始”不好用”:

  • 搜”缓存插件”,只返回标题里有”缓存”的文章,正文里提到的全搜不到;
  • 搜”Redis”,返回一堆不相关的(因为 MySQL 的匹配很粗糙);
  • 中文搜”优化”和”优化方案”结果完全不一样,没有分词;
  • 搜错一个字就零结果,没有容错。

这不是 bug,是 WordPress 默认搜索的设计就如此简陋。想做好,得先知道它到底怎么查的。

原理分析:默认搜索到底查了什么

WordPress 的默认搜索生成这样的 SQL 条件:

AND (
  (wp_posts.post_title LIKE '%关键词%')
  OR (wp_posts.post_excerpt LIKE '%关键词%')
  OR (wp_posts.post_content LIKE '%关键词%')
)
AND wp_posts.post_status = 'publish'
AND wp_posts.post_type IN ('post','page')

四个硬伤一目了然:

  1. 只搜三个字段:标题、摘要、正文。不搜自定义字段、分类名、标签名、评论。
  2. 用 LIKE '%...%':无法走索引,数据量大时全表扫描,慢。
  3. AND 逻辑:多个词之间是”与”关系,且必须出现在同一个字段里,词一多就零结果。
  4. 无分词、无相关性排序:结果按日期倒序,不是按匹配度。中文没有空格分词,LIKE 匹配的是整串子串,所以”wordpress缓存”这种连写搜不到。

理解这四点,改进方向就很清楚了。

保姆级实操

方案一:扩展搜索范围(改代码,零插件)

/**
 * 让搜索支持分类名、标签名、自定义字段
 */
function mmf_search_join( $join, $query ) {
    global $wpdb;
    if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
        return $join;
    }
    $join .= " LEFT JOIN {$wpdb->term_relationships} tr ON {$wpdb->posts}.ID = tr.object_id ";
    $join .= " LEFT JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id ";
    $join .= " LEFT JOIN {$wpdb->terms} t ON tt.term_id = t.term_id ";
    $join .= " LEFT JOIN {$wpdb->postmeta} pm ON {$wpdb->posts}.ID = pm.post_id ";
    return $join;
}
add_filter( 'posts_join', 'mmf_search_join', 10, 2 );

function mmf_search_where( $where, $query ) {
    global $wpdb;
    if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
        return $where;
    }
    $term  = '%' . $wpdb->esc_like( $query->get( 's' ) ) . '%';
    $extra = $wpdb->prepare(
        " OR (t.name LIKE %s) OR (t.slug LIKE %s)
          OR (pm.meta_value LIKE %s AND pm.meta_key IN ('_seo_desc','subtitle','keywords')) ",
        $term, $term, $term
    );
    return $where . ' ' . $extra;
}
add_filter( 'posts_where', 'mmf_search_where', 10, 2 );

// JOIN 会产生重复行,必须去重
function mmf_search_distinct( $distinct, $query ) {
    if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
        return 'DISTINCT';
    }
    return $distinct;
}
add_filter( 'posts_distinct', 'mmf_search_distinct', 10, 2 );

注意两个必须做的保护:is_admin() 判断(否则后台列表搜索也被改),和 DISTINCT(否则结果重复)。

方案二:按相关性排序(标题命中优先)

add_filter( 'posts_orderby', function( $orderby, $query ) {
    global $wpdb;
    if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
        return $orderby;
    }
    $s = $query->get( 's' );
    if ( empty( $s ) ) {
        return $orderby;
    }
    $like = '%' . $wpdb->esc_like( $s ) . '%';
    return $wpdb->prepare(
        " CASE
            WHEN {$wpdb->posts}.post_title LIKE %s THEN 1
            WHEN {$wpdb->posts}.post_excerpt LIKE %s THEN 2
            WHEN {$wpdb->posts}.post_content LIKE %s THEN 3
            ELSE 4
          END ASC, {$wpdb->posts}.post_date DESC ",
        $like, $like, $like
    );
}, 10, 2 );

方案三:限定搜索范围(减少噪音)

add_action( 'pre_get_posts', function( $q ) {
    if ( ! is_admin() && $q->is_main_query() && $q->is_search() ) {
        $q->set( 'post_type', array( 'post' ) );
        $q->set( 'posts_per_page', 15 );
    }
});

方案四:换用专业搜索插件

代码方案解决不了中文分词和模糊容错,这两块必须靠插件:

插件 原理 中文分词 适合场景
Relevanssi 自建索引表,模糊匹配、权重、词干 一般(靠模糊匹配补救) 内容站、文档站,免费版够用
SearchWP 索引正文 + PDF + 自定义字段 + 短代码展开内容 一般 商业站,付费
Ivory Search 增强默认搜索,支持 WooCommerce 一般 轻量需求
Ajax Search Lite 实时下拉联想 一般 提升前端体验
ElasticPress + Elasticsearch 外部搜索引擎 好(配 IK 分词器) 万篇以上大站

中文站的务实选择:文章量 2000 篇以内,用 Relevanssi 免费版 + 开启模糊匹配,再配一个 Ajax 实时搜索插件做体验,成本最低效果最好。

Relevanssi 关键设置:

  • 索引里勾上”自定义字段””分类””标签””摘要”;
  • 搜索设置里把模糊匹配设为 “Partial matches”;
  • 排序选 relevance(相关性)而不是 date;
  • 装完必须点一次”建立索引”,之后增量更新。

方案五:搜索结果页的 SEO 与体验

搜索结果页(/?s=关键词)绝不能让搜索引擎索引,否则会产生大量低质量页面:

add_action( 'wp_head', function() {
    if ( is_search() ) {
        echo '<meta name="robots" content="noindex,follow">' . "\n";
    }
});

主流 SEO 插件默认已处理,自定义主题要自己加。

零结果页要做引导,别只显示”找不到”:

if ( ! have_posts() ) {
    echo '<p>抱歉,没有找到与「' . esc_html( get_search_query() ) . '」相关的内容。</p>';
    echo '<p>你可以试试:</p><ul>';
    echo '<li>检查拼写,或使用更短的关键词</li>';
    echo '<li><a href="' . esc_url( home_url( '/sitemap/' ) ) . '">浏览网站地图</a></li>';
    echo '</ul>';
}

常见踩坑与失败案例

  1. 案例一:加了 posts_join 扩展搜索后,文章列表页出现重复项——JOIN 产生了笛卡尔积。必须配 posts_distinct 返回 DISTINCT。
  2. 案例二:改了搜索代码后后台搜索也变了(后台列表搜出奇怪结果)。根因是没加 is_admin() 判断。
  3. Relevanssi 索引不更新:定时任务没跑(wp-cron 被禁用)。手动重建索引,或参见 wp-cron 定时任务原理与系统 cron 替代。
  4. 案例三:某站上了 Elasticsearch,但中文搜”数据库优化”零结果——没装 IK 分词器,默认 standard 分词把中文切成单字。中文站上 ES 必须配 IK。
  5. 搜索被整页缓存:搜索结果页必须排除缓存,否则所有用户看到同一份结果。宝塔静态缓存要手动加排除规则。
  6. 搜索 URL 美化:把 /?s=xxx 改成 /search/xxx,利于统计和体验:

php
add_action( 'template_redirect', function() {
if ( is_search() && ! empty( $_GET['s'] ) && false === strpos( $_SERVER['REQUEST_URI'], '/search/' ) ) {
wp_redirect( home_url( '/search/' ) . urlencode( get_query_var( 's' ) ) . '/' );
exit;
}
});

总结

WordPress 默认搜索的四个硬伤是只搜三个字段、LIKE 全表扫描、AND 逻辑、无分词无相关性排序。改进路线分三档:轻量需求用 posts_join / posts_where / posts_orderby 三个钩子扩展范围和排序(记得加 is_admin() 和 DISTINCT);中文站文章量中等用 Relevanssi 开模糊匹配;万篇以上大站上 Elasticsearch + IK 分词。无论哪种,搜索结果页必须 noindex。零结果页做好引导,能把一部分搜索流失转成浏览转化。

U
UFO_PF作者

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

留下你的想法

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