#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. Я рассмотрю создание базы данных, спасибо за комментарий.
Ответ №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, которая начнет импортировать новые файлы в базу данных или файл.