使用Boost.Spirit从HTML中提取某些标记/属性

时间:2011-12-14 09:13:34

标签: c++ boost-spirit boost-spirit-qi

所以我一直在学习Boost.Spirit,以便在我的很多代码中替换正则表达式的使用。主要原因是纯粹的速度。对于一些相对简单的任务,我发现Boost.Spirit的速度比PCRE快50倍。

我的一个应用程序中存在的一个重大瓶颈是使用一些HTML,查找所有“img”标记,并提取“src”属性。

这是我目前的正则表达式:

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

我一直在玩它试图让一些东西在Spirit工作,但到目前为止,我已经空了。关于如何创建一组精神规则的任何提示将完成与此正则表达式相同的事情将是非常棒的。

3 个答案:

答案 0 :(得分:2)

当然,不能错过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 :(得分:1)

  

<强>更新

     

我做了基准测试。

     

完全披露在此处:https://gist.github.com/c16725584493b021ba5b

     

它包括使用的完整代码,编译标志和使用的测试数据主体(文件bench)。

     

简而言之

     
      
  • 正则表达式确实更快,更简单
  •   
  • 不要低估我调整Spirit语法以使其正确的时间!
  •   
  • 已采取措施消除“意外”差异(例如:      
        
    • 在实现中保持handle_attribute不变,即使它主要仅适用于Spirit实现。)
    •   
    • 使用相同的行式输入样式字符串迭代器   
  •   
  • 现在,所有三个实现都会产生完全相同的输出
  •   
  • 在g ++ 4.6.1(c ++ 03模式),-O3
  • 上构建/定时的所有内容   
     
     

编辑以回复您不应使用正则表达式解析HTML的膝跳(和正确的)响应:

     
      
  • 你不应该使用regexen来解析非平凡的输入(主要是带有语法的东西。当然Perl 5.10 + 'regex grammars'是一个例外,因为它们不再是孤立的正则表达式
  •   
  • HTML基本上无法解析,它是非标准的标签汤。严格(X)HTML,是另一回事
  •   
  • 根据Xaade的说法,如果你没有足够的时间使用符合标准的HTML阅读器来制作完美的实现,你应该      
        

    “询问客户是否需要狗屎。如果他们想要狗屎,你会向他们收取更多费用。狗屎比他们花费更多。” - Xaade

      
  •   
     

那说有些情况下我会按照我的建议去做:使用正则表达式。主要是,如果要进行一次性快速搜索或每天获取已知数据等的粗略统计数据,YMMV和你应该自己打电话。

     

有关时间安排和摘要,请参阅:

     
      
  • 提升正则表达式回答below
  •   
  • 提升回答here
  •   
  • 精神回答here
  •   

我衷心建议在这里使用正则表达式:

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 Expressive,静态,正则表达式?

答案 2 :(得分:1)

出于好奇,我使用静态编译的正则表达式重写了基于Boost Xpressive的正则表达式样本:

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

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

有趣的是,使用动态正则表达式时没有明显的速度差异;然而,总的来说, Xpressive版本比Boost Regex版本表现更好(大约10%)

  

IMO的真正好处在于,包括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;
}