Этот сайт использует файлы cookies. Оставаясь на сайте, Вы соглашаетесь с использованием файлов cookies и принимаете Соглашение об использовании сайта.

программа для парсинга

Web parsing:
задачи, проблемы, инструменты

1832

Время от времени разработчик сталкивается с задачами, связанными со сбором какой-то информации с различных сайтов для анализа или поддержания какого-либо процесса. Хорошо, если сайт имеет API, с помощью которого можно получить нужную информацию, но иногда API отсутствует, либо не отдаёт нужную информацию. Что делать в таком случае?


Введение

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

Процесс извлечения структурированной полезной информации с сайта называется парсингом (parsing), а инструменты для реализации данного процесса — парсерами (parsers).

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

  • маркетинговые исследования;
  • мониторинг СМИ в реальном времени;
  • анализ общественного мнения;
  • автоматическое ценообразование (при разумном применении) на базе анализа цен конкурентов;
  • построение списка потенциальных пользователей на базе информации о пользователях ресурсов конкурентов;
  • создание API для сайтов без API.

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

Что такое парсеры?

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

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

Методы извлечения информации

Можно выделить следующие методы извлечения информации с сайтов:

  1. Ручной — в данном случае роль парсера выполняет человек. Он производит всю цепочку действий, необходимую для получения требуемой информации. Информация собирается обычным копипастом.
  2. Гибридный — пользователь по-прежнему выполняет основные действия для получения информации, но может использовать вспомогательные программные средства для автоматизации сбора, например, браузерный плагин, который на основе конфигурации извлекает и структурирует информацию из определенных мест страницы при активации.
  3. Автоматический — получение и структурирование информации выполняется автоматически.

Для того чтобы один раз получить значения нескольких полей с 10−15 страниц, писать отдельный парсер может быть и нецелесообразно, однако в случае большого числа страниц, полей или высокой периодичности сбора информации, стоит задуматься об автоматизации данного процесса.

Процесс извлечения информации с отдельной веб-страницы можно разбить на следующие этапы:

  1. Построение запроса для получения информации.
  2. Выполнение запроса и получение ответа.
  3. Обработка ответа, извлечение и структурирование необходимой информации.
  4. Передача полученной информации для последующей обработки.

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

Если в первом случае всё достаточно просто, то во втором, чтобы за разумное время разработать парсер, стоит прибегнуть к использованию headless-браузеров (без графического интерфейса) с поддержкой сценариев вроде PhantomJS для извлечения данных для оптимизации времени на изучение того, как сайт взаимодействует с бэкендом. Также для этих целей можно прибегнуть к Selenium WebDriver с одним из реальных браузеров.

Инструменты

Для автоматизированного извлечения информации с веб-страниц существует 3 типа инструментов:

1. Библиотеки.

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

К таким инструментам относятся многочисленные библиотеки для различных языков программирования: jSoup для Java, SimpleHTMLDom для PHP, lxml.html для Python и другие.

2. Headless-браузеры.

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

К таким инструментам можно отнести PhantomJS и SlimerJS.

3. SaaS решения.

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

К таким сервисам можно отнести Mozenda, Octoparse (бесплатный), Import.io (бесплатный).

4. Настольные приложения.

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

Пример: IRobotSoft.

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

Способы защиты от парсеров

Для владельцев сайтов с полезной информацией парсеры — нежеланные гости. Они впустую расходуют вычислительные ресурсы сервера, трафик, за который возможно нужно платить, а плохо спроектированные парсеры могут обрушить огромное число запросов на сервер приводя к DoS (Denial of Service). Извлечённые данные могут использоваться в корыстных целях, вроде воровства материалов с одного ресурса и публикации на другом от чужого имени, нарушая авторство.

В связи с этим веб-мастера могут применять различные меры борьбы с автоматическими парсерами, а именно:

  1. Динамическое изменение структуры кода страницы с целью усложнения извлечения информации из блоков.
  2. Показ капчи при авторизации/регистрации/превышении числа запросов за единицу времени.
  3. Блокировка клиентов по среднему объему трафика в единицу времени.
  4. Анализ поведения клиентов и блокировка/требование пройти капчу для продолжения работы при подозрительном поведении.

Эти проблемы решаемы:

  • Если информация очень важна, то можно прибегнуть к screen scraping’у — извлечение информации не из текста страницы, а с её изображения.
  • Распознавание капчи можно передать сторонним сервисам.
  • Растянуть процесс парсинга во времени.
  • Имитировать поведение реального пользователя: клики, движения мыши, пролистывание страницы.

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

Пример из практики

В качестве примера рассмотрим следующую проблему:

Имеется сайт, подключенный к 20+ рекламным биржам, использующим разные системы отчетности. Клиент захотел централизованно собирать информацию с них и вести статистику по доходам/расходам рекламных кампаний посуточно.

Чтобы построить такой отчет, нужно было бы пройтись по всем площадкам, и для каждой выполнить следующий набор действий:

  1. авторизоваться;
  2. сформировать запрос для получения статистики за последние сутки;
  3. получить ответ;
  4. сохранить информацию из ответа в таблицу.

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

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

Идея была следующей:

  1. Под каждую площадку написать свой парсер, который авторизовался бы в системе и получал всю информацию за нас.
  2. Написать CRON задачу, которая выполнялась бы раз в сутки, запускала парсеры, собирала их ответы и сохраняла в базу.
  3. Создать интерфейс, позволяющий визуализировать полученную информацию.

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

Каждый парсер должен был принимать на вход следующие параметры:

  • логин для доступа на площадку;
  • пароль для доступа на площадку;
  • дата, за которую нужно получить данные;
  • таймаут исполнения скрипта (на случай, если что-то пошло не так, чтобы скрипт не висел вечно).

Для парсеров доходов на выход выдавалась следующая информация:

  • число показов;
  • число кликов;
  • доход;
  • CTR.

А для парсеров расходов:

  • ID кампании;
  • число кликов;
  • расход;
  • название кампании.

Пример исходного кода отдельного парсера:


var system = require('system');
var args = system.args;


//проверяем наличие необходимых параметров
if (args.length < 3) {
    console.log("Missing required parameters");
    phantom.exit();
}


//задаем таймаут переданный в параметрах или используем 60 секунд, если параметр не указан
var timeout = 60000;
if (typeof args[4] != 'undefined') {
    timeout = parseInt(args[4]);
}
//считываем логин/пароль из параметров
var credentials = {username: args[1], password: args[2]};
//считываем текущую дату из параметров
var strDate = args[3];


//разбиваем дату на компоненты (дата в формате ГГГГ-ММ-ДД)
var dateComps = strDate.split('-');


//индикатор процесса загрузки страницы
var loadInProgress = false,
    interval = 0,
    //объект страницы, на которой мы находимся
    page = require('webpage').create(),
    //URL страницы аутентификации
    loginUrl = 'LOGIN URL',
    //URL страницы с данными
    dataUrl = 'DATA URL?year='+dateComps[0]+'&month='+dateComps[1];


//коллбэк начала загрузки страницы
page.onLoadStarted = function() {
    loadInProgress = true;
};


//коллбэк окончания загрузки страницы
page.onLoadFinished = function() {
    loadInProgress = false;
};


//таймаут прерывания работы парсера
setTimeout(function(){
    console.log(JSON.stringify({result: 'error', 'error': 'Task reached timeout'}));
    phantom.exit();
}, timeout);


//задаем User Agent, на случай если нас захотят выследить
page.settings.userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.6 Safari/537.11";
//открываем страницу аутентификации
page.open(loginUrl, function(status) {
    //evaluate выполняет наш код в контексте страницы. логинимся
    page.evaluate(function(creds) {
        document.getElementsByName('email')[0].value = creds.username;
        document.getElementsByName('password')[0].value = creds.password;
        document.getElementById('login').submit();
    }, credentials);
    //ждем загрузку страницы после аутентификации
    interval = setInterval(function(){
        if(!loadInProgress) {
            //переходим на страницу с данными
            page.open(dataUrl, function(status) {
                //извлекаем информацию со страницы с помощью jQuery
                var data = page.evaluate(function(date){
                    var items = jQuery('#dataTables td:contains("'+date+'")').closest('tr').children();

                    return {
                        'impressions': items.eq(2).text().replace(',', '').trim(),
                        'revenue': items.eq(3).text().replace('$', '').trim(),
                        'ctr': items.eq(4).text().trim(),
                        'clicks': items.eq(5).text().trim()
                    }
                }, strDate);


                //отдаем JSON результат в консоль
                console.log(JSON.stringify(data));
                phantom.exit();
            });

            clearInterval(interval);
        }
    },50);
});

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

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

К счастью, с капчей в рамках решения данной задачи сталкиваться не пришлось.

На разработку одного парсера в зависимости от сложности системы уходило до 4-х часов (часть площадок использовали одинаковые системы отчетности, что позволяло использовать некоторые парсеры повторно).

Заключение

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

Если у вас есть вопросы или предложения, не стесняйтесь и пишите их в комментариях.