Xunsearch 教程:从入门到精通

目录

  1. 什么是 Xunsearch?
    • 核心特点
    • 适用场景
  2. 快速入门:第一个搜索应用
    • 环境准备
    • 安装 Xunsearch
    • 第一步:定义索引结构 (ini 文件)
    • 第二步:创建索引数据 (PHP 代码)
    • 第三步:执行搜索 (PHP 代码)
    • 运行与验证
  3. 核心概念详解
    • 索引:什么是索引?为什么需要它?
    • 字段id, title, body 等字段的类型和作用
    • 字段类型title, body, numeric, date, id, string 的区别
    • 索引与搜索的一致性:为什么添加数据后不能立刻搜索到?
  4. API 核心类与方法详解
    • XS: 入口类,管理整个生命周期
    • XSIndex: 索引操作类 (增删改)
    • XSSearch: 搜索操作类 (查询)
    • XSDocument: 数据文档类
  5. 进阶功能与实战技巧
    • 数据更新与删除
    • 高级搜索语法:, , , field:, 通配符
    • 排序与分页
    • 权重/相关性控制
    • 模糊搜索与拼音搜索
    • 增量索引与重建索引
    • 中文分词与自定义词典
  6. 常见问题与解决方案 (FAQ)
    • Q: 添加数据后为什么搜索不到?
    • Q: 如何处理大数据量的索引?
    • Q: 搜索结果不准确怎么办?
    • Q: 如何在项目中集成?
  7. 总结与资源

什么是 Xunsearch?

Xunsearch (原名 Sphinx) 是一个功能强大、简单易用的专业全文检索引擎,它由 C++ 语言开发,为 PHP 应用提供了量身定制的 API。

xunsearch教程
(图片来源网络,侵删)

核心特点

  • 高性能:基于 C++,索引和搜索速度极快,适合海量数据。
  • 功能丰富:支持全文检索、模糊搜索、拼音搜索、高亮显示、字段排序、地理位置搜索等。
  • 简单易用:提供了清晰的 ini 配置文件和简洁的 PHP API,学习成本低。
  • 稳定可靠:经过大量商业项目验证,稳定性和可靠性高。
  • 开源免费:遵循 BSD 协议,可以免费用于商业项目。

适用场景

  • 网站站内搜索:电商、论坛、博客、新闻门户等。
  • 日志数据分析:快速检索和分析服务器日志、应用日志。
  • 数据挖掘与分析:从大量非结构化文本中提取有价值信息。
  • 任何需要快速、精准文本检索的应用

快速入门:第一个搜索应用

我们将创建一个简单的文章搜索功能,包含标题和内容。

环境准备

  • PHP: 版本 >= 5.2.0 (推荐 7.0+)
  • 操作系统: Linux, macOS, Windows
  • Web 服务器: Apache, Nginx (可选,CLI 模式即可运行)

安装 Xunsearch

  1. 下载:从 Xunsearch 官网 下载最新版的稳定版。
  2. 解压:将下载的压缩包解压到你的项目目录或任意位置,/usr/local/xunsearch
  3. 安装依赖:Xunsearch 依赖一些第三方库(如 libiconv, libevent, libmysqlclient),请根据你的系统运行其自带的 sh setup.sh 脚本,它会自动检测并提示你如何安装依赖。
  4. 环境变量 (推荐):为了方便使用,可以将 Xunsearch 的 bin 目录添加到系统 PATH 环境变量中。
    • Linux/macOS: export PATH=$PATH:/path/to/xunsearch/bin
    • Windows: 将 bin 目录路径添加到系统环境变量的 Path 中。

第一步:定义索引结构 (ini 文件)

这是 Xunsearch 的核心配置,告诉引擎如何处理你的数据。

在你的项目目录下创建一个 ini 文件,blog.ini

; blog.ini - 博客文章搜索索引定义
project.name = blog_project ; 项目名称,用于生成索引文件前缀
; 字段定义
[type]字段,用于标题,类型为标题,会进行分词和索引,权重较高= title
; content 字段,用于文章内容,类型为正文,会进行分词和索引
content = body
; id 字段,用于唯一标识,类型为ID,必须是整数且唯一
id = id
; author 字段,用于作者,类型为字符串,不进行分词,但可搜索和排序
author = string
; post_time 字段,用于发布时间,类型为数值,可用于排序和范围过滤
post_time = numeric
; url 字段,用于文章链接,类型为字符串,不进行分词,但可搜索和排序
url = string
[search]
; 默认搜索的字段,可以多个,用逗号隔开
default_field = title, content

第二步:创建索引数据 (PHP 代码)

现在我们写一个 PHP 脚本来添加一些示例数据到索引中,创建文件 indexer.php

xunsearch教程
(图片来源网络,侵删)
<?php
// 引入 Xunsearch SDK 的核心文件
// 请根据你的实际安装路径修改
require_once '/usr/local/xunsearch/sdk/php/lib/XS.php';
try {
    // 创建一个 XS 对象,ini 文件路径相对于当前脚本或绝对路径
    $xs = new XS('blog.ini');
    // 获取索引对象
    $index = $xs->index;
    // 清空旧索引(可选,第一次运行可以注释掉)
    // $index->clean();
    // 创建一个文档对象
    $doc = new XSDocument();
    // 添加数据到文档
    // 注意:字段名必须和 ini 文件中定义的一致
    $doc->setField('id', 1);
    $doc->setField('title', 'Xunsearch 入门教程');
    $doc->setField('content', 'Xunsearch 是一个强大的全文检索引擎,本教程将带你快速入门。');
    $doc->setField('author', '张三');
    $doc->setField('post_time', time() - 86400); // 一天前
    $doc->setField('url', '/article/1');
    // 将文档添加到索引
    $index->add($doc);
    // 添加第二篇文章
    $doc2 = new XSDocument();
    $doc2->setField('id', 2);
    $doc2->setField('title', 'PHP 开发最佳实践');
    $doc2->setField('content', '在 PHP 开发中,遵循一些最佳实践可以让你的代码更健壮、更高效,使用命名空间、依赖注入等。');
    $doc2->setField('author', '李四');
    $doc2->setField('post_time', time() - 43200); // 12小时前
    $doc2->setField('url', '/article/2');
    $index->add($doc2);
    echo "索引添加成功!\n";
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

运行脚本: 在命令行中执行 php indexer.php,如果一切正常,你会看到 "索引添加成功!",在你的项目目录下会生成一些 .ini, .dat, .idx 等文件,这就是索引数据。

第三步:执行搜索 (PHP 代码)

我们创建一个搜索页面,创建文件 search.php

<?php
require_once '/usr/local/xunsearch/sdk/php/lib/XS.php';
// 获取搜索关键词,从 GET 参数 'q' 中获取
$keyword = isset($_GET['q']) ? trim($_GET['q']) : '';
if ($keyword) {
    try {
        // 创建一个 XS 对象
        $xs = new XS('blog.ini');
        // 获取搜索对象
        $search = $xs->search;
        // 执行搜索
        // search() 方法返回一个 XSSearchResult 对象
        $result = $search->search($keyword);
        echo "搜索关键词: " . htmlspecialchars($keyword) . "<br>";
        echo "找到 " . $result->count . " 条结果 (耗时 " . $result->took . " 毫秒)<br><hr>";
        // 遍历结果
        foreach ($result as $doc) {
            // $doc 是一个 XSDocument 对象
            echo "<h3><a href='" . htmlspecialchars($doc->url) . "'>" . htmlspecialchars($doc->title) . "</a></h3>";
            echo "<p>作者: " . htmlspecialchars($doc->author) . " | 发布时间: " . date('Y-m-d H:i:s', $doc->post_time) . "</p>";
            echo "<p>" . htmlspecialchars($doc->content) . "</p>";
            echo "<hr>";
        }
    } catch (Exception $e) {
        echo "搜索错误: " . $e->getMessage() . "<br>";
    }
} else {
    echo "请输入搜索关键词。<br>";
    echo "<form action='' method='get'>"
       . "<input type='text' name='q' placeholder='输入关键词...'>"
       . "<input type='submit' value='搜索'>"
       . "</form>";
}

运行搜索: 通过浏览器访问 http://localhost/your_project/search.php?q=php,你应该能看到与 "php" 相关的搜索结果。


核心概念详解

索引

索引可以理解为书籍的目录,Xunsearch 会对你的文本数据进行预处理,建立一种可以快速查找的数据结构(如倒排索引),没有索引,搜索就需要遍历所有数据(全表扫描),效率极低。建立索引是搜索的前提

xunsearch教程
(图片来源网络,侵删)

字段

字段是数据的基本单元,对应你数据表中的列或文档中的某个部分,在 ini 文件中定义字段时,需要指定其类型。

字段类型 (标题): 最常用,会进行分词,支持模糊搜索,搜索权重高,适用于标题、关键词等简短文本。

  • body (正文): 最常用,会进行分词,支持模糊搜索,但权重低于 title,适用于文章内容、描述等长文本。
  • id (ID): 唯一标识,必须是整数,且值必须唯一,用于数据的精确更新和删除。
  • numeric (数值): 整数或浮点数,可用于排序和范围过滤(如 price:100-200),但不支持全文搜索。
  • date (日期): Unix 时间戳格式,可用于排序和范围过滤(如 post_time:>1672531200),不支持全文搜索。
  • string (字符串): 不进行分词,可以搜索和排序,但只能做精确匹配或前缀匹配,适用于作者、标签、URL 等。
  • bool (布尔): truefalse,可用于过滤,不支持搜索。

索引与搜索的一致性

当你添加或删除数据时,这些变更会先写入一个日志文件,你需要通过 index->flush()index->reindex() 等操作,将变更真正合并到主索引文件中,这个过程称为 提交

  • index->add() / index->update() / index->del() 只是操作日志。
  • index->flush() 将日志合并到主索引,搜索结果会立即更新
  • index->reindex() 是重建索引,更彻底,但耗时更长。

indexer.php 的例子中,我们没有显式调用 flush(),因为 Xunsearch 的 XSIndex 对象在脚本执行结束时会自动提交,但在后台任务或 Web 环境中,最好显式调用。


API 核心类与方法详解

XS: 入口类

$xs = new XS('ini_file_path'); // 初始化,传入 ini 文件路径
$index = $xs->index;          // 获取索引操作对象
$search = $xs->search;        // 获取搜索操作对象

XSIndex: 索引操作类

$index = $xs->index;
// 添加文档
$doc = new XSDocument();
$doc->setField('id', 123);
$doc->setField('title', '新文章');
$index->add($doc);
// 更新文档 (本质是删除再添加)
// 必须有 id 字段
$doc->setField('id', 123);
$doc->setField('title', '更新后的标题');
$index->update($doc);
// 删除文档
$index->del(123); // 根据 ID 删除
// 提交日志到主索引
$index->flush();
// 清空所有索引
$index->clean();
// 重建索引 (先清空再重建)
$index->reindex();

XSSearch: 搜索操作类

$search = $xs->search;
// 设置搜索关键词
$search->setQuery('php教程'); // 最简单的搜索
// 高级搜索语法
$search->setQuery('+标题 -"全文检索" author:张三');
// 设置排序
$search->setSort('post_time', false); // 按发布时间倒序排列,false表示降序,true表示升序
// 设置分页
$search->setLimit(10, 20); // 每页10条,从第21条开始 (page 3)
// 执行搜索
$result = $search->search();
// 获取搜索结果
$total = $result->count; // 总结果数
$docs = $result->docs;   // 结果文档数组 (XSDocument 对象)
$took = $result->took;   // 搜索耗时(毫秒)

XSDocument: 数据文档类

作为数据载体,用于在索引操作时传递数据。

$doc = new XSDocument();
// 设置字段值
$doc->setField('id', 1);
$doc->setField('title', '我的文档');
// 从数组创建
$data = ['id' => 1, 'title' => '我的文档'];
$doc = new XSDocument($data);
// 获取字段值
$id = $doc->id;
$title = $doc->title;

进阶功能与实战技巧

数据更新与删除

如上所述,更新和删除都依赖于 id 字段,确保你的数据有唯一的 id

高级搜索语法

Xunsearch 支持类 Google 的搜索语法:

  • (与): 关键词1 关键词2+关键词1 +关键词2,同时包含两个词。
  • (非): 关键词1 -关键词2,包含 关键词1 但不包含 关键词2
  • (短语): "全文检索",精确匹配这个短语。
  • field: (字段): author:张三,只在 author 字段中搜索。
  • (通配符): php* 可以匹配 "php", "php教程" 等。*教程 可以匹配 "入门教程", "高级教程" 等。注意:通配符不能出现在词的开头

排序与分页

// 综合排序 + 自定义排序
// 先按相关性排序(默认),相关性相同时,按发布时间倒序
$search->setMultiSort(['@rank' => 'desc', 'post_time' => 'desc']);
// 分页
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$pageSize = 10;
$offset = ($page - 1) * $pageSize;
$search->setLimit($pageSize, $offset);

权重/相关性控制

ini 文件中,可以通过 weight 属性设置字段的默认权重。

[type]= title; weight=80
content = body; weight=60
author = string; weight=40

在搜索时,也可以动态调整权重:

// 让 'title' 字段的权重在本次搜索中变为 100
$search->addWeight('title', 100);

模糊搜索与拼音搜索

这需要在 ini 文件中开启支持。

[search]
; 开启模糊搜索,fuzzy=1 表示开启
fuzzy = 1
; 开启拼音搜索,pinyin=1 表示开启
pinyin = 1

开启后,搜索 php 也能匹配到 PHP,搜索 ruanjian 也能匹配到 软件

增量索引与重建索引

  • 重建索引 (reindex):适用于数据量不大或索引损坏的情况,它会清空所有旧索引,然后重新从数据源(如 MySQL)构建一遍。非常耗时
  • 增量索引:适用于数据量大且频繁更新的场景,核心思想是:
    1. 定时任务(如 crontab)运行一个脚本,从数据库中获取自上次索引以来新增或修改的数据。
    2. 使用 index->add()index->update() 将这些数据添加到索引日志。
    3. 运行 index->flush() 将日志合并到主索引。

中文分词与自定义词典

Xunsearch 默认使用 SCWS (Simple Chinese Word System) 进行中文分词。

  • 自定义词典:你可以添加自己的词语,让分词更准确。
    1. ini 文件中指定词典路径:
      [general]
      dict = /path/to/your/dict.txt
    2. 创建 dict.txt 文件,格式为 词语 权重 词性 (词性和权重可选)。
      Xunsearch 100 n
      全文检索 100 n
  • 停用词词典:过滤掉无意义的词(如“的”、“是”、“在”)。
    [general]
    stop = /path/to/your/stop.txt

    创建 stop.txt,每行一个停用词。


常见问题与解决方案 (FAQ)

Q: 添加数据后为什么搜索不到?

A: 这是最常见的问题,原因通常是:

  1. 未提交索引:忘记调用 $index->flush(),在 CLI 脚本中,脚本结束时会自动提交,但在 Web 环境或长时间脚本中必须手动调用。
  2. 搜索的字段类型不支持全文搜索:比如你在 stringnumeric 字段中搜索,它们不会被分词和索引。
  3. 拼写错误:检查 ini 文件中的字段名和 PHP 代码中的字段名是否一致。

Q: 如何处理大数据量的索引?

A:

  1. 使用增量索引:避免频繁 reindex
  2. 分批处理:在添加数据时,使用 setLimit 分批从数据库读取数据,然后分批添加到索引,避免内存溢出。
  3. 优化服务器配置:确保服务器有足够的内存和磁盘空间给 Xunsearch。
  4. 调整 ini 配置:可以调整 general 段落的 memory_limit 等参数。

Q: 搜索结果不准确怎么办?

A:

  1. 调整字段权重:提高重要字段的 weight 值。
  2. 使用自定义词典:添加行业术语或专有名词,确保分词正确。
  3. 使用停用词词典:过滤掉干扰词。
  4. 调整搜索算法:尝试使用 fuzzy 模糊搜索或 pinyin 拼音搜索。

Q: 如何在项目中集成?

A:

  1. 封装成服务类:创建一个 XunsearchService 类,对外提供 add, update, del, search 等方法,内部封装 XS 的操作。
  2. 结合 MVC 框架:在 Laravel, ThinkPHP 等框架中,可以创建一个 Service Provider 或 Facade,方便在控制器中调用。
  3. 使用队列:对于数据的更新,可以将任务推送到队列(如 Redis Queue, RabbitMQ),由后台消费者进程来执行索引的添加和刷新,避免阻塞 Web 请求。

总结与资源

Xunsearch 是一个值得学习的工具,它为 PHP 应用提供了企业级的全文检索能力,通过本教程,你应该已经掌握了从安装配置到基本搜索,再到进阶优化的全过程。

官方资源

学习建议

  1. 动手实践:跟着教程敲一遍代码,然后尝试修改 ini 文件,添加新字段,观察搜索行为的变化。
  2. 阅读官方文档:官方文档是最好的老师,特别是配置选项部分。
  3. 从简单到复杂:先实现一个基础的站内搜索,再逐步加入排序、高亮、拼音等高级功能。

希望这份教程对你有帮助!祝你在使用 Xunsearch 的过程中一切顺利!