На днях стояла задача посчитать количество пользователей сайта vk.com, поделившихся определенным постом (т.е. количество репостов). Для этого существует приложение «Вирусоанализатор», но в процессе использования выяснилось, что оно не считает большое количество репостов (от 100 и более). Ползунок доходит почти до конца и на этом останавливается. А т.к. уже был объявлен конкурс на наибольшее количество репостов на определенную запись, то нужна была альтернатива. Которой не оказалось…
Поэтому пришлось обращаться к API Вконтакте и искать как реализовать данную задачу. Сразу скажу, что не пришлось создавать standalone-приложения для этой цели. Все получилось реализовать при помощи методов, не требующих access_token. Ниже представлен список методов API, используемых в данной задаче:
- likes.getList — получение списка пользователей, который нажали «Мне нравится» или «поделились» постом
- users.get — получение информации о пользователях по их uid или коротким именам
- wall.get — получение новостей со страницы пользователя.
Из плюсов данного расчета можно выделить только то, что не нужно создавать приложение для этой цели.
Из минусов (если изучить эти API методы): неудобный поиск нашего репоста у пользователя. Вконтакте разрешает получить методом wall.get максимум 100 новостей. Разумеется данный метод поддерживает параметр offset (смещение по новостям), но все же я ограничился определенным количеством новостей для обработки (500 штук).
Некоторые возникшие проблемы:
Итак, нам нужно было данные о тех пользователях и их постах, которые поделились нашим репостом. Для получения этого списка используется метод API likes.getList, у которого есть параметр filter, принимающий два возможных значения:
- likes — пользователи, которые нажали «Мне нравится»
- copies — пользователи, которые поделились постом. Именно это и нужно.
Но когда уже что-то было готово, выявилось, что данные, полученные при помощи API не сходятся с реальными.
Например, пользователь который «поделился» не доставался методом likes.getList?filter=copies, но находился в списке, которым просто «понравилась» запись. К тому попался еще один пользователей с ситуацией с точностью до наоборот.
Поэтому для более верных результатов пришло обрабатывать весь список (пользователи, нажавшие «Мне нравится» и «Поделиться»).
Ниже приведен класс, написанный на PHP для поиска количества репостов:
class VKLIKES {
private $url = 'https://api.vk.com/method/'; // URL к методам API
private $owner_id; //ID автора поста
private $post_id; //ID поста
private $count = 1000; //По сколько "репостов" и "лайков" доставать
private $users = array(); //Массив с пользователями
private $countReposts; //Количество репостов у текущего пользователя
private $findPost; //ID найденного репоста в пользовательских новостях
private $find; //Флаг найден/не найден репост у пользователя
public function __construct($owner_id = '', $post_id = '') {
$this->owner_id = $owner_id;
$this->post_id = $post_id;
}
//Сообщение о действиях
private function printProgress($text, $start = true, $error = false) {
if ($error) $color = 'red';
else if ($start) $color = '#444';
else $color = 'green';
echo '<li style="color: '.$color.';">'.$text.'</li>';
ob_flush();
flush();
}
/*Считываем всех пользователей, кто поделился нашим постом
* $onwer_id - ID автора поста
* $post_id - ID-поста
* $fitler - "likes" или "copies"
* $offset - смещение по пользователям. Можно достать максимум 1000 пользователей
* $onlyCount - вернуть только количество репостов
* $start - используется для рекурсии
*/
private function getUsers($owner_id, $post_id, $filter, $offset = 0, $onlyCount = false, $start = true) {
//формируем URL со всеми параметрами
$url = $this->url.'likes.getList?type=post&friends_only=0&offset='.$offset.'&count='.$this->count.'&owner_id='.$owner_id.'&item_id='.$post_id.'&filter='.$filter;
//получаем результат запроса в JSON-фомате
$json = file_get_contents($url);
//преобразуем JSON в ассоциативный массив
$data = json_decode($json, true);
//если ответ, не содержит нужных данных
if (!isset($data['response'])) return false;
$response = $data['response'];
$count = $response['count']; //Получаем количество пользователей
//Понадобится, когда нужно будем определить ТОЛЬКО количество репостов у пользователя
if ($onlyCount) {
$this->countReposts = $count;
return true;
}
//Далее рекурсивно будет получать пользователей до тех пор, пока не считаем все. При этом сдвигаем offset.
$users = $response['users'];
if (count($users) == 0) return false;
if ($start) {
$this->users = $users;
} else {
$this->users = array_merge($this->users, $users);
}
$offset += $this->count;
$this->getUsers($owner_id, $post_id, $filter, $offset, $onlyCount, false);
}
//Для удобства я изменил ключи в массиве. Ключами являются - ID пользователя сайта vk.com
private function remakeUsersArray($usersWithInfo) {
$new = array();
foreach ($usersWithInfo as $value) {
$new[$value['uid']] = $value;
}
return $new;
}
/* Получить информацию о пользователях
* $vkIDs - массив с ID пользователей
*/
function getUsersInfo($vkIDs) {
//$count = 1000;
//Для получения информации о пользователе, используются положительные ID (ID со знаком минус имеют группы, сообщества)
foreach ($vkIDs as $key => $val) {
if ((int)$val < 0) unset($vkIDs[$key]);
}
$uids = implode(',', $vkIDs);
$fields = 'uid,first_name,last_name,nickname,screen_name,sex,city,country,timezone,photo,photo_medium,photo_big,has_mobile,rate,online,counters';
$url = $this->url.'users.get?&uids='.$uids.'&fields='.$fields.'&name_case=nom';
$json = file_get_contents($url);
$data = json_decode($json, true);
if (isset($data['response'])) {
$response = $data['response'];
return $response;
}
return 0;
}
//Получам посты пользователя
private function getUsersPosts($owner_id, $offset = 0) {
$maxNews = 600; //Максимальное колчиство новостей для поиска
$count = 100; //100 - это максимальное количество новостей, которые можно получить за один запрос
//Если обыскали $maxNews новостей и не нашли
if ($offset > $maxNews - $count) {
$this->printProgress('<b>Репост не был найден среди '.$maxNews.' новостей...</b>', false, true);
$this->find = false;
return false;
}
//Формируем URL
$url = $this->url.'wall.get?';
$url .= 'owner_id='.$owner_id.'&';
$url .= 'offset='.$offset.'&';
$url .= 'count='.$count.'&';
$url .= 'filter=owner';
$json = file_get_contents($url); //Получаем JSON-ответ
$data = json_decode($json, true);
//Если вдруг страница пользователя "заморожена" или удалена
if (!isset($data['response'])) {
$this->printProgress('<b>Ошибка получения нововстей</b>', false, true);
$this->find = false;
return false;
}
$response = $data['response'];
$this->printProgress('Поиск нашего репоста среди '.($count + $offset).' новостей..');
//Обрабатываем $count новостей
foreach ($response as $news) {
if (!is_array($news)) continue;
/* copy_owner_id - ID моей страницы или группы
* copy_owner_id - ID моего поста
*/
if (isset($news['copy_owner_id'], $news['copy_post_id']) and $news['copy_owner_id'] == $this->owner_id and $news['copy_post_id'] == $this->post_id) {
$this->users[$news['from_id']]['repost_id'] = $news['id'];
$this->printProgress('<b>Репост успешно найден найден #'.$news['id'].'</b>', false);
$this->findPost = $news['id'];
$this->find = true;
return true;
}
}
$offset += $count; //Увеличиваем смещение
$this->getUsersPosts($owner_id, $offset); //Рекурсия
}
//Поиск репоста
function findReposts() {
echo '<ul>';
$this->printProgress('Получаю список ID пользователей, сделавших репост...');
$this->getUsers($this->owner_id, $this->post_id, 'copies');
$this->printProgress('Список ID успешно получен', false);
$copies = $this->users;
$this->printProgress('Получаю список ID пользователей, сделавших лайк...');
$this->getUsers($this->owner_id, $this->post_id, 'likes');
$this->printProgress('Список ID успешно получен', false);
foreach ($this->users as $id) {
if (in_array($id, $copies)) continue;
$copies[] = $id;
}
$this->users = $copies;
$this->printProgress('<b>Уникальных ID пользователей для получения их информации: '.count($this->users).'</b>');
$this->printProgress('Получаю информацию о пользователях по их ID...');
$usersWithInfo = $this->getUsersInfo($this->users);
$this->printProgress('Информация была успешно получена', false);
$this->printProgress('<b>Уникальных ID пользователей с информации: '.count($usersWithInfo).'</b>');
$this->printProgress('Подготавливаю массив с информацией о пользователях...');
$this->users = $this->remakeUsersArray($usersWithInfo);
$this->printProgress('Массив успешно сформирован', false);
$this->printProgress('Начинаем искать репосты у пользователей...');
$k = 1;
foreach ($this->users as $id => $data) {
$this->printProgress('<i>'.$k.') Обрабатывается пользователь: <a href="http://vk.com/id'.$id.'">id'.$id.'</a> - '.$data['last_name'].' '.$data['first_name'].'</i>');
$this->getUsersPosts($id);
if ($this->find) {
$this->printProgress('Определяем количество репостов #'.$this->findPost.' у пользователя...');
$this->getUsers($id, $this->findPost, 'copies', 0, true);
$status = '<span';
if ($this->countReposts > 0) $status .= ' style="font-size: 20px;"';
$status .= '>Количество репостов #'.$this->findPost.': <b>'.$this->countReposts.'</b></span>';
$this->printProgress($status, false);
$this->users[$id]['count_reposts'] = $this->countReposts;
//тут можно добавлять $this->user[$id] в сессию
}
$k++;
}
$this->printProgress('Поиск репостов завершен', false);
}
}
Вышеприведенный класс позволяет получить количество репостов с пользовательскими данными. Подобным образом можно считать и лайки (необходимо лишь установить ?filter=likes)
Данное решение — очень удобный вариант отображения данных при встройке на свой сайт, при проведении различных викторин и конкурсов. К примеру мы это реализовали так
Автор: m0dE