SQLite FTS5: локальный поиск по папке расшифровок через Python
Как собрать временный полнотекстовый индекс TXT, найти фразу в интервью и вывести контекст через SQLite FTS5. Рабочий пример, проверка русских слов и ограничения.
Подготовьте текстовый архив
Когда интервью накопились в одной папке, полезно искать не только название файла, но и обсуждавшуюся фразу. В этом примере результат содержит короткий отрывок разговора, поэтому Вы можете сразу понять, к какой встрече вернуться. Решение рассчитано на человека, который может запустить небольшой файл Python 3 на своём компьютере.
Получите текст через преобразование аудио в текст и сохраните отдельный TXT для каждой записи. Используйте понятные имена: например, I12-2026-10-01.txt. Сохраняйте таймкоды в самом тексте, если хотите потом быстро открыть нужную реплику в аудио.
Создайте папку transcripts рядом со скриптом. В пример входят только файлы верхнего уровня с расширением .txt. DOCX, PDF и вложенные папки этот код не обрабатывает. Если Вы ведёте расшифровку в JSONL, сначала выделите текстовую копию; не переименовывайте JSONL в TXT в надежде убрать служебные поля.
Запустите поиск на небольшой папке
Сохраните код в search_transcripts.py и выполните python search_transcripts.py из его папки. Он создаёт временный индекс, добавляет тексты и ищет точную последовательность слов «срок поставки». Для другого запроса измените значение query. Кавычки внутри этой строки относятся к синтаксису полнотекстовой фразы.
from pathlib import Path
import sqlite3
folder = Path("transcripts")
files = sorted(folder.glob("*.txt"))
query = '"срок поставки"'
with sqlite3.connect(":memory:") as con:
con.execute("CREATE VIRTUAL TABLE docs USING fts5(filename UNINDEXED, body)")
for path in files:
text = path.read_text(encoding="utf-8-sig")
con.execute("INSERT INTO docs(filename, body) VALUES (?, ?)",
(path.name, text))
sql = """SELECT filename, snippet(docs, 1, '[', ']', ' … ', 24)
FROM docs WHERE docs MATCH ? ORDER BY bm25(docs) LIMIT 20"""
for filename, fragment in con.execute(sql, (query,)):
print(filename)
print(fragment)
print()
Значения текста и запроса передаются в SQL отдельными параметрами. Не собирайте INSERT путём вставки содержимого интервью в строку SQL: кавычки в репликах способны сломать такой запрос. Колонка filename не индексируется — она служит подписью результата.
Проверьте работу на двух учебных файлах. В первом напишите «Обсудили срок поставки оборудования», во втором — «Обсудили бюджет проекта». В выводе должна появиться первая запись с выделенной фразой. Это проверка механики на известном содержимом, а не замер скорости большого архива.
Проверьте, что означает найденное совпадение
Фраза в кавычках ищет последовательность токенов, а не произвольное побуквенное совпадение вместе со всеми знаками пунктуации. Если нужен поиск буквального текста в исходных файлах, используйте ripgrep с контекстом. Здесь задача другая: выдача по словам с короткими фрагментами.
Попробуйте отдельно запросы «поставка» и «поставки» на своих примерах. Не предполагайте, что русские окончания будут автоматически сведены к одному слову. Для исследовательского разбора заранее составьте список нужных словоформ и проверяйте несколько запросов.
Порядок выдачи помогает выбрать, что открыть первым; он не показывает важность встречи или достоверность высказывания. Откройте TXT, прочитайте соседние реплики, затем вернитесь к записи по таймкоду. Короткий фрагмент может не включать отрицание или оговорку, находящуюся в предыдущем предложении.
Разберите пустой результат и ошибки
Если список пустой, проверьте, найдены ли файлы: временно добавьте print(len(files)) после строки с glob. Ноль означает проблему пути, уровня папки или расширения. Если файлы есть, вставьте в query слово, которое точно встречается в одном из них.
Сообщение no such module: fts5 означает, что используемая сборка SQLite не предоставляет этот модуль. Узнайте версию через sqlite3.sqlite_version и выберите сборку Python/SQLite с поддержкой FTS5. Не пытайтесь исправить это заменой слов запроса.
Ошибка декодирования требует проверки настоящей кодировки TXT. Не включайте игнорирование ошибочных символов: тогда индекс может получить искажённые слова. Временная база в памяти исчезает после работы программы, и следующий запуск заново читает папку. Это удобно для первого испытания; для постоянного большого архива отдельно продумайте обновление индекса и сохранение версий.
Учебная проверка на двух интервью
Файлы и реплики ниже вымышлены. Сначала подтвердите ожидаемый результат на таком простом наборе.
| Файл | Текст | Ожидание |
|---|---|---|
| I12.txt | Обсудили срок поставки оборудования | Найдётся точная фраза |
| I13.txt | Обсудили бюджет проекта | Не найдётся эта фраза |
| I14.txt | Поставки обсуждали отдельно | Проверить другой запрос |
После учебной проверки добавьте несколько собственных текстов и найдите известную цитату. Полезность поиска оценивайте по тому, удаётся ли вернуться к нужной записи.
Карточка испытания поиска
Папка TXT: [путь] Найдено файлов: [количество] Кодировка: [UTF-8] Запрос FTS5: [строка] Контрольный файл: [имя] Ожидаемая цитата: [текст] Результат: [совпало / не совпало] Проверенный таймкод: [время]
Частые вопросы
Файлы отправляются на сервер?
Этот пример читает местную папку и строит индекс в памяти процесса. Сетевых обращений в коде нет.
Можно положить в папку DOCX?
Нет. Пример читает TXT. Подготовьте текстовую копию документа и сохраните её в проверенной кодировке.
Почему «поставка» не находит «поставки»?
Не рассчитывайте на автоматическую обработку русских словоформ. Проверяйте варианты запроса на известном тексте.
Почему после запуска нет файла базы?
В примере используется база :memory:. Это временный индекс, который создаётся заново при каждом запуске.