【迅搜19】扩展(二)TNTSearch和JiebaPHP方案

news2024/11/15 8:33:15

扩展(二)TNTSearch和JiebaPHP方案

搜索引擎系列的最后一篇了。既然是最后一篇,那么我们也轻松一点,直接来看一套非常有意思的纯 PHP 实现的搜索引擎及分词方案吧。这一套方案由两个组件组成,一个叫 TNTSearch ,另一个则是大名鼎鼎的结巴分词的 PHP 版本。它们都是纯 PHP 实现的,非常轻量级的搜索引擎和分词工具,最主要的是,如果各位大佬有兴趣,可以深入学习它们的源码。之前就一直在强调,所有的原理都是相通的,通过对这两个组件的学习,其实就能清楚 Xapian 和 SCWS 也就是 XS 整个系统是怎么运行的。甚至也可以说,就能了解到 ES 和 IK 是大致是怎么运行的了。

TNTSearch 与 Jieba-php 集成

这两个工具包都是开源的,直接在 GitHub 就可以下载。文档也都在它们的 Readme 文件里。

https://github.com/teamtnt/tntsearch

https://github.com/fukuball/jieba-php

对于我们 PHP 的使用来说,直接 composer 安装就好了,方便得很。

composer require teamtnt/tntsearch
composer require fukuball/jieba-php:dev-master

安装完成之后就开始写代码啦,非常非常简单,保证分分钟之内搭起你的搜索引擎应用。

require_once 'vendor/autoload.php';

use TeamTNT\TNTSearch\TNTSearch;
use TeamTNT\TNTSearch\Support\AbstractTokenizer;
use TeamTNT\TNTSearch\Support\TokenizerInterface;

// 自定义分词器
class JiebaTokenizer extends AbstractTokenizer implements TokenizerInterface
{
    public function tokenize($text,$stopwords='') {
       // 默认的结巴分词使用,之前我们就用过了
        ini_set("memory_limit", "-1");
        \Fukuball\Jieba\Jieba::init();
        \Fukuball\Jieba\Finalseg::init();
        return \Fukuball\Jieba\Jieba::cut($text);;
    }
}

// 实例化 TNTSearch 对象
$tnt = new TNTSearch;
// tnt 对象加载配置信息
$tnt->loadConfig([
    'driver'    => 'mysql', // 驱动方式
    'host'      => 'localhost',
    'database'  => 'zyblog',
    'username'  => 'root',
    'password'  => '123456',
    'storage'   => './',   // 数据存储路径
    'tokenizer' => JiebaTokenizer::class, // 分词器
    'stemmer'   => \TeamTNT\TNTSearch\Stemmer\PorterStemmer::class//optional,没查到这玩意是干嘛的,官网例子上带的,先复制过来吧
]);

上面这些就是我们的基础配置代码了,是不是简单到没朋友。不需要配置文件,直接在代码中配置即可。注释也都写清楚了,所以也就不多做解释啦!

索引操作

有了上面的配置之后,我们就可以开始来操作索引了。在这里要先换一个概念,那就是 TNTSearch 是有点类似于 Sphinx 这样的搜索引擎。也就说,它的数据来源是针对数据库的,或者说让数据库来做为数据源是比较方便的。

因此,它建立索引的方式也和 Sphinx 很像,直接连 MySQL 去查表建索引。

$indexer = $tnt->createIndex('zyblog');  // 创建索引
$indexer->query('SELECT * FROM zy_articles_xs_test where status = 1 limit 10;'); // 查询语句
$indexer->run(); // 执行索引操作

这样我们就初始化了一个索引项目,并且使用指定数据库中的数据填充到这个索引项目中了。

> php 19.php 
Total rows 10

执行完成后会返回插入成功的数量信息。这里我们插入了 10 条数据,是因为 TNTSearch 建立索引的速度一般般哦,并不是很快。也有可能是我并没有深入的学习,也不知道有没有别的什么更快的方式。反正如果是全部的我那300多篇文章的话,是要跑半天的。

除了这样全量的操作索引数据外,也可以进行单条或多条数据的增、删、改,这些操作非常简单,而且就和写 SQL 语句一样,非常简单,大家可以自己去 GitHub 上看下文档哈,我就不具体演示了,下面就主要再看看怎么查询数据。

检索数据

在搜索这一块,也非常简单,选择好要操作的索引,然后直接一个 search() 方法就可以了。

$tnt->selectIndex("zyblog");
$res = $tnt->search("链表", 10);
print_r($res);
// Array
// (
//     [ids] => Array
//         (
//             [0] => 4
//             [1] => 5
//             [2] => 2
//             [3] => 6
//             [4] => 7
//             [5] => 1
//             [6] => 8
//         )

//     [hits] => 7
//     [docScores] => Array
//         (
//             [4] => 0.70105075187958
//             [5] => 0.69908289011992
//             [2] => 0.68591335372833
//             [6] => 0.57067991030197
//             [7] => 0.47556659191831
//             [1] => 0.35667494393873
//             [8] => 0.35667494393873
//         )

//     [execution_time] => 533.8249 ms
// )

search() 方法的第一个参数是查询语句,第二个参数是返回数量,默认这个数量值是 100 。返回这么多数据?你再看看它返回的内容就知道为啥能返回这么多数据了。

TNTSearch 的搜索结果,返回的也是和 Sphinx 非常像的,它们都只是返回索引的 ID 信息。也就说,它们在底层可能连文档信息都不会存,只是存词项与文档 ID 之间的关系以及这些词项与文档的评分情况。

TNTSearch 也是实现的 BM25 评分算法。可以看到返回结果的顺序不是按 ID 排序的,现在 docScores 也有各文档的关键词评分结果。

这种搜索引擎的使用方式,就是通过检索返回的主键 ID ,再去数据库进行主键查询获取完整的数据。因为主键在数据库中有着非常好的查询性能,因此,即使上亿的量,使用主键也是非常快的。

用过 Sphinx 的小伙伴对这种查询方式一定不会陌生,而如果你之前没用过 Sphinx 也没关系,试试 TNTSearch ,如果未来有可能用到 Sphinx 了,也会马上就能上手了。

看看源码

是不是感觉打开了一扇新世界的大门呀。要说 XS ,其实是和 ES 比较像的,它们会直接存储并返回元数据信息,也就是我们具体的文档字段内容。而 TNTSearch 和 Sphinx 这种则是另一种形式的,只返回主键 ID ,而且它们都和关系型数据的关系比较好,一般直接通过非常类似操作 SQL 语句一样的方式来操作索引。

这就是工具多样性的一个体现了。但是基础原理上,它们还是一样的倒排索引引擎。根本上还是一家人。

由于是完全的 PHP 实现,其实 TNTSearch 的源码就很容易让大家看明白了。即使我没有深入的学习,但也大致了解到它是如果实现倒排索引的。TNTSearch 的倒排索引库是使用 SQLite 实现的(对应 XS 中的那些 .glass 文件,就是上节课学的)。

TNTSearch 倒排索引实现

在 TNTSearch 的 loadConfig 中,我们有一个字段是 storage ,设置的是 "./" ,也就是将数据保存在当前相对路径下。因此,你在我们的测试目录下,就会看到运行之后会生成一个叫做 zyblog 的 sqlite 文件。这个文件名也就是我们创建索引时使用 createIndex() 时传递的参数名,它正是我们的索引名。这个 SQLite 库也就是针对这个索引项目的库。直接使用 PHPStorm 就可以查看这个 SQLite 数据库里面的内容。

1472766a0c11817b63c7980e49a2bddb.png

当然,你用命令行也可以看,前提是本地已经安装了 SQLite 工具。

> sqlite3 zyblog
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
sqlite> select * from wordlist limit 10;
1|PHP|131|19
2|数据结构|89|10
3|与|21|10
4|算法|71|10
5|1|144|10
6|在|171|10
7|学|7|2
8|和|108|10
9|的|1065|10
10|时候|35|8

是不是有点意思啊,从表名我们就能看到,它的表名和之前在 XS 中学习过的那些 .glass 文件名是很像的。wordlist 应该是分词表、doclist 应该是文档表。那么我们就来尝试一下,先在 wordlist 表中找到“链表”这个词。就是我们在上面进行检索查询时测试的那个关键词。

9f3175bc8d9c5ef95b2fdbe7f807c625.png

对应的词项表id是 456 。接下来,到 doclist 文档表中查找词项id(term_id)为 456 的数据。

cfc37dbd02f0dcd204d1ecc01cbda56b.png

看看是不是我们前面检索出来结果那几条。doc_id 对应的就是文档的 id 主键,hit_count 代表的是关键词在文档中出现的次数 TF 。这个字段和 wordlist 表中的其它字段一起做为 BM25 算法的 TF 和 IDF ,进行最终的评分计算。这一块的计算代码也是直接在 PHP 源码中的,大家可以自己找找哦。

好了,对照一下之前我们学习倒排索引原理时的那张图,看看它的实现是不是和我们讲述的概念是一模一样的。现在,你是不是能够彻底地了解到底什么是倒排索引了吧。毕竟真实的例子和源码就摆在你眼前了。

接下来,我们再看一下,它在搜索时通过这两张表的查询,完成了数据的检索。但为什么能非常快呢?这其实还是靠得数据的索引。

bd159e6a000a8f854cda34985db06bed.png

同样还是之前在倒排索引的原理时就讲过,分词之后的词项表,大部分还是通过B+树这样的存储方式来实现快速查找的。这里还需要过多解释吗?在 wordlist 中,对 term 这个字段,也就是分词词项建了个索引。然后在 doclist 中,又对 term_id 建立了索引。因此,在直接的检索过程中,这两块都是走了数据库索引的,速度是完全有保障的。

怎么样,怎么样,之前在理论中讲的东西不是侃大山吧,看到真实的实现了吧,而且是咱们各位 PHPer 们都能看懂的,PHP+SQLite 的实现。最后再归结回去,不管 XS、ES、Sphinx或者其它,只要是搜索引擎应用或中间件,最终的原理都是和这一套是类似的,但具体的实现形式以及功能和语言各有不同。

结巴的词库

说了半天引擎,分词的内容咱们也看一眼。结巴分词在 Python 领域是一哥,同时也是现在非常流行的一套分词组件。它有 PHP 的版本,也是全 PHP 实现的,简单实用。之前我们其实都已经在 XS 中用过了,另外关于分词的概念之前在 SWCS 中也讲过一些了,这里我们就是看下结巴的词库在哪里。一般来说,结巴如果是通过 composer 安装的话,那么它的默认词库是 /vendor/fukuball/jieba-php/src/dict 目录中。

858814bdaaed2302d75d99a5cb0ca32f.png

额,没啥可解释的了吧。dict.xxxx.txt 是系统的默认词库,而且全是 txt 格式的,另外还有 json 格式的,大家可以直接用文本工具打开看看。user_dict.txt 肯定是我们的自定义词库啦,stop_words.txt 是停用词库。和 SCWS 以及 IK 的命名都是非常接近甚至一样的。

另外还要说一点,正是由于结巴使用的是 txt 格式词库,虽说看着大小不大,但在程序加载及运行过程中,结巴对于内存的需求非常大。所以在使用结巴时,我都会给代码前加上一行。

ini_set("memory_limit", "-1");

也就是不限制内存使用,否则可能报出内存溢出的错误。这也是结巴 PHP 版本比较让人诟病的一点。也许也有其它的解决方案或者参数方法可以使用,反正我是没有继续深入研究了,有兴趣的小伙伴可以继续深入学习。

框架集成

在 Laravel 的官方组件中,也有自带的一套全文检索组件,叫做 Laravel Scout 。不知道小伙伴们用过没有,反正我是没用过,为啥呢?它自带的驱动,也就是官方指定的搜索引擎完全就没听过,可能在老外那边比较流行吧。

当然,通过在 packagist 中搜索,也能找到直接集成 TNTSearch 到 Laravel Scout 的组件。而且还有我们国内的大佬,直接把各种中文分词器都集成好了。

40e07fe76089c6e8ec53d2a609698983.png

另外,XS、ES 与 Laravel Scout 集成的也有,只不过 XS 的 Star 就很少了,用得人不多,ES 相对来说还可以。

这个东西,怎么说呢,还是看大家的需求吧。Larvel Scout 和 Laravel 的 ORM 绑定比较深,对于 TNTSearch 和 Sphinx 这类的搜索引擎还是非常好用的,但是相对于 ES 和 XS 这类,其实它们本身就有自己非常完善的 Scheme 机制,能够非常灵活地处理数据格式,用不用框架 Model 形式的,还是大家自己权衡吧。

总结

说是介绍 TNTSearch 和 JiebaPHP ,但结果我们又借着它俩重温了一下搜索引擎和倒排索引的原理。好嘛,这波其实真不亏。关于这两个组件的内容,有兴趣的同学可以再深入源码进行学习。对于日常使用来说,小型项目,像是官网啊、小型文章CMS站啊,使用这一套方案完全没问题,而且非常简单,说实话,我在从来没用过的情况下,按官方文档的例子,总共也没超过 10 分钟就跑起来了上面的例子,真的是太方便了。

好了,整个搜索引擎系列的学习就到此为止了。你有什么收获?有什么感悟?或者有什么想说的?欢迎在任何一篇文章或者视频下面留言。接下来的旅程是什么呢?咱们拭目以待。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/xunsearch/source/19.php

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1398023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【0到1的设计之路】从C语言到二进制程序

C程序如何从源代码生成指令序列(二进制可执行文件) 预处理 -> 编译 -> 汇编 -> 链接 -> 执行 预处理 预处理 文本粘贴 #include <stdio.h> #define MSG "Hello \ World!\n" int main() {printf(MSG /* "hi!\n" */); #ifdef __riscvpr…

PE解释器之PE文件结构(二)

接下来的内容是对IMAGE_OPTIONAL_HEADER32中的最后一个成员DataDirectory&#xff0c;虽然他只是一个结构体数组&#xff0c;每个结构体的大小也不过是个字节&#xff0c;但是它却是PE文件中最重要的成员。PE装载器通过查看它才能准确的找到某个函数或某个资源。 一&#xff1…

2种数控棋

目录 数控棋1 数控棋2 数控棋1 棋盘&#xff1a; 初始局面&#xff1a; 规则&#xff1a; 规则&#xff1a;双方轮流走棋&#xff0c;可走横格、竖格、可横竖转弯&#xff0c;不可走斜格。每一步均须按棋所在格的数字走步数&#xff0c;不可多不可少。 先无法走棋的一方为…

如何有效防爬虫?一文讲解反爬虫策略

企业拥抱数字化技术的过程中&#xff0c;网络犯罪分子的“战术”也更难以觉察&#xff0c;并且这些攻击越来越自动化和复杂&#xff0c;也更加难以觉察。在众多攻击手段中&#xff0c;网络爬虫是企业面临的主要安全挑战。恶意爬虫活动可能导致数据滥用、盗窃商业机密等问题&…

ctfshow php特性(web89-web101)

目录 web89 web90 web91 web92 web93 web94 web95 web96 web97 web98 web99 web100 web101 php特性(php基础知识) web89 <?php include("flag.php"); highlight_file(_FILE_);if(isset($_GET[num])){$num$_GET[num];if(preg_match("/[0-9]/&…

ffmpeg 常用命令行详解

概述 ffmpeg 是一个命令行音视频后期处理软件 1. 裁剪命令 参数说明 -i 文件&#xff0c;orgin.mp3 为待处理源文件-ss 裁剪时间&#xff0c;后跟裁剪开始时间&#xff0c;或者开始的秒数-t 裁剪时间output.mp3 为处理结果文件 ffmpeg -i organ.mp3 -ss 00:00:xx -t 120 o…

Oracle 隐式数据类型转换

目录 Oracle类型转换规则&#xff1a; 看如下实验&#xff1a; 1、创建一张表&#xff0c;字段id的类型为number&#xff0c;id字段创建索引&#xff0c;插入一条测试数据 2、我们做如下查询&#xff0c;id的值设置为字符型的1 3、查看执行计划&#xff1a; Oracle类型转换…

MySQL之索引结构

索引概述 索引是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#xff09;数据&#xff0c;这样就可以在这些数据结构上实现…

JMeter实操入门之登录

JMeter实操入门之登录 前言初级-无变量的登录线程组取样器-HTTP请求 进阶-定义变量的登录用户定义的变量获取JSON返回的数据-tokentoken设置全局变量 前言 安装及环境配置教程可移步&#xff1a;JMeter安装与配置环境 本篇文章针对小白进一步的认识及运用JMeter&#xff0c;围绕…

从数据角度分析年龄与NBA球员赛场表现的关系【数据分析项目分享】

好久不见朋友们&#xff0c;今天给大家分享一个我自己很感兴趣的话题分析——NBA球员表现跟年龄关系到底大不大&#xff1f;数据来源于Kaggle&#xff0c;感兴趣的朋友可以点赞评论留言&#xff0c;我会将数据同代码一起发送给你。 目录 NBA球员表现的探索性数据分析导入Python…

ChatGPT与文心一言:AI助手之巅的对决

随着科技的飞速发展&#xff0c;人工智能助手已经渗透到我们的日常生活和工作中。 而在这个充满竞争的领域里&#xff0c;ChatGPT和文心一言无疑是最引人注目的两款产品。它们各自拥有独特的优势&#xff0c;但在智能回复、语言准确性、知识库丰富度等方面却存在差异。那么&am…

【设计模式】责任连模式怎么用?

我将通过一个贴近现实的故事——请假审批流程&#xff0c;带你了解和掌握责任链模式。 什么是责任链模式&#xff1f; 责任链模式是一种行为设计模式&#xff0c;它让你可以避免将请求的发送者与接收者耦合在一起&#xff0c;让多个对象都有处理请求的机会将这个对象连成一条…

RabbitMQ入门篇【图文并茂,超级详细】

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 前言 1.什么是MQ 2.理解MQ 3.生活…

探秘网络爬虫的基本原理与实例应用

1. 基本原理 网络爬虫是一种用于自动化获取互联网信息的程序&#xff0c;其基本原理包括URL获取、HTTP请求、HTML解析、数据提取和数据存储等步骤。 URL获取&#xff1a; 确定需要访问的目标网页&#xff0c;通过人工指定、站点地图或之前的抓取结果获取URL。 HTTP请求&#…

MySQL中SELECT字句的顺序以及具体使用

目录 1.SELECT字句及其顺序 2.使用方法举例 3.HAVING和WHERE 1.SELECT字句及其顺序 *下表来自于图灵程序设计丛书&#xff0c;数据库系列——《SQL必知必会》 2.使用方法举例 *题目来源于牛客网 题目描述 现在运营想要查看不同大学的用户平均发帖情况&#xff0c;并期望结…

【JavaScript】面向后端快速学习 笔记

文章目录 JS是什么&#xff1f;一、JS导入二、数据类型 变量 运算符三、流程控制四、函数五、对象 与 JSON5.1 对象5.2 JSON5.3 常见对象1. 数组2. Boolean对象3. Date对象4. Math5. Number6. String 六、事件6.1 常用方法1. 鼠标事件2. 键盘事件3. 表单事件 6.2 事件的绑定**1…

深入Docker5:安装nginx部署完整项目

目录 准备 为什么要使用nginx mysql容器构建 1.删除容器 2.创建文件夹 3.上传配置文件 4.命令构建mysql容器 5.进入mysql容器&#xff0c;授予root所有权限 6.在mysql中用命令运行sql文件 7.创建指定数据库shop 8.执行指定的sql文件 nginx安装与部署 1.拉取镜像 2…

/var/run/yum.pid 已被锁定,PID 为 2762 的另一个程序正在运行解决方法

一、问题 /var/run/yum.pid 已被锁定&#xff0c;PID 为 2762 的另一个程序正在运行 二、原因 这个提示意味着在你的Linux系统中&#xff0c;有一个yum&#xff08;或者dnf&#xff0c;在较新版本的Fedora和RHEL/CentOS 8中&#xff09;进程正在运行&#xff0c;并且它已经创建…

Vue基知识五

一 vue配置代理 1.1 跨域 JQuery大多数封装的是对DOM的操作&#xff0c;而VUE是要减少对DOM的操作&#xff0c;所以VUE里很少用JQuery&#xff0c;而是用axios发送请求&#xff1b;JQuery与axios都是对xhr进行的封装&#xff1b; 下载并引入axios npm i axios点击按钮请求后…

tx2开发板升级JetPack至最新

最近一个项目用到了tx2, 上面的jetpack太老了需要更新&#xff0c;很久没和开发板打交道了&#xff0c;记录一下。中间没怎么截图&#xff0c;所以可能文字居多。 准备工作 Ubuntu 18.04的机器&#xff0c;避免有坑&#xff0c;不要使用虚拟机&#xff0c;一定要是物理机&…