本文约 7,089 字,预计阅读 18 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:站内搜索是访客主动表达需求的地方,却被做得最敷衍
访客愿意在搜索框里输入关键词,说明他有明确需求——这是全站转化率最高的场景之一。但多数主题的搜索结果页长这样:顶部一行「搜索结果:xxx」,下面列出十篇文章标题,仅此而已。搜不到时只有一句冷冰冰的「抱歉,没有找到相关内容」。于是访客关掉页面走人,你连他想要什么都不知道。
搜索结果页有三个可以立刻改善的点:空结果时的兜底(给出近义词建议、热门内容、搜索技巧)、结果质量(限定搜索范围、排除无用内容、按相关性排序)、结果呈现(显示摘要、分类、日期,而不是只有标题)。WordPress 的默认搜索只查 post_title、post_content、post_excerpt 三个字段,且不支持分词(中文尤其吃亏),这些都是可以在主题层面改善的。
原理分析:默认搜索查什么、怎么排序
WordPress 的搜索由 WP_Query 的 s 参数驱动,SQL 层面会生成类似 AND ((wp_posts.post_title LIKE '%关键词%') OR (wp_posts.post_excerpt LIKE '%关键词%') OR (wp_posts.post_content LIKE '%关键词%')) 的条件,并按 post_date 或相关性排序。这带来几个已知短板:
| 短板 | 表现 | 可行对策 |
|---|---|---|
| 只查标题/摘要/正文 | 自定义字段、分类名搜不到 | 用 posts_search 扩展 SQL,或换搜索插件 |
| 中文无分词 | 搜「WordPress 主题优化」几乎无结果 | 依赖插件或外部搜索服务 |
| 按日期而非相关性排序 | 最相关的旧文章排在后面 | 用 posts_orderby 调整,或换引擎 |
| 搜索页面、附件等类型 | 结果里混入无关类型 | 用 pre_get_posts 限定 post_type |
主题能做的、不需要插件的改进有三类:一是用 pre_get_posts 限定搜索范围(只搜文章、排除某些分类、限制条数);二是用 posts_search 或 posts_where 给 SQL 加条件(比如把自定义字段纳入搜索);三是改造 search.php 的呈现与空结果兜底。
如果搜索量较大且中文分词是刚需,别硬撑——换用专门的搜索方案(如支持中文分词的搜索插件或外部搜索服务)更划算。但无论底层用什么引擎,search.php 这一层的体验改造都是通用且必须做的。
另外值得利用的是 REST 搜索端点 /wp-json/wp/v2/search,它能给搜索框做实时补全建议,实现成本低、效果明显。
保姆级实操
- 建或改 search.php:没有就新建,WordPress 会优先使用它。
- 显示查询词与结果数:用
get_search_query()和$wp_query->found_posts,让访客知道搜到了什么。 - 限定搜索范围:
pre_get_posts里只对前台搜索生效,排除页面与指定分类。 - 结果项补信息:显示分类、日期、摘要,而不是只有标题。
- 空结果兜底:
found_posts为 0 时输出热门文章、分类入口和搜索建议。 - 加搜索补全:用 REST 端点做简单的下拉建议。
- 避免被收录:搜索结果页通常应设为 noindex,避免产生大量低质量页面。
<?php
// 子主题 functions.php:只在前台搜索时限定范围
add_action( 'pre_get_posts', 'mimangfei_search_scope' );
function mimangfei_search_scope( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_search() ) {
$query->set( 'post_type', 'post' );
$query->set( 'posts_per_page', 12 );
$query->set( 'cat', '-35,-41' ); // 排除「公告」「广告」等分类
$query->set( 'post_status', 'publish' );
}
}
<?php
/**
* search.php
*
* @package lumen-mimangfei-child
*/
get_header();
global $wp_query;
$keyword = get_search_query();
$found = (int) $wp_query->found_posts;
?>
<main id="primary" class="site-main site-main--search">
<header class="search-header">
<h1>搜索:<?php echo esc_html( $keyword ); ?></h1>
<p class="search-count">
<?php
printf(
'共找到 %d 条结果',
$found
);
?>
</p>
<?php get_search_form(); ?>
</header>
<?php if ( have_posts() ) : ?>
<ul class="search-results">
<?php
while ( have_posts() ) :
the_post();
?>
<li class="search-result">
<h2 class="search-result__title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<div class="search-result__meta">
<?php echo esc_html( get_the_date() ); ?>
<?php
$cats = get_the_category();
if ( $cats ) {
echo ' · ' . esc_html( $cats[0]->name );
}
?>
</div>
<p class="search-result__excerpt"><?php echo esc_html( get_the_excerpt() ); ?></p>
</li>
<?php
endwhile;
?>
</ul>
<?php the_posts_pagination(); ?>
<?php else : ?>
<section class="search-empty">
<h2>没有找到与「<?php echo esc_html( $keyword ); ?>」相关的内容</h2>
<p>可以试试:换用更短的关键词、检查是否有错别字,或者看看下面这些热门内容。</p>
<h3>热门文章</h3>
<ul>
<?php
$q = new WP_Query( array(
'posts_per_page' => 6,
'orderby' => 'comment_count',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
) );
while ( $q->have_posts() ) :
$q->the_post();
?>
<li><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></li>
<?php
endwhile;
wp_reset_postdata();
?>
</ul>
<h3>热门标签</h3>
<?php wp_tag_cloud( array( 'number' => 20, 'orderby' => 'count', 'order' => 'DESC' ) ); ?>
</section>
<?php endif; ?>
</main>
<?php
get_footer();
// 搜索框实时建议:调用 REST 搜索端点
const input = document.querySelector( 'input[name="s"]' );
if ( input ) {
let timer;
input.addEventListener( 'input', () => {
clearTimeout( timer );
const kw = input.value.trim();
if ( kw.length < 2 ) {
return;
}
timer = setTimeout( async () => {
const res = await fetch(
`${ window.location.origin }/wp-json/wp/v2/search?search=${ encodeURIComponent( kw ) }&per_page=5&_fields=id,title,url`
);
const list = await res.json();
renderSuggestions( list );
}, 250 );
} );
}
function renderSuggestions( list ) {
const box = document.querySelector( '[data-search-suggestions]' );
if ( ! box ) {
return;
}
box.innerHTML = list
.map( ( item ) => `<a href="${ item.url }">${ item.title }</a>` )
.join( '' );
}
<?php
// 搜索结果页通常应设 noindex,避免产生大量低质量索引页
add_filter( 'wp_robots', 'mimangfei_search_noindex' );
function mimangfei_search_noindex( $robots ) {
if ( is_search() ) {
$robots['noindex'] = true;
$robots['nofollow'] = true;
}
return $robots;
}
常见踩坑
pre_get_posts没判断is_main_query():把后台、侧边栏的小查询也改了,出现莫名其妙的结果。两个判断缺一不可。- 空结果页直接 return:只在
have_posts()为 true 时输出内容,else分支没做,访客看到空白页。空结果分支必须认真写。 - 案例:某站搜索「发票」永远返回 0 条,但文章里明明有相关内容。原因是这些文章在分类 35(「站内公告」)里,而主题早在半年前用
pre_get_posts排除了公告分类,没人记得。最后把排除逻辑集中到一个带注释的配置数组里,并加了后台说明。教训:搜索排除规则要集中管理并写注释,否则半年后没人知道为什么搜不到。 - 搜索结果页被大量收录:没设 noindex,Google 收录了几千个
?s=xxx页面,全是低质量页面。用wp_robots过滤器加 noindex,并在 robots.txt 里屏蔽/?s=。 get_search_query()没转义:直接 echo 会把用户输入原样输出,存在 XSS 风险。必须用esc_html()。- REST 端点未授权问题:
/wp-json/wp/v2/search默认只返回公开内容,若站点启用了鉴权插件需要确认可匿名访问,否则补全功能失效。 - 中文分词问题没解决就盲目优化:底层搜不到,前端做得再好也没用。中文站点应尽早评估专门的搜索方案。
- 搜索表单被缓存:全页缓存把带搜索结果的 HTML 缓存了,不同关键词拿到同一份结果。搜索页必须排除缓存。相关取舍可结合
主题首页静态页 vs 最新文章的选择一起规划缓存策略。
总结
搜索结果页的判断标准是:搜到了能快速定位目标,搜不到也能留下来继续逛。行动清单:建 search.php → 显示查询词与结果数 → pre_get_posts 限定范围并集中管理排除规则 → 结果项补摘要、分类、日期 → 空结果分支加热门文章与标签云 → 用 REST 端点做补全 → 给搜索页加 noindex → 排除页面缓存。做完这套,还能顺手处理 主题 404 页面的自定义设计,把两条「兜底通路」的体验统一起来。
