Использование Boost.Spirit для извлечения определенных тегов / атрибутов из HTML - PullRequest
1 голос
/ 14 декабря 2011

Итак, я немного узнал о Boost.Spirit, чтобы заменить использование регулярных выражений во многих моих кодах.Основная причина - чистая скорость.Я обнаружил, что Boost.Spirit работает в 50 раз быстрее, чем PCRE для некоторых относительно простых задач.

Одна вещь, которая является большим узким местом в одном из моих приложений, - это взять немного HTML, находя все «img»теги и извлечение атрибута "src".

Это мое текущее регулярное выражение:

(?i:<img\s[^\>]*src\s*=\s*[""']([^<][^""']+)[^\>]*\s*/*>)

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

Ответы [ 3 ]

2 голосов
/ 14 декабря 2011

И, конечно, вариант Boost Spirit нельзя пропустить:

sehe@natty:/tmp$ time ./spirit < bench > /dev/null

real    0m3.895s
user    0m3.820s
sys 0m0.070s

Если честно, код Spirit немного более универсален, чем другие варианты:

  • фактически он разбирает атрибуты немного умнее, поэтому было бы легко обрабатывать различные атрибуты одновременно, возможно, в зависимости от содержащего элемента
  • Синтаксический анализатор Spirit будет легче адаптировать для сопоставления между строками. Этого легче всего достичь

    • с использованием spirit::istream_iterator<> (что, к сожалению, очень медленно)
    • с использованием отображенного в память файла с необработанными const char* в качестве итераторов; Последний подход одинаково хорошо работает и для других методов

Код выглядит следующим образом: (полный код на https://gist.github.com/c16725584493b021ba5b)

//#define BOOST_SPIRIT_DEBUG
#include <string>
#include <iostream>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

void handle_attr(
        const std::string& elem, 
        const std::string& attr, 
        const std::string& value)
{
    if (elem == "img" && attr == "src")
        std::cout << "value : " << value << std::endl;
}

typedef std::string::const_iterator It;
typedef qi::space_type Skipper;

struct grammar : qi::grammar<It, Skipper>
{
    grammar() : grammar::base_type(html)
    {
        using namespace boost::spirit::qi;
        using phx::bind;

        attr = as_string [ +~char_("= \t\r\n/>") ] [ _a = _1 ]
                >> '=' >> (
                    as_string [ '"' >> lexeme [ *~char_('"') ] >> '"' ]
                  | as_string [ "'" >> lexeme [ *~char_("'") ] >> "'" ]
                  ) [ bind(handle_attr, _r1, _a, _1) ]
            ;

        elem = lit('<') 
            >> as_string [ lexeme [ ~char_("-/>") >> *(char_ - space - char_("/>")) ] ] [ _a = _1 ]
            >> *attr(_a);

        html = (-elem) % +("</" | (char_ - '<'));

        BOOST_SPIRIT_DEBUG_NODE(html);
        BOOST_SPIRIT_DEBUG_NODE(elem);
        BOOST_SPIRIT_DEBUG_NODE(attr);
    }

    qi::rule<It, Skipper> html;
    qi::rule<It, Skipper, qi::locals<std::string> > elem;
    qi::rule<It, qi::unused_type(std::string), Skipper, qi::locals<std::string> > attr;
};

int main(int argc, const char *argv[])
{
    std::string s;

    const static grammar html_;

    while (std::getline(std::cin, s))
    {
        It f = s.begin(),
           l = s.end();

        if (!phrase_parse(f, l, html_, qi::space) || (f!=l))
            std::cerr << "unparsed: " << std::string(f,l) << std::endl;
    }

    return 0;
}
1 голос
/ 14 декабря 2011

Из любопытства я переделал свой образец регулярных выражений на основе Boost Xpressive, используя статически скомпилированные регулярные выражения:

sehe@natty:/tmp$ time ./expressive < bench > /dev/null

real    0m2.146s
user    0m2.110s
sys 0m0.030s

Интересно, что , при использовании динамического регулярного выражения нет заметной разницы в скорости; однако в целом версия Xpressive работает лучше, чем версия Boost Regex (примерно на 10%)

Что действительно хорошо, ИМО, так это то, что практически нужно было включить xpressive.hpp и изменить несколько пространств имен для перехода с Boost Regex на Xpressive. Интерфейс API (насколько он использовался) точно такой же.

Соответствующий код выглядит следующим образом: (полный код на https://gist.github.com/c16725584493b021ba5b)

typedef std::string::const_iterator It;

int main(int argc, const char *argv[])
{
    using namespace boost::xpressive;
#if DYNAMIC
    const sregex re = sregex::compile
         ("<img\\s+[^\\>]*?src\\s*=\\s*([\"'])(.*?)\\1");
#else
    const sregex re = "<img" >> +_s >> -*(~(set = '\\','>')) >> 
        "src" >> *_s >> '=' >> *_s
        >> (s1 = as_xpr('"') | '\'') >> (s2 = -*_) >> s1;
#endif

    std::string s;
    smatch what;

    while (std::getline(std::cin, s))
    {
        It f = s.begin(), l = s.end();

        do
        {
            if (!regex_search(f, l, what, re))
                break;

            handle_attr("img", "src", what[2]);
            f = what[0].second;
        } while (f!=s.end());
    }

    return 0;
}
1 голос
/ 14 декабря 2011

Обновление

Я сделал тесты.

Полное раскрытие здесь: https://gist.github.com/c16725584493b021ba5b

Включает полный используемый код,используемые флаги компиляции и тело тестовых данных (файл bench).

Короче говоря

  • Регулярные выражения действительно быстрее и проще здесь
  • Делатьне стоит недооценивать время, которое я потратил на отладку грамматики Духа, чтобы получить ее правильно !
  • Были приняты меры для устранения «случайных» различий (например,
    • , сохраняющих handle_attribute неизменными во всех реализациях, даже если это имеет смысл в основном только для реализации Spirit).
    • с использованием одного и того же стиля ввода строки и строковых итераторов для обоих
  • В настоящее время все три реализации приводят к одинаковому выводу
  • Все построено / рассчитано на g ++ 4.6.1 (режим c ++ 03), -O3

Редактировать в ответ на рывок колена (и правильный ) ответ, что вы не должны анализировать HTML с помощью регулярных выражений:

  • Вы не должны использовать регулярное выражение для анализа нетривиальных входных данных (в основном, всего с грамматикой.курс Perl 5.10+ 'грамматики регулярных выражений' являются исключением, поскольку они больше не являются изолированными регулярными выражениями
  • HTML в принципе не может быть проанализирован, это нестандартный теговый супСтрогий (X) HTML, это другое дело
  • Согласно XaadНапример, если у вас недостаточно времени для создания идеальной реализации с использованием совместимого со стандартами средства чтения HTML, вам следует

    "спросить клиента, хотят ли они дерьма или нет.Если они хотят дерьмо, вы платите им больше.Дерьмо стоит тебе больше, чем они. " - Xaade

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

Сроки и резюме см. В

Я от всей души предлагаю использовать здесь регулярное выражение:

typedef std::string::const_iterator It;

int main(int argc, const char *argv[])
{
    const boost::regex re("<img\\s+[^\\>]*?src\\s*=\\s*([\"'])(.*?)\\1");

    std::string s;
    boost::smatch what;

    while (std::getline(std::cin, s))
    {
        It f = s.begin(), l = s.end();

        do
        {
            if (!boost::regex_search(f, l, what, re))
                break;

            handle_attr("img", "src", what[2]);
            f = what[0].second;
        } while (f!=s.end());
    }

    return 0;
}

Используйте его как:

./test < index.htm

Я не вижу никакой причины, почему подход, основанный на духе, должен / мог бы быть еще быстрее?

Редактировать PS. Если вы утверждаете, что статическая оптимизация будет ключом, почему бы просто не преобразовать ее вBoost Выразительное, статическое, регулярное выражение?

...