Архивы для категории ‘Эксперименты’.

Тест. Кто быстрее: preg_replace() или str_replace()?

Недавно к моему посту “Парсинг выдачи Google. Практика.” [YS.PRO] оставил коммент следующего содержания:

$Result=preg_replace(”/[\n\r\t]/”, ”, $Result);

Ох не нравится мне это, зачем комп мучать?
$result = str_replace(array(”\n”, “\r”, “\t”), ”, $result);

Конечно же сомневаться в том, что на данной операции str_replace() быстрее мне не приходится, но мне все-таки захотелось протестировать разницу в скорости при использовании этих двух функций.

Начальные условия:

  • PHP 5
  • Apache 2
  • Параллельно запущено 4 громадных скрипта, которые уже проработали 3е суток, так что определенную погрешность из-за них можно ожидать

Тестирование производилось при помощи следующего кода.
Код №1

<?php 
require_once 'timer.class.php';
 
$text="Почему я делаю это?\r\nНе знаю, но это весело!\r\nВедь надо же стать легендой )";
echo $text;
 
$timer=new Timer();
$timer->start();
$Result=preg_replace("/[nrt]/",'', $text);
$point=$timer->stop();
echo $point;
 
if($fh=fopen('preg.txt','a')) {
	fwrite($fh,$point."\r\n");
	fclose($fh);
} else {
	echo 'Вечная ошибка с файловой системой!';
}
 
?>

Код №2

<?php 
require_once 'timer.class.php';
 
$text="Почему я делаю это?\r\nНе знаю, но это весело!\r\nВедь надо же стать легендой )";
echo $text;
 
$timer=new Timer();
$timer->start();
$result = str_replace(array("n", "r", "t"), '', $text);
$point=$timer->stop();
echo $point;
 
if($fh=fopen('str.txt','a')) {
	fwrite($fh,$point."\r\n");
	fclose($fh);
} else {
	echo 'Вечная ошибка с файловой системой!';
}
 
?>

Класс Timer имеет следующий вид:

<?php 
 
 class Timer
 {
 	var $timers		= array();
 	var $precision	= '';
 
 	function Timer($prec=6)
 	{
 		$this->precision=$prec;
 	}
 
 	function get_precision()
 	{
 		return $this->precision;
 	}
 
 	function set_precision($new_precision)
 	{
 		$this->precision=$new_precision;
 	}
 
 	function _getmicrotime() 
 	{
 		list($msec,$sec)=explode(" ",microtime());
 		return ($sec.substr($msec,1));
 	}
 
 	function start($timer='def')
 	{
 		$this->timers[$timer]=$this->_getmicrotime();
 		return true;
 	}
 
 	function stop($timer='def')
 	{
 		if(!array_key_exists($timer,$this->timers)) return false;
 		return round($this->_getmicrotime()-$this->timers[$timer],$this->precision);
 	}
 } 
?>

В результате тестирования были получены следующие результаты для preg_replace():
6.6E-5
0.212309
0.212233
-1.0E-6
-1.0E-6
-1.0E-6
0.000571
0.000126
0.000132
0.000443
И как результат среднее время: 0.0425877 секунд.

В свою очередь для str_replace() были получены такие результаты:
4.0E-5
4.2E-5
-1.0E-6
-2.0E-6
-1.0E-6
0.000291
0.212443
0.000484
0.000212
-1.0E-6
И как результат среднее время: 0.0213507 секунд.

Среднее время измерялось при помощи следующего скрипта:

 
$nums1=@file('str.txt');
$nums2=@file('preg.txt');
$sum1=0;
$sum2=0;
foreach ($nums1 as $num) {
	$sum1+=$num;
}
foreach ($nums2 as $num) {
	$sum2+=$num;
}
 
echo 'str:',round($sum1/10,12)."\r\n";
echo 'preg:',round($sum2/10,12)."\r\n";
?>

Выводы:
1. Для операции операции по скипу вайтспейсес лучше использовать str_replace, так как он в 2 раза быстрее аналогичного синтаксиса с preg_replace.
2. Так как операция по скипу вайтспейсов чаще всего проводиться 1 раз на цикл (или лучше сказать, один раз на этап обработки конечной страницы), то временем задержки в 0,02 секунды можно пренебречь как незначительным.

ЗЫ. Данные тестирования я считаю важными, так как они помогают максимально оптимизировать используемые механизмы для достижения наивысшим результатов. Поэтому в дальнейшем я постараюсь их проводить как можно чаще!

Лучшая проверка - в бою.

На днях закончил проверку новой системы GrabVIA для сбора изображений, аудио- и видеоконтента.

В течении двух суток данной системой было собрано 194 766 изображение суммарным размером около 5 ГБ.

Так что я думаю, что система вполне работоспособная и готова к выполнению даже самых сложных заданий.

Новая услуга. Сбор изображений, аудио- и видеофайлов.

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

Спецификой работы по сбору подобного контента является то, что БД имеет второстепенное значение, в то время как выдвигаются дополнительные требования к дисковой системе и скорости интернет-канала. Все эти особенности учтены в разработанной мной системе GrabVIA, предназначенной ИСКЛЮЧИТЕЛЬНО для сбора вышеуказанного контента.

ТТХ услуги:

  • 2 сервера (для очень крупных заказов возможно выделение до 4 серверов);
  • неограниченное время сбора информации (24/7/365);
  • сбор информации блоками до 100 Гб (100 Гб собрали -> отгрузили заказчику -> приступили к сбору очередных 100 Гб).

В качестве входных параметров для начала предоставления данной услуги могут выступать:

  • текстовый файл с адресами файлов для скачивания;
  • парсинг страниц для нахождения контента для скачивания.

Также возможна автоматизация работы:

  • вы даете мне параметры для доступа к FTP и весь собранный контент, с указанной вами регулярностью, синхронизируется с вашим сервером (также возможно настроить синхронизацию БД, если попутно со сбором контента будет собираться какая-то информация или параметры).

Обнаружение и удаление дублирующихся записей

В данной заметке я хочу затронуть вопрос нахождения и удаления дублирующихся записей в MySQL.

И это касается не только вопроса сбора информации. Предлагаемые методы будут очень полезны начинающих программистам, которые ищут оптимальное решение в данном вопросе.

Для того, чтобы было проще объяснять код я создам тестовую таблицу work с ней буду производить свои эксперименты.

Таблица work имеет следующую структуру:

CREATE TABLE work( 
 id mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, 
 region varchar(255) NOT NULL, 
 job_mask varchar(255) NOT NULL, 
 job_group varchar(255) NOT NULL, 
 job_info text, 
 PRIMARY KEY(id));

Вдаваться в тонкости создания таблиц мы не будем, так как это не является целью данной статьи.

Предположим, что таблица work уже заполнена данными.

Теперь вам надо оставить в таблице только уникальные данные в поле job_info для каждого значения region, job_group, job_mask.

Первым делом вы должны определить есть ли у вас дублирующиеся данные и сколько их (ведь если их нет, то вам и делать ничего не надо). Для этого вам стоит выполнить следующий запрос:

SELECT COUNT(*) AS dub, region, job_group, job_mask, job_info 
FROM work GROUP BY region, job_group, job_mask, job_info HAVING dub>1;

В результате выполнения запроса вы увидите таблицу с уже знакомыми вам полями и с дополнительным полем dub, которое будет содержать количество дублирующихся записей.

Далее удаление можно организовать следующими путями:

  1. Циклический обход-1.
    Данный метод я называю “циклический обход-1″, потому что одну запись мы всегда оставляем не тронутой.

    Реализуется данные метод следующим способом:

     
     // соединение с БД  
     
     $query='SELECT COUNT(*) AS dub, region, job_group, job_mask, job_info 
     FROM work GROUP BY region, job_group, job_mask, job_info HAVING dub>1'; 
     $res=$db->query($query); 
     if(!$res) { 
    	echo $db->error(); 
     } else { 
    	while($row=$db->fetchAssoc($res)) { 
    	        $region=$row['region']; 
      	        $job_mask=$row['job_mask']; 
      	        $job_group=$row['job_group']; 
    	        $job_info=$row['job_info']; 
     
    	        $query="SELECT id FROM work 
    WHERE region='$region' AND job_mask='$job_mask' 
    AND job_group='$job_group' AND job_info='$job_info'"; 
     
    		$result=$db->query($query); 
    		if(!$result) { 
    			echo $db->error(); 
    		} else { 
    			$i=1; 
    			while ($subrow=$db->fetchArray($result)) { 
    				if ($i==1) { 
    				    $i++; 
    				    continue; 
    				} 
     
    				$query="DELETE FROM work 
                                                 WHERE id='{$subrow[0]}'"; 
    				$del_res=$db->query($query); 
    				if(!$del_res) { 
    					echo $db->error(); 
    				} else { 
    					$deleted++; 
    				} 
    				$i++; 
    			} 
    		} 
    	} 
    }

    Методы объекта $db, которые используются по ходу выполнения скрипта, являются методами моего стандартного абстрактного класса для доступа к БД. Имена методов очень тесно переплетаются с именами стандартных функций для работы с MySQL, поэтому я не вижу смысла останавливаться на этом более детально.

    В своих скриптах вы можете использовать привычные вам инструменты.

    Главным преимуществом данного решения есть простота. Однако есть и очень большой недостаток - низкая скорость выполнения. На больших таблицах с большим количеством записей данный скрипт будет выполняться очень долго. Так что не забудьте поставить лимит времени и лимит памяти побольше.

  2. Использование запроса SELECT.
    Второй вариант решения поставленной проблемы также достаточно распространен и предполагает некоторые знания в области SQL.Далее я приведу несколько запросов направленных на удаление дублирующихся записей и дам детальное их описание.

    Выборка уникальных данных в отдельную таблицу. Реализуется данный метод следующим образом:

     CREATE TABLE original 
     SELECT DISTINCT * FROM work ORDER BY id;

    Данный запрос создаст таблицу original с оригинальными значениями, выбранными из таблицы work.

    Также можно использовать следующую модификацию вышеуказанного выражения:

     CREATE TABLE original 
     SELECT * FROM work GROUP BY region, job_group, job_mask, job_info 
     ORDER BY id;

Дополнительная информация:

P.S. Думаю, что для начала этого инструментария будет достаточно. Но я не оставляю данную тему и надеюсь в ближайшем будущем предоставить дополнительную информацию по обработке дублирующихся записей в MySQL.

Парсинг выдачи Google. Практика.

Очень часто перед веб-мастерам встает задача парсинга выдачи поисковиков.

Это может быть нужно для того, чтобы:

  • автоматизировать процесс обработки какой-то статистической информации;
  • посчитать количество ссылающихся на ваш сайт ресурсов, определить их качество и прочие параметры;
  • найти урлы сайтов, которые натырили у вас контент (хотя тут лучше использовать что-то наподобии CopyScape, однако я заметил, что он иногда выдает бред);
  • определить частоту запросов.

И это лишь малый диапазон задач, в которых вам может понадобиться парсинг выдачи поисковиков. А точнее это то, что мне пришло в голову :)

А ведь еще у нас есть такие ребята как серые и черные оптимизаторы, которые при помощи поисковика ищут спамилки. Но это вообще отдельный разговор, который ничего общего с нашими благими целями не имеет :)

В данной статье я хотел бы рассказать как проще всего из выдачи Google доставать такие параметры как:

  • время генерации ответа (может понадобиться для статистики и расчета общего времени парсинга);
  • количество страниц, соответствующих вашему запросу (частота);
  • ну и собственно ссылки на конечные ресурсы.

Реализовывать механизм получения выдачи вы можете на чем угодно: curl, сокеты, fopen. Особой разницы для нас между ними нет. Предположим, что получать содержимое выдачи вы уже умеете (а если не умеете, то читать дальше вам просто незачем).

И вот тут начинаются вопросы.

1. Как достать из выдачи время генерации и количество страниц на данный запрос?

Я делаю это таким кодом (выдрано из тучи моих классов и гигантского ядра парсера, так что если найдете какую-то ошибку - напишите в комментах):

$blue_line_pcre = "(Результаты|Результати|Results)(.*)";
$parse_time_pcre= '\<b\>([\d\,\.]+)\<\/b\>\s[^\d]+$';
$parse_num_pcre	= '\<b\>([\d\,]+)\<\/b\>[^\d]+';
 
if(preg_match("/$blue_line_pcre/Ui",$Result,$matches)) {
    unset($matches[0]);
    if(!empty($matches[1])) {
        $BlueLine=preg_replace("/\&nbsp\;/",'',$matches[2]);
    } else {
        $BlueLine='';
    }
}
 
$find=$parse_num_pcre.$parse_time_pcre;
if(preg_match("/$find/Ui",$BlueLine,$matches)) {
    unset($matches[0]);
    $query_results_num=$matches[1];
    $query_time=$matches[2];
}

То есть, предполагается, что в переменной $Result хранится html код выдачи (и очено важно к этому коду не забыть применить

$Result=preg_replace("/[\n\r\t]/", '', $Result);

, чтобы потом не ломать голову почему половина регулярок не работают).
После выполнения моего скрипта в переменной $BlueLine будет html код синей полоски Гугла, в которой будет написано, что-то на подобии “Результаты 1 - 10 из примерно 87 300 000 для вакансии. (0,04 секунд)”.

В переменной query_results_num будет количество сайтов, подходящих под ваш запрос, а в переменной query_time - время генерации выдачи.

Из кода выше вы могли заметить, что регулярное выражение для нахождения кода “синей линии” имеет следующий непонятный вид:

(Результаты|Результати|Results)(.*)

Это сделано исключительно для того, чтобы с равной точностью парсить выдачу как google.com, так и google.ru и google.com.ua.

Заметка: результаты выдачи по одному и тому же запросу на разных локализованных версиях Google отличаются! Хоть иногда эту разницу тяжело обнаружить, но все же она есть. Можете на досуге поиграться с каким-то ВЧ запросом. Расскажете о результатах своих наблюдений.

Также некоторые вопросы могут вызывать регулярные выражения для нахождения времени генерации и количества страниц:

$parse_time_pcre = '\<b\>([\d\,\.]+)\<\/b\>\s[^\d]+$';
$parse_num_pcre	= '\<b\>([\d\,]+)\<\/b\>[^\d]+';

Для чего я использую вот такие конструкции [\d\,\.]? Исключительно для совместимости. Делаю я это потому, что разделителем целой и дробной части в нашей стране является “.”, а в США - “,”. В регулярном выражении для количества страниц вы также можете видеть символ “\,” - это используется для того, чтобы правильно находить количество страниц, так как американцы разделяют тысячи этим знаком.

2. Как достать ссылки из выдачи?

Эта даже проще чем выдирать параметры. Я делаю это при помощи следующего кода:

$find = $parse_links_pcre = "<a\shref=\"([^\"]+)\".*sclass=l";
if(preg_match_all("/$find/Ui",$Result,$matches)) {
    unset($matches[0]);
    $links=$matches[1];
}

В массиве $links будут все ссылки, найденный на текущей странице. Недостатки у такой унификации тоже есть: в результирующем массиве вы получите все ссылки “Translate” и этот метод не очень-то и быстро работает из-за использования в регулярном выражении “.*”. Но это место всегда можно подрихтовать под ваши конкретные требования. В оригинальном виде я тоже не особо часто использую данное регулярное выражение. Но есть также и плюсы у этого кода: он не загребает ссылки с оплаченных объявлений, а они, как вы знаете, вылазят на каждой странице поиска и могут очень сильно нарушить ваши статистические исследования или сбор ссылок.

Спасибо, что дочитали до конца. Рад буду выслушать ваши предложения и дополнения.