Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Data acquisition, часть 1

16 комментариев

Одно из приемуществ всеобщего удешевления аппаратуры и интернета в том, что сбор информации из разных источников в интернете почти ничего не стоит и может производиться без особых проблем. Задача получения и обработки больших объемов данных является коммерчески превлекательной ввиду спроса на считывание (“скрейпинг”) веб-сайтов со стороны заказчиков (обычно это описывается термином ‘social media analysis’, т.е. анализ социальных медиа). Ну и в принципе это достаточно интересно – по крайней мере по сравнению с рутинной разработкой сайтов, отчетов, и т.д.

В этой статье я начну рассказ про то, как можно реализовать сбор и обработку данных с использованием платформы .Net. Было бы интересно послушать про то как делать то же самое в стеке Java, поэтому если кто-то хочет присоединиться к данной статье в качестве соавтора – милости прошу.

Все исходники находятся тут: http://bitbucket.org/nesteruk/datagatheringdemos

Обзор задачи

Итак, у нас пожалуй самая “размытая” из возможных задач – получение, обработка и хранение данных. Для чтого чтобы получить работующую систему, нам нужно знать

  • Где находятся данные и как к ним правильно обращаться
  • Как обработать данные чтобы получить только то, что нужно
  • Где и как хранить данные

Источники данных

Давайте рассмотрим те источники данных, с которых нужно получать информацию:

  • Форумы
  • Twitter
  • Блоги
  • Новостные сайты
  • Каталоги, листинги
  • Публичные веб-сервисы
  • Прикладное ПО

Сразу хочу подчеркнуть, что веб-браузер не является единственным источником данных. Тем не менее, если работа с веб-сервисами или, скажем, использование API какой-то социальной платформы, является достаточно понятной задачей и не требует много телодвижений, разбор HTML является намного более сложной задачей. И HTML это не предел – порой приходится разбирать JavaScript или даже визуальную информацию с картинок (к пр. для обхода “капчи”).

Другой проблемой является то, что порой контент подгружается динамически через AJAX, что делает нужным разного сорта ‘учет состояний’ для того чтобы получать контент именно тогда, когда он доступен.

Обработка данных

Обработка данных – это самая трудоемкая и дорогостоящая (с точки зрения потенциального заказчика) операция. С одной стороны, может показаться что тот же HTML должен очень просто разбираться существующими средствами, но на самом деле это не так. Во-первых, HTML в большинстве случаев не является XHTML, иначе говоря сделав XElement.Parse() вы попросту получите исключение. Поэтому нужно как минимум иметь возможность “корректировать” плохо написаный HTML.

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

Не следует забывать и про более “приземленный” процессинг данных, то есть некие трансформации или произвольные действия над полученными данными. Например, получив IP-адрес вам захочется узнать местоположение или наличие веб-сервера по этому адресу, что потребует дополнительных запросов. Или, скажем, при получении новых данных вам нужно постоянно пересчитывать движимое среднее (streaming OLAP).

Хранение данных

Получив данные, их нужно где-то хранить. Вариантов храниния много – использование сериализации, текстовый файлов, а также объектно- и документно-ориентированных а также конечно реляционных баз данных. Выбор хранища в коммерческом заказе зависит скорее всего либо от заказчика (“мы хотим MySQL”) либо от финансовых предпочтений заказчика. В .Net-разработке базой “по умолчанию” является SQL Server Express. Если же вы делаете хранилище для себя, позволительно использовать все что угодно – будь то MongoDB, db4o или например SQL Server 2008R2 Datacenter Edition.

В большинстве случаев, хранилища данных не требуют особой сложности, т.к. пользователи просто проецируют базу в Excel (ну или SPSS, SAS, и т.п.) а дальше используют привычные методы для анализа. Варианты вроде SSAS (SQL Server Analysis Services) используются намного реже (ввиду минимального ценника в $7500 – см. тут), но знать о них тоже стоит.

Небольшой пример

Давайте посмотрим на минимальный кусочек кода, который поможет нам скачать и “распарсить” страницу. Для этих задач, мы воспользуемся двумя пакетами:

  • WatiN – это библиотека для тестирования веб-интерфейсов. Ее хорошо использовать для автоматизированного нажатия кнопочек, выбора элементов из списка, и подобных вещей. WatiN также предоставляет объектную модель заполученной страницы, но я бы ей не пользовался. Причина в целом одна – WatiN нестабильная и весьма капризная библиотека, которую нужно с опаской использовать (только в 32-битном режиме!) для управления браузером.
  • HTML Agility Pack – библиотека для разбора HTML. Сам HTML можно взять из WatiN, загрузить, и даже если он плохо сформирован, Agility Pack позволит делать в нем поиски и выборки с помощью XPath.

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

// Загрузка сайта с использованием WatiN + HTML Agility Pack
[STAThread]
static void Main()
{
  using (var browser = new IE("http://www.pokemon.com"))
  {
    var doc = new HtmlDocument();
    doc.LoadHtml(browser.Body.OuterHtml);
    var h1 = doc.DocumentNode.SelectNodes("//h3").First();
    Console.WriteLine(h1.InnerText);
  }
  Console.ReadKey();
}

В примере выше, мы получили страницу через WatiN, загрузили тело страницы в HTML Agility Pack, нашли первый элемент типа H3 и выписали в консоль его содержание.

Поллинг

Наверное для вас очевидно, что запись данных в какое-то хранилище не делается из консольного приложения. В большинстве случаев, для этого используется сервис (windows service). А то чем занимается сервис – это в большинстве случаев поллинг, то есть регулирное скачивание ресурса и обновление нашего представления о нем. Скачивание обычно происходит с интервалом раз в N минут/часов/дней.

// Простой поллинг-сервис
public partial class PollingService : ServiceBase
{
  private readonly Thread workerThread;
  public PollingService()
  {
    InitializeComponent();
    workerThread = new Thread(DoWork);
    workerThread.SetApartmentState(ApartmentState.STA);
  }
  protected override void OnStart(string[] args)
  {
    workerThread.Start();
  }
  protected override void OnStop()
  {
    workerThread.Abort();
  }
  private static void DoWork()
  {
    while (true)
    {
      log.Info("Doing work...");
      // do some work, then
      Thread.Sleep(1000);
    }
  }
}

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

// Использование сервиса в консоли
var service = new PollingService();
ServiceBase[] servicesToRun = new ServiceBase[] { service };
 
if (Environment.UserInteractive)
{
  Console.CancelKeyPress += (x, y) => service.Stop();
  service.Start();
  Console.WriteLine("Running service, press a key to stop");
  Console.ReadKey();
  service.Stop();
  Console.WriteLine("Service stopped. Goodbye.");
}
else
{
  ServiceBase.Run(servicesToRun);
}

Другая полезная фича – это саморегистрация, чтобы вместо использования installutil можно было установить сервис через myservice /i. Для этого существует отдельный класс…

// Утилита саморегистрации
class ServiceInstallerUtility
{
  private static readonly ILog log = 
    LogManager.GetLogger(typeof(Program));
  private static readonly string exePath = 
    Assembly.GetExecutingAssembly().Location;
  public static bool Install()
  {
    try { ManagedInstallerClass.InstallHelper(new[] { exePath }); }
    catch { return false; }
    return true;
  }
  public static bool Uninstall()
  {
    try { ManagedInstallerClass.InstallHelper(new[] { "/u", exePath }); }
    catch { return false; }
    return true;
  }
}

Класс установки использует мало знакомую сборку System.Configuration.Install. Используется она прямо из Main():

// Разбор пути для саморегистрации
if (args != null && args.Length == 1 && args[0].Length > 1
    && (args[0][0] == '-' || args[0][0] == '/'))
{
  switch (args[0].Substring(1).ToLower())
  {
    case "install":
    case "i":
      if (!ServiceInstallerUtility.InstallMe())
        Console.WriteLine("Failed to install service");
      break;
    case "uninstall":
    case "u":
      if (!ServiceInstallerUtility.UninstallMe())
        Console.WriteLine("Failed to uninstall service");
      break;
    default:
      Console.WriteLine("Unrecognized parameters.");
      break;
  }
}

Ну и последняя фича это конечно же использование логирования. Я использую библиотеку log4net, а для записывания логов в консоль можно использвать очень вкусную фичу под названием ColoredConsoleAppender. Сам процесс логирования примитивен.

Несколько важных правил

На первый раз достаточно информации. К концу хочу напомнить несколько простых правил:

  • Запуск IE требует single-thread apartment; я правда использую FireFox т.к. мне нравится FireBug
  • WatiN следует исполнять в 32-битной программе (x86)
  • Поллинг, приведенный выше неидеален, т.к. не учитывает тот факт, что сам по себе WatiN протормаживает и парсинг HTML – тоже операция небыстрая

Кстати о птичках… вместо сервиса можно в принципе сделать EXE и запускать его через sheduler. Но это как-то неопрятно.

Спасибо за внимание. Продолжение следует :)

Реклама

Written by Dmitri

20 мая 2010 в 9:18

Опубликовано в .NET, Technology

Tagged with ,

комментариев 16

Subscribe to comments with RSS.

  1. spasibo.

    alex

    20 мая 2010 at 15:43

  2. Интересное совпадение, никогда не приходилось заниматься Web scrap-ингом, а вот сейчас, похоже, придется.

    Вопрос: а зачем нам WatiN, если нам нужно просто выудить html — ведь есть HttpRequest, и он не будет открывать нам окошек с браузером.

    С другой стороны, если уж мы запустили WatiN, почему бы нам не воспользоваться его родными, типизированными средствами парсинга?

    ulu

    7 июня 2010 at 22:33

    • Ответ простой: если в сайте есть ajax или нужно взаимодейтсвие с UI или URL не имеет прямого соответствия с сайтом (см например gz-spb.ru) или сайт активно защищается от роботов, то использование «честного» браузера через WatiN — единственный вариант.

      А с парсингом проблем нет благодаря SgmlReader, HtmlAgilityPack, и т.п.

      Dmitri

      7 июня 2010 at 22:41

  3. Привет, Дмитрий.
    Очень понравилось про умные windows service. Правда так красиво у меня не прикрутилось. Зато нашёл опенсорс библиотеку, которая делает практически тоже самое и даже больше :)! http://code.google.com/p/ncron/. Очень понравилась. Написал в песочницу про неё http://habrahabr.ru/sandbox/15121/ — 7 дней повисит ). Может кому и поможет. А может и на хабр попаду.

    Dennis

    16 июня 2010 at 12:33

    • Класс, большое спасибо за наводку на NCron — сейчас буду смотреть. Инвайта к сожалению нет (все бездарно раздал), но надеюсь кто-нибудь из коллег поможет.

      Dmitri

      16 июня 2010 at 18:28

      • Может помогут :). Хотя думаю сейчас мало людей читают песочницу. Думаю читали, когда она только появилась, сейчас опять читают только родные статьи. По крайней мере сам так делаю :).
        Есть вопрос, маленький.
        Вот у тебя в 3м листинге есть
        service.Start();
        а я когда делал сервис, также вроде как и ты (3.5 фреймворк). То у меня этого метода не было. Ты его дописывал или это новый метод в 4м фрейворке ?

        Dennis

        16 июня 2010 at 20:12

        • Это мой метод, но если такой метод уже есть, значит у меня прописано new. А весь этот проект (который кстати в сети лежит) использует полновесный .net framework 4.

          Dmitri

          16 июня 2010 at 22:57

      • Нашёлся добрый человек =). Теперь и я на хабре.
        Понравился NCron?

        Dennis

        17 июня 2010 at 11:00

        • Инвайт нашелся сразу как я запостил на хабре, спасибо @rystsov.
          NCron еще не попробовал.

          Dmitri

          17 июня 2010 at 11:52

      • Наконец-то уже и я могу поставить плюс за эту статью =).

        Dennis

        18 июня 2010 at 10:26

  4. Новичок…не разбираюсь в некоторых терминах

    Ser.

    7 декабря 2010 at 16:03

  5. используете ООП, странно ? Мне кажется что простими методами єто можно бьстрее сделать

    samsim

    3 декабря 2013 at 0:23

    • Хмм, а что странного, ООП єто просто механiзм. Просто iспользуется все єто дело в «промьішленньіх масштабах», поєтому хорошо органiзовьівать все єто дело серьезно.

      Dmitri

      3 декабря 2013 at 0:33


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: