Новая платформа

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

Как вы уже могли видеть, основным языком, которым я пользовался ранее для разработки парсеров и грабберов был PHP. Язык на удивление простой, хорошо документированный и достаточно мощный. На начальных порах, когда в месяц обрабатывалось порядка 500к-800к страниц мне его было достаточно, но с увеличением объемов я начал ощущать, что он не совсем подходит для выполнения подобной работы. Что меня в нем не устроило:

  1. Постоянное “вываливание” Apache по segmentation fault (segfault), что приводило к рестарту Apache и соответственно к перезапуску с САМОГО НАЧАЛА всех скриптов.
    Проблема достаточно дотошная, потому что бороться с segfault очень сложно. И методы борьбы так или иначе влияют на производительность, что в нашем деле, как вы сами понимаете, очень плохо. Возникает данная проблема в результате быстрого нарастания используемой Apache’ем памяти, что понятное дело является следствием “течки” (простите за выражение) PHP.
    Я временно решил данную проблему написав контроллер процесса выполнения, но он увеличивал продолжительность сбора информации в среднем на 8-10%, что мало приятно.
  2. Ограничения Apache.
    Тут есть целый ряд проблем. Сначала Apache не давал мне запускать более 4х пауков, потом некоторые процессы он убивал считая их висячими и т.п…. Данные проблемы удалось решить перейдя на CLI версию PHP, но и с ним пришлось возиться еще больше.

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

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

Конечно же постоянные рабочие проекты и проекты “под заказ” будут крутиться на PHP, но все новые проекты я буду стараться реализовать на новой платформе. Я надеюсь получить увеличение производительности на 40-70% в результате переноса.

Так что лучше, выше, быстрее!

С Новым Годом

Новый Год с ParserPro - профи по парсингу и граббингу информации

Поздравляю всех своих клиентов, подписчиков, партнеров и друзей с Новым Годом. Я уверен, что в этом году вы добились многого, но я верю, что Новый Год откроет перед вами бесчисленные горизонты возможностей и вы достигнете всех поставленных целей и осуществите свои самые заветные мечты.

Отдельно я хотел бы поздравить своих клиентов, которые доверили свои проекты моим рукам и оставили столь приятные отзывы о моей работе на Weblancer.net и Webmoney.ru. Спасибо вам огромное! Обещаю, что Новый Год мы с вами встретим с новой парсинг-платформой, которая позволит собирать и обрабатывать миллионы записей в сутки.

Поздравляю всех! УДАЧИ!

P.S. До 6ого января я буду в отпуске, поэтому прошу всех моих великолепных заказчиков немного потерпеть. Когда я вернусь с новыми силами я удивлю вас своей производительностью! ;)

Посещаемость

Как-то странно упала посещаемость моего сайта. Притом упала в разы. Видно Гугл и Яндекс наказывают меня за то, что я с них ссылки собирал :)))

Где я?

Очень часто в процессе парсинга информации наши скрипты циклически проходят набор каких-либо урлов.

Если парсинг идет с одного источника с изменением, например, некоторых GET или POST параметров, то в любую единицу времени вы можете точно определить месторасположение своего скрипта. Но бывают случаи когда массив урлов собирается, например, с поисковика. И все было бы хорошо, если бы человек не придумал такую штуку как редирект и если бы ваш заказчик не требовал заносить УРЛ конечной страницы в базу данных.

А можете ли вы однозначно утверждать, что урл, который вы достали из выдачи Гугла однозначно равен урлу, на который зашел ваш скрипт и на котором он производил поиск информации? Если вы говорите “ДА”, то вы сильно ошибаетесь. Можете попробовать поэкспериментировать с такими запросами к Google как “Viagra” - половина сайтов будут либо дорвеями, либо прочими видами редиректеров.

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

В качестве других типичных примеров можно привести сборы урлов из каталогов компаниий или каталогов галерей картинок. Как часто вы видите подобные урлы: http://www.site.com/redirect.php?id=700? Понятное дело, что подобное записывать в поле URL в своей базе просто стыдно :), потому что через пару часов вы уже не сможете гарантировать, что вышеуказанная ссылка будет ссылаться на тот сайт, на котором вы произвели поиск информации.

Я столкнулся с подобной проблемой при работе над одним проектом по сбору картинок с галерей изображений. Редиректеры вытворяли что-то нереальное: одна и таже ссылка на редиректере вела на разные сайты. И даже не надо было ждать пару часов - это происходило ежесекундно!

И это бы ничего, ведь даже file() умеет следовать редиректам. Но ведь заказчик потребовал в базе в отдельное поле писать конечный URL…

Решить данную проблему можно достаточно просто и вариантов ее решение есть несколько.

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

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

function GetLastURL() {
	return curl_getinfo($this->curl_handler, CURLINFO_EFFECTIVE_URL);
}

В результате использования данной незатейливой функции вы получите последний урл, куда был перенаправлен ваш скрипт.

А что же делать, если вы не пользуетесь функционалом curl’a, а используете, например, file_get_contents()? Тогда прийдется немного поиграться в функцией stream_get_meta_data(). Вот типичное решение, которое я использовал в одном из своих проектов, где по ТЗ нельзя было использовать CURL:

$content = file_get_contents($url);
$fp = fopen($url,'r');
$meta_data = stream_get_meta_data($fp);
foreach($meta_data['wrapper_data'] as $response) {
	if (substr(strtolower($response), 0, 10) == 'location: ') {
		$last_url = substr($response, 10);
	}
}
fclose($fp);

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

Как мы видим второе решение намного более громоздкое по количеству написанного кода, да и ограничений у него побольше будет. Но тем не менее каждый из вышеуказанных вариантов имеет право на жизнь.

Пользуйтесь ими на здоровье в своих парсерах, грабберах и анализаторах и не никогда не теряйтесь! ;)

Такое сякое разное …

Наконец-то удалось немного разгрести гору заказов и написать.

На прошлой неделе очень интересно получилось с моим тестом-сравнением скорости работы preg_replace() и str_replace(). Я провел измерения, результаты были вполне предсказуемые, но YS.PRO подкинул новый опыт, по результатам которого скорость str_replace() превысила preg_replace() в 24 раза! Конечно, это больше чем я ожидал, хотя также вполне прогнозируемо.

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

Поэтому пока никаких больше тестов. Надо завершить все проекты, подготовиться к Новому Году и уехать куда-то отдыхать! :)

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

На этом все. Удаляюсь. В следующих постах обещаю рассказать об интересных функциях curl и умном взвешивании результатов при сборе картинок.

Удачи!

Тест. Кто быстрее: 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 секунды можно пренебречь как незначительным.

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

Оптимизируем работу с регулярными выражениями. 2 простые функции.

Главным оружием любого профессионального парсера являются регулярные выражение (особенно перловые PCRE). И я, если честно, вообще не представляю как без них можно делать просто и быстро свою работу.

Одним словом просто прелесть. И эту прелесть мы каждый день используем.

Сечас я приведу 2 классических варианта использования регулярных выражений:

if (preg_match("/$price_find/Ui",$content,$m)) {
        unset($m[0]);
	$price=mysql_escape_string(trim($m[1]));
} else {
        $price='';
}
 
if (preg_match_all("/$blocks_find/Ui",$content,$m)) {
	unset($m[0]);
        $blocks=$m[1];
} else {
        $blocks=array();
}

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

function pregm($what, $where, $return=1, $keys='Ui') {
	$search="/$what/$keys";
	if (preg_match($search,$where,$matches)) {
		unset($matches[0]);
		if ($return==1) {
			return $matches[1];
		} else {
			return $matches;
		}
	} else {
		return false;
	}
}
 
function pregma($what, $where, $return=1, $keys='Ui') {
	$search="/$what/$keys";
	if (preg_match_all($search,$where,$matches)) {
		unset($matches[0]);
		if ($return==1) {
			return $matches[1];
		} else {
			return $matches;
		}
	} else {
		return false;
	}
}

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

$price=mysql_escape_string(trim(pregm($price_find,$content));
$blocks=pregma($blocks_find,$content);

Вот так все просто. Мы сократили объем кода в несколько раз и сделали его более прозрачным и простым.

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

$param=array(
'Название',
'Цена',
'Размер',
'Автор'
//...
);
 
$find=array();
 
// Получаем контент и делаем прочие преобразования
 
foreach ($param as $key=>$item) {
	$find_it=$item."\:(.*)$";					
        $find[$key]=strip_tags(pregm($find_it,$block));
}

Получится, что в массиве $find у нас будут все найденные параметры. Потом можно просто сделать implode("','",$find), добавить пару кавычек и практически целиком залить эту строку в базу. Получается очень быстро и удобно.

Суть этой заметки. Регулярные выражения - мощный инструмент, а свои надстройки над ними - еще более мощный и удобный инструмент!

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

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

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

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

Странные комменты

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

Это относится к тем, кто вчера целый день в комментах писал подобные вещи:

“>alert(’xss’)

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

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

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

ТТХ услуги:

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

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

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

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

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