как искать строку во множестве файлов с помощью PHP

#php #performance #search

#php #Производительность #Поиск

Вопрос:

Во-первых, я новичок в PHP, поэтому я не имею ни малейшего представления о том, как это сделать. У меня есть папка, в которую постоянно создаются txt файлы разного размера и текста. Я пытаюсь создать что-то вроде «поисковой системы» в системе Linux, написанной на PHP. Пока я использую приведенный ниже код.

 if ( $_SERVER['REQUEST_METHOD'] == 'POST'){
    $path = '/example/files';
    $findThisString = $_POST['text_box'];
    $dir = dir($path);
    while (false !== ($file = $dir->read())){   
        if ($file != '.' amp;amp; $file != '..'){
            if (is_file($path . '/' . $file)){
                $data = file_get_contents($path . '/' . $file);
                if (stripos($data, $findThisString) !== false){
                    echo '<p></p><font style="color:white; font-family:Arial">Found     Match - <a href="http://test.example.com/files/'. $file .'">'. $file .'</a>    <br>';
                }
            }
        }
    }
}
$dir->close();
  

Теперь этот код отлично работает! Но есть одна проблема: когда в папке находится около 40 000 файлов, поиск занимает много времени, чтобы получить какие-либо результаты. Теперь я не могу использовать какие-либо команды, такие как greb . Он должен быть написан на чистом PHP, как приведенный выше код.

Есть ли способ оптимизировать приведенный выше код, чтобы работать быстрее? Или есть лучшая функция поиска, которую я могу использовать в PHP?

Комментарии:

1. Я вижу одну небольшую настройку — достаточно использовать is_file, вам не нужно также проверять ‘.’ и ‘..’, так как они завершатся ошибкой is_file().

2. Я думаю, что Lucene, Solr или Elastic Search справятся с этой задачей лучше, и все они имеют интерфейсы PHP.

3. Единственное возможное решение — разделить это на две части. Один скрипт, который считывает существующие и новые файлы и создает индекс слов, который хранится в базе данных. Этот скрипт может быть написан на PHP, просто его следует запускать не с веб-сервера, а из командной строки. Вторым сценарием будет ваш поиск, который просто запрашивает базу данных. В любом случае, ваш вопрос слишком широк для этого сайта, поэтому я голосую за его закрытие.

4. Я рассмотрю создание базы данных, спасибо за комментарий.

5. mullie.eu/mysql-as-a-search-engine

Ответ №1:

Существует много причин, по которым скрипт работает так медленно, и именно то, что вам нужно сделать, чтобы сократить время, которое требуется, полностью зависит от того, какие именно части кода вызывают замедление.
Это означает, что вам нужно пропустить код через профилировщик, а затем настроить те части кода, которые, как сообщается, являются причиной. Без профилировщика все, что мы можем делать, это догадываться. Не обязательно правильно.

Как отмечалось в комментариях к вашему вопросу, использование уже созданной поисковой системы было бы гораздо лучшим решением. Особенно то, что предназначено для чего-то подобного, поскольку это значительно сократит время.
Даже встроенная grep команда для оболочек Linux была бы улучшением.

Тем не менее, я подозреваю, что причина, по которой ваш код такой медленный, заключается в том, что вы читаете и просматриваете содержимое всех файлов в PHP. stripos() здесь особенно вероятен подозреваемый, поскольку это довольно медленный поиск.
Другим фактором могут быть read() вызовы в цикле, поскольку я считаю, что они выполняют операцию ввода-вывода при каждом вызове. Кроме того, наличие большого количества вызовов echo в скрипте может / также приведет к замедлению, в зависимости от того, сколько из них у вас есть. Пара сотен на самом деле не заметна, но наличие нескольких тысяч будет.

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

 <?php

if (isset ($_POST['text_box'])) {
    $path = '/example/files';
    $result = search_files ($_POST['text_box'], $path);
}

/**
 * Searches through the files in the given path, for the search term.
 *
 * @param string $term The term to search for, only "word characters" as defined by RegExp allowed.
 * @param string $path The path which contains the files to be searched.
 * 
 * @return string Either a list of links to the files, or an error message.
 */
function search_files ($term, $path) {
    // Ensuring that we have a closing slash at the end of the path, so that
    // we can add a file-descriptor for glob() to use.
    if (substr ($path, -1) != '/') {
        $path .= '/';
    }

    // If we don't have a valid/readable path we ened to throw an error now.
    // This only happens if the code itself is wrong, as it's not user-supplied,
    // thus an exception is thrown.
    if (!is_dir ($path) || !is_readable ($path)) {
        throw new InvalidArgumentException ("Not a valid search path!");
    }

    // This should be validated to ensure you get sane input,
    // in order to avoid erroneous responses to the user and
    // possible attacks.
    // Addded a simple test to ensure we only accept "word characters".
    if (!preg_match ('/^w \z/', $term)) {
        // Invalid input. Show warning to user.
        return 'Not a valid search string.';
    }

    // Using glob so that we retrieve a list of all files in one operation.
    $contents = glob ($path.'*');

    // Using a holding variable, as this many echo statements take
    // noticable longer time than just concatenating strings and
    // echoing it out once.
    $output = '';

    // Using printf() templates to make the code easier to reach.
    // Ideally the HTML-code shouldn't be in this string either, but adding
    // a templating system is far beyond the reach of this Qamp;A.
    $outTemplate = '<p class="found">Found Match - <a href="http://test.example.com/files/%1$s">%2$s</a></p>';

    foreach ($contents as $file) {
        // Skip the hardlinks for parent and current folder.
        if ($file == '.' || $file == '..') {
            continue;
        }

        // Skip if the path isn't a file.
        if (!is_file ($path . '/' . $file)) {
            continue;
        }

        // This one is the big issue. Reading all of the files one by one will take time!
        $data = file_get_contents ($path . '/' . $file);

        // Same with running a case-insensitive search!
        if (stripos ($data, $term) !== false) {
            // Added output escaping to prevent issues with possible meta-characters.
            // (A problem also known as XSS attacks)
            $output .= sprintf ($outTemplate, htmlspecialchars (rawurlencode($file)), htmlspecialchars($file));
        }
    }

    // Lastly, if the output string is empty we haven't found anything.
    if (empty($output)) {
        return "Term not found";
    }

    return $output;
}
  

Комментарии:

1. Только что попробовал использовать улучшенный код, при отправке сообщения результат ничего не возвращает.

2. Хотя я не тестировал код, я почти уверен, что нет пути, который привел бы к возвращению пустой строки функцией. Вы пробовали a var_dump() on $result ? Также проверьте настройки отчетов об ошибках на вашем сервере разработки или журналы на наличие ошибок, которые, возможно, не были показаны клиенту.

Ответ №2:

если вы не можете использовать команду Linux, когда у вас есть два способа: 1) Сохранить файлы в базе данных и после этого, когда вам нужно найти, вызвать запрос из базы данных для поиска файлов. 2) Создается один индексированный файл (файлы, которые будут сохранены в файлах списка him)

способы 1 и 2 помогают сэкономить время на выполнение скрипта. Для файлов обновления вы можете написать задачу Cron, которая начнет импортировать новые файлы в базу данных или файл.