loupe / loupe
一个基于PHP和SQLite的全文搜索引擎,具有分词、词干提取、拼写容错、过滤和地理支持功能
0.7.0
2024-09-18 12:25 UTC
Requires
- php: ^8.1
- ext-intl: *
- doctrine/dbal: ^3.6
- doctrine/lexer: ^2.0 || ^3.0
- mjaschen/phpgeo: ^4.2
- nitotm/efficient-language-detector: ^2.0
- psr/log: ^3.0
- toflar/state-set-index: ^2.0.1
- voku/portable-utf8: ^6.0
- wamania/php-stemmer: ^3.0
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
- symfony/filesystem: ^6.2
- symfony/finder: ^6.2
- symfony/var-dumper: ^6.2
- symplify/easy-coding-standard: 11.2.4.72
README
一个基于SQLite,仅使用PHP的全文搜索引擎。
Loupe…
- …只需要PHP和SQLite,你不需要其他任何东西 - 没有容器,什么都没有
- …具有拼写容错能力(基于State Set Index算法和Levenshtein)
- …支持使用引号 " 进行短语搜索
- …支持对任何属性进行过滤(和排序),使用任何基于SQL的过滤器语句
- …支持基于地理距离进行过滤(和排序)
- …根据典型的TF-IDF余弦相似度算法排序相关性
- …自动检测语言
- …支持词干提取
- …非常易于使用
- …总之,这是替换您老式的SQL
LIKE %...%
查询的最简单方法,提供更好的搜索体验,而不需要额外的服务来管理。SQLite无处不在,只需要您的文件系统。
介绍性博客文章
如果您第一次遇到Loupe,您可能想阅读Medium上的博客文章或Markdown文件,以了解更多关于原因以及您可以使用它的信息。请注意,该博客文章中引用的一些实现细节(例如使用的库)可能已经不再是最新的。
性能
性能取决于许多因素,但以下是一些基于对~32k电影固定数据和该存储库中bin
目录中的测试文件进行索引的大致数字
- 索引(
php bin/index_performance_test.php
)将在5分钟以内完成(约每秒110个文档) - 查询(
php bin/search_performance_test.php
)对于启用拼写容错并按相关性排序的“Amakin Dkywalker”,大约需要80 ms
请注意,任何超过50k文档的东西可能都不是Loupe的使用案例。请阅读文档中的性能章节。您可以在相关讨论中报告您自己的性能测量结果和更多详细信息。
致谢
如果您熟悉MeiliSearch,您会注意到API受到了很大的启发。这样做的原因很简单
- 首先和最重要的是:我认为,他们在从开发者的角度保持配置简单易懂方面做得非常出色。基本的搜索工具不应该复杂。
- 如果Loupe不再满足您的使用案例(您需要高级功能、更好的性能等),切换到MeiliSearch应该易如反掌。
我甚至冒昧地复制了一些测试数据,以用于Loupe的功能测试。
安装
- 请确保您已安装
pdo_sqlite
,并且您的SQLite版本至少为3.16.0。这是添加PRAGMA函数的时候,没有这些函数,无法进行模式比较。建议您至少使用版本3.35.0,这是数学函数被引入SQLite的时候。否则,Loupe必须使用polyfill,这将导致轻微的性能损失。 - 运行
composer require loupe/loupe
。
使用方法
<?php namespace App; require_once 'vendor/autoload.php'; use Loupe\Loupe\Config\TypoTolerance; use Loupe\Loupe\Configuration; use Loupe\Loupe\LoupeFactory; use Loupe\Loupe\SearchParameters; $configuration = Configuration::create() ->withPrimaryKey('uuid') // optional, by default it's 'id' ->withSearchableAttributes(['firstname', 'lastname']) // optional, by default it's ['*'] - everything is indexed ->withFilterableAttributes(['departments', 'age']) ->withSortableAttributes(['lastname']) ->withTypoTolerance(TypoTolerance::create()->withFirstCharTypoCountsDouble(false)) // can be further fine-tuned but is enabled by default ; $loupeFactory = new LoupeFactory(); $loupe = $loupeFactory->create('path/to/my_loupe_data_dir', $configuration); // or create in-memory search: $loupe = $loupeFactory->createInMemory($configuration); $loupe->addDocuments([ [ 'uuid' => 2, 'firstname' => 'Uta', 'lastname' => 'Koertig', 'departments' => [ 'Development', 'Backoffice', ], 'age' => 29, ], [ 'uuid' => 6, 'firstname' => 'Huckleberry', 'lastname' => 'Finn', 'departments' => [ 'Backoffice', ], 'age' => 18, ], ]); $searchParameters = SearchParameters::create() ->withQuery('Gucleberry') ->withAttributesToRetrieve(['uuid', 'firstname']) ->withFilter("(departments = 'Backoffice' OR departments = 'Project Management') AND age > 17") ->withSort(['lastname:asc']) ; $results = $loupe->search($searchParameters); print_r($results->toArray()); /* Array ( [hits] => Array ( [0] => Array ( [uuid] => 6 [firstname] => Huckleberry ) ) [query] => Gucleberry [processingTimeMs] => 4 [hitsPerPage] => 20 [page] => 1 [totalPages] => 1 [totalHits] => 1 ) */
文档
你可能会问:“为什么是Loupe?”在法语中,“Loupe”意为“放大镜”,我觉得这个名字非常适合这个库。在给我的PHP爬虫库起一个法语名字之后,我有了这样的感觉 :-)