- PVSM.RU - https://www.pvsm.ru -

Получаем список участников сообщества ВКонтакте определенного пола и возраста

На создание данной статьи я был вдохновлен публикацией «Получение участников сообщества vk.com за считанные секунды» [1]. Моя статья написана новичком и отражает опыт решения одной задачи. Основная цель написания этой статьи для меня — собрать мнения, отзывы и критику примененного подхода от более опытных коллег. Кроме того, надеюсь, что кому-то приведенная здесь информация будет полезна.

Не так давно в одном из тестовых задания на вакансию младшего php-программиста мне попалась простая, но интересная для меня задача.

«Сделайте скрипт на php, который возвращает список id пользователей «ВКонтакте», разделенный символами перевода строки, которые являются мужчинами старше 25 лет и состоят в группе vk.com/habr [2]».

Доступ к информации из базы «ВКонтакте» осуществляется с использованием VK API. Начинать знакомство с VK API лучше с официальной документации [3]. Для того чтобы вызвать метод API ВКонтакте, необходимо осуществить POST или GET запрос по протоколу HTTPS на URL следующего вида:

api.vk.com/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN [4], где METHOD_NAME – название метода из списка методов API, PARAMETERS – параметры соответствующего метода, ACCESS_TOKEN – ключ доступа.

В нашей задаче используем метод groups.getMembers [5], который возвращает список участников сообщества. Все параметры метода описаны в документации. Метод не требует ключа доступа. В стандартной форме ответ приходит в виде JSON-файла. В одном запросе можно получить данные не более 1000 пользователей. Чтобы вживую посмотреть вывод метода, достаточно в адресной строке браузера ввести простейший запрос: api.vk.com/method/groups.getMembers?group_id=habr [6].

Получаем JSON-структуру с общим количеством членов сообщества vk.com/habr [2] и тысячей первых id в списке по умолчанию отсортированном по возрастанию.

По условию задачи нам нужно вывести id пользователей определенного пола и возраста. Очевидный способ — выбирать запросами VK API пользователей группы вместе с их данными о поле и возрасте, а потом в PHP-коде анализировать их и выводить только нужные. Другой возможный способ — метод execute [7] — позволяет в одном запросе передать скрипт на специальном языке VKScript для манипуляции с данными на сервере и вернуть уже обработанные данные. Сразу скажу, что мне не удалось, решить задачу с помощью метода execute. Может быть в комментариях кто-то укажет такое решение.

Пойдем по первому пути. Метод groups.getMembers с помощью значения sex параметра fields может выдавать пол пользователя, но он не выдает возраст. Вместо этого параметр fields имеет поле bdate — дата рождения. Кроме того, в запросах мы выбираем по тысяче пользователей, значит каждый следующий запрос должен выдать следующую тысячу. Для этого есть параметр offset, который показывает с какой позиции начинать выборку. Укажем в запросе еще и версию API.

В итоге запрос будет иметь примерно такой вид: https://api.vk.com/method/groups.getMembers?group_id=habr&offset=0&fields=sex,bdate&version=5.27 [8]

Чтобы забирать файл по ссылке, в PHP есть функция file_get_contents() [9]. Она получает контент по ссылке и возвращает его в виде строки. Нужно учесть, что для того, чтобы file_get_contents() понимала протокол HTTPS нужна поддержка openssl в веб-сервере.

Потом полученный JSON-контент можно преобразовать в массив функцией json_decode() [10]. Массив будет содержать и id, и пол. Дата рождения может быть вообще не указана.
Если дата рождения всё же указана, осталось из даты рождения получить возраст.

Даты рождения в bdate хранятся в строках формата ДД.ММ.ГГГГ, если указан год рождения, или ДД.ММ, если год рождения не указан. Чтобы узнать в каком формате строка фактически, я использовал первое, что пришло в голову: count(explode(".", $user_array['bdate'])) равно 2 или 3. Этот способ работает и не думаю, что это самое узкое место скрипта.

Для вычисления возраста по дате рождения нашел формулу hashcode.ru/questions/137939#137940 [11]. Функция strtotime() [12] понимает формат поля bdate.

Проверяем пол и возраст. Если они удовлетворяют условию, выводим id.

Весь код на PHP

// Номер пакета запроса
$packet = 0;
// Размер пакета запроса
$limit = 1000;
do {
    // Каждый запрос начинаем там, где остановились в предыдущем запросе.
    $offset = $ packet * $limit;
    // Выполнение запроса.
    // Результат - JSON-файл с общим количеством и данными пользователей.
    // Чтобы file_get_contents() работал с https на веб-сервере apache
    // должен быть активен модуль openssl.
    $contents = file_get_contents("https://api.vk.com/method/groups.getMembers?group_id=samsung&offset=$offset&fields=sex,bdate&version=5.27")
    // Преобразуем JSON в массив
    $members = json_decode($contents, true);
    // Данные пользователей хранятся в подмассиве users.
    // Каждый элемент users - ассоциированный массив с данными.
    foreach ($members['response']['users'] as $user_array) {
        // Если пользователь указал дату рождения и пользователь - мужчина...
        if ((isset($user_array['bdate'])) && ($user_array['sex'] == 2)) {
            // ... и если в дате рождения три компонента (ДД.ММ.ГГГГ)...
            if (count(explode(".", $user_array['bdate'])) == 3) {
                // то вычисляем возраст (формулу нашел в интернете)
                $age = floor((time()-strtotime($user_array['bdate']))/(60*60*24*365.25));
                // Если возраст нам подходит, выводим id пользователя с переводом строки
                if ($age > 25) {
                    echo $user_array['uid'] . "<br/>";
                }
            }
        }
    }
    // Переходим на следующий пакет.
    $packet++;
} while ($members['response']['count'] > $offset + $limit);

Этот вариант прекрасно работает на относительно небольших группах, но на группах более 100 тысяч подписчиков скрипт отрабатывает не до конца — в какой-то момент почему-то вываливается ошибка «file_get_contents(...): failed to open stream: Connection timed out in … on line ...». Пробовал увеличивать время выполнения скрипта и таймаут веб-сервера — не помогло. Так и не смог найти закономерность.

Тогда нашелся другой вариант — для загрузки ответа запроса использовать cURL [13]. Чтобы применить такой метод, необходимо установить в ОС библиотеку libcurl, например, в Ubuntu —

sudo apt-get install libcurl3

и включить в PHP поддержку cURL, например, в Ubuntu —

sudo apt-get install php5-curl

Теперь можно открыть в PHP-скрипте сеанс curl функцией curl_init() [14], установить параметры соединения (в том числе URL) функцией curl_setopt() [15] и скачивать контент JSON-файлов в строку функцией curl_exec() [16]. Потом следует закрыть сеанс — curl_close() [17]. Остальной код остается без изменений:

Весь код с cURL на PHP

// Номер пакета запроса
$packet = 0;
// Размер пакета запроса
$limit = 1000;
// Инициализируем cURL.
// Для работы с cURL должна быть установлена библиотека libcurl
// и включена поддержка cURL в PHP.
$ch = curl_init();
do {
    // Каждый запрос начинаем там, где остановились в предыдущем запросе.
    $offset = $ packet * $limit;
    // Параметры запроса
    curl_setopt($ch, CURLOPT_URL, "https://api.vk.com/method/groups.getMembers?group_id=samsung&offset=$offset&fields=sex,bdate&version=5.27");
    curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
    // Выполнение запроса.
    // Результат - JSON-файл с общим количеством и данными пользователей.
    $content = curl_exec ($ch);
    $members = json_decode($contents, true);
    // Данные пользователей хранятся в подмассиве users.
    // Каждый элемент users - ассоциированный массив с данными.
    foreach ($members['response']['users'] as $user_array) {
        // Если пользователь указал дату рождения и пользователь - мужчина...
        if ((isset($user_array['bdate'])) && ($user_array['sex'] == 2)) {
            // ... и если в дате рождения три компонента (ДД.ММ.ГГГГ)...
            if (count(explode(".", $user_array['bdate'])) == 3) {
                // то вычисляем возраст (формулу нашел в интернете)
                $age = floor((time()-strtotime($user_array['bdate']))/(60*60*24*365.25));
                // Если возраст нам подходит, выводим id пользователя с переводом строки
                if ($age > 25) {
                    echo $user_array['uid'] . "<br/>";
                }
            }
        }
    }
    // Переходим на следующий пакет.
    $packet++;
} while ($members['response']['count'] > $offset + $limit);
// Закрываем cURL
curl_close ($ch);

Как я уже говорил, думаю, возможен подход с методом execute, но мне пока не удалось получить в этом направлении удовлетворительный результат.

P. S. Прошу не думать, что я хочу получить от аудитории «Хабра» решение тестового задания. Вышеприведенные варианты я уже давно отправил и получил ответ. Просто немало времени потратил на эту задачу и хотел бы узнать, в правильном направлении ли я двигался и какие еще подходы можно было бы использовать.

Автор: Simonyan

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/80654

Ссылки в тексте:

[1] «Получение участников сообщества vk.com за считанные секунды»: http://habrahabr.ru/post/248725/

[2] vk.com/habr: https://vk.com/habr

[3] официальной документации: https://vk.com/dev

[4] api.vk.com/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN: https://api.vk.com/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN

[5] groups.getMembers: https://vk.com/dev/groups.getMembers

[6] api.vk.com/method/groups.getMembers?group_id=habr: https://api.vk.com/method/groups.getMembers?group_id=habr

[7] execute: https://vk.com/dev/execute

[8] https://api.vk.com/method/groups.getMembers?group_id=habr&offset=0&fields=sex,bdate&version=5.27: https://api.vk.com/method/groups.getMembers?group_id=habr&offset=0&fields=sex,bdate&version=5.27

[9] file_get_contents(): http://php.net/manual/ru/function.file-get-contents.php

[10] json_decode(): http://php.net/manual/ru/function.json-decode.php

[11] hashcode.ru/questions/137939#137940: http://hashcode.ru/questions/137939#137940

[12] strtotime(): http://php.net/manual/ru/function.strtotime.php

[13] cURL: http://php.net/manual/ru/book.curl.php

[14] curl_init(): http://php.net/manual/ru/function.curl-init.php

[15] curl_setopt(): http://php.net/manual/ru/function.curl-setopt.php

[16] curl_exec(): http://php.net/manual/ru/function.curl-exec.php

[17] curl_close(): http://php.net/manual/ru/function.curl-close.php

[18] Источник: http://habrahabr.ru/post/248827/