Как стать автором
Обновить

Вытаскиваем данные из Instagram

Время на прочтение17 мин
Количество просмотров116K

Давайте разберемся, как с помощью достаточно простого кода на python можно вытащить из инстаграмма разные данные, находящиеся в открытом доступе.


Intro

При фазовом переходе из состояния Employed в Self-Employed я погрузился в собственные проекты, которые давно хотел сделать. После парочки телеграм ботов с e-acquiring я решил попытать счастья с Instagram. Как человеку, прежде работавшему только с готовыми и подчищенными данными, мне было интересно познакомиться с процессом добычи данных поближе.

Если вам лень читать всё, то вот демонстрационный бот в Телеграм, который может вытаскивать некоторые данные из Instargam.

Что может бот?

На данный момент бот может обрабатывать несколько запросов

  • Случайный выбор подписчика / подписчиков аккаунта

  • Случайный выбор пользователя / пользователей, поставивших лайк под постом

  • Случайный выбор комментатора / комментаторов, оставивших комментарий под постом

  • Общие подписчики для двух пользователей

  • user-info JSON (информация о пользователе Instagram в том виде в котором она хранится на серверах)

  • media-info JSON (информация о публицации в Instagram в том виде, в котором она хранится на серверах)

P.S. Надеюсь, он не упадет под натиском запросов

P.P.S И надеюсь, что профиль в инсте, через который проводятся запросы не заблочат

Inspiration

Во многом меня вдохновила статья, где анализируются самые популярные геотеги Москвы, и телеграм канал её автора. Мне стало интересно, а как вообще вытаскиваются данные из социальных сетей.

Какое API выбрать?

Начнем с того, какие библиотеки использовать. Так как я пишу на питоне, то библиотеки выбирал под него.

У Facebook есть официальное API для взаимодействия с Instagram. Это API Graph и API Instagram Basic Display. Процесс его настройки и использования показался мне чрезмерно сложным, поэтому я решил поискать решение попроще.

Почему сложно?

Вы только посмотрите как выглядит начало работы с API Instagram Basic Display.

Из неофициальных API есть сравнительно популярный InstaPy (12k GitHub), работающий на базе Selenium. Мне такой фреймворк показался громоздким.

После нескольких часов поисков мой выбор пал на достаточно удобную библиотеку instabot, сама библиотека, документация.

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

Инстраграм не особо хорошо относится к использованию такого рода библиотек в целях раскрутки, да и в целом, к «нечеловеческой» активности относится негативно. Поэтому не рекомендую использовать их на своём основном аккаунте. Не знаю, какая вероятность того, что вас могут заблокировать, но она явно отлична от нуля.

Мой основной интерес был в том, чтобы поиграться с данными.

Что можно сделать?

В рамках данной статьи я расскажу о том, как можно получать следующую информацию:

  • Подписки и подписчики определенного аккаунта

  • Пользователи поставившие лайк / оставившие комментарий

  • Посты определенного пользователя

  • Информация о пользователе

  • Загрузка изображений из Instagram

Гораздо интереснее рассматривать такой процесс сбора информации не как изолированную задачу, а как задачу прикладную. Поэтому для каждого пункта я нашёл некоторые реальные задачи и показал, как их можно решить.

Список подписчиков

Представим ситуацию, что вы юный блоггер, и для расширения аудитории решили провести розыгрыш. Новый год, тем более скоро, так что пример актуальный. Допустим, что основной критерий розыгрыша - быть подписанным на вас.

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

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

from instabot import Bot
bot = Bot()
bot.login(username = INST_USERNAME,  password = INST_PASSWORD)

После того, как мы авторизовались - мы можем получить список подписчиков и список подписок для любого пользователя с открытым аккаунтом. Осуществляется это следующим образом.

user_followers = bot.get_user_followers(username) # Список подписчиков
user_following = bot.get_user_following(username) # Список подписок

Стоит заметить, что в данном случае мы увидим что-то вида

['1537613519', 
'7174630295', 
'5480786626', 
... ,
'6230009450', 
'4294562266', 
'27518898596']

Это user_id пользователей. Для того чтобы получить юзернеймы пользователей нужно сделать следующее:

user_id = user_followers[i]
username = bot.get_username_from_user_id(user_id)

Однако стоит иметь ввиду, что запрос get_username_from_user_id работает не мгновенно и внутри программы лучше работать с user_id а резолвить его в юзернейм только при необходимости.

Выбрать случайным образом несколько подписчиков username можно, например, вот так

user_followers = bot.get_user_followers(username)
amount = len(user_followers)
winners = np.random.choice(amount, N, replace=False)
winners_usernames = [bot.get_username_from_user_id(users_followers[i]) for i in winners]

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

Список людей, поставивших лайк

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

Для начала нужно получить media_pk из ссылки на ваш пост:

media_link = 'https://www.instagram.com/p/CJQRFj4Jq1G/?utm_source=ig_web_copy_link'
media_pk = bot.get_media_id_from_link(media_link)

Тогда для списка людей, поставивших лайк:

users_liked = bot.get_media_likers(media_pk)

Список людей, оставивших комментарий:

users_commented = bot.get_media_commenters(media_pk)

Также можно получить список комментариев под постом

comments = bot.get_media_comments(media_pk) # 20 last comments
all_comments = bot.get_media_comments_all(media_pk) #all comments

Дальше с этими списками можно работать точно также, как и в предыдущем пункте. Например, можно выбирать победителей среди тех пользователей, которые подписаны на вас и которые оставили лайк и комментарий под последними N публикациями.

Посты пользователя

Давайте перейдём к более интересным вещам и посмотрим, какую информацию о наших постах предоставляет Instagram. В библиотеке есть методы, позволяющие получить информацию о постах пользователя.

Например вот так можно получить идентификаторы последних 20 постов пользователя:

twony_last_medias = bot.get_user_medias(username, filtration = None)

Параметр filtration отвечает за фильтрацию постов. Он выбрасывает посты, количество лайков которых либо меньше bot.min_likes_to_like, либо большеbot.max_likes_to_like Эти параметры можно настроить и поставить filtration = True.

В данном случае twony_last_medias будет иметь вид:

twony_last_medias = [
'2442850452985735104_381142195', 
... , 
'2242166462844436702_381142195']

Где первая часть, до _ - это идентификатор поста, а вторая часть - user_id, то есть media_id = {media_pk}_{user_id}

Посмотрим, какую информацию о постах нам может дать Instagram:

media_id = twony_last_medias[i]
media_info = bot.get_media_info(media_id)[0]
# [0] - потому что функция возвращает массив, где всего один элемент 

media_info - это JSON следующего формата:

Пример полноценного JSONa

Давайте посмотрим как выглядит JSON для поста в инстаграме. Вы можете получить такой же JSON для любой своей публикации, обратившись к боту.

В media_info содержится вся доступная о посте информация, будь это геопозиция, отмеченные пользователи, комментарии и количество лайков.

Ниже пример поста и JSON соответсвующий ему.

{
  "taken_at": 1584960820,
  "pk": 2271138434299490600,
  "id": "2271138434299490618_381142195",
  "device_timestamp": 1584960663222320,
  "media_type": 1,
  "code": "B-EtF98KHE6",
  "client_cache_key": "MjI3MTEzODQzNDI5OTQ5MDYxOA==.2",
  "filter_type": 0,
  "location": {
    "pk": 500801171,
    "short_name": "Bolshaya Nikitskaya Street",
    "facebook_places_id": 103200906400409,
    "external_source": "facebook_places",
    "name": "Bolshaya Nikitskaya Street",
    "address": "",
    "city": "",
    "lng": 37.598333333333,
    "lat": 55.7575
  },
  "lat": 55.7575,
  "lng": 37.598333333333,
  "user": {
    "pk": 381142195,
    "username": "oak.raw",
    "full_name": "ℕ????? ???????",
    "is_private": false,
    "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/83355342_622022825292167_3709019308832063488_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=dvMUAIilXsIAX9jbV-1&tp=1&oh=7fdc985600993dfa2b1a5da2a117f58c&oe=6011B3AD",
    "profile_pic_id": "2233042169810667649_381142195",
    "friendship_status": {
      "following": true,
      "outgoing_request": false,
      "is_bestie": false,
      "is_restricted": false
    },
    "is_verified": false,
    "has_anonymous_profile_picture": false,
    "is_unpublished": false,
    "is_favorite": false,
    "latest_reel_media": 1608919065,
    "account_badges": [],
    "story_reel_media_ids": []
  },
  "can_viewer_reshare": true,
  "caption_is_edited": false,
  "comment_likes_enabled": true,
  "comment_threading_enabled": true,
  "has_more_comments": true,
  "next_max_id": 18104082043182496,
  "max_num_visible_preview_comments": 2,
  "preview_comments": [
    {
      "pk": 17865118795863322,
      "user_id": 1232001680,
      "text": "О, магазинус Лебедева:)",
      "type": 0,
      "created_at": 1590937324,
      "created_at_utc": 1590937324,
      "content_type": "comment",
      "status": "Active",
      "bit_flags": 0,
      "did_report_as_spam": false,
      "share_enabled": false,
      "user": {
        "pk": 1232001680,
        "username": "juliam_moroz",
        "full_name": "Julia Moroz",
        "is_private": false,
        "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/130863979_442658400430632_6468083327923303795_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=QoXEey9rFtsAX-KQPXk&tp=1&oh=925b051f9b220184e2c6da8b13dced83&oe=600F1F50",
        "profile_pic_id": "2463153806610276064_1232001680",
        "is_verified": false
      },
      "is_covered": false,
      "media_id": 2271138434299490600,
      "has_translation": true,
      "has_liked_comment": false,
      "comment_like_count": 1,
      "private_reply_status": 0
    },
    {
      "pk": 18104082043182496,
      "user_id": 381142195,
      "text": "@juliam_moroz да)",
      "type": 2,
      "created_at": 1590937557,
      "created_at_utc": 1590937557,
      "content_type": "comment",
      "status": "Active",
      "bit_flags": 0,
      "did_report_as_spam": false,
      "share_enabled": false,
      "user": {
        "pk": 381142195,
        "username": "oak.raw",
        "full_name": "ℕ????? ???????",
        "is_private": false,
        "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/83355342_622022825292167_3709019308832063488_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=dvMUAIilXsIAX9jbV-1&tp=1&oh=7fdc985600993dfa2b1a5da2a117f58c&oe=6011B3AD",
        "profile_pic_id": "2233042169810667649_381142195",
        "is_verified": false
      },
      "is_covered": false,
      "media_id": 2271138434299490600,
      "parent_comment_id": 17865118795863322,
      "has_liked_comment": false,
      "comment_like_count": 0,
      "private_reply_status": 0
    }
  ],
  "can_view_more_preview_comments": true,
  "comment_count": 7,
  "inline_composer_display_condition": "impression_trigger",
  "inline_composer_imp_trigger_time": 5,
  "image_versions2": {
    "candidates": [
      {
        "width": 1080,
        "height": 1350,
        "url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-15/e35/p1080x1080/90302974_101307918155463_880925762909832594_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=p4i6ab9qK3MAX_Je38D&tp=1&oh=47acaea745cfc8601a20d95e0d847370&oe=6011BB64&ig_cache_key=MjI3MTEzODQzNDI5OTQ5MDYxOA%3D%3D.2",
        "scans_profile": "e35",
        "estimated_scans_sizes": [
          34282,
          68565,
          102848,
          137131,
          171414,
          191812,
          244088,
          277418,
          308546
        ]
      },
      {
        "width": 360,
        "height": 450,
        "url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-15/e35/p360x360/90302974_101307918155463_880925762909832594_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=p4i6ab9qK3MAX_Je38D&tp=1&oh=7911d48a74cbf2c1c05bf0ef5b16e666&oe=60121616&ig_cache_key=MjI3MTEzODQzNDI5OTQ5MDYxOA%3D%3D.2",
        "scans_profile": "e35",
        "estimated_scans_sizes": [
          4342,
          8685,
          13028,
          17370,
          21713,
          26141,
          754408,
          39084,
          39084
        ]
      }
    ]
  },
  "original_width": 1440,
  "original_height": 1800,
  "like_count": 108,
  "has_liked": false,
  "like_and_view_counts_disabled": false,
  "top_likers": [
    "yar.ok"
  ],
  "facepile_top_likers": [
    {
      "pk": 1226384634,
      "username": "yar.ok",
      "full_name": "YAROSLAV OKATEV",
      "is_private": false,
      "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/91143836_1482909661889518_2687135057402920960_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=ZAsnt6I4_7sAX88ZA8A&tp=1&oh=88639d9237f3fc4d578890d57e66f21a&oe=601281EA",
      "profile_pic_id": "2275072905746358933_1226384634",
      "is_verified": false
    },
    {
      "pk": 306914465,
      "username": "nastianastacia",
      "full_name": "Nastia",
      "is_private": false,
      "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/15802713_1158590314260662_7281072673235402752_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=0lg_5gFmnsUAX-07OSA&tp=1&oh=875accdaf1bc2d1bb72d9442445450a9&oe=6011653B",
      "profile_pic_id": "1423562998005751579_306914465",
      "is_verified": false
    },
    {
      "pk": 236827679,
      "username": "terzivladida",
      "full_name": "Терзи Владислав | Фотограф",
      "is_private": false,
      "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/122422351_3763193670359540_4602498721748722003_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=df9YioDKtSsAX8kLHJT&tp=1&oh=952d54cb77d1029bc88070ccaf6d99bb&oe=600FFBAD",
      "profile_pic_id": "2425286083698432257_236827679",
      "is_verified": false
    }
  ],
  "photo_of_you": false,
  "usertags": {
    "in": [
      {
        "user": {
          "pk": 253581477,
          "username": "temalebedev",
          "full_name": "Artemy Lebedev",
          "is_private": false,
          "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/58411085_1127266294129169_3154601655836606464_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=aTk3egGzdp4AX8aexRy&tp=1&oh=b689c9d264aae438a0430d5d34a0c64c&oe=600FB372",
          "profile_pic_id": "2032755399167097999_253581477",
          "is_verified": true
        },
        "position": [
          0.6723027252,
          0.18760061960000002
        ],
        "start_time_in_video_in_sec": null,
        "duration_in_video_in_sec": null
      }
    ]
  },
  "can_see_insights_as_brand": false,
  "caption": {
    "pk": 17866702039715754,
    "user_id": 381142195,
    "text": "ТАКС. ДАВНО НИЧЕГО НЕ ПИСАЛ ⠀\n\nТак получается, что по возвращению из путешествий сложно заставить себя что-либо написать и выложить. Нет необходимых инфоповодов, мотивирующих это делать. Нет внутреннего ощущения насыщенности жизни, которое и побуждает  делиться классными кадрами и интересным историями. ⠀\n\nВ данном случае в качестве инфоповода послужил классный респиратор.",
    "type": 1,
    "created_at": 1584960823,
    "created_at_utc": 1584960823,
    "content_type": "comment",
    "status": "Active",
    "bit_flags": 0,
    "did_report_as_spam": false,
    "share_enabled": false,
    "user": {
      "pk": 381142195,
      "username": "oak.raw",
      "full_name": "ℕ????? ???????",
      "is_private": false,
      "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/83355342_622022825292167_3709019308832063488_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=dvMUAIilXsIAX9jbV-1&tp=1&oh=7fdc985600993dfa2b1a5da2a117f58c&oe=6011B3AD",
      "profile_pic_id": "2233042169810667649_381142195",
      "friendship_status": {
        "following": true,
        "outgoing_request": false,
        "is_bestie": false,
        "is_restricted": false
      },
      "is_verified": false,
      "has_anonymous_profile_picture": false,
      "is_unpublished": false,
      "is_favorite": false,
      "latest_reel_media": 1608919065,
      "account_badges": [],
      "story_reel_media_ids": []
    },
    "is_covered": false,
    "media_id": 2271138434299490600,
    "has_translation": true,
    "private_reply_status": 0
  },
  "can_viewer_save": true,
  "organic_tracking_token": "eyJ2ZXJzaW9uIjo1LCJwYXlsb2FkIjp7ImlzX2FuYWx5dGljc190cmFja2VkIjp0cnVlLCJ1dWlkIjoiYTk2MWE2OWMzY2ViNDQ2NGE1NDMzMWRlMjNiNWEyODAyMjcxMTM4NDM0Mjk5NDkwNjE4Iiwic2VydmVyX3Rva2VuIjoiMTYwODk5MzA4ODMxMXwyMjcxMTM4NDM0Mjk5NDkwNjE4fDQ1Mzc1MjU2NzQzfDgxMzI2NWY4YzM2MmIwZDVhMzg3ZTdmNWQ2YjlmYmQyNmQ3ODQ5NzUzMWIzYTk0ZDhkM2VlZmUzNTVkMjQ4MzEifSwic2lnbmF0dXJlIjoiIn0=",
  "sharing_friction_info": {
    "should_have_sharing_friction": false,
    "bloks_app_url": null
  },
  "is_in_profile_grid": false,
  "profile_grid_control_enabled": false,
  "is_shop_the_look_eligible": false,
  "deleted_reason": 0,
  "integrity_review_decision": "pending"
}

Для постов другого формата

В постах содержащих видео или несколько изображений (карусель) содержатся ещё такие поля:

"video_versions"
"video_duration"
"carousel_media"
"number_of_qualities"
"title"
"video_dash_manifest"
"view_count"
"product_type"
"video_codec"
"is_post_live"
"media_cropping_info"
"thumbnails"
"is_dash_eligible"
"carousel_media_count"
"has_audio" 
"nearly_complete_copyright_match"

Как уже было замечено ранее, в media_info содержится вся доступная о посте информация, будь это геопозиция, отмеченные пользователи, комментарии (а если быть точным их превью,preview_comments) и количество лайков.

Ниже расшифровка для некоторых полей:

  • taken_at, pk, id, device_timestamp - время создания поста и его идентификаторы.

  • media_type - тип контента внутри поста. Одно изображение / несколько изображений / видео / пост в IGTV.

  • location, lng, lat - всё что относится к геопозиции, указанной в посте. Объект location содержит информацию о геотеге (например facebook_places_id, address, city), lng и lat - координаты геотега. Интересно кстати, что координаты дублируются, они есть как в объекте location, так и в самом media_info.

  • usertags - объект, содержащий информацию об отмеченных пользователях.

Давайте посмотрим на некоторые необычные поля, которые также есть в этом JSON. Больше всего меня смутили два поля - top_likers и facepile_top_likers. Как оказалось, это лайки тех пользователей, которые отображаются непосредственно до количества лайков ("Нравится username и еще N пользователям", в мобильной версии есть еще три маленьких круглых картинки до этой надписи). Готов предположить, что Instagram показывает таким образом лайки тех пользователей, которых он считает наиболее интересными и важными для нас.

top_likers содержит в себе только один username и используется для текстовой аннотации.facepile_top_likers отвечает за визуальную аннотацию лайков, этот объект содержит в себе три профиля пользователей, где у каждого профиля указана ссылка на его аватарку (profile_pic_url).

Как это выглядит?

Интересно, кстати, что media_info содержит поле organic_tracking_token. Как утверждает Инстраграм - таким образом происходит поддержка брендов, занимающихся производством органической продукции.

А какие данные можно получить о пользователе?

Посмотрим, какую информацию Instagram предоставляет о нас - рядовых пользователях. На примере моего любимого фотографа.

Такая структура данных содержит основные данные о профиле (в том случае, если он открытый, если профиль закрытый - то данных будет гораздо меньше). Также тут есть некоторые поля для отображения общих подписчиков. Ещё в такой структуре данных Инстаграм может присылать список аккаунтов, на которые он рекомендует вам подписаться.

{
  "pk": 566674424,
  "username": "shortstache",
  "full_name": "Garrett King",
  "is_private": false,
  "profile_pic_url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s150x150/130951392_133135768583933_6026107970965456642_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=gmxi213GpY4AX8cbdpz&tp=1&oh=ad1ca4820958615dc5e99859ef101b1d&oe=60134F33",
  "profile_pic_id": "2463797374104100463_566674424",
  "is_verified": false,
  "has_anonymous_profile_picture": false,
  "media_count": 2594,
  "geo_media_count": 0,
  "follower_count": 485647,
  "following_count": 1090,
  "following_tag_count": 0,
  "biography": "?Huntington Beach, CA\nBehind the scenes @longstache \ninfo@shortstache.com",
  "biography_with_entities": {
    "raw_text": "?Huntington Beach, CA\nBehind the scenes @longstache \ninfo@shortstache.com",
    "entities": [
      {
        "user": {
          "id": 2264445914,
          "username": "longstache"
        }
      }
    ]
  },
  "external_url": "http://santogallery.com/garrett-king",
  "external_lynx_url": "https://l.instagram.com/?u=http%3A%2F%2Fsantogallery.com%2Fgarrett-king&e=ATOlDiBCCUHXljpYDutSTWgfd8g3dL5JwubDPnxKKowaNqriJSOcQSmZoZp6cEyK1gYK5DnB3Zo5UXaFQfTISio&s=1",
  "total_igtv_videos": 30,
  "has_igtv_series": false,
  "total_clips_count": 1,
  "total_ar_effects": 0,
  "usertags_count": 7399,
  "is_favorite": false,
  "is_favorite_for_stories": false,
  "is_favorite_for_igtv": false,
  "is_favorite_for_highlights": false,
  "live_subscription_status": "default",
  "is_interest_account": true,
  "has_chaining": true,
  "hd_profile_pic_versions": [
    {
      "width": 320,
      "height": 320,
      "url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s320x320/130951392_133135768583933_6026107970965456642_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=gmxi213GpY4AX8cbdpz&tp=1&oh=2947433220502b82b19ddc9c7931bb43&oe=601512CB"
    },
    {
      "width": 640,
      "height": 640,
      "url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/s640x640/130951392_133135768583933_6026107970965456642_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=gmxi213GpY4AX8cbdpz&tp=1&oh=43803ae7d9967b4195b1b6751227723f&oe=60161EF2"
    }
  ],
  "hd_profile_pic_url_info": {
    "url": "https://scontent-hel3-1.cdninstagram.com/v/t51.2885-19/130951392_133135768583933_6026107970965456642_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_ohc=gmxi213GpY4AX8cbdpz&oh=b8f622f8d82b633018a28b4c5b59b24b&oe=6016354B",
    "width": 1080,
    "height": 1080
  },
  "mutual_followers_count": 1,
  "profile_context": "Followed by oak.raw",
  "profile_context_links_with_user_ids": [
    {
      "start": 12,
      "end": 19,
      "username": "oak.raw"
    }
  ],
  "profile_context_mutual_follow_ids": [
    381142195
  ],
  "show_shoppable_feed": false,
  "shoppable_posts_count": 0,
  "can_be_reported_as_fraud": false,
  "merchant_checkout_style": "none",
  "seller_shoppable_feed_type": "none",
  "has_highlight_reels": true,
  "has_guides": false,
  "is_eligible_for_smb_support_flow": true,
  "displayed_action_button_partner": null,
  "smb_delivery_partner": null,
  "smb_support_partner": null,
  "smb_donation_partner": null,
  "smb_support_delivery_partner": null,
  "displayed_action_button_type": "",
  "direct_messaging": "",
  "address_street": "",
  "business_contact_method": "",
  "category": "Photographer",
  "city_id": 0,
  "city_name": "",
  "contact_phone_number": "",
  "is_call_to_action_enabled": false,
  "latitude": null,
  "longitude": null,
  "public_email": "info@shortstache.com",
  "public_phone_country_code": "",
  "public_phone_number": "",
  "zip": "",
  "instagram_location_id": "",
  "is_business": false,
  "account_type": 3,
  "professional_conversion_suggested_account_type": 3,
  "can_hide_category": true,
  "can_hide_public_contacts": true,
  "should_show_category": true,
  "should_show_public_contacts": true,
  "account_badges": [],
  "whatsapp_number": "",
  "include_direct_blacklist_status": true,
  "is_potential_business": true,
  "show_post_insights_entry_point": true,
  "is_bestie": false,
  "has_unseen_besties_media": false,
  "show_account_transparency_details": true,
  "auto_expand_chaining": false,
  "highlight_reshare_disabled": false,
  "is_memorialized": false,
  "open_external_url_with_in_app_browser": true
}

Как скачать изображение

Для того чтобы скачать изображение необходимо найти в этом JSON ссылку на фотографию, которая хранится либо в image_versions2, в случае когда в посте всего одна фотография, либо в carousel_media, когда в посте фотографии несколько. carousel_media содержит в себе изображения, которые хранятся в виде объектов image_versions2

Пример объекта image_versions2:

image_versions2
{'candidates': [{'width': 1080,
   'height': 1350,
   'url': 'https://scontent-hel3-1.cdninstagram.com/v/t51.2885-15/e35/p1080x1080/125189871_796604151185655_2119263110300122485_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=EmwfsFYiGrEAX8tQBmB&tp=1&oh=e3dea4bf769d4b40061da8a61664df1c&oe=60118BF5&ig_cache_key=MjQ0Mjg1MDQ0OTgzMTY4MDY1Ng%3D%3D.2',
   'scans_profile': 'e35',
   'estimated_scans_sizes': [10780,
    21560,
    32341,
    43121,
    53901,
    60316,
    76754,
    87235,
    97023]},
  {'width': 360,
   'height': 450,
   'url': 'https://scontent-hel3-1.cdninstagram.com/v/t51.2885-15/e35/p360x360/125189871_796604151185655_2119263110300122485_n.jpg?_nc_ht=scontent-hel3-1.cdninstagram.com&_nc_cat=103&_nc_ohc=EmwfsFYiGrEAX8tQBmB&tp=1&oh=65ecaab230598f4502f3a909e3163149&oe=600F039A&ig_cache_key=MjQ0Mjg1MDQ0OTgzMTY4MDY1Ng%3D%3D.2',
   'scans_profile': 'e35',
   'estimated_scans_sizes': [2680,
    5360,
    8041,
    10721,
    13402,
    16135,
    465655,
    24124,
    24124]}]}

Можно заметить, что Инстаграм хранит у себя две версии фотографии: большего и меньшего разрешения. Судя по всему, фотография меньшего разрешения нужна для того чтобы отображать пост в ленте.

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

"""
    filename goes without an extention
"""
import requests
def donwnload_photo(media_id, filename):
    media = bot.get_media_info(media_id)[0]
    if ("image_versions2" in media.keys()):
        url = media["image_versions2"]["candidates"][0]["url"]
        response = requests.get(url)
        with open(filename + ".jpg", "wb") as f:
            response.raw.decode_content = True
            f.write(response.content)
    elif("carousel_media" in media.keys()):
        for e, element in enumerate(media["carousel_media"]):
            url = element['image_versions2']["candidates"][0]["url"]
            response = requests.get(url)
            with open(filename + str(e) + ".jpg", "wb") as f:
                response.raw.decode_content = True
                f.write(response.content)

Представим себе ситуацию, что мы хотим скачать некоторое количество изображений из профиля фотографа, который нам нравится:

twony_last_medias = bot.get_user_medias("shortstache", filtration = None)
for e,media_id in enumerate(twony_last_medias):
        donwnload_photo(media_id, "img_" + str(e))
Результат

Несколько загруженных таким образом фотографий невероятного shortstache. Удивительно, насколько Инстаграм сжал эти фотографии без видимой потери качества.

Всего 160 Kb
Всего 160 Kb
Всего 119 Kb
Всего 119 Kb
278 Kb
278 Kb

Outro

С каждым часом, проведённым в социальных сетях, мы предоставляем информационным компаниям всё больше и больше информации о себе. А развитие технологий анализа этих данных позволяет уже не просто знать что-то о нас и нашем поведении, но и предсказывать наши наиболее вероятные действия, или даже ненавязчиво влиять на процесс совершения этих действий. Если честно, то меня всегда завораживала та сила, которая есть у компаний, возможно, знающих о нас больше, чем мы сами.

Надеюсь, у меня получилось вдохновить вас на какой-нибудь интересный проект, базирующийся на данных из Instagram. Лично у меня чешутся руки поискать какие-нибудь нетривиальные закономерности, например, сравнить как отличается контент и геотеги публикаций travel блоггеров за 2019 и 2020 год. Ммм, А если ещё и прикрутить куда-нибудь нейросетки и попробовать самому сделать рекомендательную систему для классных фотографий (не зря же два года CV занимался). Ненавязчиво оставлю ссылку на мой телеграм канал, где я буду писать про дальнейшие свои изыскания в этой области.

К слову, если вы вдруг будете делать что- то связанное с Instagram, то вот максимально актуальная на сегодняшний день библиотека.

Теги:
Хабы:
Всего голосов 28: ↑25 и ↓3+22
Комментарии19

Публикации

Истории

Работа

Python разработчик
137 вакансий
Data Scientist
61 вакансия

Ближайшие события