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

Using Boost.Spirit to extract certain tags/attributes from HTML

本文关键字:标签 属性 提取 Boost HTML 使用      更新时间:2023-10-16

所以我一直在学习一些关于Boost的知识。Spirit取代了正则表达式在我的很多代码中的使用。主要原因纯粹是速度。我找到Boost了。对于一些相对简单的任务,Spirit将比PCRE快50倍。

在我的一个应用程序中,一个很大的瓶颈是使用一些HTML,找到所有的"img"标签,并提取"src"属性。

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

(?i:<imgs[^>]*srcs*=s*[""']([^<][^""']+)[^>]*s*/*>)

我一直在玩它,试图得到一些工作在精神,但到目前为止,我一无所获。任何关于如何创建一组Spirit规则的技巧,将完成与此正则表达式相同的事情,将是很棒的。

当然,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_("= trn/>") ] [ _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;
}

我做基准测试。

全部信息在这里:https://gist.github.com/c16725584493b021ba5b

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

不足
  • 正则表达式在这里确实更快更简单
  • 不要低估我为使正确而调试Spirit语法所花费的时间!
  • 已采取措施消除"偶然"差异(例如:
    • 在实现中保持handle_attribute不变,即使它只对Spirit实现有意义)。
    • 使用相同的逐行输入样式字符串迭代器用于
  • 现在,所有三种实现结果完全相同的输出
  • 所有在g++ 4.6.1 (c++03模式)上构建/计时的东西,-O3

编辑以回复不应该使用正则表达式解析HTML的下意识(和正确的)响应:

  • 您不应该使用regexen来解析不重要的输入(主要是带有语法的任何内容)。当然Perl 5.10+ '正则表达式语法'是个例外,因为它们不再是孤立的正则表达式了
  • HTML基本上不能被解析,它是不标准的标签汤。严格的(X)HTML,是另一回事
  • 根据Xaade的说法,如果你没有足够的时间使用符合标准的HTML阅读器来生成一个完美的实现,你应该

"询问客户是否想要大便。如果他们想要什么,你就多收钱。屎比他们花你更多的钱。"——Xaade

也就是说,在某些情况下,我会做我在这里建议的事情:使用正则表达式。主要是,如果是做一次性快速搜索或获得每日,已知数据的粗略统计等。嗯,你应该自己打。

时间和摘要,见:

  • Boost expression answer 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表现力,静态,正则表达式?

出于好奇,我使用静态编译的正则表达式重新编写了基于Boost expression的正则表达式示例:

sehe@natty:/tmp$ time ./expressive < bench > /dev/null
real    0m2.146s
user    0m2.110s
sys 0m0.030s

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

真正好的是,在我看来,这几乎是包括xpressive.hpp和改变一些名称空间的问题,从Boost Regex改变为expression。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;
}