Как собирать данные о компаниях в инстаграм без мобильного приложения

Если для вашей работы вам было нужно собирать данные с корпоративных профилей Инстаграм, вы наверняка использовали для этого мобильное приложение, поскольку в веб версии корпоративные данные отсутствовали. В частности, было невозможно определить корпоративный это профиль или персональный. Теперь появилась возможность обрабатывать их автоматически парсером, использующим мобильное API. Мы нашли его на просторах интернета, один из наших пользователей написал его и поделился с общественностью на одном из известных маркетинговых интернет ресурсах. Давайте разберем, как этот парсер работает.

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

---
config:
    agent: Firefox
    debug: 2
do:
- variable_set:
    field: username
    value: ИМЯ ВАШЕГО АККАУНТА В ИНСТАГРАМ
- variable_set:
    field: password
    value: ПАРОЛЬ ДЛЯ ВАШЕГО АККАУНТА В ИНСТАГРАМ
- variable_set:
    field: accounts
    value: СПИСОК АККАУНТОВ ИНСТАГРАМ, РАЗДЕЛЕННЫХ ЗАПЯТОЙ, ДЛЯ КОТОРЫХ НУЖНО ИЗВЛЕЧЬ БИЗНЕС ДАННЫЕ
- walk:
    to: https://www.instagram.com/
    do:
    - find:
        path: body
        do:
        - parse:
            filter: window\._sharedData\s+\=\s+([^;]+);
        - normalize:
            routine: json2xml
        - to_block
        - find:
            path: config>csrf_token
            do:
            - parse
            - variable_set: token
        - walk:
            to:
                post: https://www.instagram.com/accounts/login/ajax/
                headers:
                    x-csrftoken: 
                    x-instagram-ajax: 1
                    x-requested-with: XMLHttpRequest
                data:
                    username: 
                    password: 
            do:
            - find:
                path: status
                do:
                - parse
                - if:
                    match: "fail"
                    do:
                    - cannot_login_probably_checkpoint_is_required
                    - exit
            - find:
                path: authenticated
                do:
                - parse
                - if:
                    match: "true"
                    else:
                    - wrong_login_or_password
                    - exit
                - cookie_get: mid
                - variable_set: mid
                - cookie_get: rur
                - variable_set: rur
                - cookie_get: ds_user_id
                - variable_set: dsuserid
                - cookie_get: sessionid
                - variable_set: sessionid
                - variable_get: accounts
                - to_block
                - split:
                    context: text
                    delimiter: ','
                - find:
                    path: div.splitted
                    do:
                    - parse
                    - space_dedupe
                    - trim
                    - variable_set: account
                    - walk:
                        to: https://www.instagram.com//
                        do:
                        - find:
                            path: script:contains("window._sharedData")
                            do:
                            - parse
                            - space_dedupe
                            - trim
                            - filter: 
                                args:
                                    - window\._sharedData\s+\=\s+(.+)\s*;\s*$
                            - normalize:
                                routine: json2xml
                            - to_block
                            - find: 
                                path: body_safe 
                                do: 
                            - find:
                                path: entry_data > profilepage > graphql > user > id
                                do:
                                - parse
                                - variable_set: id
                                - walk:
                                    to: https://i.instagram.com/api/v1/users//info/
                                    headers:
                                        X-IG-App-ID: 567067343352427
                                        X-IG-Capabilities: 3brDAw==
                                        X-IG-Connection-Type: WIFI
                                        X-IG-Connection-Speed: 3400
                                        X-IG-Bandwidth-Speed-KBPS: -1.000
                                        X-IG-Bandwidth-TotalBytes-B: 0
                                        X-IG-Bandwidth-TotalTime-MS: 0
                                        Cookie: mid=; csrftoken=; rur=; ds_user_id=; sessionid=; ig_or=;
                                        X-FB-HTTP-Engine: Liger
                                        Accept: '*/*'
                                        Accept-Language: en-US
                                    do:
                                    - find:
                                        path: body_safe > user
                                        do:
                                        - object_new: item
                                        - find:
                                            path: address_street
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: address_street
                                        - find:
                                            path: category
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: category
                                        - find:
                                            path: city_name
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: city_name
                                        - find:
                                            path: contact_phone_number
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: contact_phone_number
                                        - find:
                                            path: external_url
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: external_url
                                        - find:
                                            path: full_name
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: full_name
                                        - find:
                                            path: is_business
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: is_business
                                        - find:
                                            path: latitude
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: latitude
                                        - find:
                                            path: longitude
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: longitude
                                        - find:
                                            path: pk
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: id
                                        - find:
                                            path: public_email
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_email
                                        - find:
                                            path: public_phone_country_code
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_phone_country_code
                                        - find:
                                            path: public_phone_number
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_phone_number
                                        - find:
                                            path: username
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: username
                                        - find:
                                            path: zip
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: zip
                                        - object_save:
                                            name: item
                                    - sleep: 5

Как вы уже наверное знаете, секция config предназначена для предустановки парсера, в данном случае для установки уровня режима отладки (который в принципе нужен только для разработки и его можно было бы опустить) и имени браузера от лица которого парсер будет отправлять запросы на сервер. Технически здесь мог бы быть Chrome или Safari, но автор решил что должен быть Firefox. К слову сказать, иногда сервера могут отдавать разные данные, в зависимости от выставленного имени браузера. Также, иногда может потребоваться использовать не пресет, а полную строку User-Agent, их можно найти тут.

config:
    agent: Firefox
    debug: 2

Основной логический блок парсера находится в секции do. В самом начале происходит инициализация переменных вашим логином, паролем для аккаунта инстаграм и списком аккаунтов:

- variable_set:
    field: username
    value: ИМЯ ВАШЕГО АККАУНТА В ИНСТАГРАМ
- variable_set:
    field: password
    value: ПАРОЛЬ ДЛЯ ВАШЕГО АККАУНТА В ИНСТАГРАМ
- variable_set:
    field: accounts
    value: СПИСОК АККАУНТОВ ИНСТАГРАМ, РАЗДЕЛЕННЫХ ЗАПЯТОЙ, ДЛЯ КОТОРЫХ НУЖНО ИЗВЛЕЧЬ БИЗНЕС ДАННЫЕ

Далее парсер загружает домашнюю страницу Инстаграм и проходит в в тег body

- walk:
    to: https://www.instagram.com/
    do:
    - find:
        path: body
        do:

Парсит весь текст и извлекает объект Javascript, транслирует его в XML и превращает в DOM блок, переходя в его контекст.

        - parse:
            filter: window\._sharedData\s+\=\s+([^;]+);
        - normalize:
            routine: json2xml
        - to_block

Сейчас в нашем контексте находится извлеченный объект Javascript (JSON) как DOM и мы можем ходить по его элементам, как если бы это была обычная HTML страница. Поэтому мы находим ноду config а в ней ноду csrf_token, парсим содержимое и извлекаем токен, который нам нужен для логина в инстаграм и записываем его в переменную token. После чего логинимся в Инстаграм, используя токен, логин и пароль, которые мы храним в переменных:

        - find:
            path: config>csrf_token
            do:
            - parse
            - variable_set: token
        - walk:
            to:
                post: https://www.instagram.com/accounts/login/ajax/
                headers:
                    x-csrftoken: 
                    x-instagram-ajax: 1
                    x-requested-with: XMLHttpRequest
                data:
                    username: 
                    password: 

Далее парсер проверяет, авторизовал ли нас Инстаграм

            - find:
                path: status
                do:
                - parse
                - if:
                    match: "fail"
                    do:
                    - cannot_login_probably_checkpoint_is_required
                    - exit

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

            - find:
                path: authenticated
                do:
                - parse
                - if:
                    match: "true"
                    else:
                    - wrong_login_or_password
                    - exit
                - cookie_get: mid
                - variable_set: mid
                - cookie_get: rur
                - variable_set: rur
                - cookie_get: ds_user_id
                - variable_set: dsuserid
                - cookie_get: sessionid
                - variable_set: sessionid

Затем парсер считывает переменную со списком аккаунтов, которые нужно забрать, в регистр и переводит текст в регистре в блок. Это делается для того, чтобы использовать команду split, так как команда работает с содержимимым блока, а не регистра. После разбивки, парсер итерирует по каждому аккаунту и выполняет команды в блоке do:

                - split:
                    context: text
                    delimiter: ','
                - find:
                    path: div.splitted
                    do:

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

                    - parse
                    - space_dedupe
                    - trim
                    - variable_set: account

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

                    - walk:
                        to: https://www.instagram.com//
                        do:
                        - find:
                            path: script:contains("window._sharedData")
                            do:
                            - parse
                            - space_dedupe
                            - trim
                            - filter: 
                                args:
                                    - window\._sharedData\s+\=\s+(.+)\s*;\s*$
                            - normalize:
                                routine: json2xml
                            - to_block
                            - find: 
                                path: body_safe 
                                do: 
                            - find:
                                path: entry_data > profilepage > graphql > user > id
                                do:
                                - parse
                                - variable_set: id

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

                                - walk:
                                    to: https://i.instagram.com/api/v1/users//info/
                                    headers:
                                        X-IG-App-ID: 567067343352427
                                        X-IG-Capabilities: 3brDAw==
                                        X-IG-Connection-Type: WIFI
                                        X-IG-Connection-Speed: 3400
                                        X-IG-Bandwidth-Speed-KBPS: -1.000
                                        X-IG-Bandwidth-TotalBytes-B: 0
                                        X-IG-Bandwidth-TotalTime-MS: 0
                                        Cookie: mid=; csrftoken=; rur=; ds_user_id=; sessionid=; ig_or=;
                                        X-FB-HTTP-Engine: Liger
                                        Accept: '*/*'
                                        Accept-Language: en-US
                                   do:

Мобильный API возвращает ответ в JSON. Диггернаут автоматически конвертирует его в XML и даем возможность работать вам с DOM структурой, используя стандартную команду find. Так что весь дальнейший код просто забирает данные используя определенные CSS селекторы и сохраняет их в структуру данных.

                                        - object_new: item
                                        - find:
                                            path: address_street
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: address_street
                                        - find:
                                            path: category
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: category
                                        - find:
                                            path: city_name
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: city_name
                                        - find:
                                            path: contact_phone_number
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: contact_phone_number
                                        - find:
                                            path: external_url
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: external_url
                                        - find:
                                            path: full_name
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: full_name
                                        - find:
                                            path: is_business
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: is_business
                                        - find:
                                            path: latitude
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: latitude
                                        - find:
                                            path: longitude
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: longitude
                                        - find:
                                            path: pk
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: id
                                        - find:
                                            path: public_email
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_email
                                        - find:
                                            path: public_phone_country_code
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_phone_country_code
                                        - find:
                                            path: public_phone_number
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: public_phone_number
                                        - find:
                                            path: username
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: username
                                        - find:
                                            path: zip
                                            do:
                                            - parse
                                            - space_dedupe
                                            - trim
                                            - object_field_set:
                                                object: item
                                                field: zip
                                        - object_save:
                                            name: item

В целом логика работы парсера проста, единственный сложный момент, это процесс маскировки под мобильное приложение. Пример полученных данных приведен ниже:

{
  item : {
    category :  "Product/Service",
    username :  "adidas",
    is_business :  "true",
    contact_phone_number :  "",
    zip :  "91074",
    public_phone_number :  "",
    longitude :  "10.9094251",
    latitude :  "49.5831932",
    public_phone_country_code :  "",
    full_name :  "adidas",
    city_name :  "Herzogenaurach",
    address_street :  "Adi-Dassler-Str. 1",
    id :  "20269764",
    public_email :  "",
    external_url :  "http://a.did.as/BuiltToDefy"
  }
}
Михаил Сисин: Со-основатель облачного сервиса по сбору информации и парсингу сайтов Diggernaut. Работает в области сбора и анализа данных, а также разработки систем искусственного интеллекта и машинного обучения  более десяти лет.
Related Post

This website uses cookies.