Парсим Instagram при помощи PhantomJS

Парсинг, в контексте данного повествования, это автоматизация манипулирования содержимым портала - периодический сбор информации об интересующих аккаунтах (лайки, коментарии, новые посты) чтобы, например, наблюдать динамику развития популярности личности.
Instagram (как и другие популярные соц сети) предоставляет 2 интерфейса для взаимодействия с его серверами:
  • Web https://www.instagram.com - через браузер
  • API - через регистрацию своего приложения

Забегая вперед, скажу, что для поставленных целей второй вариант к сожалению не подходит, так как Instagram ОЧЕНЬ строго относится к использованию своего API сторонними разработчиками: требует обоснования необходимости выдачи токена вплоть до отсылки видео с описанием будущего проекта:

Остается первый вариант - работать с веб интерфейсом для автоматизации посещения страниц интересующих аккаунтов.

Жертва

Для примера возьмем аккаунт популярной косплеерши Джессики:
https://www.instagram.com/jessicanigri/

Алгоритм

Рассмотрим алгоритм отслеживая новых постов в Инстаграме и получения ссылок на страницы с полноразмерными изображениями новых постов.

Помним, что при открытии страницы профиля будут подгружены только последние 12 изображений, и новые НЕ подгрузятся, если не проскролить страницу вниз. В этом можно убедиться выполнив поиск элемента изображения по xpath ( //div[@class='_4rbun']/img - поиск соответствий, где тег изображения img находится внутри тега div с классом _4rbun ). Получаем массив из 12ти элементов:
Кроме того, на этом скриншоте, если обратить внимание на названия CSS-классов (нелогичные сочетания символов), можно увидеть стремление разработчиков инстаграмма обезопасить портал от вэб-парсинга, которым мы сейчас как раз займемся =)

Алгоритм:
  1. Заходим на страницу аккаунта
  2. Получаем ссылки на последние 12 постов

Используемый инструмент

Есть два пути автоматизировано зайти на веб-страницу: через открытый браузер и без него, у каждого варианта есть свои плюсы и минусы.
  • В открытом браузере (посредством Selenium Webdriver) удобно ловить элементы через xpath, переходить на другие страницы нажатием на кнопки, скролить страницу (чтоб вызвать подгрузку более старых постов). Кроме того, в браузере открывается полная (не мобильная) версия портала.
  • Парсить без открытия браузера (посредством Mechanize или PhantomJS например) удобно тем, что для работы скрипта вообще не требуется рабочий стол, поэтому скрипт можно запускать хоть на самом слабом VPS-сервере. К минусам можно отнести ограниченный функционал и неудобства с отладкой. Этот вариант подходит для нашего несложного алгоритма.

Основной код выполним на Руби, а для серфинга используем PhantomJS (широко известный как “безоконный браузер”) так как он позволяет исполнять jаvascript-скрипты на странице и вообще имитировать полноценный браузер, без чего инстаграм-страница не предстанет в удобном для парсинга виде (полная версия сайта).

Реализация

Для Руби существует гем 'phantomjs', добавим его в Gemfile:
source 'https://rubygems.org'
gem 'phantomjs'

Выполним команду bundle, находясь в папке проекта, чтоб установить библиотеку.

Обращаемся к объекту PhantomJS командой:
require 'phantomjs'
Phantomjs.run("file.js", "jessicanigri") { |line| p line }

Первым аргументом передаем путь к JS-файлу, в котором будут инструкции для браузера, остальными аргументами можно передать, например, идентификатор аккаунта Инстаграма, в нашем случае - jessicanigri. Каждая строка вывода из JS-скрипта будет ловиться Руби-скриптом и выводиться в терминал:

Приступим к написанию JS-скрипта file.js:
Инициализируем объект system, чтоб получить из него переданный в Руби аргумент (id аккаунта), и составим URL - адрес для перехода на нужную веб-страницу:
var system = require('system');
var arg1 = system.args[1];
var url = 'https://www.instagram.com/' + arg1; 

Запишем в переменную agent строку алиаса браузера и инициализируем объект page для работы с браузером:
var agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36';
var page = require('webpage').create();

Переход на страницу выполняется самовызывающейся функцией, к объекту page с передачей адреса (переменная url). Внутри функции выставляем настройки для имитации полноценного браузера чтобы вызвать открытие полной версии портала:
page.open(url, function (status) {
   page.settings.userAgent = agent;
   page.viewportSize = { width: 1200, height: 900 };


Взаимодействие с элементами страницы также производим самовызывающейся функцией к объекту page. Результат ее выполнения получим в переменной links, которую потом передадим в Руби:
var links = page.evaluate(function() {

Чтобы правильно локализовать нужные ссылки на посты и отличить их от других ссылок исследуем HTML-код страницы:

В качестве отличительного  признака ссылок на посты можно взять фрагмент “/p/” из атрибута href ссылок, за которым идет уникальный идентификатор поста.
Вернемся к JS-коду. Получим все элементы страницы с тэгом “a” (ссылки), переберем в цикле каждый из них, и сохраним в отдельном массиве только те, которые содержат в атрибуте href сочетание “/p/”. Вернем полученный массив и закроем функцию:
var l = document.getElementsByTagName("a"); 
   var a = [];
   for (i=0;i<l.length;i++) { 
   if (l[i].href.includes('/p/')) { 
   a.push(l[i].href); 
   } 
   }
   return a;
});

Вернем в Руби полученный массив командой console.log и закроем браузер:
console.log(links);
phantom.exit();
});

Результат выполнения:
Двенадцать ссылок на последние посты Инстаграм аккаунта jessicanigri.

Полный код:
file.js
var system = require('system'); 
var arg1 = system.args[1]; 
var url = 'https://www.instagram.com/' + arg1; 
var agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36'; 
var page = require('webpage').create(); 
page.open(url, function (status) { 
    page.settings.userAgent = agent; 
    page.viewportSize = { width: 1200, height: 900 }; 
    var links = page.evaluate(function() { 
        var l = document.getElementsByTagName("a"); 
        var a = []; 
        for (i=0;i<l.length;i++) { 
            if (l[i].href.includes('/p/')) { 
                a.push(l[i].href); 
            } 
        } 
        return a; 
   }); 
console.log(links); 
phantom.exit(); 
});

script.rb
require 'phantomjs' 
Phantomjs.run("file.js", "jessicanigri") { |line| p line }

Gemfile
source 'https://rubygems.org' 
gem 'phantomjs'

Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.