Быстрый поиск текста в более чем 600 000 файлах


У меня есть сервер php, linux. В нем есть папка под названием notes_docs, которая содержит более 600 000 текстовых файлов. Структура папок notes_docs выглядит следующим образом -

 - notes_docs
   - files_txt
     - 20170831
           - 1_837837472_abc_file.txt
           - 1_579374743_abc2_file.txt
           - 1_291838733_uridjdh.txt
           - 1_482737439_a8weele.txt
           - 1_733839474_dejsde.txt
     - 20170830
     - 20170829

Я должен предоставить утилиту быстрого текстового поиска, которая может отображать результаты в браузере. Поэтому, если мой пользователь ищет "нью-Йорк", все файлы, в которых есть "нью-Йорк", должны быть возвращены в массиве. Если пользователь ищет "foo", все файлы с "foo" в них должны быть возвращены.

Я уже пробовал код, используя scandir, и Directory Iterator, что слишком медленно. На поиск уходит больше минуты, даже тогда поиск не был завершен. Я попробовал ubuntu find, который снова был медленным, и на его завершение ушло больше минуты. потому что слишком много итераций папок, и notes_docs текущий размер превышает 20 ГБ.

Приветствуется любое решение, которое я могу использовать, чтобы сделать его быстрее. Я могу внести изменения в дизайн, интегрировать свой PHP-код, чтобы свернуться с кодом на другом языке. Я также могу вносить изменения в инфраструктуру в крайних случаях (как при использовании в память-то).

Я хочу знать, как люди в промышленности делают это? Люди в Indeed, Zip Recruiter, все предоставляют поиск файлов.

Пожалуйста, обратите внимание, что у меня 2 ГБ - 4 ГБ оперативной памяти, поэтому загрузка всех файлов в оперативную память все время неприемлема.

РЕДАКТИРОВАТЬ - Все приведенные ниже входные данные великолепны. Для тех, кто придет позже, мы закончили тем, что использовали Lucene для индексирования и текстового поиска. Это сработало действительно хорошо

Author: Nalin Agrawal, 2017-09-07

5 answers

Чтобы все было просто: нет быстрого способа открывать, искать и закрывать 600 тысяч документов каждый раз, когда вы хотите выполнить поиск. Ваши тесты с "более чем минутой", вероятно, относятся к отдельным тестовым учетным записям. Если вы планируете искать их через многопользовательский веб-сайт, вы можете быстро забыть об этом, потому что ваш disk IO будет зашкаливать и заблокирует весь ваш сервер.

Таким образом, ваш единственный вариант - проиндексировать все файлы. Так же, как и любая другая утилита быстрого поиска. Независимо от того, используете ли вы Solr или ElasticSearch, как указано в комментариях, или создайте что-то свое. Файлы будут проиндексированы.

Учитывая, что файлы txt являются текстовыми версиями файлов pdf, которые вы получаете, я готов поспорить, что самое простое решение - записать текст в базу данных вместо файла. В любом случае это не займет намного больше места на диске.

Затем вы можете включить full text search в своей базе данных (mysql, mssql и другие поддерживают это), и я уверен, что время отклика будет намного лучше. Имейте в виду, что создание этих indexes действительно требуется место для хранения, но то же самое относится и к другим решениям.

Теперь, если вы действительно хотите ускорить процесс, вы можете попробовать проанализировать резюме на более детальном уровне. Попробуйте получить информацию о местоположении, образовании, разговорных языках и другую информацию, которую вы регулярно ищете, и поместите их в отдельные таблицы/столбцы. Это очень сложная задача и почти самостоятельный проект, но если вы хотите получить ценный результат поиска, это правильный путь. Потому что поиск в тексте без контекст дает совсем другие результаты, просто подумайте о своем примере "нью-Йорк":

  1. Я живу в Нью-Йорке
  2. Я учился в Нью-Йоркском университете
  3. Мне нравится песня "Нью-Йорк" от Алисии Киз в личной биографии
  4. Я работал в "Нью-Йорк Пицца"
  5. Я родился в Нью-Йоркшире, Великобритания
  6. Я провел лето, разводя нью-йоркширских терьеров.
 23
Author: Hugo Delsing, 2017-09-11 13:27:38

Я не буду углубляться слишком глубоко, но попытаюсь дать рекомендации по созданию доказательства концепции.

1

Сначала загрузите и извлеките эластичный поиск отсюда: https://www.elastic.co/downloads/elasticsearch а затем запустите его:

bin/elasticsearch

2

Скачать https://github.com/dadoonet/fscrawler#download-fscrawler извлеките его и запустите:

bin/fscrawler myCustomJob

Затем остановите его (Ctrl-C) и отредактируйте соответствующий myCustomJob/_settings.json (Он имеет был создан автоматически, и путь был напечатан на консоли).
Вы можете отредактировать свойства: "url" (путь для сканирования), "update_rate" (вы можете сделать это 1m), "includes" (например, ["*.pdf","*.doc","*.txt"]), "index_content" ( сделайте его ложным, чтобы оставаться только на имени файла).

Запустите снова:

bin/fscrawler myCustomJob

Примечание: Индексирование - это то, что вы, возможно, позже захотите выполнить с помощью кода, но сейчас это будет сделано автоматически с помощью fscrawler, который напрямую взаимодействует с elastic.

3

Теперь начните добавлять файлы в каталог, указанный вами в свойстве "url".

4

Загрузите расширенный клиент rest для chrome и выполните следующие действия POST:

URL-АДРЕС: http://localhost:9200/_search

Исходная полезная нагрузка:

{
  "query": { "wildcard": {"file.filename":"aFileNameToSearchFor*"} }
}

Вы получите обратно список совпадающих файлов. Примечание: fscrawler индексирует имена файлов под ключом: file.filename.

5

Теперь вместо использования расширенного клиента rest вы можете использовать PHP для выполнения этот запрос. Либо с помощью вызова REST по указанному выше URL-адресу, либо с помощью API php-клиента: https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_search_operations.html

То же самое означает индексирование: https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/_indexing_documents.html

 12
Author: Marinos An, 2017-09-11 17:04:41

Если вы хотите сохранить всю информацию о файлах в базе данных:

<?php 
function deep_scandir( $dir, &$query, &$files) {

    $count = 0;

    if(is_dir($dir)) {
        if ($dh = opendir($dir)) {
            while (($item = readdir($dh)) !== false) {
                if($item != '.' && $item != '..') {
                    if(is_dir($dir.'/'.$item)){
                        deep_scandir($dir.'/'.$item, $query, $files);
                    }else{
                        $count++;
                        preg_match("/(\d\_\d+)\_(.*)\.txt/i", $item, $matches);
                        if(!empty($matches)){
                            $no = $matches[1];
                            $str = $matches[2];
                            $files[$dir][$no] = $str;
                            $content = addcslashes( htmlspecialchars( file_get_contents($dir.'/'.$item) ), "\\\'\"" );
                            $query[] =  "INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES\n(NULL, '$no', '$str', '$dir/$item', '$content');";
                        }
                    }
                }
            }
            closedir($dh);
        }
    }
}

echo '<pre>';
$dir = 'notes_docs/files_txt';
$query = [];
$files = [];
deep_scandir($dir, $query, $files);
print_r($files);
echo '<br>';
print_r($query);

Теперь вы можете выполнить каждую строку в массиве

foreach($query as $no=>$line){
    mysql_query($line) or trigger_error("Couldn't execute query no: '$no' [$line]");
}

Вывод:

Array
(
    [notes_docs/files_txt/20170831] => Array
        (
            [1_291838733] => uridjdh
            [1_482737439] => a8weele
            [1_579374743] => abc2_file
            [1_733839474] => dejsde
            [1_837837472] => abc_file
        )

)

Array
(
    [0] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_291838733', 'uridjdh', 'notes_docs/files_txt/20170831/1_291838733_uridjdh.txt', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus in nisl quis lectus sagittis ullamcorper at faucibus urna. Suspendisse tristique arcu sit amet ligula cursus pretium vitae eu elit. Nullam sed dolor ornare ex lobortis posuere. Quisque venenatis laoreet diam, in imperdiet arcu fermentum eu. Aenean molestie ligula id sem ultricies aliquet non a velit. Proin suscipit interdum vulputate. Nullam finibus gravida est, et fermentum est cursus eu. Integer sed metus ac urna molestie finibus. Aenean hendrerit ante quis diam ultrices pellentesque. Duis luctus turpis id ipsum dictum accumsan. Curabitur ornare nisi ligula, non pretium nulla venenatis sed. Aenean pharetra odio nec mi aliquam molestie. Fusce a condimentum nisl. Quisque mattis, nulla suscipit condimentum finibus, leo ex eleifend felis, vel efficitur eros turpis nec sem. ');
    [1] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_482737439', 'a8weele', 'notes_docs/files_txt/20170831/1_482737439_a8weele.txt', 'Nunc et odio sed odio rhoncus venenatis congue non nulla. Aliquam dictum, felis ac aliquam luctus, purus mi dignissim magna, vitae pharetra risus elit ac mi. Sed sodales dui semper commodo iaculis. Nunc vitae neque ut arcu gravida commodo. Fusce feugiat velit et felis pharetra posuere sit amet sit amet neque. Phasellus iaculis turpis odio, non consequat nunc consectetur a. Praesent ornare nisi non accumsan bibendum. Nunc vel ultricies enim, consectetur fermentum nisl. Sed eu augue ac massa efficitur ullamcorper. Ut hendrerit nisi arcu, a sagittis velit viverra ac. Quisque cursus nunc ac tincidunt sollicitudin. Cras eu rhoncus ante, ac varius velit. Mauris nibh lorem, viverra in porttitor at, interdum vel elit. Aliquam imperdiet lacus eu mi tincidunt volutpat. Vestibulum ut dolor risus. ');
    [2] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_579374743', 'abc2_file', 'notes_docs/files_txt/20170831/1_579374743_abc2_file.txt', 'Vivamus aliquet id elit vitae blandit. Proin laoreet ipsum sed tincidunt commodo. Fusce faucibus quam quam, in ornare ex fermentum et. Suspendisse dignissim, tortor at fringilla tempus, nibh lacus pretium metus, vel tempus dolor tellus ac orci. Vestibulum in congue dolor, nec porta elit. Donec pellentesque, neque sed commodo blandit, augue sapien dapibus arcu, sit amet hendrerit felis libero id ante. Praesent vitae elit at eros faucibus volutpat. Integer rutrum augue laoreet ex porta, ut faucibus elit accumsan. Donec in neque sagittis, auctor diam ac, viverra diam. Phasellus vel quam dolor. Nullam nisi tellus, faucibus a finibus et, blandit ac nisl. Vestibulum interdum malesuada sem, nec semper mi placerat quis. Nullam non bibendum sem, vitae elementum metus. Donec non ipsum quis turpis semper lobortis.');
    [3] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_733839474', 'dejsde', 'notes_docs/files_txt/20170831/1_733839474_dejsde.txt', 'Nunc faucibus, enim non luctus rutrum, lorem urna finibus turpis, sit amet dictum turpis ipsum pharetra ex. Donec at leo vitae massa consectetur viverra eget vel diam. Sed in neque tempor, vulputate quam sed, ullamcorper nisl. Fusce mollis libero in metus tincidunt interdum. Cras tempus porttitor nunc nec dapibus. Vestibulum condimentum, nisl eget venenatis tincidunt, nunc sem placerat dui, quis luctus nisl erat sed orci. Maecenas maximus finibus magna in facilisis. Maecenas maximus turpis eget dignissim fermentum. ');
    [4] => INSERT INTO `mytable` (id, key, value, path, content)
                            VALUES
(NULL, '1_837837472', 'abc_file', 'notes_docs/files_txt/20170831/1_837837472_abc_file.txt', 'Integer non ex condimentum, aliquet lectus id, accumsan nibh. Quisque aliquet, ante vitae convallis ullamcorper, velit diam tempus diam, et accumsan metus eros at tellus. Sed lacinia mauris sem, scelerisque efficitur mauris aliquam a. Nullam non auctor leo. In mattis mauris eu blandit varius. Phasellus interdum mi nec enim imperdiet tristique. In nec porttitor erat, tempor malesuada ante. Etiam scelerisque ligula et ex maximus, placerat consequat nunc consectetur. Phasellus suscipit ligula sed elit hendrerit laoreet. Suspendisse ex sem, placerat pharetra ligula eu, accumsan rhoncus ex. Sed luctus nisi vitae metus maximus scelerisque. Suspendisse porta nibh eget placerat tempus. Nunc efficitur gravida sagittis. ');
)
 5
Author: Hisham Dalal, 2017-09-16 14:54:19

Я бы попробовал сначала создать простую базу данных. Основная таблица будет содержать имя файла и текстовое содержимое. Таким образом, вы сможете использовать функции запросов ядра БД и, вероятно, значительно повысите производительность по сравнению с текущим решением.

Это было бы самым простым решением, и оно могло бы вырасти за счет добавления дополнительных таблиц, связывающих слова с именами файлов (это может быть сделано динамически, например, при наиболее частых поисках)

 3
Author: tru7, 2017-09-16 09:49:42

Файловая система или база данных?
Для работы с большим объемом данных вам необходимо использовать какую-то базу данных. Как это делает Google!

Так следует ли мне использовать реляционные базы данных, такие как Mysql?
Во многих случаях (как в вашем случае) реляционные базы данных и SQL не являются правильным выбором. Сегодня большинство поисковых систем и анализаторов больших данных используют Базы данных NOSQL.
Базы данных NOSQL намного быстрее, чем базы данных SQL. Но они обычно используют много аппаратных ресурсов (ОЗУ, ...).

Ваше решение

  1. Найдите информацию о базах данных nosql и проверьте их различия. Также вы должны выполнить полнотекстовый поиск в своей базе данных, поэтому ищите информацию о реализации полнотекстового поиска в каждом из них. Выберите базу данных NOSQL: Aerospike (Самая быстрая) - Кассандра - Монгодб - ...
  2. Импортируйте все данные ваших файлов в вашу базу данных.
  3. Выберите правильный и быстрый язык программирования (я рекомендую не использовать PHP).
  4. Разработайте и создайте свою поисковую систему как сервис.
  5. Подключите вашу основную программу к вашей поисковой системе.
 -2
Author: ShayanKM, 2017-09-17 09:35:51