最近接了一个小项目,客户需要一个简单的搜索引擎框架,用来在自己的网站上实现产品搜索功能。这东西听起来高大上,但其实实现起来并不复杂,尤其用PHP来搞,简直就是小菜一碟。今天就来分享一下我是怎么用PHP搞出一个轻量级搜索引擎框架的。
我们得明确一下需求。客户的需求并不复杂:
1. 支持关键词搜索。
2. 搜索结果需要按相关性排序。
3. 支持简单的分页功能。
4. 搜索速度要快,不能卡顿。
看上去没啥难度,对?但别急着动手,我们在做之前得先想清楚架构。毕竟搜索引擎这东西涉及到大量数据处理和优化,搞不好就翻车了。
架构设计
最简单的搜索引擎框架其实可以分为两个部分:索引构建和查询处理。索引构建就是把我们要搜索的数据提前处理好,存到一个高效的数据结构里,方便后续查询。查询处理就是根据用户输入的关键词,在索引中查找匹配的结果,并按相关性排序返回给用户。
这里我们选择用一种叫做倒排索引(Inverted Index)的数据结构来存储数据。倒排索引是搜索引擎中最常用的索引结构,它的核心思想是把每个关键词映射到包含它的文档列表。比如,我们有三个文档:
1. 文档1:"苹果是水果"
2. 文档2:"香蕉是水果"
3. 文档3:"苹果和香蕉都是水果"
那么倒排索引就是:
苹果:文档1, 文档3
香蕉:文档2, 文档3
是:文档1, 文档2, 文档3
这样一来,当用户搜索"苹果"时,我们只需要查找"苹果"对应的文档列表就行了,效率非常高。
数据准备
我们得有一些数据来构建索引。这里假设我们的数据存储在MySQL数据库中,表结构如下:
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
description TEXT
);
为了方便演示,我们插入几条测试数据:
INSERT INTO products (name, description) VALUES ('苹果手机', '这是一款苹果公司出品的智能手机。');

INSERT INTO products (name, description) VALUES ('香蕉牛奶', '这是一款用香蕉和牛奶制作的饮品。');
INSERT INTO products (name, description) VALUES ('苹果和香蕉', '这是一款结合了苹果和香蕉的混合水果。');
数据有了,接下来就是构建索引。
构建倒排索引
我们写一个PHP脚本来读取数据库中的数据,然后构建倒排索引。这里我们用一个简单的数组来存储索引,实际项目中可以考虑使用更高效的数据结构,比如Redis或者Elasticsearch。
代码如下:
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', 'password');
$stmt = $pdo->query('SELECT FROM products');
$invertedIndex = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$words = array_merge(
explode(' ', $row['name']),
explode(' ', $row['description'])
);
foreach ($words as $word) {
if (!isset($invertedIndex[$word])) {
$invertedIndex[$word] = [];
}
$invertedIndex[$word][] = $row['id'];
}
}
print_r($invertedIndex);
这段代码的作用是读取数据库中的每一条记录,把name和description字段拆分成单词,然后构建倒排索引。运行后,你会得到一个数组,数组的键是单词,值是该单词出现在哪些文档中。
处理查询
有了倒排索引,接下来就是处理用户输入的关键词,并返回匹配的文档。这里我们简单地把用户输入的关键词拆分成多个单词,然后在倒排索引中查找这些单词对应的文档列表,最后把这些列表取交集,得到最终的结果。
代码如下:
function search($query, $invertedIndex) {

$keywords = explode(' ', $query);
$results = [];
foreach ($keywords as $keyword) {
if (isset($invertedIndex[$keyword])) {
if (empty($results)) {
$results = $invertedIndex[$keyword];
} else {
$results = array_intersect($results, $invertedIndex[$keyword]);
}
// 如果有一个关键词没有匹配到任何文档,直接返回空
return [];
}
return $results;
}
$query = '苹果 香蕉';
$results = search($query, $invertedIndex);
print_r($results);
这段代码的作用是把用户输入的关键词拆分成多个单词,然后在倒排索引中查找这些单词对应的文档列表,最后取交集得到结果。比如用户输入"苹果 香蕉",我们会查找"苹果"和"香蕉"对应的文档列表,然后取交集,最终返回文档3。
相关性排序
现在我们已经能返回匹配的文档了,但输出结果是无序的,用户肯定希望能按相关性排序。这里的相关性可以简单地用词频(TF)来衡量,即一个关键词在文档中出现的次数越多,相关性越高。
我们修改一下代码,计算每个文档的相关性得分,然后按得分排序:
$scores = [];
foreach ($invertedIndex[$keyword] as $docId) {
if (!isset($scores[$docId])) {
$scores[$docId] = 0;
}
}
// 按相关性得分排序
arsort($scores);
$rankedResults = array_keys($scores);
return $rankedResults;
}
现在,搜索结果会按相关性得分从高到低排序,用户看到的结果会更加合理。
分页功能
最后我们来加个分页功能。分页其实很简单,就是把结果数组分成多个小数组,每次只返回一页的数据。
代码如下:
function paginate($results, $page, $perPage) {
$offset = ($page - 1)
$perPage;return array_slice($results, $offset, $perPage);
}
$page = 1;
$perPage = 2;
$paginatedResults = paginate($results, $page, $perPage);
print_r($paginatedResults);
这段代码的作用是把结果数组分成多个小数组,每次只返回一页的数据。比如每页显示2条结果,用户查看第1页时,返回数组中的前2条结果。
性能优化
当然了,这个简单的搜索引擎框架还有很多可以优化的地方。比如:
1. 索引存储:实际项目中,索引数据量可能非常大,用PHP数组存储可能不够高效。可以考虑使用Redis或者Elasticsearch来存储索引。
2. 分词优化:现在我们只是简单地把字符串按空格拆分,实际中文分词需要考虑更多情况,比如停用词、词干提取等。
3. 缓存:每次查询都重新计算相关性得分比较耗时,可以考虑把结果缓存起来,下次查询时直接返回缓存结果。
总结
用PHP打造一个轻量级搜索引擎框架并不复杂,关键是要理解搜索引擎的基本原理,比如倒排索引、相关性排序等。虽然这个框架比较简单,但它已经能满足大部分小网站的需求了。如果你有兴趣,可以继续优化它,比如加入中文分词、缓存、分布式索引等功能,让它变得更加强大。
当然了,如果你不想自己造轮子,直接用现成的搜索引擎框架比如Elasticsearch也是不错的选择。但自己动手搞一个,不仅能让你更深入地理解搜索引擎的工作原理,还能在下次面试时秀一把技术,何乐而不为?
886

被折叠的 条评论
为什么被折叠?



