Парсер выдачи «статистики запросов Яндекса» (wordstat)
Делал для себя парсер выдачи http://wordstat.yandex.ru. Подозреваю, что кому-то может пригодиться (скажем для роботизированного поиска низкочастотников с целью клепания страничек под них), а потому выкладываю исходник.
Скрипт работает на момент публикации этого поста (11 апреля 2009). Он перестанет работать, если Яндекс сменит вывод (вёрстку, оформление) wordstat`а.
Чтобы код понять, надо немного знать php. Комментировал я всё неприлично подробно. Чтобы парсер использовать — достаточно скопировать весь код в файл parser.php, сохранить файл и закинуть куда-то к себе на сервер. Нужно, правда, чтобы на сервере кроме интерпретатора php была ещё и библиотека cURL, но оная имеется на любом адекватном хостинге, даже на пятикопеечных. Также нужно нормальное время исполнения скрипта, в худшем случае он будет работать 40 секунд (для пяти страниц выдачи), приходится искусственно притормаживать перед запросом очередной страницы у wordstat`а. Если этого не делать, то wordstat начнёт просить капчу (если вы на капчу таки нарвались, то подождите часок-другой, пока wordstat простит ваш IP-адрес).
Собственно, тем кто код понимает, вряд ли составит труда нечто подобное написать за час самостоятельно. Но вдруг нужна таки готовая функция.
Новичкам готов рассказать дополнительно как и чего работает. И если людям это нужно, то возможно буду что-то и в будущем выкладывать в паблик с подробными комментариями.
<? function yandexwordstat($query, &$keywords = null, $maxpage = 5) { // Аргументы функции: запрос, ссылка на создаваемый функцией массив, максимальное количество страниц для обработки. srand(time()); // Нам потребуются случайные числа, чтобы вносить нерегулярную задержку перед повторными запросами к Вордстату, иначе он после n-ого запроса затребует вводить капчу. $page = 1; // Предполагаем, что по нашему запросу в выдаче Вордстата будет лишь единственная страница. $query = urlencode($query); // Кодируем в запросе символы так, как положено, чтобы не осталось там русских букв. do { $repeat = true; // Предполагаем, что парсить выдачу мы будем бесконечно. Позже мы остановимся, когда не останется страниц для дальнейшего анализа. $url = "http://wordstat.yandex.ru/?cmd=words&geo=1&page=".$page."&text=$query&text_geo=%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0+%D0%B8+%D0%BE%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D1%8C"; // Ссылка с запросом и порядковым номером страницы выдачи по этому запросу. Выбран регион «Москва и область». Если регион не нужен, то убирайте из запроса ключ text_geo и его значение. $ch = curl_init(); // Создаём объект-страницу. Нам помогает великая библиотека cURL -- лучший друг любого бота, который хочет претвориться браузером живого пользорвателя. curl_setopt($ch, CURLOPT_URL, $url); // Указываем откуда затащить содержимое в наш объект. curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)"); // Прикинемся каким-нибудь популярным браузером. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $html = curl_exec($ch); // Получаем объект-страницу в виде длинной символьной строки. curl_close($ch); // Убиваем объект-страницу, он нам больше не нужен, исходный код страницы уже в строковой переменной. // Регулярное выражение, выбирающее со страницы ключевики. Этой регуляркой выбираются вообще все ключевики со страницы, в том числе и запросы из колонки «Что еще искали люди, искавшие...». Благо в последнем случае показываются только самые популярные -- добавить в наш набор их будет полезно. $pattern = '/<tr class="tlist" bgcolor="#......">[[:space:]]*<td>[[:space:]]*<a href=".*?">(.*?)<\/a>[[:space:]]*<\/td>[[:space:]]*<td align="right">(.*?)<\/td>[[:space:]]*<\/tr>/s'; $ismatches = preg_match_all($pattern, $html, $out); // Вытягиваем в соответствии с регулярным выражением $pattern все его вхождения в великую строку $html, результаты отправляем в многомерный ассоциированный массив $out. А возвращает функция истину, если хоть одно вхождение нашлось. if ($ismatches) { // Если нашлись слова, то вытаскиваем их из сложноустроенного массива и складываем в просто устроенный ассоциированный, вида $keywords['ключевик'] = кол-во запросов. foreach ($out[1] as $key => $value) { $value = str_replace('+','', $value); // Заодно выбросим плюсики из ключевиков. $keywords[$value] = $out[2][$key]; // Кстати, индекс ассоциированного массива в виде ключевика гарантирует нам, что дублей ключевиков в массиве не будет. } } $pattern2 = "/page="; $pattern2 .= $page+1 . "&/s"; // Новое регулярное выражение, с помощью которого мы будем выявлять, есть ли по заданному ключевику следующая страница выдачи. if(preg_match($pattern2,$html)) { $repeat = true; // Страница есть, будем повторять цикл парсинга, но уже для следующей страницы. sleep(rand()%3*3+2); // Но перед повторением немного покурим, чтобы Вордстат не принял нас за интенсивных ботов. В текущем варианте парсер будет тратить от 2 до 8 секунд на страницу выдачи. Уменьшайте аккуратно, дабы не схлопотать капчу. $page++; // Номер страницы выдачи меняем на следующий. } else { $repeat = false; // Нету следующей страницы, пора цикл завершать. } } while ($repeat && ($page < $maxpage)); // Повторим цикл, если имеется следующая страница выдачи. А также если нам разрешено обрабатывать так много страниц (см. третий апарметр функции). arsort(&$keywords); // Отсортируем массив так, чтобы сначала шли ключевики с наибольшей частотой. return $page; // А возвращает-то функция количество обработанных страниц выдачи. } $keywords = array(); // Создаём пустой массив. После вызова функции в него отправятся ключевики с их частотой. $pages = yandexwordstat($_POST['keyword'], $keywords); // Вызываем функцию, передавая ей ключевик, прочитанный из текстового поля формы. if (count($keywords) > 0) { // Будем что-то стряпать для дальнейшего вывода только если в массив попали какие-то ключевики. // Соберём из кусочков таблицу о двух столбцах: Запросы и количество показов в месяц по данным Яндекса. $str = '<table class="keywords"><tbody>'; // Начало таблицы. $str .= '<tr class="header"><th>Поисковый запрос</th><th>Месячное количество запросов в Яндексе</th></tr>'; // Шапка таблицы. $i = 1; // Порядковый номер генерируемой строки таблицы. foreach ($keywords as $key => $value) { // Разбираем на запчасти массив ключевиков и количества запросов. // Открывать строки таблицы мы будем тегами с разными атрибутами в зависимости от чётности строки. if ($i%2 == 0) { // Строчки с чётным номером я буду выводить с одним стилем. Класс even. // JavaScript поможет нам подсветить иным цветом ту строку таблицы, над которой завис курсор мыши. Для строк под указателем добавим css-класс current. Удобно. $str .= '<tr onmouseover="this.className=\'current even\';" onmouseout="this.className=\'even\';" class="even"><th>'; } else { // А с нечётным -- с другим стилем. Класс odd. Такую «зебру» зрительно воспринимать удобнее. $str .= '<tr onmouseover="this.className=\'current odd\';" onmouseout="this.className=\'odd\';" class="odd"><th>'; } $i++; // Следующая строка со следующим номером. Нас, впрочем, только его чётность будет волновать. $str .= $key; // В первом столбце показываем ключевик. $str .= '</th><td>'; $str .= $value; // Во втором -- количество запросов. $str .= '</td>'; $str .= '</tr>'; // Закрываем строку таблицы. } $str .= '</tbody></table>'; // Конец таблицы. $str .= '<p>Обработано страниц: '.$pages.'</p>'; // Покажем, сколько страниц пришлось переварить нашему парсеру. } else { $str .= '<p>По указанному запросу никто и ничего в Яндексе не искал.</p>'; } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Парсер статистики запросов Яндекса</title> <style> body { font-family: sans-serif; font-size: 12px; } table.keywords { border-collapse: collapse; } table.keywords th, table.keywords td { border: 1px solid #EE9900; padding: 6px; background-color: #FFCC33; } table.keywords th { text-align: left; } tr.odd td, tr.odd th { background-color: #FFFFCC; } tr.even td, tr.even th { background-color: #FFFFEE; } tr.current td, tr.current th { background-color: #FFDD66; } </style> </head> <body> <?php print $str; ?> <!--// Выводим то, что сгенерировал нам скрипт: таблицу, либо сообщение об отсутствии результатов. //--> <form action="parser.php" method="post"> <p> <strong>Запрос:</strong> <input name="keyword" type="text" value="<?php print $_POST['keyword']; ?>" /> <input type="submit" /> </p> </form> </body> </html>
Если появятся вопросы — вы задавайте, не робейте.


Не работает
viperson, поподробнее бы рассказали в чём дело, если действительно пробовали запускать и хотите чтобы заработало. Чего видите после отправки запроса на парсинг? Разрешает ли ваш хостинг 40 секундное исполнение скрипта (если нет, то уменьшайте кол-во страниц: в параметре функции $page ставьте значение по умолчанию 3, например, вместо 5)?
Данные, которые выдает скрипт, и данные самого Вордстата отличаются иногда в два раза.
почему так?
Плюсовал, минусовал – логики нет.
ИнтерБомж, а регион у вас на вордстате какой стоит? Скрипт по умолчанию считает только для «Москвы и области». Если в вордстате вы проверяете какой-то запрос по всем регионам, то количество на нём должно быть больше, чем только по Москве.
Понятно!
Имхо, по дефолту лучше сделать поиск по всем регионам.
ИнтерБомж, тут момент вот в чём: я хотел в примере показать, как заточить скрипт для поиска по нужному региону.
А как отключить геотаргетинг в комментарии к соответствующей строке написано: «Если регион не нужен, то убирайте из запроса ключ text_geo и его значение».
Практически полезнее, на мой взгляд, показать максимум возможностей и объяснить как отключить ненужные.
Значит всё-таки отреагировали… Не быстро
Не уверен в большой необходимости такого скрипта. Думаю лучше организовать просто страничку статистики поисковых запросов acvarif.info/wbsphp/apphp/sstat.html Разделить их потом если необходимо на поисковики. Хотя я даже ничего не разделял. Оказалось очень полезная страничка. Хорошо просматриваются запросы по которым приходят и откуда и уже можно принимать решение каких запросов не достает.
Привет, а дописать можешь что бы была реакция на капчу?
алгоритм: «если появилась, то ждем N минут, а далее пытаемся сканить»
Будет время — допишу. Но вообще лучше не перегружать вордстат своими запросами, ставить поболее паузу между ними.
Проверку на капчу можно организовать так:
if(preg_match(‘|<img src="http:\/\/captcha.yandex.net\/image\?key=|', $html)) {код при появлении капчи}
Мне тоже не совсем понятна необходимость такого скрипта. Для чего он нужен?
Для того, чтобы автоматизировать сбор кеев по определённой тематике.
Спасибо, работает
Скрипт отличнейший!
Из всего разнообразия в сети первый, котрый дает одинаковые результаты с ручными с запросами.
А как модифицировать, что бы не по одному а по 200-300 запросам получить статистику за месяц. В иделале за весь период что сейчас Яша дает- 18 месяцев, по конкретному запросу и без сопуствующих запросов и без
«Что искали со словом «» »
Что еще искали люди, искавшие.
И вывод в файлик сделать, хотя можно и в Броузер оставить.
Собственно очень лень 200-300 раз лезть на Вордстат и пописывать нужные запросы и ручками перписывать. А потом запросы меняются…
Получится очень полезная штука имхо ))
Maikl, технически сделать это можно, но при такой массированной дойке, Яндекс врубит капчу. О том, как передать капчу с Яндекса к пользователю есть выше в комментах, но скрипт не будет работать автономно, надо будет сидеть и вводить ему капчи. Соответственно, тут надо или к сервису антикапче цеплять или же ручками и потихонечку
Ручной парсер Yandex wordstat
моя реализация.
У меня появляется капча все время. С чем это связано?
Слишком мног запросов?
У меня дает капчу сразу при первом запросе. Может яндекс на куки проверяет?
А показать пользователю капчу легко, он введет значение, оно передается в скрипт и скрипт должен сделать запрос уже с оветом на капчу. Кто-то пробовал, работает?
А при таком запросе капчу не дает
function rip_from_url($url, $ref = «», &$cookie = false, &$page, $reg = «», $mode = 0) {
$agent = «Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0″;
$url = parse_url($url);
if ($proxy != «») {$proxy = explode(«:», $proxy); $fp = fsockopen ($proxy[0], $proxy[1], $errno, $errstr, 30);}
else $fp = fsockopen ($url['host'], 80, $errno, $errstr, 30);
if (!$fp) {$err = 1;} else {
$err = 0;
$request = «GET {$url['path']}».(isset($url['query']) ? «?» : «»).»{$url['query']} HTTP/1.1\r\n»;
$request .= «Host: {$url['host']}\r\n»;
$request .= «User-Agent: {$agent}\r\n»;
$request .= «Referer: {$ref}\r\n»;
if ($cookie) $request .= «Cookie: $cookie\r\n»;
$request .= «Connection: close\r\n»;
$request .= «\r\n»;
fwrite ($fp, $request);
$responseHeader = »;
$responseContent = »;
do {$responseHeader.= fread($fp, 1);}
while (!preg_match(‘/\\r\\n\\r\\n$/’, $responseHeader));
if (!strstr($responseHeader, «Transfer-Encoding: chunked»))
{
while (!feof($fp))
{
$responseContent.= fgets($fp, 128);
}
}else{
while ($chunk_length = hexdec(fgets($fp)))
{
$responseContentChunk = »;
$read_length = 0;
while ($read_length 0) $cookie = $out[1];
}
if (preg_match(‘~HTTP/1.[01] 302~mi’, $responseHeader)!==false) {
preg_match(‘~Location: (.*?)\r\n~mi’, $responseHeader, $out);
if (count($out)>0) {
$redirect = $out[1];
rip_from_url($redirect, $ref, $cookie, $responseContent);
}
}
$page = $responseContent;
if (!isset($reg) || $reg != «»){
preg_match_all($reg, $responseContent, $out);
switch ($mode) {
case 0: return $out[1]; break;
}}
}
}