Предыстория
Мы делаем сервис для постинга в Instagram по расписанию и используем API для получения информации об аккаунтах. Самим постингом занимаются телефоны в автоматическом режиме. Нам отказали в доступе к API после 1 июня (пробовали пройти модерацию два раза) поэтому было решено найти замену.
Сначала расскажу как мы использовали официальный API:
- При добавлении аккаунта забираем из Instagram информацию об аккаунте: имя, фото профайла, количество постов, подписчиков, подписок.
- Перед тем как опубликовать фото/видео мы запрашиваем количество постов, и тоже самое после публикации, если число постов увеличилось считаем публикацию успешной.
- Если публикация прошла успешно забираем ссылку на последнее фото в профайле.
- Если пользователь удаляет фото из нашего сервиса, то перед тем как выполнить задачу нужно проверить существует ли такой пост в Instagram (или его удалили).
Реализация
У Instagram есть веб-версия. С помощью нее в приватных аккаунтах можно получить информацию о количестве постов, подписок и подписчиков, а в публичных еще и сами посты, комментарии и лайки. Поэтому, в силу простоты получения, я подумал, что уже написаны подобные библиотеки. Пошел гуглить и нашел только для NodeJS. И для PHP нашелся какой-то код, но всем четырем пунктам не соответствовал. В итоге было решено писать свою библиотеку.
Не буду вдаваться в детали, так как вы можете посмотреть код на GitHub. Расскажу только ключевые моменты.
Получение информации об аккаунте
Если зайти в профайл (например, instagram.com/kevin) и посмотерть исходный код страницы, то прокрутив вниз можно увидеть зашитый прямо в страницу JSON объект с информацией об аккаунте.
Довольно просто вытаскиваем его (для удобства я использовал mashape/unirest-php), парсим и записываем в массив:
$response = Request::get('https://instagram.com/kevin');
$pageString = $response->body();
$arr = explode('window._sharedData = ', $pageString);
$json = explode(';</script>', $arr[1]);
$userArray = json_decode($json[0], true);
$userData = $userArray['entry_data']['ProfilePage'][0]['user'];
echo $userData['username']; // Теперь можно делать вот так
echo $userData['follows']['count'] // или вот так
echo $userData['is_private']; // ну вы поняли
Получение всех постов в аккаунте
Как оказалось, можно получить готовый JSON последних 20 постов добавив к URL аккаунта
/media
: https://instagram.com/kevin/media
Но, что делать если нам нужны все посты? Достаточно добавлять в URL параметр
max_id
с id
последнего поста из 20-ки в цикле, пока все посты не кончатся: https://instagram.com/kevin/media?max_id=id
. Для удобства даже есть поле more_available
, которое принимает значение true
или false
.Информация об отдельном посте
Что если у вас есть ссылка на пост в Instagram (например, www.instagram.com/p/9BDXa_L7bm) и вы хотите получить о нем информацию? Тоже самое, что и со страницей профайла, туда вшит JSON с данными о посте.
Тоже как в первом пункте: вытаскиваем, парсим и, бум, у нас есть инфо о посте.
Бонус. Как получить фото из Instagram в лучшем качестве?
Самое лучшее качество фотографии в Instagram на данный момент
1080
пикселей. Но наше решение отдает лишь 640
. Методом тыка мы поняли, что если, например, заменить в URL фото
https://scontent.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/12950481_1753078061593396_874826488_n.jpg
часть с
640x640
на 1080x1080
: https://scontent.cdninstagram.com/t51.2885-15/s1080x1080/sh0.08/e35/12950481_1753078061593396_874826488_n.jpg
То получим фото в максимально возможном качестве.
Заключение
В нашем случае с помощью библиотеки удалось полностью перекрыть потребности в API от Instagram.
Репозиторий: github.com/raiym/instagram-php-scraper
Почти тоже самое на Java: github.com/raiym/instagram-java-scraper
Сайт проекта: postaddict.me
Update: Большое спасибо simpel и toly за информацию. На основе ваших комментариев были перписаны запросы к Instagram.
Комментарии (41)
toly
30.05.2016 12:35+6Информацию о пользователе можно получить сразу в JSON: https://www.instagram.com/kevin/?__a=1
Тоже самое с отдельным постом: https://www.instagram.com/p/BGBgSw0tpHQ/?__a=1raiym
30.05.2016 13:46О, вот это круто! Надеюсь переживет 1 июня, добавим как альтернативу
simpel
02.06.2016 16:10Похоже, что пережило.
raiym
02.06.2016 16:13Ага, я уже залил изменения, но до релиза еще надо дописать один метод и все оттестить
raiym
02.06.2016 16:14вы не знаете есть способ для нахождения фотографий по местам? lat=...&long=...?
simpel
02.06.2016 16:25Да, вот такой https://www.instagram.com/explore/locations/278608830/?__a=1 только нужно прописать в хедер REFERER = https://www.instagram.com и будет JSON, а не пустой ответ
raiym
02.06.2016 16:30Спасибо. как понимаю location id из фэйсбука берется. И referer не указывал, вернулся не пустой JSON
Evengard
30.05.2016 16:10+1Я наверное нехороший буратино, но я бы наверное пошёл по пути разбора официального приложения и дёргания их API с их ключами. Собственно, мало чем отличается, только так есть хотя бы документированный API.
raiym
30.05.2016 16:14Тогда вам придется сначала указать логин и пароль, насколько я понимаю.
Вот, кстати, живой проект по этой теме https://github.com/mgp25/Instagram-API
sphinks
30.05.2016 16:23+1А вы постите действительно реальными устройствами или все же через эмулятор? И, кстати, почему №1 вы для постинга?) Никогда не слышал про вас.
raiym
30.05.2016 16:32Да, действительно с реальных устройств, вот видео-пруф: https://www.youtube.com/watch?v=CsJKb4RX-jo
Эмуляторы не используем, но со счетов их не скидываем.
Парень, который занимался PR предложил так написать =D Когда разбирались в теме, мы нашли только одну компанию, которая заявила, что использует настоящие устройства schedugr.am, но подтверждение их слов мы не видели. Остальные сервисы пользуются приватным API.
Кажется о нас вообще мало кто слышал, потому что рекламой мы активно не занимаемся пока.
simpel
30.05.2016 22:59Максимально доступное качество фото можно получить, удалив все кроме /t/ из ссылки — https://scontent.cdninstagram.com/t/12950481_1753078061593396_874826488_n.jpg, через параметры в ссылке можно управлять размером и кропом.
или вот так: https://instagram.com/p/9BDXa_L7bm/media/?size=l
Лента залогиненного пользователя в JSON: https://www.instagram.com/?__a=1
Без сессии ответ будет пустой {}simpel
30.05.2016 23:08поиск по тэгу: https://www.instagram.com/explore/tags/test/?__a=1
и по имени https://www.instagram.com/web/search/topsearch/?query=nameraiym
06.06.2016 23:25Зарепортили баг с пагинацией по хештегам
В коде библиотеки пагинация сделана через передачу параметра max_id.
И сейчас он не работает, то есть первая пачка медиа есть, но следующая возвращается с пустым массивом.
На сколько я понял параметр max_id, то работает, то не работает. Не знаете стабильный способ?simpel
06.06.2016 23:50вроде с tag.media.page_info.end_cursor в качестве max_id что-то меняется в фиде, пока не разбирался
toly
07.06.2016 11:13С тегами не проверял, но про страницу геолокации (https://www.instagram.com/explore/locations/278324317/) точно могу сказать, что фид меняется и в случае использования start_cursor, в случае использования end_cursor. Причем если смотреть на время картинок, то всегда выдаются более поздние картинки (и при start_cursor, и при end_cursor)
toly
07.06.2016 11:22Перепутал — выдаются более ранние картинки
raiym
07.06.2016 11:27А вы не знаете как получить из user_id username?
toly
07.06.2016 15:03Хотя… Если для каждого пользователя хранить какую-либо картинку (идентификатор картинки), то получив информацию о картинке, можно узнать новое имя пользователя
raiym
07.06.2016 15:09Например, можно добавить метом getUserByMediaId. А ссылку на фото/видео получать из mediaId вот таким путем
Но все равно не очень удобно получается.toly
07.06.2016 15:17Можно сразу сохранять user.media.nodes[0].code из https://www.instagram.com/username/?__a=1
raiym
30.05.2016 23:30Круто!
А как управлять размером и кропом через параметры?
Если не секрет, каким способом находятся такие ссылки?
Обязательно добавим это в библиотеку.simpel
30.05.2016 23:34у меня популярное приложение на instagram api и я озадачился этим вопросом достаточно давно. нашел перебором параметров и изучением скриптов.
управление кропом описано тут
sborod
01.06.2016 10:53Вот gem для Ruby на основе информации из этого поста и комментариев:
https://github.com/sborod/ruby-instagram-scraper
MorozovNsk
03.06.2016 01:16Это хороший конечно способ, но может быть у нас уже появился рынок верифицированных клиентов, или хотя-бы access_token?
raiym
03.06.2016 08:45Интересно. Я о таком не знаю.
Плюс зависеть от чужого токена не оч приятно. Кстати, как-то я искал на github рабочие клиенты/токены как-то давно и находил рабочие
Sepaka
07.06.2016 11:19Передо мной тоже была цель вытянуть несколько фотографий, так как с начала месяца старые скрипты рухнули. И казалось бы https://www.instagram.com/username/media/ прекрасно отдает данные json. Но ведь ajax'ом нельзя вытянуть данные на моем домене с домена инстаграма. Ок, берем dataType: jsonP. Не работает. Все же результат json <> jsonp
//JSON {"name":"stackoverflow","id":5} //JSONP func({"name":"stackoverflow","id":5});
Нагуглил решение прогонять запрос через сторонний сервис: http://www.whateverorigin.org/
Получилось типа того: http://jsfiddle.net/VpGVL/
CnapoB
За два последних года исходный код страницы в веб-версии Instagram кардинально менялся два раза (меняли названия переменных, содержание), плюс они могут добавлять и удалять пробелы в синтаксисе по своему усмотрению.
Ну и использовать explode для получения подстроки в строке, это по меньшей мере кощунство. Можно было применить substr+strpos, preg_match, strstr.
raiym
Будем следить за этим, например, в мобильном приложении мы поддерживаем два вида постинга (отличаются расположением кнопок).
Спасибо за замечание.