https://habrahabr.ru/post/347774/- Интернет-маркетинг
- Визуализация данных
- Python
Предисловие
Сейчас многие используют инстаграм (далее инста): кто-то там собирает альбомы, кто-то продает, кто-то покупает, а я там ленюсь. Мне всегда было интересно как там поживают мои друзья, одноклассники, коллеги и инста в этом помогала. Захотел узнать, что там нового — зашел, полистал ленту, увидел все, что интересовало ушел… НО! Мне почему-то всегда нужно было лайкнуть каждый пост (не могу обьяснить зачем, но такие вот дела). И вот представьте, неделю туда не заходил, сидишь, лайкаешь недельный пул, а когда у тебя 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-аккаунту