1.前言
-
最近在做项目.需要解析解析第三方的网页,如下厨房的网页.这里就以下厨房的为例子.如下图, 这是下厨房移动端显示的网页内容.(我是用chrome的开发者工具, 改为移动端进行查看, chrome的相关操作不是介绍重点, 也很简单, 可以自行百度).这是网页地址, 可以参考.http://www.xiachufang.com/recipe/100426232
-
下面是最终解析完成的效果截图.只截了一小部分.
好了看过了效果图, 准备进入正题了.
2.攻欲善其固,必先利其器
我主要从以下方面进行介绍
- 1.框架工具的选择
- 2.原理的介绍
- 3.实例
- 4.待改进的地方
2.1 框架工具的选择
- Hpple.github地址: Hpple . 但是我觉得不是很好用.因为其即不支持css选择器的语法.也不支持标准的DOM模型下的JS语法.让我觉得很是蛋疼.
- Gumbo.github地址: OCGumbo.这是今天要重点介绍的框架, Gumbo只是基于google的gumbo-parser库的一层OC语法封装.gumbo-parser基于C语言编写.只所以强力推荐OCGumbo有几个原因.
- 选择的第三方框架, 最好是有人维护.我查看了下虽然频率不高, 但是最近还是有更新.这样保证出现了bug, 能够得到即时的解决
- 其不仅十分方便的能够像JS的DOM模型下那样获取各个属性节点, 还能支持CSS的选择器语法.(CSS, 可能有的做移动端的人不是特别了解, 这里就不重点介绍这个了)
2.2 原理的介绍
这里主要简单的介绍一下, DOM的模型.DOM模型是网页中的一个概念.简单来说即所有的网页其在内存中的数据都是通过一颗树的数据结构组织起来.
- D(Document,文档)与O(Object, 对象), 当创建个创建了一个网页, 并加载到浏览器浏览时候, 就将该网页转换为文档对象(DO).文档对象主要功能就是处理网页的内容.
- M(Model, 模型).所以DOM就是文档对象模型之意, 所谓的模型就是一种数据结构.DOM的数据结构就是一颗树.DOM把一个网页表示为一颗节点树.所以有父子节点(parent node,child node), 兄弟节点的概念(sibling node)
举个简单的例子, 如下html代码对应的的DOM图(3.png).
Just a test
body tag!!!
分析:
- 1.html标签是这个文档的树根, 即html代表者整个文档.
- 2.head与body标签是兄弟关系, 又有各自的子元素, 一个相同的父元素(html)
利用这种节点树, 我们可以把一份文档的各个元素都表示出来.实际在内存中就是这样组织这些数据的-
节点(node)
一个DOM即是一个节点(node)集合.而这个集合里的元素(element)又主要分为三种类型, 元素节点(element node), 文本节点(text node), 属性节点(attribute node).- 元素节点(element node)
即用来构建html文档的标签, 诸如p标签, ul标签等. - 文本节点(text node)
即如代码中标签包围着的body tag!!!就是文本节点.所以上面那的图可以继续完善, 只是画出了主体. - 属性节点(attribute node)
即element node中的属性,用来描述element node的.如
.其中title="test"就是一个attribute node(属性节点).显而易见, 属性节点是元素节点的一个子节点.p tag
- 元素节点(element node)
-
如何拿到DOM中相应的节点
简单的说来, 网页中的很多节点为了定位, 都会给其一个id,或者class属性.id可以用来唯一表示, class可以用来归类.这如果要细说, 又涉及到CSS的一些基本知识, 可自行了解.拿到对应的节点主要有三种方式.(以下是JS封装的Document对象对应的接口).- 1.是通过对象的唯一ID拿, 对应的接口是getElementById
- 2.是通过标签名拿, getElementByTag.
- 3.是通过标签所属于的类拿, getElementByclassName.
- 注意:后两者返回的都是数组,因为可能有多个同名标签, 和多个标签属于同一个类(但在OCGumbo中返回的都是数组, 可看下面介绍).这里的类不是传统编程语言中的类, 只是个分类而已.
2.3 实例(Talk is Cheap, show me the code)
介绍了基本的原理,其实网页解析抓取数据, 也就是解析DOM, 拿到想要的节点里的数据.就以下厨房的某个网页为例子来简单介绍一下OCGumbom框架的使用.
使用步骤:
- 1.集成到项目中
导入OCGumbom到工程中, 这个是我编译好的, 上传到github上的, 你也可以自己编译静态库自己去集成.如图, 下载下来后拖拽到你工程的相应文件夹中.
在PCH文件中直接导入该框架, 或者要用的地方再导入.
- 2.将网页转字符串.并创建文档对象
NSError *error = nil;
NSURL *xcfURL = [NSURL URLWithString:url];
NSString *htmlString = [NSString stringWithContentsOfURL:xcfURL encoding:NSUTF8StringEncoding error:&error];
OCGumboDocument *document = [[OCGumboDocument alloc] initWithHTMLString:htmlString];
其中的url即是网页的地址, 是一个字符串对象.
可以看到其是先将网页转为字符串, 然后根据字符串创建文档对象.这样, 我们就可以通过该文档对象进行网页的解析.
- 3.拿节点,抓取对应数据
我主要简单的介绍一下, 三种拿数据的方法.能满足99%的解析业务需求.
OCGumbom框架中的文档对象,只要提供了三个方法Query方法与find方法,attr方法,来拿到相应的节点或者节点集合-
1 根据节点的id拿到对应的节点
如果是根据id来拿节点, 那么返回的肯定就是一个元素.并且语法和CSS一样.举个例子, 如下看下新浪博客的网页的某部分结构.
那么如果要拿到这个id = "layout-content"
的这个div标签.要怎么拿呢?只要通过document对象即可.示例代码如下:
BKLog(@"%@", document.Query(@"#layout-content"));
打印如下
2016-04-23 20:24:57.697 beike[91910:4570788] ( "
" )
因为div是元素节点, 所以其是Element对象(OCGumboElement),需要注意的是返回的是一个数组.所以如果如果要去第一个元素可以用first()方法.其实不管是Query还是find方法, 返回的都是一个数组
BKLog(@"%@", document.Query(@"#layout-content").first());
.
相应的其它方法可以参考OCGumbom框架的头文件.
如果是id的话, 需要在其名称前加#号 -
2 根据节点的类别拿到对应的节点集合
根据节点的类别可以取到节点集合, 我们以下厨房的为例子.
如图的网页结构, 我们想拿到这个配方的名称, 可以用如下代码拿到这个h1标签.然后再调用element.text(),可以拿到其文本值.
OCGumboElement *element = document.Query(@"body").find(@".main-panel").find(@".page-title").first();
-
3 根据节点的属性拿到节点的属性值
根据节点的属性拿值, 最常用的是在获取图片的地址或者视频的地址上, 依然以下厨房的网页为例子, 如下网页
如果要拿到其img标签对应的图片地址, 那么我们首先就是得先拿到这个img节点, 然后再通过attr(@"src")
拿到其src属性的值.代码如下:
-
OCGumboElement *element = document.Query(@"body").find(@".block").find(@".cover").find(@"img").first();
NSLog(@"src = %@", element.attr(@"src'"));
**注意**: 这就是主要的三种常用方法.需要注意的是, 我们拿到节点之后, 我们要进行判断, 判断该节点是否是nil, 因为很可能抓取的网页结构变了, 如果不进行判断, 直接**节点元素对象**的相应方法或者是都会导致程序崩溃.上面举的三个例子, 是为了方便, 实际项目代码中, **务必每次取到节点或者节点集合, 都加一层判断, 看是不是有自己想拿的元素, 有再去调用相应的方法**
顺便提供下OCGumbo框架公开出的接口如下图.
![Snip20160423_7.png](http://upload-images.jianshu.io/upload_images/570808-d0b51381faa99fba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
由于不可能提供完整的项目代码, 这是基本的职业道德, 为了能与大家交流.gumbom-parse我已经编译好, 可以导入工程直接使用,支持模拟器和真机调试.
2.4 待改进
虽然OCGumbom框架已经很好用了, 当考虑到和安卓端的统一, 所以实际项目中, 可以对其进行二次封装.如果安卓端的框架提供的API和标准的JS, DOM原生的API一致, 那么二次封装就可以节省iOS端很多的开发时间, 只要安卓端的解析一做完, 几乎拿过来可以快速移植.
3.后续
接下去我会分析该框架的源码与大家分享!敬请关注.链式编程实践与DOM底层剖析
and~会保持每天一篇博客的更新.