Лень пораБОТила instagram



    Предисловие


    Сейчас многие используют инстаграм (далее инста): кто-то там собирает альбомы, кто-то продает, кто-то покупает, а я там ленюсь. Мне всегда было интересно как там поживают мои друзья, одноклассники, коллеги и инста в этом помогала. Захотел узнать, что там нового — зашел, полистал ленту, увидел все, что интересовало ушел… НО! Мне почему-то всегда нужно было лайкнуть каждый пост (не могу обьяснить зачем, но такие вот дела). И вот представьте, неделю туда не заходил, сидишь, лайкаешь недельный пул, а когда у тебя 200+ подписок — это вообще ад.

    Активные действия


    В итоге, как и любому нормальному человеку, мне стало лень лайкать все подряд и я забил. Вроде бы все стало хорошо, я перестал тратить кучу времени на бесполезные лайки, но меня съедала совесть. Я понимал, что подписчикам плохо без моего царского лайка, они грустят и бла бла бла… В общем, было решено, что нужно написать что-то простое и легкое, которое сможет решить проблему негодования, а может и помочь еще кому-то. От знакомых много слышал о python и о том, как клево тестить приложения с помощью selenium или использовать его в качестве некого crawler'a. Было решено использовать python и selenium в связке с phantom js, все это было для меня ново, т.к. до этого я с данными технологиями вообще не был знаком.

    Почему Selenium и phantom?


    Тут все очень просто. Клиентская часть instagram написана на react, следовательно, какие-либо данные можно дернуть там только после того, как страница будет срендерена. Т.к. selenium как раз и служит для автоматизации действий в браузере, а phantom js помогает делать это все без какого-либо отображения, было решено их использовать. Забегая наперед скажу, что от phantom js я решил отказаться в силу того, что он достаточно медленный, а у chrome появилась опция headless, что и позволило использовать его в качестве «безголового» браузера.

    Почему python?


    Я много слышал и читал о том, что этот язык отлично подходит для работы с big data, отсюда я сделал вывод, что в нем удобно работать вообще с какими-либо данными (парсить, сортировать, сравнивать, форматировать и т.д.), также я где-то читал, что к нему удобно и быстро писать свой мини-библиотеки (а это то, что нужно для бота, чтоб сделал его как можно универсальнее). Взвесив все, решил остановиться на python3 (до этого часть проекта уже была написана с возможностью запуска на python2 и python3).


    Разработка библиотеки для бота


    Весь процесс описывать глупо, поэтому остановимся на самых интересных моментах:

    • Авторизация

      Так как бот — это повторение большого кол-ва одних и тех же действий для которых нужно быть авторизованным, то нужно было что-то придумать с этим процессом. Каждый раз логиниться через форму очень подозрительно, было решено попытаться дернуть куки и использовать их для авторизации.

      Оказалось, что у instagram с этим все просто (а вот mail ru доставил мне дикую головную боль):
      import pickle
      import time
      import tempfile
      import os
      import selenium.common.exceptions as excp
      
      
      def auth_with_cookies(browser, logger, login, cookie_path=tempfile.gettempdir()):
          """
          Authenticate to instagram.com with cookies
          :param browser: WebDriver
          :param logger:
          :param login:
          :param cookie_path:
          :return:
          """
          logger.save_screen_shot(browser, 'login.png')
          try:
              logger.log('Trying to auth with cookies.')
              cookies = pickle.load(open(os.path.join(cookie_path, login + '.pkl'), "rb"))
              for cookie in cookies:
                  browser.add_cookie(cookie)
              browser.refresh()
              if check_if_user_authenticated(browser):
                  logger.log("Successful authorization with cookies.")
                  return True
          except:
              pass
      
          logger.log("Unsuccessful authorization with cookies.")
          return False
      
      
      def auth_with_credentials(browser, logger, login, password, cookie_path=tempfile.gettempdir()):
          logger.log('Trying to auth with credentials.')
          login_field = browser.find_element_by_name("username")
          login_field.clear()
          logger.log("--->AuthWithCreds: filling username.")
          login_field.send_keys(login)
          password_field = browser.find_element_by_name("password")
          password_field.clear()
          logger.log("--->AuthWithCreds: filling password.")
          password_field.send_keys(password)
          submit = browser.find_element_by_css_selector("form button")
          logger.log("--->AuthWithCreds: submitting login form.")
          submit.submit()
          time.sleep(3)
          logger.log("--->AuthWithCreds: saving cookies.")
          pickle.dump([browser.get_cookie('sessionid')], open(os.path.join(cookie_path, login + '.pkl'), "wb"))
          if check_if_user_authenticated(browser):
              logger.log("Successful authorization with credentials.")
              return True
          logger.log("Unsuccessful authorization with credentials.")
          return False
      
      
      def check_if_user_authenticated(browser):
          try:
              browser.find_element_by_css_selector(".coreSpriteDesktopNavProfile")
              return True
          except excp.NoSuchElementException:
              return False

      При неудачной авторизации куками, авторизируемся логином/паролем, сохраняет куку и используем ее в дальнейшем, стандартная схема.

      #TODO: никак не дойдут руки до проверки возраста куки
    • Лайкинг ленты новостей

      Т.к. в первую очередь я писал это для себя, мне было интересно, чтоб у меня всегда была отлайкана новостная лента. Изначально все было просто, листается сверху до последнего обработанного поста, веб-элементы постов заносятся в массив, включается задняя и лайкается все на обратном пути, проложенном через веб-элементы постов, которые лежат в ранее созданном массиве. Я был счастлив, что все работает именно так, как мне того хотелось, но где-то через два месяца «луна была козероге» и мой бот тупо перестал работать. Проверял все как мог, на разных веб-драйверах, визуально ничего не изменилось, но при этом ничего и не работает. В общем, убил я на поиски проблемы около трех дней. Оказалось все очень просто: раньше когда бот проходил по проскролленым постам, он брал их объекты из массива, скроллил к посту (имитируя действия человека) находил там кнопку «лайк», нажимал ее и шел дальше; теперь же инстаграм решил хранить в html-разметке только ~ 9 постов из которых в структуре 5ый — активный у пользователя, предыдущие 4 и следующие 4, а все остальные из html просто удалялись. Пришлось решать вопрос собиранием тех постов, которые нужно лайкнуть в массив по их ссылке, потом при скроллинге вверх (тупо вверх) искать текущий пост в раннее собранном массиве и при наличии его там — лайкать.

      Та еще наркомания..
      for post in progress:
                  real_time_posts = br.find_elements_by_tag_name('article')
                  post_link = post.get('pl')
                  filtered_posts = [p for p in real_time_posts if self._get_feed_post_link(p) == post_link]
                  if filtered_posts.__len__():
                      real_post = filtered_posts.pop()
                      # scroll to real post in markup
                      heart = real_post.find_element_by_css_selector('div:nth-child(3) section a:first-child')
                      self.browser.execute_script("return arguments[0].scrollIntoView(false);", heart)
                      # getting need to process elements
                      author = real_post.find_element_by_css_selector('div:first-child .notranslate').text
                      heart_classes = heart.find_element_by_css_selector('span').get_attribute('class')
                      # check restrictions
                      is_not_liked = 'coreSpriteHeartOpen' in heart_classes
                      is_mine = author == login
                      need_to_exclude = author in exclude
      
                      if is_mine or not is_not_liked:
                          self.post_skipped += 1
                          pass
                      elif need_to_exclude:
                          self.post_skipped_excluded += 1
                          pass
                      else:
                          # like this post
                          time.sleep(.3)
                          heart.click()
                          time.sleep(.7)
                          self.db.likes_increment()
                          self.post_liked += 1
                          log = '---> liked @{} post {}'.format(author, post_link)
                          self.logger.log_to_file(log)

      ПОБЕДА!
    • Лимиты действий

      Чтоб не привлекать много внимания, нужно ставить боту какие-то ограничения. Чтоб придерживаться этих ограничений, нужно куда-то сохранять счетчики произведенных действий. Для хранилища всякой внутренней информации было выбрано sqlite — быстро, удобно, локально. Прям в библиотеке я написал небольшой модуль для работы в бд, туда же добавил миграции — для последующих релизов. Сохраняется в бд каждый лайк/фоллоу с часом, в который он сделан, потом считаются лайки/фолловы за сутки/текущий час, исходя из этих данных решается можно ли еще кого-то лайкать или фолловить. Лимиты пока жестко прописаны в библиотеке, нужно будет сделать их конфигурируемыми.
    • Ответвление в процессе разработки

      Пока писалась библиотека для бота, в голове засел вопрос о циферках. Стало интересно сколько у пользователя лайков, просмотров, комментов в разрезе поста или сумарно. Для удовлетворения интереса был написан небольшой класс библиотеки, который через приватное api инстаграмма собирал всю доступную (без авторизации) статистику и выдавал ее пользователю:

      Скрытый текст
      +-- https://instagram.com/al_kricha/ --------------------------+
      |   counter                    |             value             |
      +------------------------------+-------------------------------+
      |   followed                   |              402              |
      |   posts                      |              397              |
      |   comments                   |             1602              |
      |   likes                      |             20429             |
      |   following                  |              211              |
      |   video views                |             6138              |
      |                                                              |
      +--------- https://github.com/aLkRicha/insta_browser ----------+
      +--------------------------------------------------------------+
      |                       top liked posts                        |
      +--------------------------------------------------------------+
      |       https://instagram.com/p/BVIUvMkj1RV/ - 139 likes       |
      |       https://instagram.com/p/BTzJ38-DkUT/ - 132 likes       |
      |       https://instagram.com/p/BI8rgr-gXKg/ - 129 likes       |
      |       https://instagram.com/p/BW-I6o6DBjm/ - 119 likes       |
      |       https://instagram.com/p/BM4_XSoFhck/ - 118 likes       |
      |       https://instagram.com/p/BJVm3KIA-Vj/ - 117 likes       |
      |       https://instagram.com/p/BIhuQaCgRxI/ - 113 likes       |
      |       https://instagram.com/p/BM6XgB2l_r7/ - 112 likes       |
      |       https://instagram.com/p/BMHiRNUlHvh/ - 112 likes       |
      |       https://instagram.com/p/BLmMEwjlElP/ - 111 likes       |
      +--------------------------------------------------------------+

      Имея такие данные мы с другом (txwkx) решили визуализировать их и создали instameter.me — небольшой сервис, где можно посмотреть «резюме» любого открытого instagram-аккаунта.

      Пример
    • Что умеет бот?

      На сегодня бот умеет не так много как хотелось, но все же, ключевые действия он совершает:

      • Лайкает ленту новостей до последнего не лайкнутого.
      • Лайкает тег на указанное кол-во постов
      • Лайкает локацию на указанное кол-во постов
      • Автофолловит людей из постов локации/тега, при включении настройки, но не всех подряд, а только тех, которые потенциально могут стать подписчиками
      • Собирает статистику по пользователю
      • Хранит статистику по часам о совершенным действиям

    • Что хотелось бы сделать в будущем?

      • Написание ± осмысленных комментариев
      • Отписываться от ненужных аккаунтов
      • Лайкать несколько постов только что зафолловенного человека
      • Переписать алгоритм прохождения новостной ленты
      • Сравнивать несколько аккаунтов

    Заключение


    Еще много чего нужно сделать, оптимизировать, переписать. Всегда можно использовать эффективно инструмент не по назначению. Лень — это точно двигатель прогресса. Надеюсь, кому-то мой бот поможет или в работе, или в хобби. Репозиторий с pypi-пакетом может помочь начинающему автоматизатору. Репозиторий с примерами может быть полезным для SMM-щиков. Всем спасибо за внимание.

    Ссылки


    • insta_browser — моя мини-библиотека, сердце бота
    • insta_bot — examples репозиторий, сам бот (в таком виде я его и использую)
    • instameter — проект для снятия статистики по instagram-аккаунту

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 9

      0
      Вот еще хороший бот на питоне, там можно много для себя почерпнуть в плане работы с инстаграмом
        0
        да, можно почерпнуть конфигурации, увидел там пару интересных моментов. Спасибо.
          0
          Подскажите, пожалуйста, есть ли подобные боты для Twitter?
          0
          Скорее уж «поРОБОТила» :)
            +2
            Читая статьи про Python, можно подумать что он создан для написания ботов, грабберов, парсеров и прочего роботизированного. Спасибо за отличный пример, как это роботизированное становится сервисом.
              0
              Я ровным счетом не хочу ни в чем обвинять автора, но у меня такое ощущение, что есть доля лукавства в целях написания этого бота, обозначенных в начале поста :-)
                0
                а можно конкретнее, в каком моменте вы думаете я лукавил?) Отвечу честно.
                  0
                  Товарищ выше имел ввиду что ему показалось что бот писался для своего платного сервиса лайканья фото, а не якобы потому-что люди без лайков жить не могут)
                0
                Спасибо за headless, что-то я прощелкал инфу.

                Only users with full accounts can post comments. Log in, please.