Изучаем PHP. Пишем parser похожих запросов Yandex.
В жизни программиста очень часто возникает задача получения полезной информации из других источников. Это может быть всесторонняя оценка цен конкурентов по определённому товару, сбор изображений и описаний для автоматизации процесса загрузки новых позиций в интернет – магазин, поиск аудио и видео – информации и многое другое.
Здесь на выручку нам так называемый «скрипт – парсер». В задачу которого входит получение и проверка информации, поиск нужных фрагментов и исключение «мусора».
Рассмотрим на примере так называемый «parser – spider» (парсер – паук), в задачу которого будет входить сбор информации со страниц yandex для поиска похожих ключевых слов, которые мы будем использовать в раскрутке нашего проекта. И напишем данный парсер на PHP
Как же нам может помочь parser?
Мы получаем список сходных фраз, которыми пользуются посетители яндекса при поиске нашей тематики, и в дальнейшем сможем ориентироваться на них при написании материалов на нашем сайте, оцениваем спрос к тематике нашего сайта. А так же это даст нам возможность определить поисковые запросы с меньшей конкурентной борьбой сходной тематики, чтобы выйти по ним в ТОП поисковой системы Yandex. Как следствие, благодаря этой информации мы расширим аудиторию нашего проекта.
А откуда мы можем получить информацию по похожим поисковым запросам Yandex?
Дело в том, что при поиске Яндекс подсказывает сам, отображая блок внизу страницы: Вместе с «[фраза для поиска]» ищут – несколько подобных поисковых запросов.
Вверху в левой части каждой страницы указывает, сколько нашлось вариантов, например:
создать сайт – Нашлось 104 млн ответов
Итак, какие же задачи поставим перед скриптом – парсером?
Возможность рекурсивного парсинга похожих поисковых запросов с определением уровня вложенности
Если не найдено ни одного похожего запроса по указанной фразе (а такое тоже бывает), попробуем просмотреть запросы по её сокращению:
Например, если ввести фразу Создать Сайт Харьков, то похожих поисковых запросов не будет в выдаче поисковика, но если ввести: создать сайт – они появятся. Ограничимся урезанием фразы на одно слово вконце.
Обходить парсер будет не только одну фразу, а займётся обработкой целого списка из фраз (передадим массивом)
Сохранение найденных поисковых фраз и их встречаемостью в файл
Нюансы:
Поставим задержку на опрос поисковой системы (не меньше 2 секунд, а то и до 30 секунд в случайном порядке) на каждый поисковый запрос. Мы же не хотим, чтобы Яша нас «отругал» за плохое поведение?
<?php
function
getURIContent(
$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
(
$opts
as
$key
=>
$value
){
curl_setopt(
$tuCurl
,
$key
,
$value
);
}
$tuData
= curl_exec(
$tuCurl
);
curl_close(
$tuCurl
);
}
return
$tuData
;
}
function
parseRecursive(
$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.
Исследуем содержимое полученной страницы на предмет содержания «похожих поисковых запросов» ~
]*?class=”b-related__table”[^>]*?>(.*?)
~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