Pull to refresh

Data acquisition, часть 1

Reading time8 min
Views4.2K
Одно из приемуществ всеобщего удешевления аппаратуры и интернета в том, что сбор информации из разных источников в интернете почти ничего не стоит и может производиться без особых проблем. Задача получения и обработки больших объемов данных является коммерчески превлекательной ввиду спроса на считывание («скрейпинг») веб-сайтов со стороны заказчиков (обычно это описывается термином ‘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.

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

[STAThread]<br/>
static void Main()<br/>
{<br/>
  using (var browser = new IE("http://www.pokemon.com"))<br/>
  {<br/>
    var doc = new HtmlDocument();<br/>
    doc.LoadHtml(browser.Body.OuterHtml);<br/>
    var h1 = doc.DocumentNode.SelectNodes("//h3").First();<br/>
    Console.WriteLine(h1.InnerText);<br/>
  }<br/>
  Console.ReadKey();<br/>
}<br/>

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

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

public partial class PollingService : ServiceBase<br/>
{<br/>
  private readonly Thread workerThread;<br/>
  public PollingService()<br/>
  {<br/>
    InitializeComponent();<br/>
    workerThread = new Thread(DoWork);<br/>
    workerThread.SetApartmentState(ApartmentState.STA);<br/>
  }<br/>
  protected override void OnStart(string[] args)<br/>
  {<br/>
    workerThread.Start();<br/>
  }<br/>
  protected override void OnStop()<br/>
  {<br/>
    workerThread.Abort();<br/>
  }<br/>
  private static void DoWork()<br/>
  {<br/>
    while (true)<br/>
    {<br/>
      log.Info("Doing work⋮");<br/>
      // do some work, then
      Thread.Sleep(1000);<br/>
    }<br/>
  }<br/>
}<br/>

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

var service = new PollingService();<br/>
ServiceBase[] servicesToRun = new ServiceBase[] { service };<br/>
 <br/>
if (Environment.UserInteractive)<br/>
{<br/>
  Console.CancelKeyPress += (x, y) => service.Stop();<br/>
  service.Start();<br/>
  Console.WriteLine("Running service, press a key to stop");<br/>
  Console.ReadKey();<br/>
  service.Stop();<br/>
  Console.WriteLine("Service stopped. Goodbye.");<br/>
}<br/>
else<br/>
{<br/>
  ServiceBase.Run(servicesToRun);<br/>
}<br/>

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

class ServiceInstallerUtility<br/>
{<br/>
  private static readonly ILog log = <br/>
    LogManager.GetLogger(typeof(Program));<br/>
  private static readonly string exePath = <br/>
    Assembly.GetExecutingAssembly().Location;<br/>
  public static bool Install()<br/>
  {<br/>
    try { ManagedInstallerClass.InstallHelper(new[] { exePath }); }<br/>
    catch { return false; }<br/>
    return true;<br/>
  }<br/>
  public static bool Uninstall()<br/>
  {<br/>
    try { ManagedInstallerClass.InstallHelper(new[] { "/u", exePath }); }<br/>
    catch { return false; }<br/>
    return true;<br/>
  }<br/>
}<br/>

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

if (args != null && args.Length == 1 && args[0].Length > 1<br/>
    && (args[0][0] == '-' || args[0][0] == '/'))<br/>
{<br/>
  switch (args[0].Substring(1).ToLower())<br/>
  {<br/>
    case "install":<br/>
    case "i":<br/>
      if (!ServiceInstallerUtility.Install())<br/>
        Console.WriteLine("Failed to install service");<br/>
      break;<br/>
    case "uninstall":<br/>
    case "u":<br/>
      if (!ServiceInstallerUtility.Uninstall())<br/>
        Console.WriteLine("Failed to uninstall service");<br/>
      break;<br/>
    default:<br/>
      Console.WriteLine("Unrecognized parameters.");<br/>
      break;<br/>
  }<br/>
}<br/>

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

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

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

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

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

Articles

Change theme settings