Парсер linux – 10 инструментов, позволяющих парсить информацию с веб-сайтов, включая цены конкурентов + правовая оценка для России

Содержание

Установка и обновление | A-Parser


Версии A-Parser(top)

A-Parser Release - последняя полноценная версия парсера. Подробнее о выходе новых версий можно прочитать в новостях на главной странице.

A-Parser Beta - промежуточная версия парсера которая постоянно дополняется. Подробнее об изменениях можно прочитать на форуме в разделе Next Release. Чтобы скачать последнюю версию A-Parser Beta, нужно нажать кнопку Update, и после загрузки новой версии, скачать ее.

Установка на Windows(top)

1. В Members Area проверьте верно ли прописан ваш IP-адрес (для пользователей у которых динамический IP-адрес, требуется менять IP-адрес при смене на новый)

2. Перейдите во вкладку A-Parser - Downloads

3. Перед скачиванием нужной версии, следует предварительно нажать Update, для обновления до последней версии A-Parser
4. Cкачайте нужный архив и распакуйте в любую директорию
5. Зайдите в папку aparser и запустите aparser.exe
6. После появления окна сервера, откройте в браузере http://127.0.0.1:9091/index.html
7. Пароль по умолчанию пустойВнимание!
  • Известны проблемы с совместимостью с
    Norton Internet Security
    - возможна нестабильная работа парсера
  • Emsisoft Anti-Malware - возможна нестабильная работа парсера
  • Guard Mail.ru - необходимо полностью удалить из системы
  • Если парсер не запускается или падает в процессе работы с такими записями в логе:

    IO error: ... Append: cannot write
    или
    sysopen: Permission denied

    Нажмите, чтобы раскрыть...

    то нужно отключить службу индексации:
    Спойлер: Инструкция
    1. Открываем Управление службами: Пуск - Выполнить или сочетание клавиш Win + R, вводим services.msc
    2. В открывшемся окне выбираем Windows Search, нажимаем правую кнопку мыши и выбираем Свойства
    3. В окне свойств на вкладке Общие меняем Тип запуска на Отключена и нажимаем Применить
    4. Если после этого кнопка Остановить активна - нажимаем ее и останавливаем службу

Установка на Linux(top)

На данный момент доступна одна версия одновременно для 32 и 64 битных систем.
Для удобства скачивания с сервера реализованы временные ссылки, в Member Area нужно нажать Get one-time link - по полученной ссылке можно один раз скачать дистрибутив.
  1. В Members Area перейдите во вкладку A-Parser - IP Configuration и пропишите IP-адрес
  2. Перейдите во вкладку A-Parser - Downloads
  3. Перед скачиванием нужной версии, следует предварительно нажать Update, для обновления до последней версии A-Parser
  4. Получите временную ссылку для скачивания
  5. В шелле заходим в директорию куда будем ставить (нельзя ставить в директорию с веб доступом!), и выполняем следующие команды:

# wget http://a-parser.com/members/onetime/ce42f308eaa577b5/aparser.tar.gz
# tar zxf aparser.tar.gz
# rm -f aparser.tar.gz
# cd aparser/
# chmod +x aparser
# ./aparser

Нажмите, чтобы раскрыть...



Иногда на сервере может не хватать каких то библиотек, например:

# ./aparser
./aparser: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory

Нажмите, чтобы раскрыть...

Просто ставим недостающие библиотеки:

# yum -y install zlib

Нажмите, чтобы раскрыть...

И заного запускаем апарсер:

# ./aparser

Нажмите, чтобы раскрыть...

Если не выдается никаких сообщений - это свидетельствует об успешном запуске

Далее будет приведен список ошибок и пути решения:
На Debian x64:

./aparser
error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

apt-get install lib32stdc++6

Нажмите, чтобы раскрыть...

На Debian x64:

./aparser
-bash: ./aparser: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

apt-get install libc6.i386

Нажмите, чтобы раскрыть...

На Debian x64:

./aparser
./aparser: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

apt-get install lib32z1

Нажмите, чтобы раскрыть...

На Ubuntu x64:

./aparser
-bash: ./aparser: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

apt-get install ia32-libs

Нажмите, чтобы раскрыть...

На CentOS x64:

./aparser
./aparser: error while loading shared libraries: libstdc++.so.6: cannot open shared object file: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

yum -y install libstdc++.x86_64
yum -y install libstdc++.i686

Нажмите, чтобы раскрыть...

На CentOS x64:

/aparser: /lib/ld-linux.so.2: bad ELF interpreter: No such file or directory

Нажмите, чтобы раскрыть...

Решение:

yum -y install glibc.i686

Нажмите, чтобы раскрыть...

На CentOS x64:

./aparser: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory

Нажмите, чтобы раскрыть...

Решение для CentOS release 5.7 (Final):

yum -y install zlib.i386

Нажмите, чтобы раскрыть...

Решение для CentOS release 6.3 (Final):

yum -y install zlib.i686

Нажмите, чтобы раскрыть...

Установка на FreeBSD(top)

Установка аналогична как на Linux, предварительно необходимо настроить совместимость с Linux:

Обновим каталог репозитория FreeBSD:

pkg update
pkg upgrade

Нажмите, чтобы раскрыть...

Установим linux эмулятор, для начала загрузив модуль ядра linux:

kldload linux
pkg install linux_base-f10-10_7

Нажмите, чтобы раскрыть...

Установим linprocfs, добавив запись в /etc/fstab:

echo 'linproc /compat/linux/proc linprocfs rw 0 0' >> /etc/fstab

Нажмите, чтобы раскрыть...

Добавим загрузку модуля linux при загрузке системы, добавив в /etc/rc.conf строку:

echo 'linux_enable="YES"' >> /etc/rc.conf

Нажмите, чтобы раскрыть...

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

reboot

Нажмите, чтобы раскрыть...

После перезагрузки следовать инструкции по установке на Linux, используя Linux дистрибутив A-Parser

Далее будет приведен список ошибок и пути решения:

На FreeBSD x64:

pkg install linux_base-f10-10_7
...
ELF binary type "3" not known.
/compat/linux/sbin/ldconfig: Exec format error

Нажмите, чтобы раскрыть...

Решение:

kldload linux
pkg remove linux_base-f10-10_7
pkg install linux_base-f10-10_7

Нажмите, чтобы раскрыть...


Тюнинг Linux для большого числа потоков(top)


По умолчанию Linux лимитирует количество открытых файлов и сокетов до 1024 на пользователя, чтобы увеличить лимит выполните следующие команды:

echo 'root soft nofile 10240' >> /etc/security/limits.conf
echo 'root hard nofile 10240' >> /etc/security/limits.conf

Нажмите, чтобы раскрыть...

Так же необходимо увеличить размер таблицы ip_conntrack:

sysctl -w net.ipv4.netfilter.ip_conntrack_max=262144
echo 'net.ipv4.netfilter.ip_conntrack_max=262144' >> /etc/sysctl.conf

Нажмите, чтобы раскрыть...

(При отсутствии фаервола iptables на эту команду выведется ошибка - просто проигнорируйте её)

Необходимо перезайти в шелл(ssh), после чего перезапустить A-Parser
Если вы запускаете парсер не под рутом, а под другим пользователем, то замените root на имя пользователя

Для проверки текущего лимита необходимо выполнить

ulimit -n

Нажмите, чтобы раскрыть...

На некоторых системах дополнительно требуется прописать в файл /etc/pam.d/common-session следующую строчку:

session required pam_limits.so

Нажмите, чтобы раскрыть...


Обновление программы(top)

Внимание! Если не указано иное, то достаточно обновить только исполняемый файл.

Инструкция по обновлению программы для Windows и Linux через пользовательский интерфейс(top)

  • Выбрать Канал обновления в Общих настройках

  • В меню Инструменты перейти во вкладку Обновить A-parser

  • Проверить наличие последних обновлений
  • Нажать Обновить. Обновление произойдет автоматически

Инструкция по обновлению для Windows вручную(top)

В общем случае достаточно заменить исполняемый файл парсера, если другое не указано.
  1. Останавливаем A-Parser - нажимаем Stop server
  2. Скачиваем с Member Area архив и перезаписываем aparser.exe
  3. Запускаем aparser.exe

Инструкция по обновлению для Linux вручную(top)

  1. Останавливаем A-Parser - в консоли выполняем killall aparser
  2. Скачиваем с Member Area архив и перезаписываем файл aparser
  3. Запускаем aparser - в консоли выполняем ./aparser
Пример с использованием временной ссылки:

wget http://a-parser.com/members/onetime/0d19621928c25a48/aparser.tar.gz
kill $(cat files/pid)
sleep 1
tar xzf aparser.tar.gz -O aparser/aparser > aparser
rm -f aparser.tar.gz
chmod +x aparser
./aparser

Нажмите, чтобы раскрыть...

10 инструментов, позволяющих парсить информацию с веб-сайтов, включая цены конкурентов + правовая оценка для России


Инструменты web scraping (парсинг) разработаны для извлечения, сбора любой открытой информации с веб-сайтов. Эти ресурсы нужны тогда, когда необходимо быстро получить и сохранить в структурированном виде любые данные из интернета. Парсинг сайтов – это новый метод ввода данных, который не требует повторного ввода или копипастинга.

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

1. Сбор данных для исследования рынка

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

2. Извлечение контактной информации

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

3. Решения по загрузке с StackOverflow

С инструментами парсинга сайтов можно создавать решения для оффлайнового использования и хранения, собрав данные с большого количества веб-ресурсов (включая StackOverflow). Таким образом можно избежать зависимости от активных интернет соединений, так как данные будут доступны независимо от того, есть ли возможность подключиться к интернету.

4. Поиск работы или сотрудников

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

5. Отслеживание цен в разных магазинах

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

В обзор ниже не попал Российский сервис парсинга сайтов и последующего мониторинга цен XMLDATAFEED (xmldatafeed.com), который разработан в Санкт-Петербурге и в основном ориентирован на сбор цен с последующим анализом. Основная задача — создать систему поддержки принятия решений по управлению ценообразованием на основе открытых данных конкурентов. Из любопытного стоит выделить публикация данные по парсингу в реальном времени 🙂

10 лучших веб-инструментов для сбора данных:


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

1. Import.io

Import.io предлагает разработчику легко формировать собственные пакеты данных: нужно только импортировать информацию с определенной веб-страницы и экспортировать ее в CSV. Можно извлекать тысячи веб-страниц за считанные минуты, не написав ни строчки кода, и создавать тысячи API согласно вашим требованиям.

Для сбора огромных количеств нужной пользователю информации, сервис использует самые новые технологии, причем по низкой цене. Вместе с веб-инструментом доступны бесплатные приложения для Windows, Mac OS X и Linux для создания экстракторов данных и поисковых роботов, которые будут обеспечивать загрузку данных и синхронизацию с онлайновой учетной записью.

2. Webhose.io

Webhose.io обеспечивает прямой доступ в реальном времени к структурированным данным, полученным в результате парсинга тысяч онлайн источников. Этот парсер способен собирать веб-данные на более чем 240 языках и сохранять результаты в различных форматах, включая XML, JSON и RSS.

Webhose.io – это веб-приложение для браузера, использующее собственную технологию парсинга данных, которая позволяет обрабатывать огромные объемы информации из многочисленных источников с единственным API. Webhose предлагает бесплатный тарифный план за обработку 1000 запросов в месяц и 50 долларов за премиальный план, покрывающий 5000 запросов в месяц.

3. Dexi.io (ранее CloudScrape)

CloudScrape способен парсить информацию с любого веб-сайта и не требует загрузки дополнительных приложений, как и Webhose. Редактор самостоятельно устанавливает своих поисковых роботов и извлекает данные в режиме реального времени. Пользователь может сохранить собранные данные в облаке, например, Google Drive и Box.net, или экспортировать данные в форматах CSV или JSON.

CloudScrape также обеспечивает анонимный доступ к данным, предлагая ряд прокси-серверов, которые помогают скрыть идентификационные данные пользователя. CloudScrape хранит данные на своих серверах в течение 2 недель, затем их архивирует. Сервис предлагает 20 часов работы бесплатно, после чего он будет стоить 29 долларов в месяц.

4. Scrapinghub

Scrapinghub – это облачный инструмент парсинга данных, который помогает выбирать и собирать необходимые данные для любых целей. Scrapinghub использует Crawlera, умный прокси-ротатор, оснащенный механизмами, способными обходить защиты от ботов. Сервис способен справляться с огромными по объему информации и защищенными от роботов сайтами.

Scrapinghub преобразовывает веб-страницы в организованный контент. Команда специалистов обеспечивает индивидуальный подход к клиентам и обещает разработать решение для любого уникального случая. Базовый бесплатный пакет дает доступ к одному поисковому роботу (обработка до 1 Гб данных, далее — 9$ в месяц), премиальный пакет дает четырех параллельных поисковых ботов.

5. ParseHub

ParseHub может парсить один или много сайтов с поддержкой JavaScript, AJAX, сеансов, cookie и редиректов. Приложение использует технологию самообучения и способно распознать самые сложные документы в сети, затем генерирует выходной файл в том формате, который нужен пользователю.

ParseHub существует отдельно от веб-приложения в качестве программы рабочего стола для Windows, Mac OS X и Linux. Программа дает бесплатно пять пробных поисковых проектов. Тарифный план Премиум за 89 долларов предполагает 20 проектов и обработку 10 тысяч веб-страниц за проект.

6. VisualScraper

VisualScraper – это еще одно ПО для парсинга больших объемов информации из сети. VisualScraper извлекает данные с нескольких веб-страниц и синтезирует результаты в режиме реального времени. Кроме того, данные можно экспортировать в форматы CSV, XML, JSON и SQL.

Пользоваться и управлять веб-данными помогает простой интерфейс типа point and click. VisualScraper предлагает пакет с обработкой более 100 тысяч страниц с минимальной стоимостью 49 долларов в месяц. Есть бесплатное приложение, похожее на Parsehub, доступное для Windows с возможностью использования дополнительных платных функций.

7. Spinn3r

Spinn3r позволяет парсить данные из блогов, новостных лент, новостных каналов RSS и Atom, социальных сетей. Spinn3r имеет «обновляемый» API, который делает 95 процентов работы по индексации. Это предполагает усовершенствованную защиту от спама и повышенный уровень безопасности данных.

Spinn3r индексирует контент, как Google, и сохраняет извлеченные данные в файлах формата JSON. Инструмент постоянно сканирует сеть и находит обновления нужной информации из множества источников, пользователь всегда имеет обновляемую в реальном времени информацию. Консоль администрирования позволяет управлять процессом исследования; имеется полнотекстовый поиск.

8. 80legs

80legs – это мощный и гибкий веб-инструмент парсинга сайтов, который можно очень точно подстроить под потребности пользователя. Сервис справляется с поразительно огромными объемами данных и имеет функцию немедленного извлечения. Клиентами 80legs являются такие гиганты как MailChimp и PayPal.

Опция «Datafiniti» позволяет находить данные сверх-быстро. Благодаря ней, 80legs обеспечивает высокоэффективную поисковую сеть, которая выбирает необходимые данные за считанные секунды. Сервис предлагает бесплатный пакет – 10 тысяч ссылок за сессию, который можно обновить до пакета INTRO за 29 долларов в месяц – 100 тысяч URL за сессию.

9. Scraper

Scraper – это расширение для Chrome с ограниченными функциями парсинга данных, но оно полезно для онлайновых исследований и экспортирования данных в Google Spreadsheets. Этот инструмент предназначен и для новичков, и для экспертов, которые могут легко скопировать данные в буфер обмена или хранилище в виде электронных таблиц, используя OAuth.

Scraper – бесплатный инструмент, который работает прямо в браузере и автоматически генерирует XPaths для определения URL, которые нужно проверить. Сервис достаточно прост, в нем нет полной автоматизации или поисковых ботов, как у Import или Webhose, но это можно рассматривать как преимущество для новичков, поскольку его не придется долго настраивать, чтобы получить нужный результат.

10. OutWit Hub

OutWit Hub – это дополнение Firefox с десятками функций извлечения данных. Этот инструмент может автоматически просматривать страницы и хранить извлеченную информацию в подходящем для пользователя формате. OutWit Hub предлагает простой интерфейс для извлечения малых или больших объемов данных по необходимости.

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

Самое главное — правомерность парсинга?!

Вправе ли организация осуществлять автоматизированный сбор информации, размещенной в открытом доступе на сайтах в сети интернете (парсинг)?

В соответствии с действующим в Российской Федерации законодательством разрешено всё, что не запрещено законодательством. Парсинг является законным, в том случае, если при его осуществлении не происходит нарушений установленных законодательством запретов. Таким образом, при автоматизированном сборе информации необходимо соблюдать действующее законодательство. Законодательством Российской Федерации установлены следующие ограничения, имеющие отношение к сети интернет:

1. Не допускается нарушение Авторских и смежных прав.
2. Не допускается неправомерный доступ к охраняемой законом компьютерной информации.
3. Не допускается сбор сведений, составляющих коммерческую тайну, незаконным способом.
4. Не допускается заведомо недобросовестное осуществление гражданских прав (злоупотребление правом).
5. Не допускается использование гражданских прав в целях ограничения конкуренции.
Из вышеуказанных запретов следует, что организация вправе осуществлять автоматизированный сбор информации, размещенной в открытом доступе на сайтах в сети интернет если соблюдаются следующие условия:
1. Информация находится в открытом доступе и не защищается законодательством об авторских и смежных правах.
2. Автоматизированный сбор осуществляется законными способами.
3. Автоматизированный сбор информации не приводит к нарушению в работе сайтов в сети интернет.
4. Автоматизированный сбор информации не приводит к ограничению конкуренции.
При соблюдении установленных ограничений Парсинг является законным.

p.s. по правовому вопросу мы подготовили отдельную статью, где рассматривается Российский и зарубежный опыт.

Какой инструмент для извлечения данных Вам нравится больше всего? Какого рода данные вы хотели бы собрать? Расскажите в комментариях о своем опыте парсинга и свое видение процесса…

простой парсинг сложных сайтов / Habr

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

Грубо говоря, проблема в том, что в браузере работает javascript, а на сервере его нет. Нужно либо писать интерпретатор js на одном из серверных языков (jParser и jTokenizer), либо ставить на сервер браузер, посылать в него запросы и вытаскивать итоговое dom-дерево.

В древности в таких случаях мы строили свой велосипед: на отдельной машине запускали браузер, в нем js, который постоянно стучался на сервер и получал от него задания (джобы), сам сайт грузился в iframe, а скрипт извне отправлял dom-дерево ифрейма обратно на сервер.

Сейчас появились более продвинутые средства — xulrunner (crowbar) и watir. Первый — безголовый firefox. У crowbar есть даже ff-плагин для визуального выделения нужных данных, который генерит специальный парсер-js-код, однако там не поддерживаются cookies, а допиливать неохота. Watir позиционируется разработчиками как средство отладки, но мы будем его использовать по прямому назначению и в качестве примера вытащим какие-нибудь данные с сайта travelocity.com.

Watir — это ruby gem, через который идет взаимодействие с браузером. Есть версии для разных платформ — watir, firewatir и safariwatir. Несмотря на подробный мануал по установке, у меня возникли проблемы как в винде, так и в убунте. В windows (ie6) watir не работает на ruby 1.9.1. Пришлось поставить версию 1.8.6, тогда заработало. В убунте — для того, чтобы работал FireWatir (или обычный watir через firefox), в браузер нужно поставить плагин jssh. Но версия, предлагаемая для FireWatir на странице установки не заработала с моим FireFox 3.6 на Ubuntu 10.04.

Чтобы проверить, работает у вас jssh или нет, нужно запустить firefox -jssh, а потом послать что-нибудь на 9997 порт (telnet localhost 9997). Если порт не открывается, либо происходит аварийное завершение работы firefox (как у меня), значит нужно собрать свой jssh, подробная инструкция о сборке находится здесь.

Начнем писать парсер отелей с travelocity.com. Для примера выберем цены комнат во всех отелях по направлению New York, NY, USA на сегодня. Будем работать с FireWatir на Ubuntu 10.4.

Запускаем браузер и грузим страницу с формой:

require "rubygems"<br>require "firewatir"<br>ff = FireWatir::Firefox.new<br>ff.goto("http://www.travelocity.com/Hotels")<br>
Заполняем форму нужными значениями и делаем submit:

ff.text_field(:id,"HO_to").val("New York, NY, USA")<br>ff.text_field(:id,"HO_fromdate").val(Time.now.strftime("%m/%d/%Y"))<br>ff.text_field(:id,"HO_todate").val(Time.tomorrow.strftime("%m/%d/%Y"))<br>ff.form(:name,"formHO").submit<br>
Ждем окончания загрузки:

ff.wait_until{ff.div(:id,"resultsList").div(:class,"module").exists?}<br>
wait_until — очень важная инструкция. При сабмите формы на сайте делается несколько редиректов, а после — ajax запрос. Нужно дождаться финальной загрузки страницы, и только ПОСЛЕ этого работать с dom-деревом. Как узнать, что страница загрузилась? Нужно посмотреть, какие элементы появляются на странице после выполнения ajax. В нашем случае после запроса к /pub/gwt/hotel/esf/hotelresultlist.gwt-rpc в resultsPage появляется несколько элементов <div>. Ждем, пока они не появятся. Замечу, что некоторые команды, например text_field, submit, уже включают в себя wait_until, поэтому перед ними данная команда не нужна.

Теперь делаем переход по страницам:

while true do<br> ff.wait_until{ff.div(:id,"resultsList").div(:class,"module").exists?}<br> ...<br> next_link = ff.div(:id,"resultcontrol-top").link(:text,"Next")<br> if (next_link.exists?) then next_link.click else break end<br>end<br>
Там, где в коде стоит многоточие, находится непосредственное вытаскивание данных. Возникает искушение применить watir и в этом случае, к примеру, пробежать по всем дивам в resultsList такой командой:

ff.div(:id,"resultsList").divs.each.do |div|<br> if (div.class_name != "module") then next end<br> ...<br>end<br>
И из каждого дива вытащить название отеля и цену:

m = div.h3(:class,"property-name").html.match(/propertyId=(\d+)[^<>]*>([^<>]*)<\/a[^<>]*>/)<br>data["id"] = m[1] unless m.nil?<br>data["name"] = m[2] unless m.nil?<br>data["price"] = div.h4(:class,"price").text<br>
Но так делать не следует. Каждая команда watir-а к элементам dom-дерева — это лишний запрос к браузеру. У меня работает около секунды. Гораздо эффективнее за ту же секунду за раз выдернуть весь dom и мгновенно распарсить обычными регулярками:

ff.div(:id,"resultsList").html.split(/<div[^<>]*class\s*=\s*["']?module["']?[^<>]*>/).each do |str|<br>m = str.match(/<a[^<>]*propertyId=(\d+)[^<>]*>([\s\S]*?)<\/a[^<>]*>/)<br> data["id"] = m[1] unless m.nil?<br> data["name"] = m[2] unless m.nil?<br> m = str.match(/<h4[^<>]*class\s*=\s*["']?price["']?[^<>]*>([\s\S]*?)<\/h4[^<>]*>/)<br> data["price"] = m[1] unless m.nil?<br>end<br>
Советую применять watir только там, где это необходимо. Заполнение и сабмит форм, ожидание, пока браузер не выполнит js код, и затем — получение финального html-кода. Да, доступ к значениям элементов через watir кажется надежнее, чем парсинг потока кода без dom-структуры. Чтобы вытащить внутренность некоторого дива, внутри которого могут быть другие дивы, нужно написать сложночитаемое регулярное выражение. Но все равно это гораздо быстрее. Если таких дивов много, самое простое решение — несложной рекурсивной функцией разбить весь код по уровням вложенности тегов. Я писал такую штуку в одном своем классе на php.

Парсинг сайтов Python 3 | Losst

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

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

Содержание статьи:

Прежде чем начать...

Прежде чем мы перейдем к инструментам подумайте зачем вы будете это делать и будьте честными. Парсинг сайтов на Python может означать много чего. Не сканируйте веб-сайты чтобы их дублировать и представить чужую работу как свою. Помните об авторских правах и лицензировании, а также о том, документы, которые вы парсите могут быть защищены авторским правом. Учитывайте файлы robots.txt. А также не отправляйте сайту запросы чаще, чем реальные пользователи иначе рискуете получить проблемы с доступностью.

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

Парсинг данных с сайтов на Python3

1. Pyspider

Давайте начнем с Pyspider. Это веб-краулер с веб-интерфейсом, который позволяет выполнять парсинг сайтов python 3 в несколько потоков и сразу же наблюдать за процессом парсинга. Этот расширяемый инструмент с поддержкой нескольких баз данных и очередей сканирования, а также множеством дополнительных функций, начиная от приоритета и заканчивая возможностью повторной попытки открытия страницы. PySpider поддерживает как Python 2, так и 3. Вы можете настроить более быстрое сканирование запустив несколько экземпляров приложения.

Функции и методы PySpider хорошо документированы, даже есть фрагменты кода, которые мы можете использовать в своих проектах. Кроме того, есть онлайн демонстрация работы, которая поможет вам получить представление о пользовательском интерфейсе. Лицензия на ПО - Apache 2, но программа доступна на GitHub.

2. MechanicalSoup

MechanicalSoup - это библиотека краулинга, созданная на основе очень популярной и невероятно универсальной библиотеки, выполняющей парсинг html python - Beautiful Soup. Если у вас нет никаких особых требований к сканированию, но вам нужно собрать несколько полей или ввести какой-либо текст, но вы не хотите создавать собственный парсер для этой задачи - то эта библиотека может стать отличным решением.

MechanicalSoup имеет лицензию MIT. Вы можете посмотреть более подробно как его использовать в примере исходного файла example.py на странице GitHub. На данный момент у проекта нет полной документации.

3. Scrapy

Scrapy - это фреймворк для Python, с помощью которого вы сможете создать собственный инструмент парсинга. В дополнение к средствам парсинга и разбора, он включает в себя возможности экспорта в такие форматы, как JSON или CSV, а также отправку данных бэкенду. Также есть ряд расширений для таких задач, как обработка файлов cookie, подмена User Agent, ограничения глубины сканирования, а также API для создания своих расширений.

Для изучения Scrapy можно использовать онлайн документацию или один из многих ресурсов сообщества. Кодовая база Scrapy доступна на GitHub под лицензией BSDv3. Если вы не хотите программировать, то можете использовать графический интерфейс Portia.

Другие

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

  • Cola - это высокоуровневый распределенный инструмент для обхода и парсинга сайтов, но работает только на Python 2. Последний раз обновлялся два года назад;
  • Demiurge - поддерживает Python 2 и Python 3. Еще один неплохой вариант, хотя разрабатывается он все еще медленно;
  • Feedparser - небольшая библиотека, предназначенная для разбора RSS или Atom лент.
  • Lassie - упрощает получение базового контента страницы, такого как описание, название, ключевые слова или список изображений;
  • RoboBrowser - еще одна простая библиотека для Python 2 или 3 с базовыми функциями, поддерживается нажатие кнопок и заполнение форм. Уже давно не обновлялась, но по-прежнему отличный выбор.

Выводы

Это далеко не полный список, и, конечно, если вы профессиональный программист, то вы можете написать собственный инструмент и не использовать ни один из фреймворков. Однозначно, существует еще множество отличных инструментов, и не только на языке Python. А с помощью какого инструмента вы выполняете парсинг данных с сайта python или на другом языке? Какой ваш любимый? Напишите в комментариях!

Источник: https://opensource.com

щадящий режим для RSS сервера, проверка скачанного / Habr

Доброго времени суток!
Когда-то, благодаря этому топику, был написан неплохой парсер для Лостфильма. Сейчас я хотел бы поделиться собственной доработкой скрипта, связанной с изменением формата ссылок на Лостфильме, добавлением проверки на обновление ленты и проверки скачанного.

Прежде всего, что-бы снизить нагрузку на RSS сервер, при запросе ленты с помощью wget нужно использовать «If-Modified-Since:». Тогда, при отсутствии обновлений, мы не будем качать и обрабатывать всю ленту. Такой подход снизит нагрузку на сервер (и немного на наш компьютер).
Используем следующий вариант команды:

# Подготовка заголовка If-Modified-Since
lastmod="$( grep -m 1 Last-Modified response.out )"

header="If-Modified-Since:"${lastmod#*:}

В файле response.out хранится последний ответ RSS сервера.
С помощью ${lastmod#*:} из найденной в последнем ответе сервера строки Last-Modified отрезаем все ненужное до ":".

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

movies='House.M.D|IT.Crowd|Persons.Unknown|Legend.of.the.Seeker|Leverage|Warehouse.13|Futurama'

quality='\.720p\.|\.HD\.|Persons.Unknown|Legend.of.the.Seeker|IT.Crowd'
movies содержит список скачиваемых сериалов. Но на Лостфильме они обычно выходят в двух версиях качества, а я лично стараюсь качать все в высоком качестве. Значит нужен дополнительный фильтр — quality. Но не все сериалы выходили в 720p или HD, поэтому добавим их в конец фильтра что-бы они могли тоже пройти проверку на качество!

С фильтрами закончили, переходим к обработке ленты. В топике, упомянутом мной в начале, очень подробно рассматривается составление регулярных выражений для фильтров и опции команды wget, поэтому я их опускаю.
В команде используется заготовленный header и ответ сервера сохраняется тут же в response.out. Кстати, если response.out отсутствует (первый запуск или принудительное обновление) то ничего страшного не произойдет.

wget -vS -O - --header="$header" www.lostfilm.tv/rssdd.xml -o response.out | grep -ioe "http.*torrent" | egrep -i "$movies" | egrep "$quality" | while read link;
do
# заменяем в ссылке "&amp;" на "&"
link="${link/&amp;/&}"

Для удобства, сохраним отдельно имя торрента (отрезав все до "&"). Оно нам понадобится для проверки скачанного и сохранения файла торрента.
name="${link#*&}"

Теперь проверяем по имени присутствие файла в директории скачанного. Если такого файла нет, качаем по ссылке и сохраняем с этим именем. Потом (опционально) копируем в auto-load directory нашего клиента.
# скачанные файлы хранятся в ./Downloaded, для истории
if [ ! -e Downloaded/$name ]
then
wget -q --header "Cookie: uid=123456; pass=xxxxxxxxxxxx; usess=xxxxxxxxxxx" $link -O "Downloaded/$name"
cp "$name" "$path_to_your_autoload_dir/$name"
fi
done

Cookie для Лостфильма можно выдрать из любого браузера загуглив, а параметр usess находится в вашем профиле на www.lostfilm.tv

Вот полная версия моего скрипта:

#!/bin/bash

cd $your/rssdownloader/dir

# Подготовка заголовка If-Modified-Since
lastmod="$( grep -m 1 Last-Modified response.out )"

# Создаем header
header="If-Modified-Since:"${lastmod#*:}

# Фильтр сериалов
movies='House.M.D|IT.Crowd|Persons.Unknown|Legend.of.the.Seeker|Leverage|Warehouse.13|Futurama'

# Фильтр качества
quality='\.720p\.|\.HD\.|Persons.Unknown|Legend.of.the.Seeker|IT.Crowd'

wget -vS -O - --header="$header" http://www.lostfilm.tv/rssdd.xml -o response.out | grep -ioe "http.*torrent" | egrep -i "$movies" | egrep "$quality" | while read link;
do
  link="${link/&amp;/&}"
  name="${link#*&}"

# скачанные файлы хранятся в ./Downloaded, для истории
  if [ ! -e Downloaded/$name ]
  then
    wget -q --header "Cookie: uid=123456; pass=xxxxxxxxxxxx; usess=xxxxxxxxxxx" $link -O "Downloaded/$name"
    cp "Downloaded/$name" "$path_to_your_autoload_dir/$name"
  fi
done

Спасибо за внимание!
P.S.: Сознаю что код далек от идеала, буду рад принять советы по улучшению!

Update: Думаю что теперь, со всеми поправками, данный скрипт можно назвать RTM. Спасибо всем кто участвовал в доведении его до ума, особенно GreyCat с его развёрнутыми обьяснениями!

Update 2: При проблемах скачивания ленты wget выдает несколько раз нужную нам строку. Поэтому берем только первый найденный вариант, с помощью ключа " -m 1 " (максимум скачаем лишний раз старую ленту).
lastmod="$( grep -m 1 Last-Modified response.out )"

Собираем, парсим и отдаём логи с помощью Logstash / Habr

Приветствую.

Так уж сложилось, что по долгу работы мне приходится много времени уделять логам. Это и участие в выработке правил и политик сбора/хранения/использования логов, это и разбор разных инцидентов и обнаружение аномалий. За сутки наши программы, сервисы и серверы генерируют ОЧЕНЬ большое количество логов. И потребность копания в логах растёт постоянно.
Мне довелось поработать с коммерческими лог-менеджмент продуктами типа ArcSight, RSA Envision, Q1 Labs. У этих продуктов есть как плюсы, так и минусы. Но в статье речь пойдёт не о них.
Речь будет о Logstash.

Что же такое Logstash? Зачем он нужен? Что он умеет?

Logstash — это орудие для сбора, фильтрации и нормализации логов. Оно является бесплатным и open source приложением. Тип лицензии Apache 2.0.

Первой моё знакомство с LS (Logstash) произошло более года назад, и с того времени я очень плотно на него подсел. Мне нравится его идея и возможности. Для меня Logstash — это подобие мясорубки. Неважно что заходит в неё, но после не сложных манипуляций, на выходе всегда красиво и понятно оформленная информация.

Формат конфигурационного файла Logstash'а прост и понятен. Он состоит из трёх частей:

input {
  ...
}

filter {
  ...
}

output {
  ...
}

Входных, фильтрующих и выходных (или выходящих?) блоков может быть любое количество. Всё зависит от ваших потребностей и возможностей железа.
Пустые строки и строки начинающиеся с # — Logstash игнорирует. Так что комментирование конфигурационных файлов не вызовет никаких проблем.
1. INPUT
Данный метод является входной точкой для логов. В нём определяет по каким каналам будут логи попадать в Logstash.
В этой статье я попытаюсь вас ознакомит с основными типами, которые я использую — это file, tcp и udp.
1.1 fileПример конфигурации, для работы с локальными лог-файлами:
input {
  file {
    type => "some_access_log"
    path => [ "/var/log/vs01/*.log", "/var/log/vs02/*.log" ]
    exclude => [ "*.gz", "*.zip", "*.rar" ]
    start_position => "end"
    stat_interval => 1
    discover_interval => 30
  }
}

Построчное описание настроек:

type => "some_access_log"
тип/описание лога. При использовании нескольких входных блоков, удобно их разделять для последующих действий в filter или output.
path => [ "/var/log/vs01/*.log", "/var/log/vs02/*.log" ]
указывается путь к лог-файлам, которые подлежат обработке. Путь должен быть абсолютным (/path/to/logs/), а не относительным (../../some/other/path/).
exclude => [ "*.gz", "*.zip", "*.rar" ]
исключает из обработки файлы с соответствующими расширениями.
start_position => "end"
ждёт появления новых сообщений в конце файла. При обработки уже имеющихся логов, можно выставить «beginning», тогда обработка логов будет происходить построчно с начала файлов.
stat_interval => 1
как часто (в секундах) проверять файлы на изменения. При больших значения, уменьшится частота системных вызовов, но так же увеличится время чтения новых строк.
discover_interval => 30
время (в секундах) через которое будет обновлён список обрабатываемых файлов указанных в path.

1.2 tcpПример конфигурации, для работы с логами удалённых сервисов:
input {
  tcp {
    type => "webserver_prod"
    data_timeout => 10
    mode => "server"
    host => "192.168.3.12"
    port => 3337
  }
}

Построчное описание настроек:

type => "webserver_prod"
тип/описание лога.
data_timeout => 10
время (в секундах), по истечении которого не активное tcp соединение будет закрыто. Значение -1 — соединение всегда будет открыто.
mode => "server"
host => "192.168.3.12"
port => 3337
в этом случае Logstash становится сервером, и начинает слушать на 192.168.3.12:3337. При установке mode => «client» Logstash будет присоединятся к удалённому ip:port для забора логов.
1.3 udpДля udp настройки аналогичные tcp:
input {
  udp {
    type => "webserver_prod"
    buffer_size => 4096
    host => "192.168.3.12"
    port => 3337
  }
}

2. FILTER
В данном блоке настраиваются основные манипуляции с логами. Это может быть и разбивка по key=value, и удаление ненужных параметров, и замена имеющихся значений, и использование geoip или DNS запросов для ип-адресов или названий хостов.

На первый взгляд применение фильтров может показаться сложным и нелогичным, но это не совсем так.

2.1 grokПример конфигурационного файла для основной нормализации логов:
filter {
  grok {
    type => "some_access_log"
    patterns_dir => "/path/to/patterns/"
    pattern => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}"
  }
}

Построчное описание настроек:

type => "apache_access"
тип/описание лога. Здесь надо указать тот тип (type), который прописан в блоке input для которого будет происходить обработка.
patterns_dir => "/path/to/patterns/"
путь к каталогу, содержащим шаблоны обработки логов. Все файлы находящиеся в указанной папке будут загружены Logstash, так что лишние файлы там не желательны.
pattern => "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}"
указывается шаблон разборки логов. Шаблон можно использовать либо в конфигурационном файле, либо из файла шаблонов. Что бы не путаться, я для каждого формата логов создаю отдельный шаблонный файл.Подробнее про шаблоныС помощью grok фильтра можно структурировать большую часть логов — syslog, apache, nginx, mysql итд, записанных в определённом формате.
Logstash имеет более 120 шаблонов готовых регулярных выражений (regex). Так что написание фильтров для обработки большинства логов не должно вызвать особого страха или недопонимания.

Формат шаблонов относительно простой — NAME PATTERN, то есть построчно указывается имя шаблона и ему соответствующее регулярное выражение. Пример:

NUMBER \d+
WORD \b\w+\b
USERID [a-zA-Z0-9_-]+

Можно использовать любой ранее созданный шаблон:
USER %{USERID}

Шаблоны можно так же и комбинировать:
CISCOMAC (?:(?:[A-Fa-f0-9]{4}\.){2}[A-Fa-f0-9]{4})
WINDOWSMAC (?:(?:[A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2})
MAC (?:%{CISCOMAC}|%{WINDOWSMAC})

Допустим формат логов у нас следующий:
55.3.244.1 GET /index.html 15824 0.043

Среди готовых шаблонов, к счастью уже имеются некоторые регулярные выражения и не надо придумывать колёсное транспортное средство, приводимое в движение мускульной силой человека через ножные педали или через ручные рычаги (это я про велосипед если что).
С данным примером лога достаточно pattern записать в виде "%{IP:client} %{WORD:method} %{URIPATHPARAM:request} %{NUMBER:bytes} %{NUMBER:duration}", в этом случае все логи с данным форматом будут уже иметь некую логическую структуру.
После обработки наша строка будет выглядеть следующим образом:

client: 55.3.244.1
method: GET
request: /index.html
bytes: 15824
duration: 0.043

Со списком готовых grok-шаблонов можно познакомиться здесь.



2.2 mutateПример конфигурационного файла для изменения/удаления записей из логов:
filter {
  mutate {
    type => "apache_access"
    remove => [ "client" ]
    rename => [ "HOSTORIP", "client_ip" ]
    gsub => [ "message", "\\/", "_" ]
    add_field => [ "sample1", "from %{clientip}" ]
  }
}

Построчное описание настроек:
type => "apache_access"
тип/описание лога. Указывается тип (type) логов с которыми будет происходить обработка.
remove => [ "client" ]
удаление всех данных имеющих название поля client. Возможно указание нескольких названий полей.
rename => [ "HOSTORIP", "client_ip" ]
переименование название поля HOSTORIP в client_ip.
gsub => [ "message", "\\/", "_" ]
замена всех "/" на "_" в поле messages.
add_field => [ "sample1", "from %{clientip}" ]
добавление нового поля «sample1» со значением «from %{clientip}». Допускается использование названий переменных.

2.3 dateПример конфигурационого файла:
filter {
  date {
    type => "apache_access"
    match => [ "timestamp", "MMM dd HH:mm:ss" ]
  }
}

Построчное описание настроек:
type => "apache_access"
тип/описание лога. Указывается тип (type) логов с которыми будет происходить обработка.
match => [ "timestamp", "MMM dd HH:mm:ss" ]
временная метка события. Важная настройка для дальнейшей возможности сортировки или выборки логов. Если в логах время указано в unix timestamp (squid), то следует использовать match => [ «timestamp», «UNIX» ]

2.4 kvПример конфигурационного файла для обработки логов в формате key=value:
filter {
  kv {
    type => "custom_log"
    value_split => "=:"
    fields => ["reminder"]
    field_split => "\t?&"
  }
}

Построчное описание настроек:
type => "custom_log"
тип/описание лога. Указывается тип (type) логов с которыми будет происходить обработка.
value_split => "=:"
использовать символы "=" и ":" для разделения ключа-значения.
fields => ["reminder"]
название поля в котором искать 'ключ=значение'. По умолчанию разбивка будет происходить для всей строки лога.
field_split => "\t?&"
использовать символы "\t?&" для разделения ключей. \t — знак табулятора

2.5 multilineПример конфигурационного файла для «склеивания» многострочных логов (например Java stack trace):
filter {
  multiline {
    type => "java_log"
    pattern => "^\s"
    what => "previous"
  }
}

Построчное описание настроек:
type => "java_log"
тип/описание лога. Указывается тип (type) логов с которыми будет происходить обработка.
pattern => "^\s"
регулярное выражение
what => "previous"
при соответствии шаблону «pattern» строка принадлежит предыдущей (previous) строке.
3. OUTPUT
Название этого блока/метода говорит само за себя — в нём указываются настройки для исходящих сообщений. Аналогично предыдущим блокам, здесь можно указывать любое количество исходящих подблоков.
3.1 stdoutПример конфигурационного файла для вывода логов в standard output:
output {
  stdout {
    type => "custom_log"
    message => "IP - %{clientip}. Full message: %{@message}. End of line."
  }
}
type => "custom_log"
тип/описание лога.
message => "clIP - %{clientip}. Full message: %{@message}. End of line."
указывается формат исходящего сообщения. Допустимо использование переменных после grok-фильтрации.

3.2 fileПример конфигурационого файла для записи логов в файл:
output {
  file {
    type => "custom_log"
    flush_interval => 5
    gzip=> true
    path => "/var/log/custom/%{clientip}/%{type}"
    message_format => "ip: %{clientip} request:%{requri}"
  }
}
type => "custom_log"
тип/описание лога.
flush_interval => 5
интервал записи исходящих сообщений. Значение 0 будет записывать каждое сообщение.
gzip=> true
файл исходящих сообщений будет сжат Gzip.
path => "/var/log/custom/%{clientip}/%{type}"
путь и название файла куда будут сохраняться исходящие сообщения. Можно использовать переменные. В данном примере, для каждого уникального IP адреса будет создана своя папка и сообщения будут записываться в файл соответствующий переменной %{type}.
message_format => "ip: %{clientip} request:%{requri}"
формат исходящего сообщения.

3.3 elasticsearchПример конфигурационного файла для записи логов в базу Elasticsearch:
output {
  elasticsearch {
    type => "custom_log"
    cluster => "es_logs"
    embedded => false
    host => "192.168.1.1"
    port => "19300"
    index => "logs-%{+YYYY.MM.dd}" 
  }
}
type => "custom_log"
тип/описание лога.
cluster => "es_logs"
название кластера указанного в cluster.name в настроечном файле Elasticsearch.
embedded => false
указывает какую базу Elasticsearch использовать внутреннюю или стороннюю.
port => "19300"
транспортный port Elasticsearch.
host => "192.168.1.1"
IP адрес Elasticsearch
index => "logs-%{+YYYY.MM.dd}" 
название индекса куда будут записываться логи.

3.4 emailДанный плагин можно использовать для алертов. Минус в том, что любые изменения уведомлений (в принципе как и любых других настроек) требуют перезапуск logstash программы, но разработчик говорит, что возможно в будущем этого не придётся делать.
Пример конфигурационого файла:
output {
  email {
    type => "custom_log"
    from => "[email protected]"
    to => "[email protected]"
    cc => "[email protected]"
    subject => "Found '%{matchName}' Alert on %{@source_host}"
    body => "Here is the event line %{@message}"
    htmlbody => "<h3>%{matchName}</h3><br/><br/><h4>Full Event</h4><br/><br/><div align='center'>%{@message}</div>"
    via => "sendmail"
    options => [ "smtpIporHost", "smtp.gmail.com",
                          "port", "587",
                          "domain", "yourDomain",
                          "userName", "yourSMTPUsername",
                          "password", "PASS",
                          "starttls", "true",
                          "authenticationType", "plain",
                          "debug", "true"
                         ]
    match => [ "response errors", "response,501,,or,response,301",
                        "multiple response errors", "response,501,,and,response,301" ]

  }
}
type => "custom_log"
тип/описание лога.
from => "[email protected]"
to => "[email protected]"
cc => "[email protected]"
если у вас хватило сил дочитать до этих строк, значит вы можете сами определить смысл этих 3х настроек 🙂
subject => "Found '%{matchName}' Alert on %{@source_host}"
тема письма уведомления. Можно использовать переменные. Например %{matchName} — название условия совпадения из настройки «match».
body => "Here is the event line %{@message}"
htmlbody => "<h3>%{matchName}</h3><br/><br/><h4>Full Event</h4><br/><br/><div align='center'>%{@message}</div>"
тело письма.
via => "sendmail"
способ отсылки письма. Возможен один вариант из двух — smtp или sendmail.
options => ...
стандартные настройки почтовых параметров.
match => [ "response errors", "response,501,,or,response,301",
                    "multiple response errors", "response,501,,and,response,301" ]
«response errors» — название алерта (записывается в переменную %{matchName}). «response,501,,or,response,301» — критерии срабатывания алертов. В данном примере если поле response содержит значение 501 или 301, то алерт считается сработавшим. Во второй строке используется логика AND, т.е. оба условия должны быть выполнены.
4. Итого

Создаём файл habr.conf:
input {
  tcp {
    type => "habr"
    port => "11111"
  }
}
filter {
  mutate {
    type => "habr"
    add_field => [ "habra_field", "Hello Habr" ]
  }
}
output {
  stdout {
    type => "habr"
    message => "%{habra_field}: %{@message}"
  }
}

Запускаем Logstash:
java -jar logstash-1.1.9-monolithic.jar agent -f ./habr.conf

Проверяем, что Logstash запущен:
# netstat -nat |grep 11111
Если порт 11111 присутствует, значит Logstash готов принимать логи.

В новом терминальном окне пишем:
echo "Logs are cool!" | nc localhost 11111

Смотрим результат в окне где запущен Logstash. Если там появилось секретное послание, значит всё работает.

P.s.Последнюю версию Logstash можно скачать отсюда.

Спасибо за внимание,

parser combinators на примере парсера формул / Habr

Время от времени у меня возникает желание придумать свой собственный маленький язык программирования и написать интерпретатор. В этот раз я начал писать на scala, узнал про библиотеку parser combinators, и был поражён: оказывается, можно писать парсеры легко и просто. Чтобы не превращать статью в пособие по "рисованию совы", ниже приведёна реализация разбора и вычисления выражений типа "1 + 2 * sin(pi / 2)".

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

Для сравнения:


Итак, если вам не терпится увидеть результат:


Ответственный за парсинг кусочек кода
object FormulaParser extends RegexParsers with PackratParsers {

    def id: Parser[Id] = "[a-zA-Z][a-zA-Z0-9_]*".r ^^ Id

    def number: Parser[Number] = "-" ~> number ^^ (n => Number(-n.value)) |
        ("[0-9]+\\.[0-9]*".r | "[0-9]+".r) ^^ (s => Number(s.toDouble))

    def funcCall: Parser[FuncCall] = id ~ ("(" ~> expression <~ ")") ^^ {case id ~ exp => FuncCall(id, exp)}

    def value: Parser[Expression] = number | funcCall | id | ("(" ~> expression <~ ")")

    lazy val term: PackratParser[Expression] = term ~ ("*" | "/") ~ value ^^ binOperation | value

    lazy val expression: PackratParser[Expression] = expression ~ ("+" | "-") ~ term ^^ binOperation | term
    ...
}

Посмотрите на следущую строчку:

def value: Parser[Expression] = number | funcCall | id | ("(" ~> expression <~ ")")

Она подозрительно похожа на описание грамматики, но это валидный код, в котором среда разработки может сразу же обнаружить и подсветить большинство ошибок.

Это возможно по следующим причинам:


  1. В scala разрешено давать методам замечательные названия типа "~", "~>", "<~", "|", "^^". Комбинация парсеров p и q записывается как p~q, а возможность выбрать один из них: p|q. Читается намного лучше, чем p.andThen(q) или p.or(q)
  2. Благодаря неявным преобразованиям (implicits) и строчка "abc" и регулярное выражение "[0-9]+".r при необходимости превращаются в парсеры.
  3. В языке мощная статическая система типов, которая позволяет ловить ошибки сразу.

Думаю, мне удалось Вас заинтересовать, поэтому дальше всё будет по порядку.



Оглавление:


  1. Pegex Parsers
  2. Packrat Parsers
  3. код парсера целиком
  4. вычисление выражений
  5. заключение

Parser Combinators.

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


Regex parsers

Итак, самое простое — RegexParsers. Добавляют неявные преобразования из строк и регулярных выражений в парсеры.

object SimpleExample extends RegexParsers {
    def boolTrue: Parser[Boolean] = "true" ^^ (_ => true)
    // если читаем строчку "true", то вызывается функция, которая преобразует строчку в истинное значение boolean

    def bool: Parser[Boolean] = ("true" | "false") ^^ (_ == "true")
    // можно сгруппировать парсеры и применить функцию к результату

    def alternativeBool: Parser[Boolean] = "true" ^^ (_ => true) | "false" ^^ (_ => false)
    // или преобразовать каждый результат по отдельности

    def int: Parser[Int] = "[0-9]+".r ^^ (_.toInt)
    // парсим последовательность цифр и преобразуем в число.
    // метод .r создаёт регулярное выражение из строки

    def id: Parser[Id] = "[a-zA-Z][a-zA-Z0-9_]*".r ^^ Id
    // Id - функция, которая делает из строки объект типа Id
}

Кстати, значок ~ обозначает не только метод у парсера, но и имя case класса, хранящего пару значений. Кусочек кода из Parsers.scala:

case class ~[+a, +b](_1: a, _2: b) {
    override def toString = "("+ _1 +"~"+ _2 +")"
}

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

def intInBrackets: Parser[Int] = "(" ~ int ~ ")" ^^ (p => p._1._2)

Что произойдёт?


  1. "(" неявно из строки превращается в парсер, который возвращает String
  2. парсер int возвращает Int
  3. "(" ~ int создаёт парсер для ~[String, Int]
  4. "(" ~ int ~ ")" создаёт парсер, который возвращает ~[~[String, Int], String]
  5. у парсера будет вызван метод ^^
  6. в метод передаётся функция, которая принимает аргумент p с типом ~[~[String, Int], String] и возвращает Int

В данном случае скобки не несут никакой полезной информации. Можно сделать так:

def intInBrackets: Parser[Int] = "(" ~>  int <~ ")"

В этот раз скобки будут отброшены.


  • "(" ~> int создаёт парсер, который парсит скобку и потом Int, но возвращает только Int
  • int <~ ")" работает аналогично, но для левого аргумента

Выражения с оператором <~ советуют заключать в скобки, так как у него не очень высокий приоритет.
def

def funcCall: Parser[FuncCall] = id ~ ("(" ~> expression <~ ")") ^^ (pair => FuncCall(pair._1, pair._2))

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

def number: Parser[Number] = "-" ~> number ^^ (n => Number(-n.value)) |
        ("[0-9]+\\.[0-9]*".r | "[0-9]+".r) ^^ (s => Number(s.toDouble))
        // s.toDouble преобразует строку в число.

def value: Parser[Expression] = number | funcCall | id | ("(" ~> expression <~ ")")

private def binOperation(p: Expression ~ String ~ Expression) = p match {
    case e1 ~ op ~ e2 => BinOperation(e1, BinOperator(op), e2)
}

Я немножко поленился и превращаю строку в число стандартными методами. Время надо экономить)

Поскольку наше описание парсеров — это код, неоднозначные грамматики всё равно работают. В примере с парсингом number | funcCall | id мы пытаемся распарсить number, в случае неудачи — вызов функции и т.д. Порядок может быть важным, например (id | funcCall) при попытке распарсить "sin(x)" радостно распарсит Id("sin"), и парсер funcCall не будет вызван. Для корректной работы лучше написать (funcCall | id).


Packrat Parsers

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

object Ones extends RegexParsers {
    def ones: Parser[Any] =  ones ~ "1" | "1"
}

Парсинг ones начинается с того, что мы вызываем парсинг ones, который снова ...


Попытка распарсить единички приведёт к переполнению стека.

В данном случае можно изменить описание так, чтобы каждый раз "поглощалось" что-нибудь. Например:

def ones: Parser[Any] =  "1" ~ ones | "1"

Но не всегда грамматику легко переписать. Выражния типа 3-2-1 должны распознаваться именно как (3-2)-1, вариант 3-(2-1) не подойдёт. С делением будет аналогичная проблема. Как это сделать без усложнения грамматики?

Нас спасут packrat — парсеры. Их идея заключается в том, что парсер может хранить "для себя" некоторую информацию о вызовах. Например, чтобы сохранять результат работы и не парсить одно и то же дважды… или чтобы корректно работать в случаях с рекурсией.

object Ones extends RegexParsers with PackratParsers{
    lazy val ones: PackratParser[Any] =  ones ~ "1" | "1"
}

в трейте PackratParsers содержится неявное преобразование строчек и прочего в парсеры "нужного" типа.

PackratParser лучше создавать только один раз и хранить в переменной. Кроме того, если парсер p использует q, а q использует p, стоит использовать ленивую инициализацию.

    lazy val term: PackratParser[Expression] = term ~ ("*" | "/") ~ value ^^ binOperation | value

    lazy val expression: PackratParser[Expression] = expression ~ ("+" | "-") ~ term ^^ binOperation | term

Думаю, теперь понятно, как можно легко и непринуждённо распарсить 3-2-1 как (3-2)-1.

Возможно, у вас возникает вопрос: где парсер хранит информацию? Если её хранить прямо внутри PackratParser, то вызов парсера для другого ввода может дать некорректные результаты. Так вот, необходимая информация хранится вместе с "входными" данными парсера. Можно заглянуть в код библиотеки и убедиться в этом:

class PackratReader[+T](underlying: Reader[T]) extends Reader[T] { outer =>

    private[PackratParsers] val cache = mutable.HashMap.empty[(Parser[_], Position), MemoEntry[_]]
    ...
}

Поэтому парсер принимает на вход не строку, а new PackratReader(new CharSequenceReader(string))

def apply(code: String): Either[LexerError, Expression] =
    parse(expression, new PackratReader(new CharSequenceReader(code))) match {
        case Success(result, next) => Right(result)
        case NoSuccess(msg, next) => Left(LexerError(msg))
    }

Что самое крутое — использование packrat парсеров ни к чему не обязывает, их можно комбинировать с обычными парсерами и наоборот.


Парсер готов

Код целиком:

object FormulaParser extends RegexParsers with PackratParsers {

    def id: Parser[Id] = "[a-zA-Z][a-zA-Z0-9_]*".r ^^ Id

    def number: Parser[Number] = "-" ~> number ^^ (n => Number(-n.value)) |
        ("[0-9]+\\.[0-9]*".r | "[0-9]+".r) ^^ (s => Number(s.toDouble))

    def funcCall: Parser[FuncCall] = id ~ ("(" ~> expression <~ ")") ^^ {case id ~ exp => FuncCall(id, exp)}

    def value: Parser[Expression] = number | funcCall | id | ("(" ~> expression <~ ")")

    lazy val term: PackratParser[Expression] = term ~ ("*" | "/") ~ value ^^ binOperation | value

    lazy val expression: PackratParser[Expression] = expression ~ ("+" | "-") ~ term ^^ binOperation | term

    private def binOperation(p: Expression ~ String ~ Expression) = p match {
        case e1 ~ op ~ e2 => BinOperation(e1, BinOperator(op), e2)
    }

    def apply(code: String): Either[ParserError, Expression] =
        parse(expression, new PackratReader(new CharSequenceReader(code))) match {
            case Success(result, next) => Right(result)
            case NoSuccess(msg, next) => Left(ParserError(msg))
        }

    case class ParserError(msg: String)
}

sealed trait Expression

case class BinOperator(operator: String)

case class Number(value: Double) extends Expression
case class Id(name: String) extends Expression
case class BinOperation(left: Expression, op: BinOperator, right: Expression) extends Expression
case class FuncCall(funcName: Id, argument: Expression) extends Expression

Результат парсинга — либо дерево, либо сообщение об ошибке.

case классы — просто классы-обёртки над значениями, они все реализуют интерфейс Expression. слово sealed обозначает, что реализующие этот интерфейс классы должны содержаться в том же самом файле. Это позволяет с уверенностью говорить, что Expression может быть одного из четырёх типов.


Вычисление выражений

Код, который вычисляет выражения, тоже прост. Я предполагаю, что на вход подаются корректные выражения.

object Evaluator {
    def apply(expression: Expression,
              variables: (String) => Double = Map.empty,
              functions: (String) => (Double) => Double = Map.empty): Double = {

        def eval(exp: Expression) = this (exp, variables, functions)

        expression match {
            case Number(value) => value
            case Id(name) => variables(name)
            case BinOperation(left, op, right) => operator2func(op)(eval(left), eval(right))
            case FuncCall(funcId, expr) => functions(funcId.name)(eval(expr))
        }
    }

    def operator2func(binOperator: BinOperator): (Double, Double) => Double =
        binOperator.operator match {
            case "+" => (a, b) => a + b
            case "-" => (a, b) => a - b
            case "*" => (a, b) => a * b
            case "/" => (a, b) => a / b
        }
}

Фишечки скалы — можно объявить функцию eval внутри функции apply для повышения читаемости кода. Вторая фишечка — в качестве аргумента по-умолчанию мы подсовываем Map.empty. Она пустая, поэтому может быть любого типа, она неизменяемая, поэтому она останется пустой, и реально мы получим ссылки на один и тот же объект — синглтон. Map.empty имеет метод apply(a: In):Out — мы можем считать её функцией.


Почти всё

Парсинг и вычислени выражений готовы. Посчитаем получившиеся строки кода (непустые):


  1. Парсер: 18 строк
  2. case-классы для описания AST: 6
  3. вычисление выражений: 20 строк.

И всё — причём код легко читается, его легко изменять и он практчиески не содержит ничего лишнего. Красота!


А оно работает?

Об этом стоит подумать ещё на этапе написания парсера, но проверяющий код ни на что не влияет, потому приведён только только сейчас. (конечно, можно написать тестики… но эта статья о написании парсеров, а не тестов, поэтому я сделал как можно проще)


Код, который проверяет работу
object Main extends App {
    def eval(code: String,
             variables: (String) => Double = Map.empty,
             functions: (String) => (Double) => Double = Map.empty) = {
        val parsed = FormulaParser(code)
        parsed.left.foreach(error => println(s"\'$code\' parsing error: $error"))
        parsed.right.map(expr => Evaluator(expr, variables, functions)).foreach(d => println(s"\'$code\' = $d"))
    }

    eval("1")
    eval("0.1")
    eval("1.")
    eval("  1  ")
    eval("-0.1")

    eval("1+2")
    eval("2-1")
    eval("2*3")
    eval("4/2")

    val vars = Map(
        "pi" -> math.Pi,
        "e" -> math.E)

    val funcs: (String) => (Double) => Double = Map(
        "sin" -> math.sin,
        "cos" -> math.cos,
        "inc" -> { d: Double => d + 1 }
    )

    eval("pi", vars)
    eval("inc(e)", vars, funcs)

    eval("2+2*2")
    eval("1+2*(3+4*5)")
    eval("8/2/2")
    eval("8-1-2")

    eval("1. + 2.0 * sin(pi / 2)", vars, funcs)
}

Заключение

Для серьёзных целей существуют генераторы парсеров и прочие штуки.

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

Полезные ссылочки:


  1. библиотека на github
  2. Пример парсинга DSL
  3. "Programming in scala", глава "parser combinators"

Приведённый выше код я выложил на github


Как запустить

Используется система сборки sbt. Достаточно установить её, перейти в папку с проектом и набрать в консоли "sbt run"

P.S. У меня всё ещё есть цель дописать свой интерпретатор lua-подобного языка с шахматами и поэтессами. Я вроде бы продумал синтаксис языка, потом в процессе написания парсера наткнулся на противоречия — пришлось отказаться от пары поэтесс и заменить шахматы на шашки, но результат всё равно получается довольно интересным.

Отправить ответ

avatar
  Подписаться  
Уведомление о