План статьи
Введение
Еще с 1941 года Американское правительство обширно использовало OSINT. Уже в 1947 году Шерман Кент, аналитик ЦРУ, сообщил, что около 80% информации, которая используется политиками для принятия решений, берется из открытых источников. (источник, с. 428, конец 2 параграфа)
Поэтому, в наше время, грамотный подход к OSINT’у позволит раздобыть ценную информацию из открытых данных, особенно это актуально в эру масштабного использование соцсетей. Как правило, современные аккаунты соцсетей имеют большое количество различных данных, на анализ которых требуется большая часть времени.
Чтобы оптимизировать поиск данных и увеличить скорость их нахождения, в данной статье я детально расскажу, как можно написать свой собственный скрипт для анализа данных аккаунтов различных соцсетей.
Реализовать скрипт можно двумя способами: используя API (более простой и удобный путь) и написание без API, используя только запросы (долгий и трудоемкий процесс).
Каждый перечисленный способ имеет свои недостатки и преимущества, но обо всем по порядку.
Термины
В этом пункте вы можете ознакомиться со всеми непонятными терминами, которые используются в статье.
Подготовительная часть
Для написания скриптов я буду использовать Python, так как он удобен при работе с запросами, обработке ответов от серверов, и быстро справляется с массивами данных. Если конкретнее, то я буду использовать Python версии 3.7.3.
Создания скрипта без API
Декомпиляция сетевого трафика
Скрипт мы будем писать под Web версию Инстаграма, и наш скрипт не будет требовать ни единой информации от юзера, кроме нужного никнейма для скана.
Начать стоит с того, что, не имея под руками ключей для использования API, выкручиваться нужно только декомпиляцией трафика. Проанализировав GET запросы, я заметил, что для подзагрузки фотографий, запрос отправляется на https://www.instagram.com/graphql/query/:
Рисунок 1: разбор GET запроса
Перейдя по ссылке, я понял, что при валидном запросе, сервер выдаст ответ в формате json:
Рисунок 2: сервер ответил на запрос “Instagram.com/graphql/query ” ошибкой
Использовав один из ранних GET запросов:
я получил такой ответ:
Рисунок 3: ответ сервера на GET запрос о подзагрузке доп. фото
Где под номером подразумевается node, дерево, которое вмещает в себе всю информацию о посте (будто видео или фото).
Прогнав 2 подобных запроса через URL Decoder, я получил следующее:
Из этого стало понятно, что параметр id - вероятнее всего обозначает id страницы, для которой нужна подзагрузка. Параметр first – обозначает количество до загружаемых фото, но максимальное количество фотографий, которое можно дозагрузить – 50 штук.
В параметре after передается строка, которая существует при условии True у параметра has_next_page:
Рисунок 4: если “has_next_page”:False, то значения end_cursor нету
Рисунок 5: если “has_next_page”
rue, то значения end_cursor есть
Но остается вопрос: “Где берется самый первый параметр after?”
Покопавшись в .html странице Инстаграма, я заметил, что в строках 277 и 274 данные записаны в похожем стиле json:
Рисунок 6[/I]
Скопировав строки и прогнав их через JSON Formater, я получил с одной строки ~2400 строк, а с другой ~250, и не содержит в себе полезной информации.
Информация об аккаунте содержится в подкаталоге graphql:
Рисунок 7: сопоставление данных со строки 274 с данными на странице.
Мне показалось странным называть так подкаталог, и поэтому я решил обратиться к поисковикам.
Самой первой страницей, которую выдал Google, был вопрос на satckoverflow.com. Из ответа я узнал, чтобы получить JSON ответ о любой странице, нужно в конце добавить значение ?__a=1. И это сработало!
Вместо того, чтобы копаться в одной строке в структуре страницы, мы можем просто получить json ответ о любой странице:
Рисунок 8: Сравнение ответа сервера, и строки 274
Перейдем теперь к параметру
Я все же решил разобраться, откуда он берется для GET запросов дозагрузки.
Генератор
Рисунок 9
В самом файле хеш лежит в параметре queryId, но, как правило, в этом файле как минимум 4 таких параметров с разными хешами:
Рисунок 10: 4 разных queryId в .js файле
Но, именно после
Рисунок 11
И так, имея всю нужную информацию под рукой, можно перейти к следующему пункту.
Создание приблизительной схемы будущего скрипта
Рисунок 12: приблизительная блок-схема
Реализация скрипта
Для создания скрипта, который будет выполнять действия на схеме, потребуется 5 библиотек:
Тестирование и объяснение отдельных функции
Первая функция parser() принимает введенный никнейм, и отправляет его в фукнцию main()
В функции
В функции
Как только благополучно заканчивается функция
ФОТО
Стоит начать с того, что при загрузке страницы в Инстаграме, загружаются уже 12 фотографий, и это можно заметить в ответе на запрос
Рисунок 13: уже загруженные 12 фотографий
Так же в подзаголовке
Поэтому, переменная
Получив данные, функция photoes(), обратывает их, и дальше вызывает функцию
Функция
Вначале своей работы, переменная media сразу же добавляется в переменную
И так, если параметр
После того, как все посты были загружены, создается цикл, который будет поочерёдно отправлять каждый пост в функцию
В Инстаграме существует 3 вида постов: фотография, видео, коллекция. Из-за этого в функции
Так же, скрипт загружает фотографии самого высокого качества. Ссылки на фотографии лежат в параметре
Рисунок 14: подкаталог с ссылками на фото различного качества
Функция создает глобальную папку Instagram, . Из информации, берется: количество лайков, текст поста, локация, отмеченные профили (теги), комментарии и ссылка на пост; для видео: количество просмотров; для фотографии: captions.
Captions – это догадка искусственного интеллекта, что может содержать фотография. К примеру:
Рисунок 15: фото, и описание, созданное ИИ
Так же, в корневую папку, будут сохраняться еще отдельные файлы:
Комментарии
Парсинг комментариев происходит подобным образом.
При загрузке фотографии по ссылке
Рисунок 16: загруженные комментарии фотографии
И так, существует функция
В функции
Загрузив информацию о посте, и скачав фотографию, новый пост не начнет обрабатываться, пока
Рисунок 17
После того, как
Так же при загрузке комментариев иногда проскальзывает ошибка ‘rate limiting’. Эта ошибка связанна с большим количеством запросов на подзагрузку дополнительных комментариев. И решением является «простой» кода 8 минут.
Как только работа скрипта закончена, мы получаем такой результат:
Рисунок 18: демонстрация сохраненных данных
Создание скрипта с использованием API
Этот скрипт мы будет писать под Twitter.
Получение доступа к api является самым сложным пунктом. И так, что бы начать пользоваться API Twitter’a, мы должны:
Рисунок 19: выбор категории будущего приложения
Рисунок 20: заявление на выдачу API отправлено
Получив на свою почту подобное письмо:
Рисунок 21: письмо от команды Твиттера на одобрение приложения
Можно приступать к получению ключей.
Переходим в Apps:
Рисунок 22
Затем в Details:
Рисунок 23
Во вкладке Keys and tokens, будут лежать нужные нам ключи:
Рисунок 24
Приступаем к созданию схемы
Создание приблизительной схемы будущего скрипта
Рисунок 25: приблизительная блок-схема
Реализация скрипта
В этом скрипте будем использовать дополнительную библиотеку TwitterAPI
Устанавливается она, как и все доп. библиотеки, следующей командой:
Из основных библиотек, будут использоваться следующее:
Так как мы уже работаем с API, в этом скрипте будет использовать ключи, которые находятся во вкладке Apps>Details>Keys and tokens. Вставляем их в скрипт, и создаем переменную api:
Функция
Функция
После этого, функция возвращает
Далее, запускается функция
Эта функция работает с подписчиками аккаунта. Создается запрос на загрузку данных о подписчиках, далее загруженная информация преобразуется в словарь, добавляется в переменную, и если нужна еще подзагрузка (об этом говорит параметр next_cursor):
Рисунок 26: Next_cursor
То создает еще:
Как и в прошлом скрипте, передавая уже новые значения (
Функция
Так же как и во
И если больше друзей нету, то есть
А в функции friends() – строка “Friends”:
После обработки последнего друга, функция
Данная функция собирает все твиты и ретвиты пользователя в одну переменную, и в дальнейшем отправляется в функцию
Так же стоит объяснить про странную строку del media[-1]. Дело в том, что при подзагрузке твитов/ретвитов, отсутствует такой курсор, как "next_page”, имеющийся в предыдущих функциях, и отследить, загрузились ли все твиты/ретвиты невозможно. Поэтому, пришлось придумывать велосипед, и использовать id последнего твита, как начало для загрузки следующего. Но из-за того, что один и тот же твит будет в конце и в начале, нужно удалять последний твит в общей переменной.
Твиттер также дает возможность просмотреть id тех пользователей, которые ретвитнули твит указанного аккаунта. Но так как, во-первых, лимит запросов составляет всего 300 в 15 минут:
Рисунок 27: Лимит на отправку запросов
а во-вторых на каждый id нужно создавать отдельный запрос, чтобы узнать информацию об аккаунте. Все указанные действия будут происходить медленно, с интервалом в 15 минут (если скрипт за 15 минут будет использовать больше 300 запросов). Из-за указанных особенностей, данная функция использоваться не будет.
По итогу, и собрав все твиты/ретвиты, весь полученный из словарей массив, отправляется в
После создания переменных, полученные данные записываются в файл “Retweets.txt” и “Tweets.txt”.
И последняя функция имеет название
Она использует count (переменная из функции main()) для подсчета загруженных твитов.
Указанная функция схожа с предыдущей, единственное отличие - она вмещает в себе парсер, и не отправляет словарь из всех полученных твитов в отдельную функцию.
Лайкнутые твиты записывает в “Liked.txt”
После скана, получаем папку с такими файлами:
Рисунок 28: полученные файлы после работы скрипта
Сравнение вариантов реализации OSINT скриптов
Каждый скрипт имеет свои плюсы и минусы. Прежде всего, использование API всегда позволяет взять больше информации, чем без его использования.
В Инстаграме, если пользователь не вошел в аккаунт – он не может смотреть Stories, список подписчиков и список друзей.
Но в то же время, мы никак не привязаны аккаунтом, и мы можем сканировать что угодно, и сколько угодно (до ошибки rate limiting)
В Твиттере мне пришлось открыто лгать про цели и идеи моего приложения. И не факт что вам удастся таким же образом получить эти ключи для пользования API.
Но другая сторона медали – сплошное удобство.
Вы можете сравнить функции в скрипте для Твиттера и для Инстаграма. Использование библиотеки решает многие вопросы, над которыми я долго сидел при написании скрипта для Инстаграма.
Заключение
Подводя итоги, хочу сказать, что каждый из этих скриптов является утилитой для OSINT, вне зависимости от использования API.
Более анонимный способ – первый, декомпиляция трафика и создание собственных запросов
Более простой – второй, взяв токены и ключи, следовать по документации от Твиттера.
Тут уже решать за вами.
Код на GitHub
Автор: @g00db0y
взято с codeby
- Введение
- Термины, которые будут использоваться в статье
- Подготовительная часть
- Создания скрипта без API
- Декомпиляция сетевого трафика
- Создание схемы скрипта и его реализация
- Тестирование и объяснение отдельных функций
- Создание скрипта с использованием API
- Ознакомление с API и библиотек для него
- Создание схемы и реализация скрипта
- Тестирование и объяснение отдельных функций
- Заключение
Введение
Еще с 1941 года Американское правительство обширно использовало OSINT. Уже в 1947 году Шерман Кент, аналитик ЦРУ, сообщил, что около 80% информации, которая используется политиками для принятия решений, берется из открытых источников. (источник, с. 428, конец 2 параграфа)
Поэтому, в наше время, грамотный подход к OSINT’у позволит раздобыть ценную информацию из открытых данных, особенно это актуально в эру масштабного использование соцсетей. Как правило, современные аккаунты соцсетей имеют большое количество различных данных, на анализ которых требуется большая часть времени.
Чтобы оптимизировать поиск данных и увеличить скорость их нахождения, в данной статье я детально расскажу, как можно написать свой собственный скрипт для анализа данных аккаунтов различных соцсетей.
Реализовать скрипт можно двумя способами: используя API (более простой и удобный путь) и написание без API, используя только запросы (долгий и трудоемкий процесс).
Каждый перечисленный способ имеет свои недостатки и преимущества, но обо всем по порядку.
Термины
В этом пункте вы можете ознакомиться со всеми непонятными терминами, которые используются в статье.
- API (application programming interface) – это набор различных функции, протоколов, определители подпрограмм, для создания приложений. Крайне удобный и незаменимый инструмент при работе с различными онлайн-сервисами и софтами.
- URL encoding - кодирует ссылку при передачи данных по протоколу HTTP. Закодированная ссылка выглядит так: «google.com%2F%3F%7B%221%22%3A%5B%221%22%2C2%5D%7D», декодированная так: «google.com/?{"1":["1",2]}»
- JSON Fromater (JSON Beautifier) – программа, которая преобразует однострочный код, в удобный и читаемый вид.
Подготовительная часть
Для написания скриптов я буду использовать Python, так как он удобен при работе с запросами, обработке ответов от серверов, и быстро справляется с массивами данных. Если конкретнее, то я буду использовать Python версии 3.7.3.
Создания скрипта без API
Декомпиляция сетевого трафика
Скрипт мы будем писать под Web версию Инстаграма, и наш скрипт не будет требовать ни единой информации от юзера, кроме нужного никнейма для скана.
Начать стоит с того, что, не имея под руками ключей для использования API, выкручиваться нужно только декомпиляцией трафика. Проанализировав GET запросы, я заметил, что для подзагрузки фотографий, запрос отправляется на https://www.instagram.com/graphql/query/:
Рисунок 1: разбор GET запроса
Перейдя по ссылке, я понял, что при валидном запросе, сервер выдаст ответ в формате json:
Рисунок 2: сервер ответил на запрос “Instagram.com/graphql/query ” ошибкой
Использовав один из ранних GET запросов:
Код:
https://www.instagram.com/graphql/query/?query_hash=f2405b236d85e8296cf30347c9f08c2a&variables=%7B%22id%22%3A%2224025320%22%2C%22first%22%3A12%2C%22after%22%3A%22QVFBakZHYzBMN1pNeld5SWhJTUpNbkZzSGhoVWdUMk5WYjBQV0E1WmFyNmNqTDM2bmlmS3lhdWNzN21mOVJVR3RnUDQzRGxOVG83VHRPakpvSm5WbUhMYw%3D%3D%22%7D
я получил такой ответ:
Рисунок 3: ответ сервера на GET запрос о подзагрузке доп. фото
Где под номером подразумевается node, дерево, которое вмещает в себе всю информацию о посте (будто видео или фото).
Прогнав 2 подобных запроса через URL Decoder, я получил следующее:
Код:
https://www.instagram.com/graphql/query/?query_hash=f2405b236d85e8296cf30347c9f08c2a&variables={"id":"24025320","first":12,"after":"QVFBakZHYzBMN1pNeld5SWhJTUpNbkZzSGhoVWdUMk5WYjBQV0E1WmFyNmNqTDM2bmlmS3lhdWNzN21mOVJVR3RnUDQzRGxOVG83VHRPakpvSm5WbUhMYw=="}
https://www.instagram.com/graphql/query/?query_hash=f2405b236d85e8296cf30347c9f08c2a&variables={"id":"24025320","first":12,"after":"QVFEV2NTam5oekdON2hjRlluLXZHLXhORnpRczdNQmV5LTB4ZTFjM2E4NnZIM25QRHdQOGg2Q2Zrc1BtOXp3VjVxdHNMR2FoWGVMemJMS2VlWHlJX0dLcA=="}
В параметре after передается строка, которая существует при условии True у параметра has_next_page:
Рисунок 4: если “has_next_page”:False, то значения end_cursor нету
Рисунок 5: если “has_next_page”
Но остается вопрос: “Где берется самый первый параметр after?”
Покопавшись в .html странице Инстаграма, я заметил, что в строках 277 и 274 данные записаны в похожем стиле json:
Рисунок 6[/I]
Скопировав строки и прогнав их через JSON Formater, я получил с одной строки ~2400 строк, а с другой ~250, и не содержит в себе полезной информации.
Информация об аккаунте содержится в подкаталоге graphql:
Рисунок 7: сопоставление данных со строки 274 с данными на странице.
Мне показалось странным называть так подкаталог, и поэтому я решил обратиться к поисковикам.
Самой первой страницей, которую выдал Google, был вопрос на satckoverflow.com. Из ответа я узнал, чтобы получить JSON ответ о любой странице, нужно в конце добавить значение ?__a=1. И это сработало!
Вместо того, чтобы копаться в одной строке в структуре страницы, мы можем просто получить json ответ о любой странице:
Код:
Instagram.com/instagram/?__a=1
Рисунок 8: Сравнение ответа сервера, и строки 274
Перейдем теперь к параметру
query_hash. Так как f2405b236d85e8296cf30347c9f08c2a сервер принимает, и не выдает ошибок, использовать во всех дозагрузках будем именно его.Я все же решил разобраться, откуда он берется для GET запросов дозагрузки.
Генератор
query_hash лежит в файле ProfilePageContainer.js/ . Но для каждой страницы в Инастаграме существует свой файл:Рисунок 9
В самом файле хеш лежит в параметре queryId, но, как правило, в этом файле как минимум 4 таких параметров с разными хешами:
Рисунок 10: 4 разных queryId в .js файле
Но, именно после
profilePosts.byUserId.get идет нужный queryId:Рисунок 11
И так, имея всю нужную информацию под рукой, можно перейти к следующему пункту.
Создание приблизительной схемы будущего скрипта
Рисунок 12: приблизительная блок-схема
Реализация скрипта
Для создания скрипта, который будет выполнять действия на схеме, потребуется 5 библиотек:
- Requests – создание GET запросов и загрузки фотографий
- Argparse – парсинг входящих команд при старте скрипта
- Json – создание словарей из ответов сервера
- Time – заморозка скрипта при превышении лимита запросов
- Os – создание папок
Тестирование и объяснение отдельных функции
Первая функция parser() принимает введенный никнейм, и отправляет его в фукнцию main()
Python:
def parser():
parser = argparse.ArgumentParser(prog='OSINT Inst')
parser.add_argument("-n", "--nickname", help="Nickname?", dest="nickname")
args = vars(parser.parse_args())
main(args["nickname"])
main() запрашивается страница с указанным никнеймом, и парсится ответ, который пришел в json формате. Если страница существует, создается папка Instagram, где будит хранится вся информация об указанном аккаунте. Парсится так же и отдельная часть, которая в дальнейшем отправляется в функцию parse_page()
Python:
def main(nickname):
url = "https://www.instagram.com/"+nickname
response = requests.get(url + "?__a=1")
if "Page Not Found" in response.text:
print("Page Not Found")
exit()
todos = json.loads(response.text)
os.mkdir("Instagram")
parse_page(todos)
photoes(todos["graphql"]["user"])
parse_page() принимается словарь из различных значений, и создаются переменные для дальнейшей их записи в файл “info.txt” который, как уже было написано раньше, сохраняется в папке Instagram.
Python:
def parse_page(todos):
print("Getting info about profile...")
follow_inst = str(todos["graphql"]["user"]["edge_follow"]["count"])
followed_by_inst = str(todos["graphql"]["user"]["edge_followed_by"]["count"])
content_inst = str(todos["graphql"]["user"]["edge_owner_to_timeline_media"]["count"])
biography = todos["graphql"]["user"]["biography"]
name = todos["graphql"]["user"]["full_name"]
username = todos["graphql"]["user"]["username"]
verified = str(todos["graphql"]["user"]["is_verified"])
fb = str(todos["graphql"]["user"]["connected_fb_page"])
business = str(todos["graphql"]["user"]["is_business_account"])
try:
link = "\nLink: "+ todos["graphql"]["user"]["external_link"]
except:
link = ''
file = open("Instagram/info.txt", "w", encoding="utf-8")
info = "Name: "+name+"\nUsername: "+username+"\nBiography: "+biography+"\n\nVerified: "+verified+"\nFollow: "+follow_inst+"\nFollowed: "+\
followed_by_inst+"\nNumber of posts: "+content_inst+link+"\nBusiness account: "+business+\
'\nConnected to facebook: '+fb
file.write(info)
file.close()
parse_page(), в функция main() запускает следующею, а именно photoes(). Данная функция вмещает себе функции, которые отвечают за сбор информации о каждой фотографии в аккаунте, и их загрузке. Подробнее об этом в следующей части.ФОТО
Стоит начать с того, что при загрузке страницы в Инстаграме, загружаются уже 12 фотографий, и это можно заметить в ответе на запрос
Instagram.com/{nickname}/?__a=1, а именно в подзаголовке edges:
Рисунок 13: уже загруженные 12 фотографий
Так же в подзаголовке
page_info есть нужный нам параметр end_cursor.Поэтому, переменная
media вмещает в себе загруженные посты, checker содержит в себе параметр has_next_page а idprofile_inst – Id аккаунта в Инстаграме.Получив данные, функция photoes(), обратывает их, и дальше вызывает функцию
Get_req_photo()
Python:
def photoes(json_media):
media = json_media["edge_owner_to_timeline_media"]["edges"]
checker = json_media["edge_owner_to_timeline_media"]["page_info"]["has_next_page"]
idprofile_inst = json_media["id"]
get_req_photo('', media, checker, json_media, idprofile_inst)
get_req_photo() создает запросы о дозагрузке фотографии, если has_next_page имеет значeние True, то есть существует еще страница с фотографиями и её нужно дозагрузить.Вначале своей работы, переменная media сразу же добавляется в переменную
media_to_parse. Эта переменная в конце работы скрипта будет содержать все посты, которые потом будут обрабатываться.
Python:
def get_req_photo(media_to_parse, media, checker, json_media, idprofile_inst):
media_to_parse += str(media)
if checker:
end_cursor = json_media["edge_owner_to_timeline_media"]["page_info"]["end_cursor"]
data_for_get_req = 'https://www.instagram.com/graphql/query/?query_hash=f2405b236d85e8296cf30347c9f08c2a' \
'&variables=%7B%22id%22%3A%22' + idprofile_inst + \
'%22%2C%22first%22%3A50%2C%22after%22%3A%22' + end_cursor.split("==")[0] + '%3D%3D%22%7D'
gett = requests.get(data_for_get_req)
json_media = json.loads(gett.text)
checker = json_media["data"]["user"]["edge_owner_to_timeline_media"]["page_info"]["has_next_page"]
media = json_media["data"]["user"]["edge_owner_to_timeline_media"]["edges"]
get_req_photo(media_to_parse, media, checker, json_media["data"]["user"], idprofile_inst)
else:
print("Got ", len(str(media_to_parse).split("'shortcode': '")), " post(s)")
for i in range(1, len(str(media_to_parse).split("'shortcode': '"))):
link_to_page = str(media_to_parse).split("'shortcode': '")[i].split("',")[0]
req = requests.get("https://instagram.com/p/" + link_to_page + "/?__a=1")
json_page = json.loads(req.text)
parse_media(json_page["graphql"]["shortcode_media"], i)
print("Done")
И так, если параметр
has_next_page имеет значение True, то скрипт будет создавать запросы и добавлять посты до тех пор, пока параметр has_next_page не будет иметь значения False, то есть больше постов нет. После парсинга ответа от сервера, создаются более свежие переменные, которые имеют новую информацию. Дальше они передаются опять этой же функции, далее по кругу. Такие функции называются рекурсивными. Несмотря на то, что многие профессионалы рекомендуют их не использовать, в этом варианте, ошибки никакой не будет, ведь вызов той самой функции, является её последним этапом, и из-за этого не правильной работы в скрипте можно избежать.После того, как все посты были загружены, создается цикл, который будет поочерёдно отправлять каждый пост в функцию
parse_media()В Инстаграме существует 3 вида постов: фотография, видео, коллекция. Из-за этого в функции
parse_media() существует отдельно загрузка для каждого вида постов. Я решил загружать только фотографии, из-за того, что видео будут существенно тормозить работу скрипта. Было решено создавать файл с названием “video.txt”, где будет указываться ссылка на пост с видео.Так же, скрипт загружает фотографии самого высокого качества. Ссылки на фотографии лежат в параметре
display_resources
Рисунок 14: подкаталог с ссылками на фото различного качества
Функция создает глобальную папку Instagram, . Из информации, берется: количество лайков, текст поста, локация, отмеченные профили (теги), комментарии и ссылка на пост; для видео: количество просмотров; для фотографии: captions.
Captions – это догадка искусственного интеллекта, что может содержать фотография. К примеру:
Рисунок 15: фото, и описание, созданное ИИ
Так же, в корневую папку, будут сохраняться еще отдельные файлы:
- All_comments.txt – все комментарии со всех постов
- All_tagged.txt – все «затеганные» аккаунты в постах жертвы
- All_locations.txt – все локации, которые помечены в постах жертвы
Python:
def parse_media(json_page, m):
view_count = ''
os.mkdir("Instagram/post " + str(m))
captions = '\nCaption(s): '
print("Downloading post " + str(m))
if json_page["__typename"] == "GraphVideo":
view_count = str("\nView count = " + str(json_page["video_view_count"]))
out = open("Instagram/post " + str(m) + "/video.txt", "w")
out.write(str("https://instagram.com/p/"+json_page["shortcode"]))
out.close()
if json_page["__typename"] == "GraphImage":
photo = str(json_page["display_resources"][-1]["src"])
p = requests.get(photo)
out = open("Instagram/post " + str(m) + "/img" + str(m) + ".jpg", "wb")
out.write(p.content)
out.close()
captions = "\n"+json_page["accessibility_caption"]
if json_page["__typename"] == "GraphSidecar":
for i in range(len(json_page["edge_sidecar_to_children"]["edges"])):
photoes = json_page["edge_sidecar_to_children"]["edges"][i]["node"]["display_resources"][-1]["src"]
p = requests.get(photoes)
out = open("Instagram/post "+str(m)+"/img"+str(i)+".jpg", "wb")
out.write(p.content)
out.close()
try:
captions += "\nphoto №"+str(i)+"-"+ str(json_page["edge_sidecar_to_children"]["edges"][i]
["node"]["accessibility_caption"])+"\n"
except:
view_count = str("\nView count = " + str(json_page["edge_sidecar_to_children"]["edges"][i]
["node"]["video_view_count"]))
out = open("Instagram/post " + str(m) + "/video.txt", "w")
out.write(str("https://instagram.com/p/" + json_page["shortcode"]))
out.close()
likes = "\nlikes: " + str(json_page["edge_media_preview_like"]["count"])+"\n"
link = "Link - "+"instagram.com/p/"+json_page["shortcode"]+"\n"
location = json_page["location"]
if location is not None:
with open("Instagram/all_locations.txt", "a", encoding="utf-8") as file:
file.write("post "+str(m)+"\n"+str(location)+"\n")
else:
location = ''
pass
if json_page["edge_media_to_tagged_user"]["edges"] == []:
tagged_user = ''
else:
tagged = str(json_page["edge_media_to_tagged_user"]).split("node")
tagged_user = '\nTagged user(s): '
for i in range(1, len(tagged)):
tagged_user += "@"+str(json_page["edge_media_to_tagged_user"]).split("username': '")[i].split("'}")[0]+\
" ("+str(json_page["edge_media_to_tagged_user"]).split("full_name': '")[i].split("', ")[0]+")\n"
with open("Instagram/all_tagged.txt", "a", encoding="utf-8") as file:
file.write(str("post " + str(m) + ":" + tagged_user + "\n"))
file = open("Instagram/post "+str(m)+"/info.txt", "w", encoding="utf-8")
try:
text = "text: '"+str(json_page["edge_media_to_caption"]["edges"][0]["node"]["text"])+"'\n"
except:
text = "text: None\n"
all_information = str(text+likes+tagged_user+"\nLocation: "+str(location)+captions+view_count+"\n"+link)
file.write(all_information)
file.close()
try:
checker_for_comment = json_page["edge_media_to_parent_comment"]["count"]
except:
checker_for_comment = json_page["edge_media_to_comment"]["count"]
if checker_for_comment != 0:
comm(json_page, m)
else:
pass
Комментарии
Парсинг комментариев происходит подобным образом.
При загрузке фотографии по ссылке
Instagram.com/p/xxxxxxxxxxx, загружаются так же и часть комментариев (как это было с профилем и постами):
Рисунок 16: загруженные комментарии фотографии
И так, существует функция
comm() которая вызывается каждый раз из функции parse_media(). В функции создаются первые переменные, которые в дальнейшем используются в get_req_com():
Python:
def comm(json_page, m):
list_of_comments = ''
try:
try:
comments = json_page["edge_media_to_parent_comment"]["edges"]
page_info = json_page["edge_media_to_parent_comment"]["page_info"]
except KeyError:
comments = json_page["edge_media_to_comment"]["edges"]
page_info = json_page["edge_media_to_comment"]["page_info"]
except Exception as e:
print("\n\n", json_page,"\nError:", e)
exit()
get_req_com(json_page["shortcode"], comments, page_info, list_of_comments, m)
В функции
get_req_com() все загруженные комментарии с постом сохраняются в переменную list_of_comm. Только в подкаталоге page_info содержится информация о существовании следующих комментариев. Если параметр has_next_page равен True, то создается запрос о подзагрузке 49 комментариев (да, почему-то лимит постов – 50, но лимит комментариев - 49), потом создаются свежие переменные, и опять отправляются в ту же функцию.Загрузив информацию о посте, и скачав фотографию, новый пост не начнет обрабатываться, пока
has_next_page не будет равен False:
Рисунок 17
После того, как
has_next_page достиг значения False, все собранные комментарии с поста отправляются в функцию parse_comm(), где каждый коммент парсится, и собирается в единую переменную list, и после этого, функция возвращает отредактированный список комментариев в переменную comments. Comments записывается в файл comments.txt определенного поста и в файл all_comments.txt .Так же при загрузке комментариев иногда проскальзывает ошибка ‘rate limiting’. Эта ошибка связанна с большим количеством запросов на подзагрузку дополнительных комментариев. И решением является «простой» кода 8 минут.
Как только работа скрипта закончена, мы получаем такой результат:
Рисунок 18: демонстрация сохраненных данных
Создание скрипта с использованием API
Этот скрипт мы будет писать под Twitter.
Получение доступа к api является самым сложным пунктом. И так, что бы начать пользоваться API Twitter’a, мы должны:
- Создать аккаунт в Twitter
- Перейти в https://developer.twitter.com/en/apps и создать приложение
- После этого, вам нужно придумать название вашего приложения, описать его до 100 символов и придумать ссылку.
- Далее, вам нужно будет выбрать, в какой области находится ваше приложение:
Рисунок 19: выбор категории будущего приложения
- Так же, нужно будет заполнить ответить на подобные вопросы: «Для каких именно целей вы будете использовать Твиттер», «Какие лица, кроме вас, будут так же получать информацию?», «Будете ли вы распространять полученную информацию о пользователях Твиттера?» и все в этом духе. Заполнив все бланки, и нажав Next, заявление автоматически отправляется на рассмотрение в команду Твиттера:
Рисунок 20: заявление на выдачу API отправлено
Получив на свою почту подобное письмо:
Рисунок 21: письмо от команды Твиттера на одобрение приложения
Можно приступать к получению ключей.
Переходим в Apps:
Рисунок 22
Затем в Details:
Рисунок 23
Во вкладке Keys and tokens, будут лежать нужные нам ключи:
Рисунок 24
Приступаем к созданию схемы
Создание приблизительной схемы будущего скрипта
Рисунок 25: приблизительная блок-схема
Реализация скрипта
В этом скрипте будем использовать дополнительную библиотеку TwitterAPI
Устанавливается она, как и все доп. библиотеки, следующей командой:
Код:
Pip install TwitterAPI
Из основных библиотек, будут использоваться следующее:
- Os – для создания папок
- Requests – для загрузки фотографий
- Json – для создания словарей из ответов сервера
Так как мы уже работаем с API, в этом скрипте будет использовать ключи, которые находятся во вкладке Apps>Details>Keys and tokens. Вставляем их в скрипт, и создаем переменную api:
Python:
cons_key = "XXXXXXXXXXXXXXXXXXXXXXXXX"
cons_sec_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
acc_tok = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
acc_tok_sec = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
api = TwitterAPI(cons_key, cons_sec_key, acc_tok, acc_tok_sec)
main() сразу создает запрос на получение информации об аккаунте и создает словарь. Дальше, проверяет, существует ли такой аккаунт, и если он существует: создает папку, и отправляет словарь в функцию info_about_acc()
Python:
def main(nick):
reqst = api.request("users/lookup", {"screen_name": nick})
jsn = json.loads(reqst.text)
try:
if jsn["errors"][0]["code"] == 17:
print("This page doesn't exist")
elif '""errors":' in reqst.text:
print("ERROR\nResponse data:", reqst.txt)
exit()
except:
os.mkdir("Twitter")
count, count_for_tweets = info_about_acc(jsn[0])
print("Downloading list of followers...")
followers(nick, "-1", [])
friends(nick, "-1", [])
getting_tweets(nick, [], 0, count_for_tweets, "")
getting_favorites(nick, count, 0, [], '')
Функция
info_about_acc() выглядит большой только из-за своих переменных, и некоторых исключений. Основные цели функции – запись данных, взятых из словаря в файл «Info.txt» и сохранение фотографий.
Python:
def info_about_acc(jsn):
print("Getting information...")
id = str(jsn["id"])
name = jsn["name"]
nickname = jsn["screen_name"]
followers = str(jsn["followers_count"])
friends = str(jsn["friends_count"])
data_of_creation = jsn["created_at"]
count_of_liked_tweets = jsn["favourites_count"]
verified = jsn["verified"]
profile_pic = jsn["profile_image_url_https"]
statuses_count = jsn["statuses_count"]
lang = jsn["lang"]
if lang is not None:
lang = "\nLang -" + lang
else:
lang = ''
location = jsn["location"]
if location != '':
location = "\nLocation -" + location
description = jsn["description"]
if description != '':
description = "\n\tDescription -" + description
print("Downloading photos...")
background_pic = jsn["profile_background_image_url"]
if background_pic is not None:
photo_back = requests.get(background_pic)
out_back = open("Twitter/Profile background photo.jpg", "wb")
out_back.write(photo_back.content)
out_back.close()
background_pic = '\nBackground picture - ' + background_pic
else:
background_pic = ''
if not jsn["profile_use_background_image"]:
banner_pic = jsn['profile_banner_url']
photo = requests.get(banner_pic)
out = open("Twitter/Profile banner.jpg", "wb")
out.write(photo.content)
out.close()
photo = requests.get(profile_pic.split("_normal")[0]+profile_pic.split("_normal")[1])
out = open("Twitter/Profile photo.jpg", "wb")
out.write(photo.content)
out.close()
print("Writing information to the file info.txt...")
info = "ID - " + id + "\nName - " + name + "\nNickname - " + nickname + "\nFollowers - " + followers + "\nFriends - " + friends+\
"\nData of creation - " + data_of_creation + "\nCount of liked twiters - " + str(count_of_liked_tweets) + "\nVerified - "+\
str(verified) + "\nStatuses count - " + str(statuses_count) + lang + location + description + background_pic + \
"\nProfile pic - " + profile_pic
file = open("Twitter/Info.txt", "w")
file.write(info)
file.close()
return count_of_liked_tweets, statuses_count
После этого, функция возвращает
count_of_liked_tweets (количество лайкнуты) в переменную count в функции main().Далее, запускается функция
followers()Эта функция работает с подписчиками аккаунта. Создается запрос на загрузку данных о подписчиках, далее загруженная информация преобразуется в словарь, добавляется в переменную, и если нужна еще подзагрузка (об этом говорит параметр next_cursor):
Рисунок 26: Next_cursor
То создает еще:
Python:
if jsn["next_cursor"] != 0:
followers(nickname, jsn["next_cursor"], media_to_parse)
Как и в прошлом скрипте, передавая уже новые значения (
media, cursor)
Python:
def followers(nickname, cursor, media_to_parse):
r = api.request("followers/list", {"cursor": cursor, "screen_name": nickname, "skip_status": "true",
"include_user_entities": "false", "count": "200"})
jsn = json.loads(r.text)
print(jsn)
media_to_parse += jsn["users"]
if jsn["next_cursor"] != 0:
followers(nickname, jsn["next_cursor"], media_to_parse)
else:
print("Got ", len(str(media_to_parse).split("'id': "))-1, " follower(s)")
print("Downloading list of followers...")
for i in range(len(str(media_to_parse).split("'id':"))-1):
parsing_followers_friends(media_to_parse[i], "Followers")
print("Done\n")
Функция
friends() работает с данными о друзьях. В Твиттере так называются люди, на которых подписан указанный ранее аккаунт.Так же как и во
followers(), сначала происходит создание запроса, создание словаря, добавление в переменную.
Код:
def friends(nickname, cursor, media_to_parse):
r = api.request("friends/list", {"cursor": cursor, "screen_name": nickname, "skip_status": "true",
"include_user_entities": "false", "count": "200"})
jsn = json.loads(r.text)
media_to_parse += jsn["users"]
if jsn["next_cursor"] != 0:
friends(nickname, jsn["next_cursor"], media_to_parse)
else:
print("Got ", str(len(str(media_to_parse).split("'id': "))-1), " friend(s)")
print("Downloading list of friends...")
for i in range(len(str(media_to_parse).split("'id':"))-1):
parsing_followers_friends(media_to_parse[i], "Friends")
print("Done\n")
И если больше друзей нету, то есть
next_cursor равняется ‘’, то как в функции followers(), так и во friends() переменная media, которая имеет в себе весь список друзей/подписчиков, отправляется в функцию parsing_followers_friends(), где, в зависимости от friends_or_followers, функция решает куда сохранять файл. В функции followers(), в функцию отсылается строка “Followers”:
Python:
parsing_followers_friends(media_to_parse[i], [B]"Followers"[/B])
Python:
parsing_followers_friends(media_to_parse[i], [B]"Friends"[/B])
friends() окончена, и стартует функция getting_tweets()Данная функция собирает все твиты и ретвиты пользователя в одну переменную, и в дальнейшем отправляется в функцию
parsing_followers_friends():
Python:
def getting_tweets(nickname, media, count, statuses, max_id):
if max_id == "":
r = api.request("statuses/user_timeline", {"screen_name": nickname, "count": "200"})
else:
r = api.request("statuses/user_timeline", {"screen_name": nickname, "count": "200", "max_id": max_id})
jsn = json.loads(r.text)
media += jsn
count += 200
if statuses > count:
del media[-1]
getting_tweets(nickname, media, count, statuses, jsn[-1]["id_str"])
else:
print("Got "+str(len(media))+" tweet(s)")
print("Downloading tweets")
for i in range(len(media)):
parsing_tweets(media[i])
print("Done")
Так же стоит объяснить про странную строку del media[-1]. Дело в том, что при подзагрузке твитов/ретвитов, отсутствует такой курсор, как "next_page”, имеющийся в предыдущих функциях, и отследить, загрузились ли все твиты/ретвиты невозможно. Поэтому, пришлось придумывать велосипед, и использовать id последнего твита, как начало для загрузки следующего. Но из-за того, что один и тот же твит будет в конце и в начале, нужно удалять последний твит в общей переменной.
Твиттер также дает возможность просмотреть id тех пользователей, которые ретвитнули твит указанного аккаунта. Но так как, во-первых, лимит запросов составляет всего 300 в 15 минут:
Рисунок 27: Лимит на отправку запросов
а во-вторых на каждый id нужно создавать отдельный запрос, чтобы узнать информацию об аккаунте. Все указанные действия будут происходить медленно, с интервалом в 15 минут (если скрипт за 15 минут будет использовать больше 300 запросов). Из-за указанных особенностей, данная функция использоваться не будет.
По итогу, и собрав все твиты/ретвиты, весь полученный из словарей массив, отправляется в
parsin_tweets():
Python:
def parsing_tweets(tweet):
try:
author_nickname = tweet["retweeted_status"]["user"]["screen_name"]
author_name = tweet["retweeted_status"]["user"]["name"]
text = str(tweet["retweeted_status"]["text"])
link_to_tweet = "https://twitter.com/"+author_nickname+"/status/"+tweet["id_str"]
tweets_like = tweet["retweeted_status"]["favorite_count"]
tweets_retweet = tweet["retweeted_status"]["retweet_count"]
with open("Twitter/Retweets.txt", "a", encoding="utf-8") as file:
file.write(author_name+" (@"+author_nickname+")\n\t'"+text+"'\n\n\t"+str(tweets_retweet)+" Retweet(s) "+str(tweets_like)+" Like(s)"+"\n\n\tTweet - "+link_to_tweet+"\n\n"+"--"*25+"\n")
except:
text = tweet["text"]
data = tweet["created_at"]
likes = tweet["favorite_count"]
retweets = tweet["retweet_count"]
link_to_tweet = "https://twitter.com/" + tweet["user"]["screen_name"] + "/status/" + tweet["id_str"]
with open("Twitter/Tweets.txt", "a", encoding="utf-8") as file:
file.write("'"+text+"'\n\n\t"+data+"\t\t"+str(retweets)+" Retweet(s) "+str(likes)+
" Like(s)"+"\n\n\tTweet - "+link_to_tweet+"\n\n\n"+"--"*25+"\n")
file.close()
После создания переменных, полученные данные записываются в файл “Retweets.txt” и “Tweets.txt”.
И последняя функция имеет название
getting_fevorites().Она использует count (переменная из функции main()) для подсчета загруженных твитов.
Указанная функция схожа с предыдущей, единственное отличие - она вмещает в себе парсер, и не отправляет словарь из всех полученных твитов в отдельную функцию.
Лайкнутые твиты записывает в “Liked.txt”
После скана, получаем папку с такими файлами:
Рисунок 28: полученные файлы после работы скрипта
Сравнение вариантов реализации OSINT скриптов
Каждый скрипт имеет свои плюсы и минусы. Прежде всего, использование API всегда позволяет взять больше информации, чем без его использования.
В Инстаграме, если пользователь не вошел в аккаунт – он не может смотреть Stories, список подписчиков и список друзей.
Но в то же время, мы никак не привязаны аккаунтом, и мы можем сканировать что угодно, и сколько угодно (до ошибки rate limiting)
В Твиттере мне пришлось открыто лгать про цели и идеи моего приложения. И не факт что вам удастся таким же образом получить эти ключи для пользования API.
Но другая сторона медали – сплошное удобство.
Вы можете сравнить функции в скрипте для Твиттера и для Инстаграма. Использование библиотеки решает многие вопросы, над которыми я долго сидел при написании скрипта для Инстаграма.
Заключение
Подводя итоги, хочу сказать, что каждый из этих скриптов является утилитой для OSINT, вне зависимости от использования API.
Более анонимный способ – первый, декомпиляция трафика и создание собственных запросов
Более простой – второй, взяв токены и ключи, следовать по документации от Твиттера.
Тут уже решать за вами.
Python:
import requests
import argparse
import json
import time
import os
def parser():
parser = argparse.ArgumentParser(prog='OSINT Inst')
parser.add_argument("-n", "--nickname", help="Nickname?", dest="nickname")
args = vars(parser.parse_args())
main(args["nickname"])
def main(nickname):
url = "https://www.instagram.com/"+nickname
response = requests.get(url + "?__a=1")
if "Page Not Found" in response.text:
print("Page Not Found")
exit()
todos = json.loads(response.text)
os.mkdir("Instagram")
parse_page(todos)
photoes(todos["graphql"]["user"])
#getting links of the posts
def get_req_photo(media_to_parse, media, checker, json_media, idprofile_inst):
media_to_parse += str(media)
if checker:
end_cursor = json_media["edge_owner_to_timeline_media"]["page_info"]["end_cursor"]
data_for_get_req = 'https://www.instagram.com/graphql/query/?query_hash=f2405b236d85e8296cf30347c9f08c2a' \
'&variables=%7B%22id%22%3A%22' + idprofile_inst + \
'%22%2C%22first%22%3A50%2C%22after%22%3A%22' + end_cursor.split("==")[0] + '%3D%3D%22%7D'
gett = requests.get(data_for_get_req)
json_media = json.loads(gett.text)
checker = json_media["data"]["user"]["edge_owner_to_timeline_media"]["page_info"]["has_next_page"]
media = json_media["data"]["user"]["edge_owner_to_timeline_media"]["edges"]
get_req_photo(media_to_parse, media, checker, json_media["data"]["user"], idprofile_inst)
else:
print("Got ", len(str(media_to_parse).split("'shortcode': '")), " post(s)")
for i in range(1, len(str(media_to_parse).split("'shortcode': '"))):
link_to_page = str(media_to_parse).split("'shortcode': '")[i].split("',")[0]
req = requests.get("https://instagram.com/p/" + link_to_page + "/?__a=1")
json_page = json.loads(req.text)
parse_media(json_page["graphql"]["shortcode_media"], i)
print("Done")
def photoes(json_media):
media = json_media["edge_owner_to_timeline_media"]["edges"]
checker = json_media["edge_owner_to_timeline_media"]["page_info"]["has_next_page"]
idprofile_inst = json_media["id"]
get_req_photo('', media, checker, json_media, idprofile_inst)
#getting infos about account
def parse_page(todos):
print("Getting info about profile...")
follow_inst = str(todos["graphql"]["user"]["edge_follow"]["count"])
followed_by_inst = str(todos["graphql"]["user"]["edge_followed_by"]["count"])
content_inst = str(todos["graphql"]["user"]["edge_owner_to_timeline_media"]["count"])
biography = todos["graphql"]["user"]["biography"]
name = todos["graphql"]["user"]["full_name"]
username = todos["graphql"]["user"]["username"]
verified = str(todos["graphql"]["user"]["is_verified"])
fb = str(todos["graphql"]["user"]["connected_fb_page"])
business = str(todos["graphql"]["user"]["is_business_account"])
try:
link = "\nLink: "+ todos["graphql"]["user"]["external_link"]
except:
link = ''
file = open("Instagram/info.txt", "w", encoding="utf-8")
info = "Name: "+name+"\nUsername: "+username+"\nBiography: "+biography+"\n\nVerified: "+verified+"\nFollow: "+follow_inst+"\nFollowed: "+\
followed_by_inst+"\nNumber of posts: "+content_inst+link+"\nBusiness account: "+business+\
'\nConnected to facebook: '+fb
file.write(info)
file.close()
#functions for comments
def get_req_com(shortcode, comments, page_info, list_of_comments, m):
list_of_comments += str(comments)
if page_info["has_next_page"]:
end_cursor = page_info["end_cursor"]
data_for_get_req = 'https://www.instagram.com/graphql/query/?query_hash=f0986789a5c5d17c2400faebf16efd0d' \
'&variables=%7B%22shortcode%22%3A%22' + shortcode + '%22%2C%22first%22%3A50' \
'%2C%22after%22%3A%22' + end_cursor.split("==")[0] + '%3D%3D%22%7D'
get = requests.get(data_for_get_req)
json_media = json.loads(get.text)
try:
comments = json_media["data"]["shortcode_media"]["edge_media_to_comment"]["edges"]
page_info = json_media["data"]["shortcode_media"]["edge_media_to_comment"]["page_info"]
get_req_com(shortcode, comments, page_info, list_of_comments, m)
except Exception as e:
if json_media['message'] == "rate limited":
print("Rate limit. Please wait... (8 min.)")
time.sleep(480)
get_req_com(shortcode, comments, page_info, list_of_comments, m)
else:
print("Error:","\n",e,json_media)
pass
else:
comments = parse_comm(list_of_comments)
file = open("Instagram/post " + str(m) + "/comments.txt", "w", encoding="utf-8")
file.write(comments)
file.close()
with open("Instagram/all_comments.txt", "a", encoding="utf-8") as file_all_comments:
file_all_comments.write(comments)
def parse_comm(list_of_comments):
list = ''
num = str(list_of_comments).split("'node':")
for i in range(1, len(num)):
if str(num[i]).split("'edge_liked_by': {'count': ")[1].split("}")[0] == '0':
likes = ''
else:
likes = "\t" + str(num[i]).split("'edge_liked_by': {'count': ")[1].split("}")[0] + " like(s)"
username = "\n" + str(num[i]).split("'username': '")[1].split("'},")[0]
try:
text = str(num[i]).split("'text': '")[1].split("'")[0] + "'"
except:
text = str(num[i]).split(''''text': "''')[1].split('"')[0] + "'"
list += username + " '" + text +likes+"\n"
return list
def comm(json_page, m):
list_of_comments = ''
try:
try:
comments = json_page["edge_media_to_parent_comment"]["edges"]
page_info = json_page["edge_media_to_parent_comment"]["page_info"]
except KeyError:
comments = json_page["edge_media_to_comment"]["edges"]
page_info = json_page["edge_media_to_comment"]["page_info"]
except Exception as e:
print("\n\n", json_page,"\nError:", e)
exit()
get_req_com(json_page["shortcode"], comments, page_info, '', m)
#saving infos and photoes from account
def parse_media(json_page, m):
view_count = ''
os.mkdir("Instagram/post " + str(m))
captions = '\nCaption(s): '
print("Downloading post " + str(m))
if json_page["__typename"] == "GraphVideo":
view_count = str("\nView count = " + str(json_page["video_view_count"]))
out = open("Instagram/post " + str(m) + "/video.txt", "w")
out.write(str("https://instagram.com/p/"+json_page["shortcode"]))
out.close()
if json_page["__typename"] == "GraphImage":
photo = str(json_page["display_resources"][-1]["src"])
p = requests.get(photo)
out = open("Instagram/post " + str(m) + "/img" + str(m) + ".jpg", "wb")
out.write(p.content)
out.close()
captions = "\n"+json_page["accessibility_caption"]
if json_page["__typename"] == "GraphSidecar":
for i in range(len(json_page["edge_sidecar_to_children"]["edges"])):
photoes = json_page["edge_sidecar_to_children"]["edges"][i]["node"]["display_resources"][-1]["src"]
p = requests.get(photoes)
out = open("Instagram/post "+str(m)+"/img"+str(i)+".jpg", "wb")
out.write(p.content)
out.close()
try:
captions += "\nphoto №"+str(i)+"-"+ str(json_page["edge_sidecar_to_children"]["edges"][i]
["node"]["accessibility_caption"])+"\n"
except:
view_count = str("\nView count = " + str(json_page["edge_sidecar_to_children"]["edges"][i]
["node"]["video_view_count"]))
out = open("Instagram/post " + str(m) + "/video.txt", "w")
out.write(str("https://instagram.com/p/" + json_page["shortcode"]))
out.close()
likes = "\nlikes: " + str(json_page["edge_media_preview_like"]["count"])+"\n"
link = "Link - "+"instagram.com/p/"+json_page["shortcode"]+"\n"
location = json_page["location"]
if location is not None:
with open("Instagram/all_locations.txt", "a", encoding="utf-8") as file:
file.write("post "+str(m)+"\n"+str(location)+"\n")
else:
location = ''
pass
if json_page["edge_media_to_tagged_user"]["edges"] == []:
tagged_user = ''
else:
tagged = str(json_page["edge_media_to_tagged_user"]).split("node")
tagged_user = '\nTagged user(s): '
for i in range(1, len(tagged)):
tagged_user += "@"+str(json_page["edge_media_to_tagged_user"]).split("username': '")[i].split("'}")[0]+\
" ("+str(json_page["edge_media_to_tagged_user"]).split("full_name': '")[i].split("', ")[0]+")\n"
with open("Instagram/all_tagged.txt", "a", encoding="utf-8") as file:
file.write(str("post " + str(m) + ":" + tagged_user + "\n"))
file = open("Instagram/post "+str(m)+"/info.txt", "w", encoding="utf-8")
try:
text = "text: '"+str(json_page["edge_media_to_caption"]["edges"][0]["node"]["text"])+"'\n"
except:
text = "text: None\n"
all_information = str(text+likes+tagged_user+"\nLocation: "+str(location)+captions+view_count+"\n"+link)
file.write(all_information)
file.close()
try:
checker_for_comment = json_page["edge_media_to_parent_comment"]["count"]
except:
checker_for_comment = json_page["edge_media_to_comment"]["count"]
if checker_for_comment != 0:
comm(json_page, m)
else:
pass
if __name__ == '__main__':
parser()
Python:
from TwitterAPI import TwitterAPI
import os
import json
import requests
cons_key = ""
cons_sec_key = "" #Put yours keys in
acc_tok = "" #these lines
acc_tok_sec = ""
api = TwitterAPI(cons_key, cons_sec_key, acc_tok, acc_tok_sec)
def main(nick):
reqst = api.request("users/lookup", {"screen_name": nick})
jsn = json.loads(reqst.text)
try:
if jsn["errors"][0]["code"] == 17:
print("This page doesn't exist")
elif '""errors":' in reqst.text:
print("ERROR\nResponse data:", reqst.txt)
exit()
except:
os.mkdir("Twitter")
count, count_for_tweets = info_about_acc(jsn[0])
print("Downloading list of followers...")
followers(nick, "-1", [])
friends(nick, "-1", [])
getting_tweets(nick, [], 0, count_for_tweets, "")
getting_favorites(nick, count, 0, [], '')
def parsing_tweets(tweet):
try:
author_nickname = tweet["retweeted_status"]["user"]["screen_name"]
author_name = tweet["retweeted_status"]["user"]["name"]
text = str(tweet["retweeted_status"]["text"])
link_to_tweet = "https://twitter.com/"+author_nickname+"/status/"+tweet["id_str"]
tweets_like = tweet["retweeted_status"]["favorite_count"]
tweets_retweet = tweet["retweeted_status"]["retweet_count"]
with open("Twitter/Retweets.txt", "a", encoding="utf-8") as file:
file.write(author_name+" (@"+author_nickname+")\n\t'"+text+"'\n\n\t"+str(tweets_retweet)+" Retweet(s) "+str(tweets_like)+" Like(s)"+"\n\n\tTweet - "+link_to_tweet+"\n\n"+"--"*25+"\n")
except:
text = tweet["text"]
data = tweet["created_at"]
likes = tweet["favorite_count"]
retweets = tweet["retweet_count"]
link_to_tweet = "https://twitter.com/" + tweet["user"]["screen_name"] + "/status/" + tweet["id_str"]
with open("Twitter/Tweets.txt", "a", encoding="utf-8") as file:
file.write("'"+text+"'\n\n\t"+data+"\t\t"+str(retweets)+" Retweet(s) "+str(likes)+
" Like(s)"+"\n\n\tTweet - "+link_to_tweet+"\n\n\n"+"--"*25+"\n")
file.close()
def getting_tweets(nickname, media, count, statuses, max_id):
if max_id == "":
r = api.request("statuses/user_timeline", {"screen_name": nickname, "count": "200"})
else:
r = api.request("statuses/user_timeline", {"screen_name": nickname, "count": "200", "max_id": max_id})
jsn = json.loads(r.text)
media += jsn
count += 200
if statuses > count:
del media[-1]
getting_tweets(nickname, media, count, statuses, jsn[-1]["id_str"])
else:
print("Got "+str(len(media))+" tweet(s)")
print("Downloading tweets")
for i in range(len(media)):
parsing_tweets(media[i])
print("Done")
def parsing_followers_friends(acc_to_parse, friends_or_followers):
name = acc_to_parse["name"]
nickname_of_user = acc_to_parse["screen_name"]
info = name +"\t(@"+nickname_of_user+")\n\n"
if friends_or_followers == "Friends":
with open("Twitter/Friends.txt", "a", encoding="utf-8",) as file:
file.write(info)
if friends_or_followers == "Followers":
with open("Twitter/Followers.txt", "a", encoding="utf-8",) as file:
file.write(info)
def friends(nickname, cursor, media_to_parse):
r = api.request("friends/list", {"cursor": cursor, "screen_name": nickname, "skip_status": "true",
"include_user_entities": "false", "count": "200"})
jsn = json.loads(r.text)
media_to_parse += jsn["users"]
if jsn["next_cursor"] != 0:
friends(nickname, jsn["next_cursor"], media_to_parse)
else:
print("Got ", str(len(str(media_to_parse).split("'id': "))-1), " friend(s)")
print("Downloading list of friends...")
for i in range(len(str(media_to_parse).split("'id':"))-1):
parsing_followers_friends(media_to_parse[i], "Friends")
print("Done\n")
def followers(nickname, cursor, media_to_parse):
r = api.request("followers/list", {"cursor": cursor, "screen_name": nickname, "skip_status": "true",
"include_user_entities": "false", "count": "200"})
jsn = json.loads(r.text)
print(jsn)
media_to_parse += jsn["users"]
if jsn["next_cursor"] != 0:
followers(nickname, jsn["next_cursor"], media_to_parse)
else:
print("Got ", len(str(media_to_parse).split("'id': "))-1, " follower(s)")
print("Downloading list of followers...")
for i in range(len(str(media_to_parse).split("'id':"))-1):
parsing_followers_friends(media_to_parse[i], "Followers")
print("Done\n")
def info_about_acc(jsn):
print("Getting information...")
id = str(jsn["id"])
name = jsn["name"]
nickname = jsn["screen_name"]
followers = str(jsn["followers_count"])
friends = str(jsn["friends_count"])
data_of_creation = jsn["created_at"]
count_of_liked_tweets = jsn["favourites_count"]
verified = jsn["verified"]
profile_pic = jsn["profile_image_url_https"]
statuses_count = jsn["statuses_count"]
lang = jsn["lang"]
if lang is not None:
lang = "\nLang -" + lang
else:
lang = ''
location = jsn["location"]
if location != '':
location = "\nLocation -" + location
description = jsn["description"]
if description != '':
description = "\n\tDescription -" + description
print("Downloading photos...")
background_pic = jsn["profile_background_image_url"]
if background_pic is not None:
photo_back = requests.get(background_pic)
out_back = open("Twitter/Profile background photo.jpg", "wb")
out_back.write(photo_back.content)
out_back.close()
background_pic = '\nBackground picture - ' + background_pic
else:
background_pic = ''
if not jsn["profile_use_background_image"]:
banner_pic = jsn['profile_banner_url']
photo = requests.get(banner_pic)
out = open("Twitter/Profile banner.jpg", "wb")
out.write(photo.content)
out.close()
photo = requests.get(profile_pic.split("_normal")[0]+profile_pic.split("_normal")[1])
out = open("Twitter/Profile photo.jpg", "wb")
out.write(photo.content)
out.close()
print("Writing information to the file info.txt...")
info = "ID - " + id + "\nName - " + name + "\nNickname - " + nickname + "\nFollowers - " + followers + "\nFriends - " + friends+\
"\nData of creation - " + data_of_creation + "\nCount of liked twiters - " + str(count_of_liked_tweets) + "\nVerified - "+\
str(verified) + "\nStatuses count - " + str(statuses_count) + lang + location + description + background_pic + \
"\nProfile pic - " + profile_pic
file = open("Twitter/Info.txt", "w")
file.write(info)
file.close()
return count_of_liked_tweets, statuses_count
def getting_favorites(nick, count, counter, media, max_id):
if max_id == '':
reqst = api.request("favorites/list", {"count": 200, "screen_name": nick})
else:
reqst = api.request("favorites/list", {"count": 200, "screen_name": nick, "max_id": max_id})
jsn = json.loads(reqst.text)
counter += 200
media += jsn
if count > counter:
del media[-1]
getting_favorites(nick, count, counter, media, jsn[-1]["id_str"])
else:
print("Got " + str(len(media)) + " Liked tweet(s)")
print("Downloading these tweets...")
for i in range(len(media) - 1):
text = media[i]["text"]
data = media[i]["created_at"]
likes = media[i]["favorite_count"]
retweets = media[i]["retweet_count"]
link_to_tweet = "https://twitter.com/" + media[i]["user"]["screen_name"] + "/status/" + media[i]["id_str"]
author_name = media[i]["user"]["name"]
author_nickname = media[i]["user"]["screen_name"]
with open("Twitter/Liked.txt", "a", encoding="utf-8") as file:
file.write(author_name + " ( @" + author_nickname + ")\n" + "\n'" + text + "'\n\n\t" + data + "\t\t"
+ str(retweets) + " Retweet(s) " + str(likes) + " Like(s)" + "\n\n\tTweet - " +
link_to_tweet + "\n\n\n" + "--" * 25 + "\n")
print("Done")
#write a nickname
if __name__ == '__main__':
main("")
Код на GitHub
Автор: @g00db0y
взято с codeby