以前我们获取数据的方式都是使用 AFN 来 Get JSON 数据,比如 点我查看 JSON 数据.http://news-at.zhihu.com/api/4/news/latest
但例如下面的百度贴吧,和豆瓣读书等网站...并不提供我们获取数据的 API
百度贴吧:
![百度贴吧数据 百度贴吧数据]()
百度贴吧数据.png
豆瓣读书:
![豆瓣读书数据 豆瓣读书数据]()
豆瓣读书数据.png
这时我们可以解析他们的 HTML 来获取我们想要的数据.
工具准备
这时我们需要2个工具,Firefox 和FireBug.
你可以在 http://www.firefox.com.cn/download/下载 FireFox 浏览器,然后在其右上角菜单的附加组件管理器中下载 FireBug 插件.
FireBug 有很强大的 JavaScript 调试功能,还能实时编辑 HTML CSS,是前端同学喜爱的一个工具.
下载安装好以后 点击右上角的 Bug(虫子)图标来使用 FireBug 调试当前网页.
如果你不了解 XPath ,可以学习 w3school 的教程.
![打开 FireBug 打开 FireBug]()
打开 FireBug.png
Ono 开源库
Ono 是一个 Github 上的开源项目,它能方便我们解析 XML,HTML 标签,并且支持 CSS XPath 搜索特定节点.
你可能没听过这个库,但其作者你肯定知道. Mattt Thompson,它是 AFN 的作者,还是博客 NSHipster 的作者.
Swift 版本类似的开源库 Ji
Java 或 Android 可以使用 Jsoup
开始
准备工作都 Ok 了..我们开始编码.新建一个空白工程,注意,如果要在 Info.plist 中添加两行 App Transport Security Settings,和 Allow Arbitrary Loads YES, 来允许 HTTP 传输.
![App 允许 Http App 允许 Http]()
App 允许 Http.png
然后使用 CocoaPods 添加第三方库 pod 'Ono'.
这里,要解析的 HTMl 数据就用我的博客了
再创建一个 Post 类继承自 NSObject,代表每一篇文章 ,修改 .h 文件如下
- #import
-
- @class ONOXMLElement;
-
-
-
- @interface Post : NSObject
-
- @property (copy,nonatomic) NSString *title; //文章标题
-
- @property (copy,nonatomic) NSString *postDate; //文章发表时间
-
- @property (copy,nonatomic) NSString *postUrl; //文章正文内容的 Url
-
-
-
- +(NSArray*)getNewPosts; //获取所有文章
-
- +(instancetype)postWithHtmlStr:(ONOXMLElement*)element; //用 HTMl 数据创建 Post 类
-
- @end
在.m 文件中导入 Ono,并添加一个常量 Url.
- #import
-
- static NSString *const kUrlStr=@"http://BigPi.me";
然后我们可以用 AFN 等下载该 Url 的 HTML数据,再使用 XPath 获取代表每一篇文章的 XPath,
先打开 FireFox 和 FireBug ,点击下面的图
![FireBug 元素选择器 FireBug 元素选择器]()
FireBug 元素选择器.png
在适当移动鼠标,点击选择网页上的一篇文章,
![Post数据 Post数据]()
Post数据.png
这时我们可以看到,下面 FireBug 的 HTMl 树展开了,我们可以发现,每一个
标签都包含一篇文章的数据.
我们右键
,复制其 XPath
![复制 XPath 复制 XPath]()
复制 XPath.png
复制出来的结果 //*[@id="posts"],这个
节点下面的每一个子节点都代表一篇文章,
我们现在来使用这个 XPath 获取所有的 HTML 数据.在 Post.m 添加如下方法:
- +(NSArray*)getNewPosts{
-
- NSMutableArray *array=[NSMutableArray array];
-
- NSData *data= [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlStr]]; //下载网页数据
-
-
-
- NSError *error;
-
- ONOXMLDocument *doc=[ONOXMLDocument HTMLDocumentWithData:data error:&error];
-
- ONOXMLElement *postsParentElement= [doc firstChildWithXPath:@"//*[@id='posts']"]; //寻找该 XPath 代表的 HTML 节点,
-
- //遍历其子节点,
-
- [postsParentElement.children enumerateObjectsUsingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL * _Nonnull stop) {
-
- NSLog(@"%@",element);
-
- }];
-
- return array;
-
- }
并在ViewController.m 中调用这个方法:
- @implementation ViewController
-
-
-
- - (void)viewDidLoad {
-
- [super viewDidLoad];
-
- [Post getNewPosts];
-
- }
-
-
-
- @end
运行后查看 Console, 我们已经可以获取到每篇文章的 HTMl 了,然后我们再来解析每篇文章的具体数据.
切换到 FireBug,展开其中一篇文章的节点
![]()
文章 HTML 节点.png
我们可以看到<h2 class="title">节点下的
- <a href="/post/jazzhands/jazzhands-yuan-ma-shi-xian-fen-xi">
- <i class="fa fa-leaf"></i>
- JazzHands 源码实现分析</a>
标签中,有文章的具体Url, 和文章标题,
<div class="info">节点下的,
- <span class="date">
- <i class="fa fa-clock-o"></i>
- 2016-03-04 21:39</span>
标签有文章发布的时间,此时我们可以右键点击节点,复制文章标题,发布时间等节点的 XPath,
但这里我们使用相对的 XPath.
每篇文章的 HTML 结构如下:
所以我们的
- 文章 Url XPath : “h2/a”
- 文章标题 XPath : a 标签的 href 属性值
- 文章发布时间 XPath : “div[2]/span[1]”
接下来我们来解析每一篇文章的详细数据
在 Post.m 中添加方法:
- +(instancetype)postWithHtmlStr:(ONOXMLElement*)element{
-
-
-
- Post *p=[Post new];
-
- ONOXMLElement *titleElement= [element firstChildWithXPath:@"h2/a"]; // 根据 XPath 获取含有文章标题的 a 标签
-
- p.postUrl= [titleElement valueForAttribute:@"href"]; //获取 a 标签的 href 属性
-
- p.title= [titleElement stringValue];
-
- ONOXMLElement *dateElement= [element firstChildWithXPath:@"div[2]/span[1]"]; //根据 XPath 获取文章发布时间 span 标签
-
- p.postDate= [dateElement stringValue];
-
- return p;
-
- }
然后修改 +(NSArray*)getNewPosts方法:如下
- ...
-
- [postsParentElement.children enumerateObjectsUsingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL * _Nonnull stop) {
-
- //NSLog(@"%@",element);
-
- Post *post=[Post postWithHtmlStr:element];
-
- if(post){
-
- [array addObject:post];
-
- }
-
- }];
-
- ...
最后因为我们我们获取到的 HTMl 的文章 Url 是相对 Url, 类似
/post/jazzhands/jazzhands-yuan-ma-shi-xian-fen-xi
所以我们在 Setter 方法中拼接域名 , http://BigPi.me
- -(void)setPostUrl:(NSString *)postUrl{
-
- _postUrl=[kUrlStr stringByAppendingString:postUrl];
-
- }
我们在下图位置打断点查看结果:
代码断点.png
运行起来,结果如下:
![抓取文章数据结果 抓取文章数据结果]()
抓取文章数据结果.png
至此我们已经能使用 FireBug + Ono + XPath 来解析 HTML 数据
我就使用这个办法获取我们学校教务管理系统 HTML,制作了一个统计成绩,计算绩点的 App.
补充
- FireBug 是一个很强大前端调试工具.
- 还可以使用 正则表达式 来解析 HTML 数据.不过从 StackOverflow 讨论 来看 ,并推荐使用正则来解析 HTML 数据.
- RayWonderLich 有一篇比较老的教程 ,使用类似的技术解析 HTML
- 最最后,很重要的一点,HTML 数据可能经常会变动,尤其那个网页还不是我们自己能管理的网页,所以 XPath 随时可能解析失败,
- 如果你一定要使用 XPath 来解析 HTML 数据,可以在服务端进行这个操作,然后修改成 API 的形式,让手机端像以前一样 GET JSON 数据.
- 同时,服务端还可以设置异常处理,缓存等策略.