Справочники, инструменты, документация

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

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

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

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

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

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

  • 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+.

Минусы:

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

Плюсы:

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