您当前的位置:首页 > 计算机 > 编程开发 > VC/VC++

C++实现HTML解析器

时间:04-02来源:作者:点击数:

闲来无事,想做个网页爬虫。用C++来做的话,当然是先要做HTTP下载了,最简单的方式当然是用libcurl实现,然后从网页信息中提取必要的信息即可。这其中,libcurl下载可以支持chunked流及gzip压缩的特殊情况,难点在于HTML的解析。

HTML是从XML来的,我用项目之前的xml封装库做解析,发现HTML文档并不能解析成功,但是在网页浏览器却可以成功,打开开发者工具查看DOM树,发现确实是中途多了一个</div>标签。在XML规范中这个就会失败,看来简单的通过XML解析引擎去解析不是很靠谱。

既然XML不行,直接上HTML解析库,看了下C++的就一个htmlcxx,demo看着就不是很好用。果断在github去看看,确实找了几个不错的,可惜不知为何,clone代码编译之后,自带的测试DEMO都会core,看来稳定性还是有点问题的(应该是兼容性吧)。

好吧,造轮子吧!

解析原理

HTML文档其实可以看成是一个XML文档,只是有一些特有的地方。解析的话,思想就是先解析一个标签,从中取出属性,然后再找是是否是自关闭,未关闭继续在内容中递归。按照这个思路很容易写出对应的代码,设计一个元素类,内部包含熟悉、值、子元素、父元素。有了这些基本一个HTML文档就串联起来了。

当然如果一切顺利的话,解析起来不难,就如同XML引擎解析HTML失败,大量的HTML网页存在大量不规范的地方,即不符合XHTML规范。

  • 元素不关闭,这个很常见,有可能是忘记了,有可能是习惯问题。最常见的比如<br>这个换行就没有关闭,理论上应该是<br/>
  • 标签不对应,这个和上面类似,但是有的时候突然多出来某些标签的关闭标签,但是前文却没有对应的标签。
  • 还有某些特殊的标签,比如<% ... %>,应该是脚本语言引擎未正确执行吧。
  • 某些属性的"或者'不匹配。
  • 另外就是标签的交叉问题。

总之不规范的地方还是挺多的,但基本都可以被浏览器支持的,说明浏览器的容错性还是不错的,据说就是因为较早版本的HTML规范没这么严格导致的。实现的时候还是要尽量的兼容一下。

解析实现

我的实现基本上是以可以解析现有文档并提取有效信息为出发点,找了163等网站主页作为测试页,均已可以测试通过(网易主页也有很多不规范的地方)。同时为了保证内存泄露问题,采用了智能指针,子元素用shared_ptr封装,父元素用weak_ptr封装。

目前是将元素内的所有值(非子元素)全部保持到value中,比如

<html>
  <body>
  	hello<a href="" />world
  </body>
</html>

body元素的值会保存为hello world,我觉得应该分别作为子元素存在,目前还没有这么做。

另外在操作DOM的时候Javascript的getElementByXXX系列还是很好用的,有了前面的结构,通过递归也很容易实现,不过class一般可以配置多个,还需要分割。

当然存在的问题还很多,但已经基本够用。已分享到http://github.com/rangerlee/htmlparser

应用举例

写个demo测试下,用libcurl下载某网页到内存进行解析,提取美图地址。

HtmlParser parser;
shared_ptr<HtmlDocument> doc = parser.Parse(data.c_str(), data.size());

std::vector<shared_ptr<HtmlElement>> pics;
std::vector<shared_ptr<HtmlElement>> picbox = parser.GetElementByClassName("pic_box");
for (auto box : picbox) {
    std::vector<shared_ptr<HtmlElement>> tmp = box->GetElementByTagName("img");
    pics.insert(pics.end(), tmp.begin(), tmp.end());
}

for (auto v : pics) {
    printf("img: %s\n", v->GetAttribute("src").c_str());
}

当然测试写的是C++11代码,解析器其实也可以自动适配支持非C++11编译器,使用tr1里面的库。

后续更新

功能更新1

  • 针对查询指定节点很复杂的问题,实现了一个简单的XPath搜索
  • 为了防止查询节点重复,接口改为使用set(unordered_set)
  • 将文本默认改为plain节点,增加html和text输出接口

功能更新2

  • 完成Windows和Linux下的编译器(VS及GCC)支持
  • 完成对自关闭标签的解析支持,如常见的imgbrinputmeta等等
  • 完成对无引号属性值的解析支持,如<span id=xxx></span>
  • 支持对多个class值的查询支持,类似JQuery的选择器
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门