Аналитика Instagram и GAE



    Некоторое время назад на Хабре была опубликована статья про поиск похожих аккаунтов в Twitter'e. На комментарии автор, к сожалению, не реагировал, потому пришлось изобретать велосипед. Но чтобы не делать уж совсем то же самое, было решено искать похожие аккаунты в Instagram с помощью Google App Engine, да так, чтобы воспользоваться сервисом мог каждый. Так появился instalytics.ru*.


    Самое сложное, конечно же, оказалось в том, чтобы реализовать сервис для всех (ну, и остаться в пределах бесплатных квот Google App Engine и учесть ограничения Instagram API).

    Реализовано все следующим образом —
    1. Пользовательский запрос на анализ аккаунта проверяется в Instagram — если заданный пользователь найден, то запрос на его анализ добавляется в базу данных. При этом у каждого запроса есть свой приоритет (пока у всех запросов одинаковый).
    2. Раз в 15 минут с помощью cron'a запускается задача, которая выбирает из базы данных один запрос из очереди и создает новую задачу — получить всех подписчиков пользователя из запроса. Задача в случае ошибки повторяется еще:
      - name: followers-get
        rate: 1/m # 1 task per minute
        bucket_size: 1
        max_concurrent_requests: 1
        retry_parameters:
          task_retry_limit: 2
          min_backoff_seconds: 30
      

      Каждая задача, в случае, если за один запрос были получены не все подписчики, создает новую задачу:
      if users and users.get('pagination') and users.get('pagination').get('next_cursor'):
          cursor = users.get('pagination').get('next_cursor')
          url = '/task/followers-get?user_id='+user_id
          url += '&cursor=' + cursor
          taskqueue.add(queue_name='followers-get', url=url, method='GET')
      

    3. После завершения получения всех подписчиков начинается анализ каждого. Для этого создается огромное количество задач на получение списка тех пользователей, на кого подписан каждый подписчик (каждая задача при этом может создавать новые задачи, как и в случае с подписчиками выше). Для того, чтобы быть в соответствии с лимитом Instagram на 5'000 запросов в час, очередь задач настроена следующим образом:
      - name: subscriptions-get
        rate: 5000/h
      

      При этом после выполнения каждого запроса, на всякий случай, спим по 0,72 секунды (=60*60/5000).
      К сожалению, в бесплатной версии Google App Engine можно осуществить только 50'000 операций записей в базу данных в сутки. Т.к. каждая задача может создать новую задачу, то изначальный вариант — записывать результат выполнения каждой задачи в базу данных — пришлось заменить на новый — результат выполнения предыдущей задачи передается как параметр новой задачи, и лишь последняя задача записывает результат в базу:
      if users and users.get('pagination') and users.get('pagination').get('next_cursor'):
          cursor = users.get('pagination').get('next_cursor')
          params = {
              'user_id': user_id,
              'f_user_id': f_user_id,
              'cursor': cursor
          }
          if more_subscriptions:
              params['subscriptions'] = ','.join(more_subscriptions)
          taskqueue.add(queue_name='subscriptions-get', url='/task/subscriptions-get', params=params, method='POST')
      

      Некоторые пользователи (такие, как @instagram, например) имеют миллионы подписчиков. Дабы не тратить драгоценные ресурсы на получение всех их подписчиков, задача завершается после получения 100'000 подписчиков.
    4. Из-за ограничения на количество операций записей в базу данных, не удается нормально отслеживать завершились ли все задачи по конкретному пользователю или нет. Нормальным решением было бы записывать в базу данных список id запущенных задач и по завершении каждой задачи (или если сделана последняя попытка выполнить задачу) исключать задачу из списка. Но огромное количество задач помноженное на всех пользователей не позволяет этого сделать. Потому список задач хранится в memcache:
      memcache.set('subscriptions'+str(user_id), ','.join(str(x) for x in followers), 1209600) 
      

      Данные из memcache могут быть удалены в любой момент. Чтобы избежать ситуации с «зависшим» запросом (когда все задачи по запросу были выполнены, но memcache был удален и мы об этом, соответственно, не знаем), раз в несколько часов запускается задача, которая проверяет нет ли запросов, получивших статус получения подписчиков более чем 2 недели назад (пока считается, что это то время, за которое точно будут завершены все задачи). Если такие запросы находятся, то они «силой» переводятся на следующий этап.
    5. На следующем этапе считываются все полученные ранее данные из базы данных. Как оказалось, данных может быть довольно много и выделенной GAE оперативной памяти для них может не хватить. Потому данные считываются порциями, для каждой порции рассчитывается промежуточный результат, который потом добавляется к следующему промежуточному результату. В этом процессе пришлось отключить автоматические кэши:
      ctx = ndb.get_context()
      ctx.set_cache_policy(lambda key: False)
      ctx.set_memcache_policy(lambda key: False)
      

      В результате многочисленных вычислений на этом этапе выбираются 300 наиболее популярных пользователей, на которых подписаны ваши пользователи.
    6. Для каждого из 300 пользователей запускаются задачи по получению данных по ним (имена, картинки, количество подписчиков и т.п.). По аналогии с описанным выше процессом, ожидается либо завершение всех задач, либо принудительно через некоторое время запускается новый этап.
    7. На последнем этапе осуществляется расчет и выбор наиболее похожих пользователей (с учетом количества ваших подписчиков и подписчиков всего). Получается что-то типа этого, ссылка на результат отправляется на e-mail.

    Указанные выше подходы и оптимизации пока позволяют оставаться в рамках выделенных GAE бесплатных квот, хотя получение результата и занимает достаточно много времени. Нужна ваша помощь — добавляйте своих пользователей в очередь, посмотрим сколько времени займет их анализ.

    В будущем планирую добавить к сервису распознавание настоящих людей / компаний в Instagram, но без машинного обучения тут не обойтись — так что это будет отдельной задачей.

    * Русский язык на сайте пока не работает — не могу разобраться, почему django translation не работает на GAE.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      0
      А как определяется похожесть? Я полагаю, что, прежде всего, надо анализировать тэги под фотографиями.
        0
        Анализируется на кого подписаны те, кто подписан на вас (в статье про твиттер об этом более подробно описано).
        0
        Небольшая очепятка: Раз в 15 минут с помощью cron'a запускается задача, которая выбирает из базы данных один запрос из очереди и создает новую задачу — получить всех подписчиков пользователя из запроса. Задача в случае ошибкир повторяется еще

        Воспользуюсь сервисом, посмотрю :) Спасибо
          0
          Вроде интересный сервис, но пока заценить не удалось. Встал в очередь)

          Кстати вам API-ключи заапрувили? Мне для instaulsk.ru заявку второй раз не подтвердили. Видимо сайт придется в июне 2016 закрывать :/
          Хотя и видео с демонстрацией сервиса слал и описание правильное. Кстати на название тоже поругались они — puu.sh/mTrt6/c86b5d75b6.png

            0
            Нет, тоже отказали во всем. И на insta- тоже жалуются:
            Instagram Branding Guidelines (Not Compliant): Your app uses the Instagram brand name in a way that isn't permitted. Please review Instagram Brand Guidelines (http://www.instagram-brand.com) and ensure that your app doesn't imply sponsorship, partnership or endorsement from Instagram.
            Invalid Use Case: The use case described in your submission notes, screencast and website is not a valid use case that we allow on our Platform. Please see our Permissions Review and valid use cases description (https://www.instagram.com/developer/review/) for more information.

            follower_list:
            This permission (follower_list) does not support the use case you described in your submission notes, screencast and website. Please review Login Permissions (http://instagram.com/developer/authorization/) for a comprehensive list of permissions and valid use cases.

            public_content:
            This permission (public_content) does not support the use case you described in your submission notes, screencast and website. Please review Login Permissions (http://instagram.com/developer/authorization/) for a comprehensive list of permissions and valid use cases.

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

          Самое читаемое