Доброе время суток.
Хочу поделиться своим опытом написания скрипта на PHP для поиска по файлам форматов PDF, DOC и DOCX, используя Sphinx. Все ниже изложенное написано для людей, которые уже имели опыт работы с связкой Sphinx и PHP. Если нет, то в свое время мне очень помогла статья Мартина Стрейчера «Создание собственной поисковой системы с помощью PHP», опубликованная на сайте IBM.
Совсем недавно на одном из проектов, который является веб-интерфейсом для базы пользователей, я получил задание организовать поиск по документам. Проект был написан до меня и уже использовал Sphinx.
Задача вроде тривиальная, но погуглив немного, я не смог найти внятную инструкцию с конкретным примером поиска по файлам форматов PDF, DOC и DOCX, что и стало причиной написания этой статьи.
Sphinx может обрабатывать текстовые файлы только формата XML. Для обработки данных в этом формате используется драйвер xmlpipe или xmlpipe2. То есть, для индексации информации в файлах формата PDF, DOC и DOCX, ее нужно отдать sphinx-у в формате XML.
Для этого надо написать скрипт, который будет ходить файлам, считывать данные и отдавать их в нужном формате.
Ниже приведу пример конфигурации и кода, который поможет нам с индексацией.
В конфигурационном файле sphinx.conf указываем следующее:
Содержимое файла xml-parser.php, который генерирует XML для Sphinx-a:
Приведенный выше код является модифицированной версией скрипта с официального сайта Sphinx-a.
Для считывания текста из PDF файла используется утилита pdftotext. Для DOC файлов — catdoc. Для считывания текста из DOCX файлов используется Zend. Про Phar можно прочесть тут. Официальная документация по Sphinx, а так же пример правильного XML для индексера.
Сверху представлен скрипт, который считывает данные из файлов и отдает их в XML формате для индексации.
Теперь при поиске Sphinx будет выдавать id папки в котором найдено совпадение. Можно еще хранить имя файла. rednaxi в своем материале «Создание ознакомительного поискового движка на Sphinx + php» более подробно описал процесс работы с Sphinx и PHP.
Хочу поделиться своим опытом написания скрипта на PHP для поиска по файлам форматов PDF, DOC и DOCX, используя Sphinx. Все ниже изложенное написано для людей, которые уже имели опыт работы с связкой Sphinx и PHP. Если нет, то в свое время мне очень помогла статья Мартина Стрейчера «Создание собственной поисковой системы с помощью PHP», опубликованная на сайте IBM.
Совсем недавно на одном из проектов, который является веб-интерфейсом для базы пользователей, я получил задание организовать поиск по документам. Проект был написан до меня и уже использовал Sphinx.
Задача вроде тривиальная, но погуглив немного, я не смог найти внятную инструкцию с конкретным примером поиска по файлам форматов PDF, DOC и DOCX, что и стало причиной написания этой статьи.
Sphinx может обрабатывать текстовые файлы только формата XML. Для обработки данных в этом формате используется драйвер xmlpipe или xmlpipe2. То есть, для индексации информации в файлах формата PDF, DOC и DOCX, ее нужно отдать sphinx-у в формате XML.
Для этого надо написать скрипт, который будет ходить файлам, считывать данные и отдавать их в нужном формате.
Ниже приведу пример конфигурации и кода, который поможет нам с индексацией.
В конфигурационном файле sphinx.conf указываем следующее:
# Определяем источник данных
source SourceParseXml
{
# Используем xmlpipe2, как источник данных
type = xmlpipe2
# Команда, которые должна быть выполнены, чтобы получить XML. Файл xml-parser.php генерирует XML
xmlpipe_command = /path/to/php /path/to/xml-parser.php
}
# Определяем индекс
index IndexParseXml
{
# Указываем, какой источник использовать
source = SourceParseXml
# Указываем путь где хранить индекс
path = /path/where/to/store/index-data
# Минимальная длина слова для индексации
min_word_len = 1
# Указываем кодировку
charset_type = utf-8
}
# Определяем индексер
indexer
{
# Максимальный объем используемой памяти для индексации
mem_limit = 32M
}
Содержимое файла xml-parser.php, который генерирует XML для Sphinx-a:
$allowedMimes = array(
'application/pdf',
'application/zip', // docx
'application/vnd.ms-office', // doc созданный с помощью OpenOffice
'application/msword'
);
$allowedExtentions = array('pdf', 'docx', 'doc');
$pdfInfoPath = '/usr/bin/pdfinfo'; // полный путь к pdfinfo
$pdfToTextPath = '/usr/bin/pdftotext'; // полный путь к pdftotext
$catDocPath = '/usr/bin/catdoc'; // полный путь к catdoc
// загружаем библиотеку Zеnd-а для считывания файлов формата docx
define('FILE_PATH', realpath(dirname(__FILE__)) . DIRECTORY_SEPARATOR);
if (strpos(get_include_path(), 'Zend.phar.gz') === false) {
ini_set('include_path', ini_get('include_path') . ':phar\://' . FILE_PATH . 'Zend.phar.gz');
}
$phar = new Phar(FILE_PATH . 'Zend.phar.gz', 0, 'Zend.phar.gz');
if (isset($phar['Zend/Search/Lucene/Document/Docx.php'])) {
require_once($phar['Zend/Search/Lucene/Document/Docx.php']);
} else {
echo 'ERROR: can\'t load "Zend/Search/Lucene/Document/Docx.php" !' . PHP_EOL;
die;
}
// Начинаем формирование XML
$xmlWriter = new xmlWriter();
$xmlWriter->openMemory();
$xmlWriter->setIndent(true);
$xmlWriter->startDocument('1.0', 'UTF-8');
$xmlWriter->startElement('sphinx:docset');
$xmlWriter->startElement('sphinx:schema');
$xmlWriter->startElement('sphinx:field');
$xmlWriter->writeAttribute('name', 'content');
$xmlWriter->endElement(); // field
$xmlWriter->endElement(); // schema
/*
Предположим что файлы лежат в папке files,
в которой находятся другие папки с названиями,
которые соответсвуют id пользователя.
Например: files/01/file.pdf
*/
// Запускаем цикл по папке files
foreach (new DirectoryIterator(dirname(__FILE__) . '/files') as $folder) {
// Проверяем, является ли выбраные обьект папкой
if (!$folder->isDir() || $folder->isDot()) {
continue;
}
// Запускаем цикл по папке в которой теоретически должны находится файлы
foreach (new DirectoryIterator($folder->getPathname()) as $file) {
// Проверяем выбранный объект является ли файлом разрешенного типа
if ($file->isDir() || !in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), $allowedExtentions)
|| !in_array(mime_content_type($file->getPathname()), $allowedMimes)) {
continue;
}
$text = '';
$filePath = $file->getPathname();
$filePathEscape = escapeshellarg($filePath);
try {
switch (mime_content_type($filePath)) {
case 'application/pdf': {
$pdfInfo = array();
$key = '';
$val = '';
// Считываем информацию о выбранном PDF файле
foreach (explode("\n", shell_exec(escapeshellcmd($pdfInfoPath . ' ' . $filePathEscape))) as $str) {
list($key, $val) = count(explode(':', $str)) == 2 ? explode(':', $str) : array('', '');
if (trim($key) && trim($val)) {
$pdfInfo[trim($key)] = trim($val);
}
}
// Проверяем на ошибки
if (empty($pdfInfo) || (isset($pdfInfo['Error']) && $pdfInfo['Error'])) {
continue;
}
// С помощью pdftotext считываем содержимое файла
$text = shell_exec(escapeshellcmd($pdfToTextPath . ' -nopgbrk ' . $filePathEscape . ' -'));
break;
}
case 'application/zip' : {
$file = Zend_Search_Lucene_Document_Docx::loadDocxFile($filePath);
// С помощью Zend_Search_Lucene_Document_Docx считываем содержимое файла
$text = $file->getFieldValue('body');
break;
}
case ('application/vnd.ms-office' || 'application/msword'): {
// С помощью catdoc считываем содержимое файла
$text = shell_exec(escapeshellcmd($catDocPath . ' ' . $filePathEscape));
break;
}
}
if (empty($text)) {
continue;
}
$text = strip_tags($text);
$givenEncode = mb_detect_encoding($text);
// Текст должен быть в кодировке UTF-8
$text = $givenEncode ? iconv($givenEncode, 'UTF-8', $text) : mb_convert_encoding($text, 'UTF-8');
} catch (Exception $e) {
echo $e->getMessage() . PHP_EOL;
continue;
}
$xmlWriter->startElement('sphinx:document');
// $folder->getBasename() - вернет название папки в которой хранятся наши файлы.
// Название папки является идентификатором пользователя
$xmlWriter->writeAttribute('id', $folder->getBasename());
$xmlWriter->startElement('content');
$xmlWriter->writeCData($text);
$xmlWriter->endElement(); // content
$xmlWriter->endElement(); // field
}
}
$xmlWriter->endElement();
$xml = $xmlWriter->outputMemory();
$tidy = tidy_repair_string($xml, array(
'output-xml' => true,
'input-xml' => true
), 'utf8');
echo $tidy;
Приведенный выше код является модифицированной версией скрипта с официального сайта Sphinx-a.
Для считывания текста из PDF файла используется утилита pdftotext. Для DOC файлов — catdoc. Для считывания текста из DOCX файлов используется Zend. Про Phar можно прочесть тут. Официальная документация по Sphinx, а так же пример правильного XML для индексера.
Сверху представлен скрипт, который считывает данные из файлов и отдает их в XML формате для индексации.
Теперь при поиске Sphinx будет выдавать id папки в котором найдено совпадение. Можно еще хранить имя файла. rednaxi в своем материале «Создание ознакомительного поискового движка на Sphinx + php» более подробно описал процесс работы с Sphinx и PHP.