Pull to refresh

Парсим выдачу Яндекса

Reading time10 min
Views38K
Приветствую всех читателей!

Я начал заниматься SEO недавно, и сразу же столкнулся с задачей определения позиций продвигаемых сайтов по ключевым словам в поисковиках. Задача тривиальная и решается на ура различным ПО, которое у всех SEO-специалистов на устах: Semonitor, AllSubmitter, etc. Помимо проприетарности, которой попахивает от всех подобного рода программ, в них есть, как это не парадоксально, ряд технических моментов, из-за которых хочется выбросить компьютер в окно.
Я бы не прочь купить Semonitor, но попользовав демо-версию, решил от этой идеи отказаться — на оф.сайте программы доступная для скачивания версия у меня глючила, требовала обновить себя, а после выпрошенного у меня обновления и вовсе отказывалась заниматься анализом позиций. Самому настроить, как Вы понимаете, нельзя.
AllSubmitter в этом плане получше, позволяет даже кастомизировать регулярные выражения для поисковиков, что вроде как делает этот софт устойчивым к смене формата выдачи, однако и с ним не все слава богу — когда 18.08.2008г. Яндекс вдруг изменил формат выдачи результатов поиска, вместе с тем поменяв и URL ссылок (возможно, эксперименты с вводом учета переходов, подробнее об этом писалось здесь), то и AllSub оказался бессилен. Правда, на следующий день Яндекс снова вернулся к старому формату выдачи, но прецедент, тем не менее, произошел.

Я тогда ради интереса изобретал велосипед: решил написать анализатор позиций, причем на PHP. У меня не было цели дойти до production'а, просто хотелось прочувствовать, как работают всякие там Semonitor'ы и Allsubmitter'ы. А потому, написав классы для парсинга Яши, Гугла и Рамблера, потестив и убедившись, что все пашет, удовлетворенно забыл, поскольку был AllSubmitter, и огород городить было незачем, было предостаточно и других задач.

Постановка задачи


Когда на Хабре написали про PHP-класс для работы с Яндекс.XML и я обильно прокомментировался по этому поводу, то по наличию плюсов в карму понял, что тема неплоха для оформления в статью, тем более что возможность появилась — я вылез из минусов. И хотя речь там шла немного о другом — об организации поиска по сайту, используя Yandex.XML, задача анализа позиций сайта по ключевым словам и фразам пересекается с первой. Итак, моя задача:
создать анализатор позиций сайтов в выдаче поисковых систем (пока Яндекса)

Решение


Тут нету ничего сложного.

Во-первых,


все необходимое для получения результатов поиска передается через GET-параметры, а именно:
  • text — текст поискового запроса. URL-кодированный.
  • p — номер страницы выдачи (p=0, 1, 2...). Without comments
  • numdoc — число выдаваемых результатов на страницу(numdoc=10, 20, 30, 50; допустимы только эти значения и никакие более). Для анализа позиций сайта по ключевым словам лучше всего юзать наибольшее значение, т.к. явное уменьшение нагрузки на сервер и, как следствие — меньше подозрений, что «Я, робот»

Шаблон строки запроса:
yandex.ru/yandsearch?text=[KEYWORD]&p=[PAGE_NUMBER]&numdoc=[RESULTS_ON_PAGE]

Во-вторых,


Выдача Яндекса подвергается разбору на нужные нам составляющие, путем прогона через регулярное выражение вида:
#<li>.*<a[^>]*tabindex[^>]*onclick[^>]*=[^>]*"[^>]*"[^>]*href="([^<>"]+)"[^>]*>(.+)</a>.*</li>#Ui

Вот таким путем:
  1. preg_match_all(РЕГВЫР, HTML_ВЫДАЧИ, МАССИВ_РЕЗУЛЬТАТОВ, PREG_SET_ORDER);
* This source code was highlighted with Source Code Highlighter.


В результате чего получается массив (в нем numdoc элементов) массивов (3 элемента в подмассиве: html с одним результатом выдачи, url найденной страницы выдачи, ее заголовок).

В итоге,


Вот по какой схеме я работаю, чтобы найти сайт в Яндексе по определенному запросу (поиск до первого вхождения):
  1. получаю первую страницу с выдачей
  2. если это не «страница недоверия» Яндекса с капчей — прогоняю через регвыр, перебираю результаты в поиске нужного.
  3. если нахожу, возвращаю результат — номер позиции страницы в выдаче, не нахожу — получаю следующую страницу и возвращаюсь к п.2, подождав секунды 3-5

В качестве параметров к процессу анализа у меня имеется:
  • resultsLimit — ограничение по глубине поиска, по умолчанию
    люблю ставить 200
  • url — имя хоста, который ищем в выдаче. Учитывается любая страница с этого хоста
  • keyword — запрос к поисковику.

Реализовано


это посредством иерархии классов (чтобы при надобности легко расширять функциональность анализатора на другие поисковики).
Абстрактный класс — SomeAnalyzer:

  1. abstract class SomeAnalyzer{
  2.    
  3.    //// ИНТЕРФЕЙС
  4.    
  5.    // функция анализа
  6.    public abstract function analyzeThis($url);
  7.    
  8.    // получение имени хоста из url (parse_url с дополнительным функционалом, поскольку убедился что просто parse_url не всегда почему-то работает, когда url слишком неудобочитаемый)
  9.    public function getHost($url){
  10.       $url=@parse_url($url);
  11.       if($url['path'] && !$url['host'])
  12.          $url['host']=$url['path'];
  13.       $url['host']=ereg_replace("/.*$", "", $url['host']);
  14.       $url['host']=ereg_replace("^www\.", "", $url['host']);
  15.       
  16.       return $url['host'];
  17.    }
  18.    
  19.    //// РЕАЛИЗАЦИЯ
  20.    
  21.    // функция сравнения 2-х url на предмет принадлежности к одному хосту
  22.    protected function compareURL($url1, $url2){
  23.       $url1=$this->getHost($url1);
  24.       $url2=$this->getHost($url2);
  25.       
  26.       return(strtoupper($url1['host'])==strtoupper($url2['host'])? true: false);
  27.    }
  28. }
* This source code was highlighted with Source Code Highlighter.

Класс анализатора выдачи Яндекса:

  1. class YandexAnalyzer extends SomeAnalyzer{
  2.    
  3.    //// ИНТЕРФЕЙС
  4.    
  5.    // настройки
  6.    public $resultsLimit=200; // лимит результатов выдачи
  7.    public $url;
  8.    public $keyword;
  9.    
  10.    public $resultsOnPage=50; // можно только 10, 20, 30, 50
  11.    
  12.    // функция анализа
  13.    public function analyzeThis($url, $keyword=''){
  14.       $this->url=$url;
  15.       $this->keyword=$keyword;
  16.       $x=0;
  17.       while($x*$this->resultsOnPage<=$this->resultsLimit-1){
  18.          if($results=$this->analyzePage(str_replace(array("\r", "\n", "\t"), '', $this->downloadPage($x)))){
  19.             $results[0]=$x*$this->resultsOnPage+$results[0];
  20.             return $results;
  21.          }
  22.          $x++;
  23.          sleep(rand(3, 5));
  24.       }
  25.       return false;
  26.    }
  27.    
  28.    //// РЕАЛИЗАЦИЯ
  29.    
  30.    protected $regexpParseResults='#<li>.*<a[^>]*tabindex[^>]*onclick[^>]*=[^>]*"[^>]*"[^>]*href="([^<>"]+)"[^>]*>(.+)</a>.*</li>#Ui';
  31.    protected $urlMask='http://yandex.ru/yandsearch?text=[KEYWORD]&p=[PAGE_NUMBER]&numdoc=[RESULTS_ON_PAGE]';
  32.    
  33.    protected function downloadPage($pageNumber){
  34.       $mask=str_replace('[KEYWORD]', urlencode($this->keyword), $this->urlMask);
  35.       $mask=str_replace('[PAGE_NUMBER]', $pageNumber, $mask);
  36.       $mask=str_replace('[RESULTS_ON_PAGE]', $this->resultsOnPage, $mask);
  37.       
  38.       return file_get_contents($mask);
  39.    }
  40.    
  41.    protected function analyzePage($content){
  42.       
  43.       if(preg_match_all($this->regexpParseResults, $content, $matches, PREG_SET_ORDER)!==false){
  44.          if(count($matches)<=0)
  45.             deb('<br /><span style=«color: red;»>Не найдено вхождений или ошибка парсера: возможно гугл подозревает, что Вы робот!</span>');
  46.          else
  47.             foreach($matches as $num=>$match){
  48.                if($this->compareURL($match[1], $this->url))
  49.                   return array($num+1, $match[1], $match[2]);
  50.             }
  51.       }
  52.       else deb('<span style=«color: red;»>Не найдено вхождений или ошибка парсера: возможно йандекс подозревает, что Вы робот!</span>');
  53.       
  54.       return false;
  55.    }
  56. }
* This source code was highlighted with Source Code Highlighter.

Хочу обратить Ваше внимание на один момент: не знаю, что это было 18 числа этого месяца сего года, но у Яндекса поменялся формат выдачи, пришлось менять регулярки и дописывать классы, однако уже 19 числа все с изумлением наблюдали то, как яндексоиды возвратили все как было. И, почему бы и нет, это может повториться, потому привожу список изменений и дополнений в класс YandexAnalyzer, которые нужно будет сделать, если вдруг формат выдачи станет как 18.08.2008г.:
  • строка №30 теперь имеет вид:
    1. protected $regexpParseResults='#<li>.*<a[^>]*tabindex[^>]*href="([^<>"]+)"[^>]*>(.+)</a>.*</li>#Ui';
    * This source code was highlighted with Source Code Highlighter.
  • строка №48 также поменялась:
    1. if($this->compareURL($this->getUrlFromYa($match[1]), $this->url))
    * This source code was highlighted with Source Code Highlighter.

  • Вы наверно заметили, что в строке №48 используется теперь функция getUrlFromYa? Мало того что поменялся формат выдачи, ссылки стали не прямые, посему понадобилась дополнительная операция извлечение адреса хоста из огромного урла яндекса. Вот код функции, добавьте его в конец класса YandexAnalyzer:
    1. protected function getUrlFromYa($yaUrl){
    2.    if(preg_match_all('/[\*](http:.*)$/Ui', $yaUrl, $matches, PREG_SET_ORDER)!==false)
    3.       return $matches[0][1];
    4.    return false;
    5. }
    * This source code was highlighted with Source Code Highlighter.



Результат


Вот небольшой код для тестирования:

  1. $url=«vinzavod.ru»;
  2. $keywords=array(
  3.    'винзавод',
  4.    'алкоголь производство',
  5.    'производство алкоголя',
  6.    'продажа алкоголя',
  7.    'производители алкоголя',
  8.    'вино',
  9.    'вина',
  10.    'производство вина',
  11.    'продажа вина',
  12.    'коньяк',
  13.    'коньяки',
  14.    'производство коньяка',
  15.    'продажа коньяков',
  16.    'продажа коньяка',
  17.    'продажа коньяк',
  18.    'настойка',
  19.    'настойки',
  20.    'производство настоек',
  21.    'продажа настоек',
  22.    'вермут',
  23.    'вермуты',
  24.    'производство вермута',
  25.    'портвейн',
  26.    'портвейны',
  27.    'портвейн 777',
  28.    'продажа портвейнов',
  29.    'алкоголь',
  30.    'алкогольная продукция',
  31.    'фирменный алкоголь',
  32.    'алкогольные напитки',
  33.    'классические алкогольные напитки'
  34. );
  35.  
  36. $g=new YandexAnalyzer();
  37. foreach($keywords as $keyword){
  38.    if($res=$g->analyzeThis($url, $keyword)){
  39.       deb('<span style=«color: green;»>'.$res[0].'-я позиция сайта '.$url.' по фразе <a href="'.$url.'"'.$keyword.'</a>"</span>');
  40.    }
  41.    else
  42.       deb($url.' не найден в первых '.$g->resultsLimit.' результатах по фразе "'.$keyword.'"');
  43.    sleep(rand(3, 5));
  44. }
* This source code was highlighted with Source Code Highlighter.

и результат тестирования:
4-я позиция сайта vinzavod.ru по фразе «винзавод»
13-я позиция сайта vinzavod.ru по фразе «алкоголь производство»
158-я позиция сайта vinzavod.ru по фразе «производство алкоголя»
45-я позиция сайта vinzavod.ru по фразе «продажа алкоголя»
vinzavod.ru не найден в первых 300 результатах по фразе «производители алкоголя»
181-я позиция сайта vinzavod.ru по фразе «вино»
255-я позиция сайта vinzavod.ru по фразе «вина»
4-я позиция сайта vinzavod.ru по фразе «производство вина»
56-я позиция сайта vinzavod.ru по фразе «продажа вина»
94-я позиция сайта vinzavod.ru по фразе «коньяк»
56-я позиция сайта vinzavod.ru по фразе «коньяки»
7-я позиция сайта vinzavod.ru по фразе «производство коньяка»
5-я позиция сайта vinzavod.ru по фразе «продажа коньяков»
7-я позиция сайта vinzavod.ru по фразе «продажа коньяка»
5-я позиция сайта vinzavod.ru по фразе «продажа коньяк»
11-я позиция сайта vinzavod.ru по фразе «настойка»
17-я позиция сайта vinzavod.ru по фразе «настойки»
3-я позиция сайта vinzavod.ru по фразе «производство настоек»
1-я позиция сайта vinzavod.ru по фразе «продажа настоек»
30-я позиция сайта vinzavod.ru по фразе «вермут»
25-я позиция сайта vinzavod.ru по фразе «вермуты»
32-я позиция сайта vinzavod.ru по фразе «производство вермута»
32-я позиция сайта vinzavod.ru по фразе «портвейн»
15-я позиция сайта vinzavod.ru по фразе «портвейны»
93-я позиция сайта vinzavod.ru по фразе «портвейн 777»
4-я позиция сайта vinzavod.ru по фразе «продажа портвейнов»

Перспективы и планы на будущее


  • обуздать как минимум тройку Яндекс, Гугл, Рабмлер.
  • сделать человеческую оболочку для пользования, лучший вариант — ajax-приложение, с поддержкой проектов и сохранением на сервере результатов анализов

Статья готовилась в течении недели, поэтому на момент ее написания уже реализованы анализаторы для Яндекса, Гугла, Рабмлера, и пишется потихоньку приложение. Но это — тема уже следующих постов))
P.S.: Мой первый пост на Хабре, прошу больно не пинать, а конструктивно комментировать))
Tags:
Hubs:
Total votes 49: ↑28 and ↓21+7
Comments17

Articles