Столкнулся с задачей мониторинга активности пользователей всем известной социальной сети. Передо мной стояла задача собирать данные о количестве пользователей, находящихся онлайн в определенной группе или сообществе.
Поскольку сам я занимаюсь веб-разработкой, то инструменты мной использовались такие
Объясню свой выбор — Vk API — дело в том, что получить количество пользователей онлайн можно и без API, а спарсив страницу поиска по пользователям с фильтром сообщества и метки онлайн, однако я предпочел не возиться с авторизацией и разбором тегов, а применить программный интерфейс.
Реализацию условно можно разделить на 2 части. Первая — скрипт, который на id группы находит количество пользователей онлайн и записывает его в БД. Вторая — админка, позволяющая добавлять новый группы для мониторинга и просматривать статистику по уже добавленным группам.
Что бы статистика была актуальна, необходимо как можно чаще мониторить состояние группы в текущий момент времени. Скрипт стоит повесить в Cron, пусть он у нас вызывается каждых 5 минут.
Если с админкой все более-менее понятно, то вот со скриптом сбора статистики не совсем. Ознакомившись с методами, предоставляемыми API, прихожу к первому решению.
С помощью методов groups.getMembers, users.get получаем список участников группы и их статус — онлайн или оффлайн. Далее считаем сколько пользователей онлайн. Все просто. Однако кажущаяся простота в результате приносит ряд проблем.
Все бы хорошо, если у Вас группы маленькой численностью (до 1000 человек). В противном случае упираемся в ограничения API — за один раз можно получить информацию только о 1000 пользователей. Что нам это ограничение — можно же вызывать метод в цикле, но нет. Производить вызовы API разрешено не чаще 3 запросов в секунду.
Посчитаем примерное количество запросов которое понадобится. Возьмем сообщество habrahabr Vk. Оно насчитывает более 40.000 пользователей, следовательно нам понадобится ~40 запросов чтобы получить членов сообщества и 40 запросов — их статус.
Отправляемся искать новое решение.
Обнаруживаем в документации метод execute
Принимает он на вход строку с кодом написанным на так называемом VKScript (похож на javascript). Проблема лишь в том, что вменяемая документация по этому методу и самому языку отсутствует. Вероятно решение найдено, так что можно углубится в изучение API Vk и VKScript в частности.
Скачиваем класс для работы с API, предлагаемый разработчиками. Я привел его только к более приемлемому виду, чтобы он вписался в coding style применяемый в Zend Framework.
Аутентификацию и авторизацию я описывать не буду, так как она осуществляется через OAuth, много информации в рунете, да и на странице Vk API.
Осуществим пробный вызов к API — получим первые 20 постов в группе habrahabr

Сейчас сделаем тоже самое, только через метод execute
В итоге получаем один и тот же результат.
Одно что плохо — это то, что мы смешали код VKScript и PHP. Выглядит это очень плохо. Займемся рефакторингом.
Было бы неплохо, чтобы каждый скрипт хранился в отдельном файле и вызвать его можно было бы одной функцией. Еще необходимо предусмотреть то, что в последствии нам еще понадобится передавать какие-то данные в этот скрипт ( сейчас например owner_id жестко забит в код).
В корне нашего модуля создадим папку с названием «vkscripts», в нее будем складывать наши скрипты (например getWalls.vks). Пропишем путь к скриптам в config-файле application.ini
Нам нужен класс, который был бы удобен для вызова скриптов, расположенных в этой директории. Воспользуемся возможностями PHP5, а именно магическим методом __call. По названию вызываемого метода мы будем искать скрипт с таким названием.
Итак, давайте что-нибудь сделаем с этим классом.
В папку vkscripts кладем файл getWalls.vks с таким содержимым
В контроллере:
Мы получили тот же результат, только налицо существенные плюсы: мы разнесли код в отдельные файлы, сделали его более читабельным, упростили вызов execute.
Следующий шаг — добавление возможности передавать параметры в наш скрипт. Воспользуемся для этого неким представлением. В коде VKScript вначале при необходимости что-то получить на вход будем писать так:
А в нашем классе будем перед вызовом api с этим кодом заменять %VAR_NAME% на значение переменной.
Допишем наш класс Executor следующим образом
В контроллере же при необходимости передачи параметром пишем следующее
Что соответственно подставит в наш скрипт вместо %GROUP_ID% и %OFFSET% переданные значения.
Вот как выглядит структура модуля

Cуществует ограничение на вызов методов API в execute. Лимит 22 вызова (найден практически). Так же в паутине я не нашел информации о том, что и на другие операторы (например сложение, вычитание ) тоже существуют ограничения, однако они есть. Поскольку если пробегать по массиву пользователей и считать количество онлайн я получал ошибку о превышенном числе операций, то было решено возвращать из execute полный список пользователей, после чего уже на стороне моего сервера считать их количество.
Из-за ограничения в числе запросов к API в методе execute нам все равно придется выполнить как минимум 1 запрос на 10.000 участников группы, потому что для обработки 1.000 требуется 2 запроса.
Вот скрипт который получился
Немного прокомментирую свой код. Счетчик _acl — для предотвращения ошибки из-за превышения лимита операций с API. users@.online — возвращаем только список значений [0,1,1,0,0,0,1,0,1] онлайн-оффлайн.
В контроллере вызываем этот скрипт, последовательно увеличивая offset, пока не пробежимся по всем участникам группы.
Итак протестим и увидим — данные полученные через API почти совпадают с данными с vk.com, возможно эта неточность из-за кешей, или по другой причине, не видной извне.
VKScript не поддерживает функции, операторы инкремента, декремента.
Мы разработали инструментарий для работы с API vk.com через метод execute. С помошью его можно разрабатывать приложения сбора статистики и т.д. причем выглядеть это будет очень даже приглядно. Прикрутить к этому всему интерфейс — это уже тривиальная задача. В конце замечу, что другая социальная сеть Facebook предоставляет доступ к исполнению кода, написанного на языке называемом FQL (Facebook Query Language, схож с SQL), у которого возможностей явно побольше чем у VKScript со всеми его ограничениями.
VK API
Метод execute, краткое описание и пример VKScript
Facebook Query Language
Инструменты
Поскольку сам я занимаюсь веб-разработкой, то инструменты мной использовались такие
- PHP 5 (Zend Framework)
- Vk API
- Cron
Объясню свой выбор — Vk API — дело в том, что получить количество пользователей онлайн можно и без API, а спарсив страницу поиска по пользователям с фильтром сообщества и метки онлайн, однако я предпочел не возиться с авторизацией и разбором тегов, а применить программный интерфейс.
Архитектура
Реализацию условно можно разделить на 2 части. Первая — скрипт, который на id группы находит количество пользователей онлайн и записывает его в БД. Вторая — админка, позволяющая добавлять новый группы для мониторинга и просматривать статистику по уже добавленным группам.
Что бы статистика была актуальна, необходимо как можно чаще мониторить состояние группы в текущий момент времени. Скрипт стоит повесить в Cron, пусть он у нас вызывается каждых 5 минут.
Обзор Vk API
Если с админкой все более-менее понятно, то вот со скриптом сбора статистики не совсем. Ознакомившись с методами, предоставляемыми API, прихожу к первому решению.
Первое решение (неверное)
С помощью методов groups.getMembers, users.get получаем список участников группы и их статус — онлайн или оффлайн. Далее считаем сколько пользователей онлайн. Все просто. Однако кажущаяся простота в результате приносит ряд проблем.
Все бы хорошо, если у Вас группы маленькой численностью (до 1000 человек). В противном случае упираемся в ограничения API — за один раз можно получить информацию только о 1000 пользователей. Что нам это ограничение — можно же вызывать метод в цикле, но нет. Производить вызовы API разрешено не чаще 3 запросов в секунду.
Посчитаем примерное количество запросов которое понадобится. Возьмем сообщество habrahabr Vk. Оно насчитывает более 40.000 пользователей, следовательно нам понадобится ~40 запросов чтобы получить членов сообщества и 40 запросов — их статус.
Отправляемся искать новое решение.
Второе решение (верное)
Обнаруживаем в документации метод execute
Универсальный метод, который позволяет запускать последовательность других методов, сохраняя и фильтруя промежуточные результаты.
Принимает он на вход строку с кодом написанным на так называемом VKScript (похож на javascript). Проблема лишь в том, что вменяемая документация по этому методу и самому языку отсутствует. Вероятно решение найдено, так что можно углубится в изучение API Vk и VKScript в частности.
Работа с API
Скачиваем класс для работы с API, предлагаемый разработчиками. Я привел его только к более приемлемому виду, чтобы он вписался в coding style применяемый в Zend Framework.
Класс Api
<?php class Vkapi_Model_Api { private $_accessToken = null; private $_apiUrl = 'https://api.vk.com/method/'; public function __construct($accessToken) { $this->_accessToken = $accessToken; } public function api($method, $params = array()) { $params['access_token'] = $this->_accessToken; $query = $this->_apiUrl. $method . '?' . $this->_params($params); $responseStr = file_get_contents($query); if(!is_string($responseStr)){ return null; } $responseObj = json_decode($responseStr); return $responseObj; } private function _params($params) { $pice = array(); foreach($params as $k=>$v) { $pice[] = $k.'='.urlencode($v); } return implode('&',$pice); } }
Аутентификацию и авторизацию я описывать не буду, так как она осуществляется через OAuth, много информации в рунете, да и на странице Vk API.
Осуществим пробный вызов к API — получим первые 20 постов в группе habrahabr
public function wallsAction() { //....... $api = new Vkapi_Model_Api($accessToken); $response = $api->api('wall.get',array('owner_id' => '-20629724')); $this->view->walls = $response->response; }

Сейчас сделаем тоже самое, только через метод execute
public function wallsAction() { //....... $api = new Vkapi_Model_Api($accessToken); $code = " var walls = API.wall.get({ owner_id : -20629724 }); return walls; "; $response = $api->api('execute',array('code' => $code )); $this->view->walls = $response->response; }
В итоге получаем один и тот же результат.
Одно что плохо — это то, что мы смешали код VKScript и PHP. Выглядит это очень плохо. Займемся рефакторингом.
Было бы неплохо, чтобы каждый скрипт хранился в отдельном файле и вызвать его можно было бы одной функцией. Еще необходимо предусмотреть то, что в последствии нам еще понадобится передавать какие-то данные в этот скрипт ( сейчас например owner_id жестко забит в код).
Выносим VKScript в отдельные файлы
В корне нашего модуля создадим папку с названием «vkscripts», в нее будем складывать наши скрипты (например getWalls.vks). Пропишем путь к скриптам в config-файле application.ini
vkapi.scripts.path = APPLICATION_PATH "/modules/vkapi/vkscripts"
Нам нужен класс, который был бы удобен для вызова скриптов, расположенных в этой директории. Воспользуемся возможностями PHP5, а именно магическим методом __call. По названию вызываемого метода мы будем искать скрипт с таким названием.
Исходник класса
<?php class Vkapi_Model_Executor { private $_api; public function __construct($api) { $this->_api = $api; } public function __call( $methodName, $arguments ) { $script = $this->_getScript($methodName); if(count($arguments)){ $script = $this->_prepareParams($script, $arguments[0]); } $response = $this->_api->api('execute', array('code' => $script)); if( $error = $this->_getError($response) ){ throw new Exception($error->error_msg, $error->error_code); } return $response->response; } private function _getError($response) { if( isset($response->error) ){ $error = $response->error; return $error; } return null; } private function _getScript( $name ) { $scriptsPath = Zend_Registry::get('vkapi_config')->scripts->path; $filePath = $scriptsPath . '/' . $name . '.vks'; if(is_file($filePath)){ $script = file_get_contents($filePath); return $script; } return null; } }
Итак, давайте что-нибудь сделаем с этим классом.
В папку vkscripts кладем файл getWalls.vks с таким содержимым
var walls = API.wall.get({ owner_id : -20629724 }); return walls;
В контроллере:
public function wallsAction() { //....... $api = new Vkapi_Model_Api($accessToken); $executor = new Vkapi_Model_Executor($api); $response = $executor->getWalls(); $this->view->walls = $response->response; }
Мы получили тот же результат, только налицо существенные плюсы: мы разнесли код в отдельные файлы, сделали его более читабельным, упростили вызов execute.
Следующий шаг — добавление возможности передавать параметры в наш скрипт. Воспользуемся для этого неким представлением. В коде VKScript вначале при необходимости что-то получить на вход будем писать так:
var groupId = %GROUP_ID%; var offset = %OFFSET%; // .... здесь пошел наш код
А в нашем классе будем перед вызовом api с этим кодом заменять %VAR_NAME% на значение переменной.
Допишем наш класс Executor следующим образом
Исходник доработанного класса
<source lang="php"> <?php class Vkapi_Model_Executor { // ...... public function __call( $methodName, $arguments ) { $script = $this->_getScript($methodName); if(count($arguments)){ $script = $this->_prepareParams($script, $arguments[0]); } $response = $this->_api->api('execute', array('code' => $script)); if( $error = $this->_getError($response) ){ throw new Exception($error->error_msg, $error->error_code); } return $response->response; } // ...... private function _prepareParams($script, $params) { foreach ($params as $key => $value){ $script = str_replace('%' . strtoupper($key) . '%', $value, $script); } return $script; } }
В контроллере же при необходимости передачи параметром пишем следующее
public function wallsAction() { //....... $api = new Vkapi_Model_Api($accessToken); $executor = new Vkapi_Model_Executor($api); $response = $executor->getWalls(array( 'group_id' => -20629724, 'offset' => 0 )); $this->view->walls = $response->response; }
Что соответственно подставит в наш скрипт вместо %GROUP_ID% и %OFFSET% переданные значения.
Вот как выглядит структура модуля

Получаем количество пользователей онлайн
Cуществует ограничение на вызов методов API в execute. Лимит 22 вызова (найден практически). Так же в паутине я не нашел информации о том, что и на другие операторы (например сложение, вычитание ) тоже существуют ограничения, однако они есть. Поскольку если пробегать по массиву пользователей и считать количество онлайн я получал ошибку о превышенном числе операций, то было решено возвращать из execute полный список пользователей, после чего уже на стороне моего сервера считать их количество.
Из-за ограничения в числе запросов к API в методе execute нам все равно придется выполнить как минимум 1 запрос на 10.000 участников группы, потому что для обработки 1.000 требуется 2 запроса.
Вот скрипт который получился
var groupId = %GROUP_ID%; var offset = %OFFSET%; // API call limit var _acl = 22; var members = API.groups.getMembers({ gid : groupId }); _acl = _acl - 1; var count = members.count; var users = []; while( _acl > 1 && offset < count){ var _members = API.groups.getMembers({ gid : groupId, offset : offset }); _acl = _acl - 1; users = users + API.users.get({ uids : members.users, fields : "online" }); _acl = _acl - 1; offset = offset + 1000; } var result = { count : count, offset : offset, users : users@.online }; return result;
Немного прокомментирую свой код. Счетчик _acl — для предотвращения ошибки из-за превышения лимита операций с API. users@.online — возвращаем только список значений [0,1,1,0,0,0,1,0,1] онлайн-оффлайн.
В контроллере вызываем этот скрипт, последовательно увеличивая offset, пока не пробежимся по всем участникам группы.
$count = 1; $offset = 0; $nowOnline = 0; while($count > $offset){ $users = $executor->getOnline(array( 'group_id' => $groupId, 'offset' => $offset )); $count = $users->count; $offset = $users->offset; foreach ( $users->users as $online){ if($online){ $nowOnline++; } } }
Итак протестим и увидим — данные полученные через API почти совпадают с данными с vk.com, возможно эта неточность из-за кешей, или по другой причине, не видной извне.
Замечания
VKScript не поддерживает функции, операторы инкремента, декремента.
Итог
Мы разработали инструментарий для работы с API vk.com через метод execute. С помошью его можно разрабатывать приложения сбора статистики и т.д. причем выглядеть это будет очень даже приглядно. Прикрутить к этому всему интерфейс — это уже тривиальная задача. В конце замечу, что другая социальная сеть Facebook предоставляет доступ к исполнению кода, написанного на языке называемом FQL (Facebook Query Language, схож с SQL), у которого возможностей явно побольше чем у VKScript со всеми его ограничениями.
Ссылки
VK API
Метод execute, краткое описание и пример VKScript
Facebook Query Language
