Парсер Яндекса, пример на PHP

суббота, 3 ноября 2012 г.

Написание парсеров сайтов - мое любимое занятие. Вот решил поделиться своими знаниями и с читателями. Написал за 15 минут простейший парсер поисковой выдачи Яндекса.

Сразу объясняю некоторые моменты:
1. Код писал для примера. Показаны основные принципы. Чисто функциональное программирование, никакого ООП.
2. Не учитывается пейджинг (разбитие по страницам).
3. Не берется сниппет из выдачи. Только позиция, урл и текст анкора.
4. Никак не учитывается обход защиты самого Яндекса и анализ CAPTCHA.

Страницу загружать будем с помощью cURL. Это быстро, просто и удобно.

function get_page($url)
{
 $ch = curl_init();
 $options = array(
  CURLOPT_TIMEOUT => 15,
  CURLOPT_RETURNTRANSFER => TRUE,
  CURLOPT_FOLLOWLOCATION => TRUE,
  CURLOPT_URL => $url,
  CURLOPT_USERAGENT => 'Google Chrome'
 );
 // можете добавить кучу других опций
 // http://php.net/curl_setopt
  
 curl_setopt_array($ch, $options);
 $data = curl_exec($ch);
 
 curl_close($ch);
 return $data;
}

Тут все понятно - в функцию передаем URL, на выходе получаем скачанную страницу. Функция универсальная и конкретно к специфике парсинга яндекса отношения не имеет. Просто почитайте документацию по cURL и сможете написать вашу собственную "идеальную" функцию/метод.

Еще одна нужная функция.

function set_utf8_meta($page)
{
 return preg_replace('/<head[^>]*>/',
   '<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">',
   $page);
}

Дело в том, что для парсинга я буду использовать DOM и xpath, но библиотека libxml не понимает современный способ обозначения кодировки страницы в стиле html5, но кодировку нам указать необходимо, чтобы DOM адекватно "вытащил" русский текст.

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

$query = 'парсер яндекса'; // запрос
$url = 'http://yandex.ru/yandsearch?text='.urlencode($query).'&lr=213&numdoc=50';
// 213 - регион (Москва) / numdoc=50 - на первой странице выдачи будет 50 позиций, а не 10

$page = get_page($url); // скачиваем страницу
$page = set_utf8_meta($page); // выставляем нужный нам meta content


libxml_use_internal_errors(true); // дает нам управление над ошибками
$dom = new DOMDocument(); // создаем объект класса и выставляем немного настроек
$dom->preserveWhiteSpace = false;
$dom->resolveExternals = false; 
$dom->validateOnParse = false;
$dom->loadHTML($page);
$xpath = new DOMXpath($dom); // создаем объект класса DOMXpath

$serp_items = $xpath->query('//div[contains(@class, "serp-block") and not(contains(@class, "-adv"))]//*[contains(@class, "serp-item__wrap")]');
// берем только результаты выдачи, без учета блока Директа
// $serp_items->length; // кол-во результатов на странице

$links = array();

foreach ($serp_items as $k=>$item)
{
   $_tmp = array();
   $header_obj = $xpath->query('./h2', $item)->item(0);

   $_tmp['position'] = $k+1;
 
   $link_obj = $xpath->query('./a', $header_obj)->item(0);
   $_tmp['url'] = $link_obj->getAttribute('href');
   $_tmp['url_text'] = trim(preg_replace('/\s+/i', ' ', $link_obj->nodeValue));
 
   $links[] = $_tmp;
}

var_dump($links); // тут-то и находятся первые 10 результатов со страницы 
/* к примеру, данные из самой первой ссылки
[0]=>
  array(3) {
    ["position"]=>
    int(1)
    ["url"]=>
    string(40) "http://stakhov.ru/2008/08/yandex-parser/"
    ["url_text"]=>
    string(62) "Парсер Яндекса. Парсер Yandex. - stakhov.ru"
  }
*/

Теперь все три куска кода можете положить в один файл и запустить.
Дальше вы сами уже можете и с пейджингом разобраться и придумать как обходить защиту Яндекса.

Для того, чтобы доработать парсер до какого-то внятного вида, вам хорошо бы разобраться с cURL и xpath.

Кроме того, если у Яндекса верстка валидная и нам не нужно ее чистить, то для приведения верстки к адекватному виду перед парсингом других сайтов рекомендую использовать tidy.

Напоминаю - код писал на коленке, для примера. Если есть какие-то вопросы, то прошу в комментарии, я с удовольствием на них отвечу.

Для удобства весь код положил в один файлик.

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

$url = 'http://yandex.ru/yandsearch?text='.urlencode($query).'&lr=213&numdoc=50';

UPD. 21.01.2013: Яндекс обновил немного верстку, поэтому пришлось подправить пару xpath запросов:
1. Было: $serp_items = $xpath->query('//li[@class="b-serp-item"]');
Стало: $serp_items = $xpath->query('//li[contains(@class, "b-serp-item")]');
Будет немного медленней работать из-за использования функции contains.

2. Было: $header_obj = $xpath->query('./div/h2', $item)->item(0);
Стало: $header_obj = $xpath->query('./h2', $item)->item(0);

UPD. 27.08.2014: Выдача яндекса изменилась, изменились и xpath запросы. Тестировать довольно сложно, ибо яндекс капчу выдает практически сразу, так что код несет больше академический интерес, нежели практический смысл. Для адекватной работы надо делать постраничный парсинг, учет капчи и прочие плюшки. Спасибо mari за комментарий.

Код в файле обновил. Если вдруг заметите, что возвращается пустой массив или появляются другие ошибки - напишите коммент, пожалуйста, с указанием тестируемого запроса и вашим регионом в Яндексе, если он отличен от Москвы (213). Так я смогу поддерживать в актуальном состоянии парсер Яндекса и другие читатели смогут использовать рабочий код.

34 коммент.:

Анонимный комментирует...

почему не написать готовый продукт в котором нуждается интернет сообщество :) у вас кодинг занял 15 минут, значит готовый продукт займёт у вас 30 минут. Подумаете ?

GIN комментирует...

Надо понять потребности. То есть кому-то надо просто собрать конкурентов по выдаче, кому-то надо понять на каком месте по запросу находится их сайт и т.д.
Скорее всего чуть позже напишу расширенную версию парсера =)

publicstat комментирует...

Присоединяюсь к Анонимусу. Очень нужен адекватный парсер позиций сайта в Яндекс, сделанный профессионалом.

GIN комментирует...

publicstat, что Вы подразумеваете под "адекватным" парсером? Я могу сделать новую версию, которая будет учитывать пейджинг, вытаскивать больше данных. Можно вообще сделать проект опенсорсным и выложить код на github, чтобы каждый мог "допилить" до своих потребностей.

Если напишите, что может требоваться в базовой версии парсера - я мог бы написать и выложить новую версию кода. =)

Анонимный комментирует...

Соглашусь с вышеприведёнными комментариями=)) Интересно было бы посмотреть:
1)вводим урл сайта
2)поисковую фразу
3)смотрим позицию
хоть бы так=))

GIN комментирует...

Хорошо, попробую сделать что-то подобное =)

Unknown комментирует...
Этот комментарий был удален автором.
Анонимный комментирует...

странно, у меня выдаёт пустой массив

GIN комментирует...

Да, действительно теперь выдает пустой массив. Обновлю код и выложу обновленную версию в файл.

Ленивый вебмастер комментирует...

Сейчас тоже пустой выдает

GIN комментирует...

Ленивый вебмастер, я проверил сейчас - все нормально работает, массив на выходе не пустой. Вы брали код из файла или копировали из статьи?

Анонимный комментирует...

Возвращается пустой массив

GIN комментирует...

Спасибо, исправлю.

GIN комментирует...

Странно, но последняя версия моего кода все еще работает прекрасно. Пришлите, пожалуйста, пример запроса, который тестируете и Ваш регион в Яндексе, если он отличается от Москвы (213).

A. комментирует...

Не, я прямо 1 в 1 код копировал, просто чтоб проверить работает или нет. А получил:
array(0) { }

GIN комментирует...

Дело в том, что я открываю свой код, копирую, запускаю и получаю жирный массив спарсенных данных. Похоже, Яндекс умеет менять верстку под разный вариант выдачи, так что тут придется вооружиться Firebug и по аналогии с моим кодом найти все элементы и прописать для них xpath-селекторы. Либо я попробую унифицировать код.

А. комментирует...

У меня на некоторое время все заработало (выдал массив), а потом опять перестало, хотя ничего не менял. Я погуглил походу яндекс иногда блокирует автоматические запросы: legal.yandex.ru/termsofuse (пункт 2.3)

GIN комментирует...

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

Максим комментирует...

GIN Здравствуйте, а Вы можете парсер написать который бы вытаскивал определенные данные из выдачи яндекса. Как с Вами можно связатся?

GIN комментирует...

Максим, здравствуйте. Честно говоря, сейчас у меня крайне мало времени. На всякий случай напишите мне на mail@iglebov.ru что именно Вам требуется, а я подумаю. Может, там все быстро получится.

Анонимный комментирует...

скрипт неработает, ему нехватает авторедиректа и куки да и то дальше капчи неуйдеш=)

Ильмир Ахметшин комментирует...

Парсить все умеют, но как обойти капчу, т.к. програмный запрос к выдаче Яндекс запрещает. Автор, что посоветуешь? Как с куки обойти этот вопрос?

Анонимный комментирует...

Добрый день, как быть если сайт на немецком сервере, и модель не учитывает регион. Предложения попросту не возвращаются.
http://market.yandex.ru/model.xml?modelid=10615767&hid=90639

Если запускаешь парсер с Питера то всё нормально, если запускаешь с сервера то ответ уже невалидный.

GIN комментирует...

Аноним, приветствую.
Вообще странно.
Куки учитываете? Редирект возможный?
Надо бы от curl побольше инфы получить в этом случае.
У меня в Германии сервера есть, но подобных проблем не было. Не смогу помочь, не видя кода (

Mari комментирует...

Снова возвращается пустой массив

array (size=0)
empty

с чем это могло бы быть связано?
регион 38.

GIN комментирует...

Mari, спасибо, код подправил. Правда высока вероятность моментально нарваться на капчу (

Unknown комментирует...

Я так понимаю когда парсер начинает выдавать "array(0) { }" то это бан от поисковой системы? В чем именно он заключается? что при этом можно сделать? как обойти его?
Спасибо автору заранее

GIN комментирует...

Sergey, все правильно - это бан от Яндекса. Ему не нравится нездоровая активность, что довольно логично. Чтобы минимизировать риск бана надо писать парсер с поддержкой js, использовать дополнительно proxy или tor. Об этом я напишу как-нибудь отдельную статью.

Unknown комментирует...

А что конкретно запоминает яндекс для бана? Я так понимаю это не CURLOPT_USERAGENT т.к. я заменял его. И еще один вопросик со скриншотом по поводу кодировки скину на почту. Буду благодарен за ответ

Unknown комментирует...

выводит пустой массив для запроса "беседки для дачи"

Unknown комментирует...

выводит пустой массив для запроса "беседки для дачи" регион москва

Unknown комментирует...

через опен сервер запускаю - пустые массивы выводит

Марат комментирует...

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

Мне подсказали, что логику парсера можно взять из этой проги (открытый код): Модуль Yandex Parser, но я не понимаю как отправить файл (капчу) на антигейт.

Анонимный комментирует...

К сожалению, парсер больше не работает.

Copyright © 2010 WEB IT blog