Поиск в PDF-файлах с помощью MySQL и Laravel

Источник: «Efficient searching through PDFs with MySQL and Laravel»
Если на сайте представлены документы с большим объёмом текста, например, PDF-файлы, то часто возникает необходимость предоставить пользователям возможность перечисления и поиска в содержимом этих документов.

Поисковая функциональность часто оказывается крайне важной в современных веб-приложениях. Если на сайте представлены документы с большим объёмом текста, например, PDF-файлы, то часто возникает необходимость предоставить пользователям возможность перечисления и поиска в содержимом этих документов. Хотя специализированные инструменты, такие как Elasticsearch, MeiliSearch или Typesense, могут показаться привлекательными, важно учитывать их существенное влияние. При включении одного из этих инструментов в свой стек архитектура приложения становится более сложной. Необходимо не только понять, как использовать такой инструмент, но и установить, настроить, поддерживать, обеспечивать безопасность и контролировать его работу.

Однако специализированные поисковые инструменты обладают и неоспоримыми преимуществами. Вот некоторые примеры возможностей, которые может предложить современная поисковая система:

Принимая решение об использовании поискового инструмента, следует помнить, что этот вид поиска имеет множество тонкостей, присущих только ему, знать как преимущества, так и недостатки использования каждого из них и выбирать с умом.

MySQL может сделать это

Если вас устраивает базовая функция поиска без излишних изысков, то есть интересный более простой подход. Он прост в настройке и обладает привлекательными преимуществами. Давайте разберёмся в этом.

Во-первых, необходимо извлечь текст из PDF-файлов и вставить его в базу данных. Затем MySQL может проиндексировать это содержимое, что позволит осуществлять в нем поиск. Для этого существует пакет PHP, основанный на популярном инструменте pdftotext, который позволяет легко извлекать текст из PDF-документа.

composer require spatie/pdf-to-text

Он очень прост в использовании. Поскольку эта операция может быть ресурсоёмкой, рекомендуется выполнять её асинхронно в фоновом задании. Обратите внимание, что максимальная длина столбца MySQL типа "text" составляет 65 535 символов (~10000 слов), поэтому содержимое PDF-файла не должно быть слишком большим.

use App\Models\PdfDocument;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Spatie\PdfToText\Pdf;

class StorePdfDocumentAsText implements ShouldQueue
{
public function handle()
{
PdfDocument::create([
'title' => 'My great document about something',
'content' => Str::limit(
Pdf::getText(
Storage::disk('s3')->path('my_file.pdf'),
),
60000,
),
]);
}
}

Хотя использование выражения like для поиска в содержимом файлов возможно, оно может не подойти по производительности для больших или многочисленных документов. Кроме того, использование выражения like ограничивает поиск точными совпадениями. Цель данной статьи — продемонстрировать, что в MySQL имеется встроенная функция Полнотекстовый поиск на естественном языке.

Её можно рассматривать как нечто среднее между выражением like и специализированным внешним инструментом, например Elasticsearch. MySQL предоставляет такую возможность уже давно, но только с начала 2022 года Laravel позволяет использовать её в Eloquent. Это позволяет осуществлять более сложный поиск с использованием относительно простого синтаксиса. Например:

Для его настройки необходимо создать специальный индекс на нужных столбцах с помощью миграции:

class CreateFullTextIndex extends Migration
{
public function up()
{
Schema::table('pdf_documents', function (Blueprint $table) {
$table->fullText(['title', 'content'])->language('english');
});
}
}

Затем настройте его в модели. Поскольку в нем используется синтаксис атрибутов, вам потребуется как минимум PHP 8.

use Laravel\Scout\Searchable;
use Laravel\Scout\Attributes\SearchUsingFullText;

class PdfDocument extends Model
{
use Searchable;

#[SearchUsingFullText(["title", "content"])]
public function toSearchableArray()
{
return [
"title" => $this->title,
"content" => $this->content,
];
}
}

Вот и все! После запуска миграции вы можете сразу воспользоваться функцией поиска в вашей модели, используя традиционный синтаксис Laravel Scout. Синхронизация индекса не требует никаких действий с вашей стороны, MySQL выполняет её автоматически, обеспечивая постоянную актуальность индекса.

Вот пример того, как можно заполнить страницу результатов поиска на основе содержимого PDF-файлов:

$results = PdfDocument::search('my search terms')
->where('user_id', $user->id) //дополнительные условия
->paginate(20);

Запрос будет выглядеть примерно так:

select *
from users
where (match (title, content) against ('my search terms' in natural language mode))
and user_id = 29;

MySQL действительно вычисляет оценку для каждого результата и использует её для упорядочивания. Оценка используется только внутри MySQL, но представлена она таким образом:

+----+-------------------------------------+-----------------+
| id | title | score |
+----+-------------------------------------+-----------------+
| 4 | My first document about flowers | 1.5219271183014 |
| 6 | Another document about flowers | 1.3114095926285 |
+----+-------------------------------------+-----------------+
2 rows in set (0.00 sec)

На сегодня это все. Всем удачного поиска!

Дополнительные материалы

Предыдущая Статья

Как использовать защиту типов в TypeScript

Следующая Статья

Как настроить оповещения по электронной почте о входе по SSH на Linux