Снимаем точные позиции сайта с «Яндекс Поиска» бесплатно через API «Вебмастера»

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

Если условно брать topvisor, то он может показывать постоянно топ-1 позицию но это может быть далеко не так. Либо перемешивание позиций в течении дня от топ1 до топ10 в моменте. Из-за этого посмотреть реальную позицию сайта текущими сервисами фактически нереально.

Мониторинг запросов (β) в Вебмастере

В вебмастере относительно недавно появилась функция мониторинг запросом, до этого Яндекс мог отдавать данные только по API

Мониторинг запросов в Яндекс Вебмастер<br />
Мониторинг запросов в Яндекс Вебмастер

Но данная система на текущий момент сырая и плохо читаемая.

- 20 запросов на одну страницу

- отображение данных только за 2 недели

Но в любом случае это лучше чем ничего.

Выгрузка данных с API

Яндекс позволяет выгружать данные вебмастера по API, и он позволяет получить довольно полезные данные.

  • Дата / поисковый запрос
  • TOTAL_SHOWS Количество показов.
  • TOTAL_CLICKS Количество кликов.
  • AVG_SHOW_POSITION Средняя позиция показа.
  • AVG_CLICK_POSITION Средняя позиция клика.

Для выгрузки с API я написал небольшой php класс который отлично выгружает данные в удобном виде для работы.

Сам класс:

<?php class YandexWebmaster { public $parametrs = array(); function __construct() { $this->token; $this->token_webmaster; $this->user_id; $this->url = ""; $this->url_path_config = ""; $this->answer = array(); $this->parametrs = []; $this->parametrs_webmaster = []; } public function GetFromWebmaster() { $parametrs = []+$this->parametrs_webmaster; $this->GetCurlWebmaster($parametrs); return($this->answer); } public function GetFromWebmasterData ($method, $parametrs = array()) { $this->url = "https://api.webmaster.yandex.net/v4.1/user/{$this->user_id}/hosts{$method}"; $this->parametrs_webmaster = $parametrs; return (object) $this->GetFromWebmaster(); } public function GetCurlWebmaster($parametrs) { $get = curl_init($this->url . urldecode($this->http_build_query_multi($parametrs))); curl_setopt($get, CURLOPT_HTTPHEADER, array('Authorization: OAuth '.$this->token_webmaster)); curl_setopt($get, CURLOPT_RETURNTRANSFER, true); curl_setopt($get, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($get, CURLOPT_HEADER, false); $result = curl_exec($get); curl_close($get); $result = json_decode($result, true); $this->answer=$result; if ($result['error_code'] == 'INVALID_OAUTH_TOKEN') { echo 'ОШИБКА: Авторизация к API webmaster не выполнена. Невалидный токен.' .PHP_EOL; } elseif ($result['error_code'] == 'INVALID_USER_ID') { echo 'ОШИБКА: Авторизация к API webmaster не выполнена. Невалидный юзер ид вебмастера.' .PHP_EOL; } } //Стата ключей с вебмастера public function webmaster_popular_words_data ($host, $start_day, $end_day, $offset, $limit) { $method = "/{$host}/search-queries/popular?"; $parametrs = [ 'order_by' => 'TOTAL_SHOWS', 'date_from' => $start_day, 'date_to' => $end_day, 'offset' => $offset, 'query_indicator' => ['TOTAL_SHOWS', 'TOTAL_CLICKS', 'AVG_SHOW_POSITION', 'AVG_CLICK_POSITION'], 'device_type_indicator' => ['ALL'], 'limit' => $limit, ]; $webmaster_day_data = $this -> GetFromWebmasterData($method, $parametrs); if ($webmaster_day_data->queries) { //echo "Собрано ключей с вебмастера: {$host} | {$start_day} | {$limit}" .PHP_EOL; } $data_words_key = []; if ($webmaster_day_data->queries) { foreach ($webmaster_day_data->queries as $value) { $word_key = $value['query_text']; $shows = $value['indicators']['TOTAL_SHOWS']; $click = $value['indicators']['TOTAL_CLICKS']; $avg_show = $value['indicators']['AVG_SHOW_POSITION']; $avg_click = $value['indicators']['AVG_CLICK_POSITION']; $data_words_key[$word_key] = array('shows' => $shows, 'click' => $click, 'avg_show' => $avg_show, 'avg_click' => $avg_click); } } return $data_words_key; } public function data_webmaster($config_data = array()) { $period_date_list = $this->getDatesFromRange($config_data['date'], $config_data['end_date']); $data = []; foreach ($period_date_list as $date) { $config_data['date'] = $date; $data[$date] = $this -> freq_data_webmaster($config_data); } return $data; } public function freq_data_webmaster($config_data = array()) { $this->token_webmaster = $config_data['token_webmaster']; $this->user_id = $config_data['user_id']; $host = "https:{$config_data['domain']}:443"; $count_days = 1; $webmaster_data = call_user_func_array('array_replace_recursive', $this->freq_data_webmaster_per_day($host, $config_data['date'], $config_data['date'], $config_data['limit'], $config_data['pages'])); $freq_data = []; foreach ($webmaster_data as $word_key => $word_data) { $ctr = ($word_data['click'] / $word_data['shows']) * 100; $word_key_clean = $this->strpos_array($word_key, $config_data['bad_keys_arr']); if ($word_data['shows'] >= $min_shows && $word_key_clean == NULL) { $freq_data[$word_key] = array( 'webmaster_freq' => round($word_data['shows'] / $count_days, 0, PHP_ROUND_HALF_UP), 'webmaster_click' => round($word_data['click'] / $count_days, 0, PHP_ROUND_HALF_UP), 'webmaster_avg_click' => round($word_data['avg_click'], 1, PHP_ROUND_HALF_UP), 'webmaster_avg_show' => round($word_data['avg_show'], 1, PHP_ROUND_HALF_UP), 'webmaster_ctr' => round($ctr, 1, PHP_ROUND_HALF_UP),); } } foreach ($freq_data as $word_key => $value) { if ($value['webmaster_freq'] > 0) { $total_avg_pos = ($value['webmaster_avg_click'] + $value['webmaster_avg_show']) / 2; $total_data_ya_webmaster[$word_key] = array( 'wm_freq_avg' => (int)$value['webmaster_freq'], 'wm_visit_avg' => (int)$value['webmaster_click'], 'wm_avg_click' => (float)$value['webmaster_avg_click'], 'wm_avg_show' => (float)$value['webmaster_avg_show'], 'total_avg_pos' => (float)round($total_avg_pos, 1, PHP_ROUND_HALF_UP), 'wm_ctr' => (float)$value['webmaster_ctr'] ); } } return $total_data_ya_webmaster; } public function http_build_query_multi($array, $qs='',$index = false) { foreach($array as $par => $val) { if($index) $par = $index; if(is_array($val)) { $qs = $this->http_build_query_multi($val, $qs,$par); } else { $qs .= $par.'='.$val.'&'; } } return $qs; } public function getDatesFromRange($start, $end, $format = 'Y-m-d') { $array = array(); $interval = new DateInterval('P1D'); $realEnd = new DateTime($end); $realEnd->add($interval); $period = new DatePeriod(new DateTime($start), $interval, $realEnd); foreach($period as $date) { $array[] = $date->format($format); } return $array; } public function strpos_array($haystack, $needles) { if ( is_array($needles) ) { foreach ($needles as $str) { if ( is_array($str) ) { $pos = strpos_array($haystack, $str); } else { $pos = mb_strpos($haystack, $str); } if ($pos !== FALSE) { return $pos; } } } else { return mb_strpos($haystack, $needles); } } public function freq_data_webmaster_per_day($host, $start_day, $end_day, $limit, $pages) { $i = 0; $webmaster_data_arr = []; $limit = $limit; $offset = $limit; $limit_split = $pages; for ($i = 0; ; $i++) { if ($i === 0) { $offset_i = 0; } else { $offset_i = $i*$offset; } $webmaster_data_arr[$i] = $this->webmaster_popular_words_data ($host, $start_day, $end_day, $offset_i, $limit); if (empty($webmaster_data_arr[$i])) { echo 'BREAK 1'; } if ($i == $limit_split) { break; } } return $webmaster_data_arr; } } ?>

Получение данных и прототип таблички

<?php require_once 'YandexWebmaster.class.php'; //фаил с классом $config_data = array( 'token_webmaster' => 'XXXXXXXXXXX-XXXXXXXXXXX', //токен https://oauth.yandex.ru/client/new 'user_id' => 'user_id', //user id от токена 'date' => '2023-06-01', // начальная дата с которой будем собирать данные 'end_date' => '2023-06-03', // конечная 'domain' => 'domain.com', // имя домена 'limit' => 10, // количество запросов (от меньшего к большему максимально 500) 'pages' => 0, // страницы, до 5 страниц т.е 3000 запросов (считается от 0) 'bad_keys_arr' => array('.','-') //стоп слова для фильтрации ); $YandexWebmaster = new YandexWebmaster(); $webmaster_data = $YandexWebmaster->data_webmaster($config_data); ?> <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> </head> <body> <div class="container"> <table class="table"> <thead> <tr> <th scope="col">Запрос</th> <th scope="col">Частота в день</th> <?php // Динамическое создание заголовков таблицы с датами foreach($webmaster_data as $date => $data) { echo "<th scope='col'>{$date}</th>"; } ?> </tr> </thead> <tbody> <?php // Сбор всех уникальных запросов $allQueries = []; foreach($webmaster_data as $data) { foreach($data as $query => $query_data) { if(!in_array($query, $allQueries)) { $allQueries[] = $query; } } } // Создание строки для каждого запроса foreach($allQueries as $query) { echo "<tr>"; echo "<td>{$query}</td>"; // Общая частота в день для запроса $totalFreq = 0; $daysCount = 0; foreach($webmaster_data as $data) { if(isset($data[$query])) { $totalFreq += $data[$query]['wm_freq_avg']; $daysCount++; } } $avgFreq = round($totalFreq / $daysCount, 0, PHP_ROUND_HALF_UP); echo "<td>{$avgFreq}</td>"; // Данные для каждой даты foreach($webmaster_data as $data) { if(isset($data[$query])) { echo "<td><small>Позиция просмотра {$data[$query]['wm_avg_show']} <br> Поз.кл {$data[$query]['wm_avg_click']} <br> CTR {$data[$query]['wm_ctr']}<br> Визитов {$data[$query]['wm_visit_avg']}</small></td>".PHP_EOL; } else { echo "<td></td>"; } } echo "</tr>"; } ?> </tbody> </table> </div> </body> </html>

- Лимит запросов до 3000

- Можно выгружать данные за 90 дней (больше API не хранит).

- Возможность фильтровать данные от мусора

- Работает из коробки на php 7.4+

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

Как можно улучшить

Итоговый результат который можно получить.<br />
Итоговый результат который можно получить.

Можно привязать к БД и логировать данные, также можно соотнести данные с Яндекс Метрикой, добавить графики и.т.д

- Видеть ср. позицию клика и ср. позицию просмотра

- Видеть просмотры всей выдачи по всему кластеру ключей

- Видеть суточный AVG всех позиций

- Замерять суточные клики из вебмастера

А вот так выглядят позиции того же сайта в topvisor... или любом другом сервисе.<br />
А вот так выглядят позиции того же сайта в topvisor... или любом другом сервисе.

Бандит Яндекса постоянно мешает выдачу, я пока не видел проектов где AVG прошел отметку 2.5. Возможно на небольших сайтах AVG может и пробьет отметку в 2.5.

Минусы

  • Для сбора корректной информации ключ должен быть в топ10
  • Данные идут с задержкой 1-3 дня

Плюсы

  • Мы получаем реальную картину позиций сайта
  • Бесплатно
  • Мы получаем гораздо больше данных чем может дать классический сервис
  • Выбор мобилы/десктоп/планшеты

Подписываемся на Блог, будут ещё полезные материалы.

UPD: скрипт получения oath токена яндекс API https://hastebin.com/share/guxibigawo.php

20
9 комментариев

Все конечно хорошо, но будет инструкция для особо одаренных, как пользоваться скриптом?

5
Ответить

Данные идут с задержкой 1-3 дня

т.е. нет смысла устанавливать вчерашнюю дату?

Ответить
Автор

Да, в вебмастере можете посмотреть последнюю актуальную дату

2
Ответить

Офигеть, то есть, позиции в Яндексе скачут настолько часто? Спасибо за информацию.
И вопрос по строчке "Бандит Яндекса постоянно мешает выдачу, я пока не видел проектов где AVG прошел отметку 2.5" - имеете в виду, что никто в выдаче не находится всегда в топ-1 на самом деде и Яндекс постоянно смещает/возвращает сайт на 1 позицию или что-то другое?

1
Ответить
Автор

да

1
Ответить

не меняет, выдача конечно живая, но главное то, что она персональная, каждому человеку + - своя позиция

Ответить

По ссылке пусто

1
Ответить