使用Boost.从HTML中提取某些标签/属性
Using Boost.Spirit to extract certain tags/attributes from HTML
所以我一直在学习一些关于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;
}
- 如何导出包含具有"std::unique_ptr"值的"std::map"属性的
- C++概念:如何使用'concept'检查模板化结构的属性?
- 子目录是否继承属性,例如add_definitions,include_directories和父Cmakelist.t
- 通过指向指针数组的指针访问子类的属性
- MSVC是否支持C++11样式的属性而不是__declspec
- QML:修改在不同QML文件(而非main.QML)中定义的子对象的属性
- 类的C++属性似乎已重新初始化
- Qt Quick-如何仅从c++代码与qml属性交互
- 用概念检查属性的类型
- C++如何将同一类的实例作为属性
- C 和 C++ 中开关语句的案例标签的常量值,但显示不同的行为
- 按多个属性排序
- 主函数参数的属性
- "perf_event_attr"结构的"read_format"属性的选项到底是什么?
- C++删除未使用的类属性会导致 std::logic_error
- MSBUILD -TAG PropertyGroup具有属性标签,但没有记录
- 基于标签高度动态设置窗体的VertScrollBar属性-c++生成器
- 用于查找标签/属性的正则表达式在HTML代码中
- 使用Boost.从HTML中提取某些标签/属性
- Visual c++ Form -如何通过单击按钮改变标签的可见属性