本文约 5,670 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:搜”退货”,出来的是三年前的新闻稿
内容做到一定量之后,站内搜索会成为高频入口。但 WordPress 默认的搜索体验相当糟糕:它只匹配 post_title 和 post_content,按发布时间倒序排,而且是把关键词拆成多个词做 AND 匹配——用户搜”退货流程”,只要有文章没同时包含”退货”和”流程”就搜不到,搜出来的结果里最新的排在最前,哪怕那篇只是顺带提了一句。
更糟的是无结果页。默认主题在搜不到时输出一句”抱歉,没有找到相关内容”,然后就是一个空荡荡的页面,没有任何引导。用户此时流失概率极高,等于你用一个空页面把最主动的一批访客赶走了。站内搜索的优化分三件事:让用户能方便地找到搜索框、让搜出来的结果真的相关、让搜不到时有出路。本文按这三件事给出可落地的做法。
原理分析:默认搜索为什么”搜不准”
WordPress 的默认搜索由 WP_Query 的 s 参数驱动,最终拼成的 SQL 里包含多个 LIKE '%关键词%' 条件,作用字段只有标题和正文,排序固定为 post_date DESC(搜索时其实会用相关性做一次粗排,但权重非常弱,且不包含自定义字段、摘要、分类名)。这带来三个问题:
一是覆盖字段窄:产品型号写在自定义字段里、标签写在 tag 里,LIKE 扫不到,必然搜不出来。二是排序不合理:一个 2021 年发布的、标题里完整包含关键词的权威教程,会排在一个 2026 年正文里提了一嘴的短文后面。三是分词粗糙:中文没有空格,MySQL 的 LIKE 只能做子串匹配,”手机壳”和”手机 保护壳”无法互通。
| 问题 | 根因 | 解决思路 |
|---|---|---|
| 搜不到自定义字段内容 | LIKE 只扫 title/content |
用插件建索引,或 posts_clauses 扩展 SQL |
| 排序与相关度无关 | 默认按日期/弱相关排序 | 标题命中加权,用 posts_search / 插件 |
| 无结果页空荡 | 主题只输出一句话 | 自建无结果模板,加热词与推荐 |
| 中文分词不灵 | MySQL 无中文分词器 | 用 Relevanssi 或接 Elasticsearch |
改造幅度取决于内容规模:500 篇以内、字段简单,用几十行过滤器就够了;超过 2000 篇、有 WooCommerce 产品或大量自定义字段,直接用 Relevanssi 或 SearchWP 建独立索引表,别自己造轮子。
保姆级实操
- 把搜索框放到真正会被用到的位置
搜索框的标准位置是:PC 端主导航右侧、移动端顶部(点击后展开全屏搜索)、无结果页正中、404 页面。别把它塞进页脚。移动端尤其重要——小屏用户没耐心翻导航。
- 限定搜索范围,排除不该被搜到的内容
默认的 ?s= 会把页面、附件甚至产品变体一起搜出来。用 pre_get_posts 限定为文章,并排除某些分类:
<?php
// 只在前台主查询、且是搜索时生效
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_search() ) {
$query->set( 'post_type', array( 'post', 'page' ) );
// 排除"公告"类目(slug 为 notice)
$query->set( 'tax_query', array( array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => array( 'notice' ),
'operator' => 'NOT IN',
) ) );
$query->set( 'posts_per_page', 15 );
}
} );
// 让搜索也能命中自定义字段:改写 JOIN 与 WHERE
add_filter( 'posts_search', function ( $search, $query ) {
if ( ! $query->is_main_query() || ! $query->is_search() || empty( $search ) ) {
return $search;
}
global $wpdb;
$term = $query->get( 's' );
// 在标题匹配的基础上,追加 meta 值匹配(OR 关系)
$search .= $wpdb->prepare(
" OR EXISTS ( SELECT 1 FROM {$wpdb->postmeta} pm
WHERE pm.post_id = {$wpdb->posts}.ID
AND pm.meta_value LIKE %s ) ",
'%' . $wpdb->esc_like( $term ) . '%'
);
return $search;
}, 10, 2 );
- 给”标题命中”加权排序
上一步解决了”搜得到”,这一步解决”排得对”。最轻量的做法是搜索时用 posts_orderby 把标题命中的排到前面:
<?php
add_filter( 'posts_orderby', function ( $orderby, $query ) {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_search() ) {
return $orderby;
}
global $wpdb;
$term = $wpdb->esc_like( $query->get( 's' ) );
// 标题命中置顶,其次按日期
return $wpdb->prepare(
"CASE WHEN {$wpdb->posts}.post_title LIKE %s THEN 0 ELSE 1 END ASC,
{$wpdb->posts}.post_date DESC",
'%' . $term . '%'
);
}, 10, 2 );
- 做一页真正有用的无结果页
在主题里建 search.php,判断 have_posts() 为假时输出兜底内容:显示用户搜的词、给出热门搜索词(可用 wp_tag_cloud() 或直接写死一组)、列出最新 5 篇和精选 5 篇、放一个搜索框。模板骨架:
<?php get_header(); ?>
<div class="search-page">
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
<label class="screen-reader-text" for="s2">搜索</label>
<input type="search" id="s2" class="search-field" name="s"
value="<?php echo esc_attr( get_search_query() ); ?>" placeholder="换个词再试试">
<button type="submit">搜索</button>
</form>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'template-parts/content', 'search' ); ?>
<?php endwhile; the_posts_pagination(); ?>
<?php else : ?>
<p class="search-empty">没有找到与「<strong><?php echo esc_html( get_search_query() ); ?></strong>」相关的内容。</p>
<h2>试试这些高频词</h2>
<?php wp_tag_cloud( array( 'smallest' => 14, 'largest' => 14, 'number' => 20, 'unit' => 'px' ) ); ?>
<h2>或者从这里开始</h2>
<ul>
<?php
$recent = new WP_Query( array( 'posts_per_page' => 5, 'ignore_sticky_posts' => true ) );
while ( $recent->have_posts() ) : $recent->the_post(); ?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
<?php endif; ?>
</div>
<?php get_footer(); ?>
- 记录用户到底在搜什么
在搜索表单提交时把关键词记到自定义表或一个 option 里,两周后看一遍:搜不到结果的高频词,就是下一个该写的内容选题。这是成本最低、命中率最高的选题来源。
- 规模上去了就换插件建索引
内容超过 2000 篇或需要中文分词时,装 Relevanssi(免费版即可做相关度排序与中文模糊匹配)或 SearchWP(付费,支持自定义字段权重与 WooCommerce)。这类插件会自建索引表,第一次配置后要点一次 “Build index”,之后自动增量更新。
常见踩坑
pre_get_posts忘了判is_main_query():把后台文章列表、侧边栏小工具一起改了,出现莫名其妙的空白。- 案例:某站点在搜索结果里用了
$wp_query->found_posts做分页,但代码里另外跑了一个query_posts(),导致分页总数和实际列表对不上,第二页永远是空的。正确做法是用pre_get_posts改主查询,永远不要在前台用query_posts()。 - 搜索结果页被
noindex:有的 SEO 插件默认把搜索结果页设为 noindex,这是对的,但别忘了它同时会让用户搜到的内容不被收录——这是预期行为,不要去改。 posts_search拼接 SQL 没用$wpdb->prepare():直接把用户输入的关键词拼进 SQL,等于开了一个 SQL 注入口子。- 无结果页直接跳回首页:用户以为站点坏了,跳出率极高。
- 搜索框做成点击后异步加载,但 JS 报错:移动端点了没反应,等于没有搜索功能。上线前一定要在真机上测一次。
- 装了搜索插件却没建索引:搜出来还是老样子,以为是插件没生效。
总结
站内搜索的判断标准是三个:搜核心词能不能在前 3 条找到最相关的那篇、搜错词时有没有出路、搜索框在小屏上能不能一秒找到。都满足的搜索能把停留时长和页面深度拉高一大截。改造顺序建议先做无结果页(半天工作量、收益最明显),再做排序加权,最后才考虑上插件。搜索优化完后,别忘了把搜索记录里的高频词接到 冷启动新站的 90 天内容计划 里当作选题输入,同时参考 网站移动端适配的验收标准 检查小屏搜索框的点击区域。
