Изучаем PHP. Пишем parser похожих запросов Yandex.
В жизни программиста очень часто возникает задача получения полезной информации из других источников. Это может быть всесторонняя оценка цен конкурентов по определённому товару, сбор изображений и описаний для автоматизации процесса загрузки новых позиций в интернет – магазин, поиск аудио и видео – информации и многое другое.
Здесь на выручку нам так называемый «скрипт – парсер». В задачу которого входит получение и проверка информации, поиск нужных фрагментов и исключение «мусора».
Рассмотрим на примере так называемый «parser – spider» (парсер – паук), в задачу которого будет входить сбор информации со страниц yandex для поиска похожих ключевых слов, которые мы будем использовать в раскрутке нашего проекта. И напишем данный парсер на PHP
Как же нам может помочь parser?
Мы получаем список сходных фраз, которыми пользуются посетители яндекса при поиске нашей тематики, и в дальнейшем сможем ориентироваться на них при написании материалов на нашем сайте, оцениваем спрос к тематике нашего сайта. А так же это даст нам возможность определить поисковые запросы с меньшей конкурентной борьбой сходной тематики, чтобы выйти по ним в ТОП поисковой системы Yandex. Как следствие, благодаря этой информации мы расширим аудиторию нашего проекта.
А откуда мы можем получить информацию по похожим поисковым запросам Yandex?
Дело в том, что при поиске Яндекс подсказывает сам, отображая блок внизу страницы: Вместе с «[фраза для поиска]» ищут – несколько подобных поисковых запросов.
Вверху в левой части каждой страницы указывает, сколько нашлось вариантов, например:
создать сайт – Нашлось 104 млн ответов
Итак, какие же задачи поставим перед скриптом – парсером?
Возможность рекурсивного парсинга похожих поисковых запросов с определением уровня вложенности
Если не найдено ни одного похожего запроса по указанной фразе (а такое тоже бывает), попробуем просмотреть запросы по её сокращению:
Например, если ввести фразу Создать Сайт Харьков, то похожих поисковых запросов не будет в выдаче поисковика, но если ввести: создать сайт – они появятся. Ограничимся урезанием фразы на одно слово вконце.
Обходить парсер будет не только одну фразу, а займётся обработкой целого списка из фраз (передадим массивом)
Сохранение найденных поисковых фраз и их встречаемостью в файл
Нюансы:
Поставим задержку на опрос поисковой системы (не меньше 2 секунд, а то и до 30 секунд в случайном порядке) на каждый поисковый запрос. Мы же не хотим, чтобы Яша нас «отругал» за плохое поведение?
<?phpfunctiongetURIContent($url){$tuCurl= curl_init();$tuData='';if($tuCurl&&is_resource($tuCurl)){$opts=array(CURLOPT_URL =>$url,CURLOPT_HTTPGET => 1,CURLOPT_HEADER => 0,CURLOPT_RETURNTRANSFER => 1,CURLOPT_FOLLOWLOCATION => 1,CURLOPT_BINARYTRANSFER => 1,CURLOPT_AUTOREFERER => 1,CURLOPT_CONNECTTIMEOUT => 90,CURLOPT_USERAGENT =>$_SERVER['HTTP_USER_AGENT'],CURLOPT_COOKIEJAR => dirname(__FILE__)."/cookie.txt",CURLOPT_COOKIEFILE => dirname(__FILE__)."/cookie.txt",CURLOPT_REFERER =>$url);foreach($optsas$key=>$value){curl_setopt($tuCurl,$key,$value);}$tuData= curl_exec($tuCurl);curl_close($tuCurl);}return$tuData;}functionparseRecursive($question,$max_depth,$first= true){global$time_wait;$time_wait=$time_wait< 2 ? 2 :$time_wait;$rand= mt_rand($time_wait,$time_wait+ 30);sleep($rand);$question= urlencode($question);$content= getURIContent($where);$found= false;if(!empty($content)){$how_many=array();preg_match_all('~<strong[^>]*?class="b-head-logo__text"[^>]*?>(.*?)</strong>~is',$content,$how_many);$numbers='';if(is_array($how_many)&& isset($how_many[1][0])&& !empty($how_many[1][0])){$numbers= trim($how_many[1][0]);$numbers= preg_replace("~<br[^>]*?>~is",' ',$numbers);$numbers=str_ireplace(" ",' ',$numbers);$numbers=str_ireplace("\r\n",' ',$numbers);$numbers=str_ireplace("\r",' ',$numbers);$numbers=str_ireplace("\n",' ',$numbers);$numbers=str_ireplace("\t",' ',$numbers);$numbers=str_ireplace("\p",' ',$numbers);$numbers=str_ireplace("\b",' ',$numbers);$numbers= html_entity_decode($numbers,ENT_QUOTES,'UTF-8');$numbers=strip_tags($numbers);}if(!empty($numbers)){$numbers= urldecode($question) .' - '.$numbers."\n";$fp=fopen(WHERE_TO_SAVE,'a+');if($fp&&is_resource($fp)){echo'ADDING '.$numbers."<br />\n";flock($fp,LOCK_EX);fwrite($fp,$numbers);flock($fp,LOCK_UN);fclose($fp);}}$related=array();$links=array();preg_match_all('~<table[^>]*?class="b-related__table"[^>]*?>(.*?)</table>~is',$content,$related);/* <a[^>]*?href=("|\')([^"\']*?)(\1)[^>]*?>(.*?)</a> */if(is_array($related[1]) &&isset($related[1][0])&&$max_depth){--$max_depth;preg_match_all('~<a[^>]*?href=("|\')([^"\']*?)(\1)[^>]*?>(.*?)</a>~is',$related[1][0],$links);if(is_array($links)&& isset($links[2])&& isset($links[4])&& sizeof($links[2])&& sizeof($links[4])&&$max_depth){$sizeof= sizeof($links[4]);for($i= 0;$i<$sizeof;$i++){$text='';$text= trim($links[4][$i]);$text= preg_replace("~<br[^>]*?>~is",' ',$text);$text=str_ireplace(" ",' ',$text);$text=str_ireplace("\r\n",' ',$text);$text=str_ireplace("\r",' ',$text);$text=str_ireplace("\n",' ',$text);$text=str_ireplace("\t",' ',$text);$text=str_ireplace("\p",' ',$text);$text=str_ireplace("\b",' ',$text);$text= html_entity_decode($text,ENT_QUOTES,'UTF-8');$text=strip_tags($text);if(!empty($links[2][$i]) && !empty($text)){parseRecursive($text,$max_depth,false);}}}}elseif(is_array($related[1]) &&$max_depth){$question= urldecode($question);$words=array();$words=explode(' ',$question);$sizeof= sizeof($words);$words=array_map('trim',$words);--$sizeof;$words=array_slice($words,0,$sizeof);$question= join(' ',$words);if(strlen($question) > 4){--$max_depth;if($max_depth){parseRecursive($question,$max_depth,false);}}}}}ini_set('max_execution_time',999999);ini_set('max_input_time',999999);$max_depth= 2;$time_wait= 2;$questions=array('создать сайт','сайт на joomla','создать сайт Харьков','сайт на wordpress');define('WHERE_TO_SAVE',dirname(__FILE__).'/prases.txt');if(!is_file(WHERE_TO_SAVE)){$fp=fopen(WHERE_TO_SAVE,'w+');if($fp&&is_resource($fp)){fclose($fp);}if(is_file(WHERE_TO_SAVE) && !is_writable(WHERE_TO_SAVE)){chmod(WHERE_TO_SAVE,0777);}}if(is_file(WHERE_TO_SAVE)){$fp=fopen(WHERE_TO_SAVE,'w+');if($fp&&is_resource($fp)){fclose($fp);}}if(is_file(WHERE_TO_SAVE) &&is_writable(WHERE_TO_SAVE)){$sizeof= sizeof($questions);for($i= 0;$i<$sizeof;$i++){parseRecursive($questions[$i],$max_depth);};};?>
Итак, что же здесь происходит:
Для начала с помощью ini_set установим побольше время выполнения для скрипта.
В переменной $max_depth укажем, сколько вложенных уровней «обходить» (на какую глубину «погружаться») для сбора похожих фраз.
Например: $max_depth = 2; – находим похожие поисковые фразы, переходим по каждой из них, и собираем результаты по похожим уже на них поисковым фразам.
Совет: не делайте слишком большим уровень вложенности. Иначе сильно отклонитесь от первоначальной тематической фразы.
Переменной $time_wait указываем, сколько секунд ждать до следующего запроса к поисковику.
Обратите внимание – 2 секунды – это минимальное значение. Иначе сайт выдаст вам капчу или наложит бан по ай-пи адресу.
В массив $questions = array(‘создать сайт’,’сайт на joomla’,’создать сайт Харьков’,’сайт на wordpress’); – списком строк через запятую добавляем запросы для обработки parser ом.
Далее, определяем, существует ли файл prases.txt – в который мы и добавим найденный результат. Если не существует, создаём его и делаем доступным на запись.
После этого обходим массив $questions и передаём каждый запрос функции parseRecursive вместе с уровнем вложения.
(void) function parseRecursive($question,$max_depth,$first = true): принимает одну поисковую фразу (string)$questions[$i] и уровень вложенности поиска $max_depth, а так же неявный флаг (bool) $first, для определения, в какой раз подряд вызывается функция (для чего, рассмотрим ниже).
В функции мы кодируем для возможности передачи в виде запроса нашу ключевую фразу с помощью urlencode и получаем содержимое запроса в функции getURIContent, основанной на curl. После чего регулярным выражением ~]*?class=”b-head-logo__text”[^>]*?>(.*?)~is узнаём, сколько раз встречается данный поисковой запрос.
Очищаем полученный запрос от html и спец символов. Помещаем выражение и количество найденных страниц по этой поисковой фразе в файл prases.txt.
Исследуем содержимое полученной страницы на предмет содержания «похожих поисковых запросов» ~
~is.
Если похожие поисковые фразы найдены, извлекаем ссылки на них и их анкоры: ~]*?href=(“|’)([^”‘]*?)(\1)[^>]*?>(.*?)~is
При этом уменьшая на единицу наш уровень вложенности –$max_depth
Если уровень вложенности позволяет (отличен от нуля), передаём анкоры (текст, заключённый в тег a) рекурсивно в функцию parseRecursive. Обрабатываем следующий уровень.
Если же на странице не было найдено по указанной поисковой фразе ни одного результата, и вызов функции parseRecursive был осуществлён впервые (неявный флаг $first со значением true) то проверяем, содержит ли слова поисковый запрос? (Разбиваем по пробельному символу фразу на слова). Исключаем последнее слово, и передаём полученное словосочетание заново функции parseRecursive.
Примечание автора: для получения содержимого страниц по указанным поисковым запросам используется функция getURIContent, которая принимает адрес страницы и возвращает её содержимое.
Её работа основывается на curl, что даёт ряд преимуществ перед url wrapper ами функции file, fopen, file_get_contents и т.п.:
Более быстрый процесс получения информации
Передача referrer, cookie, user agent
Управление временем ожидания (таймаутом) на получение информации
Возможность корректно перейти по всем пере направлениям (Location:), если таковые встречаются
Бинарная передача данных
В следующих статьях мы поведаем Вам более подробно о возможностях функции curl и приведём другие интересные примеры её использования.
Замечание: каждый программист должен задаваться не только вопросом, что было сделано хорошо, но и «а что можно было бы сделать лучше» (оптимизация кода, повышение качества результата, удобная подача информации и т.п.).
Убрать дубли фраз, обнаруженные при поиске.
Для получения более достоверных вариантов проверять, встречается хотя бы одно слово из целевого поискового словосочетания, заданного в $questions в фразах, обнаруженных при многоуровневом обходе страниц
Каждый результат поиска записать в отдельный файл.
Визуализировать результат при помощи графиков
Задание для тех, кто учит parser ы на PHP:
Для выполнения этой же задачи напишите парсер, который обходит wordstat yandex