iOS抓取HTML ,CSS XPath解析數(shù)據(jù)
以前我們獲取數(shù)據(jù)的方式都是使用 AFN 來 Get JSON 數(shù)據(jù),比如 點(diǎn)我查看 JSON 數(shù)據(jù).http://news-at.zhihu.com/api/4/news/latest
但例如下面的百度貼吧,和豆瓣讀書等網(wǎng)站...并不提供我們獲取數(shù)據(jù)的 API
百度貼吧:
百度貼吧數(shù)據(jù).png
豆瓣讀書:
豆瓣讀書數(shù)據(jù).png
這時我們可以解析他們的 HTML 來獲取我們想要的數(shù)據(jù).
工具準(zhǔn)備
這時我們需要2個工具,Firefox 和FireBug.
你可以在 http://www.firefox.com.cn/download/下載 FireFox 瀏覽器,然后在其右上角菜單的附加組件管理器中下載 FireBug 插件.
FireBug 有很強(qiáng)大的 JavaScript 調(diào)試功能,還能實(shí)時編輯 HTML CSS,是前端同學(xué)喜愛的一個工具.
下載安裝好以后 點(diǎn)擊右上角的 Bug(蟲子)圖標(biāo)來使用 FireBug 調(diào)試當(dāng)前網(wǎng)頁.
如果你不了解 XPath ,可以學(xué)習(xí) w3school 的教程.
打開 FireBug.png
Ono 開源庫
Ono 是一個 Github 上的開源項(xiàng)目,它能方便我們解析 XML,HTML 標(biāo)簽,并且支持 CSS XPath 搜索特定節(jié)點(diǎn).
你可能沒聽過這個庫,但其作者你肯定知道. Mattt Thompson,它是 AFN 的作者,還是博客 NSHipster 的作者.
Swift 版本類似的開源庫 Ji
Java 或 Android 可以使用 Jsoup
開始
準(zhǔn)備工作都 Ok 了..我們開始編碼.新建一個空白工程,注意,如果要在 Info.plist 中添加兩行 App Transport Security Settings,和 Allow Arbitrary Loads YES, 來允許 HTTP 傳輸.
App 允許 Http.png
然后使用 CocoaPods 添加第三方庫 pod 'Ono'.
這里,要解析的 HTMl 數(shù)據(jù)就用我的博客了
再創(chuàng)建一個 Post 類繼承自 NSObject,代表每一篇文章 ,修改 .h 文件如下
- #import
- @class ONOXMLElement;
- @interface Post : NSObject
- @property (copy,nonatomic) NSString *title; //文章標(biāo)題
- @property (copy,nonatomic) NSString *postDate; //文章發(fā)表時間
- @property (copy,nonatomic) NSString *postUrl; //文章正文內(nèi)容的 Url
- +(NSArray*)getNewPosts; //獲取所有文章
- +(instancetype)postWithHtmlStr:(ONOXMLElement*)element; //用 HTMl 數(shù)據(jù)創(chuàng)建 Post 類
- @end
在.m 文件中導(dǎo)入 Ono,并添加一個常量 Url.
- #import
- static NSString *const kUrlStr=@"http://BigPi.me";
然后我們可以用 AFN 等下載該 Url 的 HTML數(shù)據(jù),再使用 XPath 獲取代表每一篇文章的 XPath,
先打開 FireFox 和 FireBug ,點(diǎn)擊下面的圖
FireBug 元素選擇器.png
在適當(dāng)移動鼠標(biāo),點(diǎn)擊選擇網(wǎng)頁上的一篇文章,
Post數(shù)據(jù).png
這時我們可以看到,下面 FireBug 的 HTMl 樹展開了,我們可以發(fā)現(xiàn),每一個
標(biāo)簽都包含一篇文章的數(shù)據(jù).
我們右鍵
,復(fù)制其 XPath
復(fù)制 XPath.png
復(fù)制出來的結(jié)果 //*[@id="posts"],這個
節(jié)點(diǎn)下面的每一個子節(jié)點(diǎn)都代表一篇文章,
我們現(xiàn)在來使用這個 XPath 獲取所有的 HTML 數(shù)據(jù).在 Post.m 添加如下方法:
- +(NSArray*)getNewPosts{
- NSMutableArray *array=[NSMutableArray array];
- NSData *data= [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlStr]]; //下載網(wǎng)頁數(shù)據(jù)
- NSError *error;
- ONOXMLDocument *doc=[ONOXMLDocument HTMLDocumentWithData:data error:&error];
- ONOXMLElement *postsParentElement= [doc firstChildWithXPath:@"//*[@id='posts']"]; //尋找該 XPath 代表的 HTML 節(jié)點(diǎn),
- //遍歷其子節(jié)點(diǎn),
- [postsParentElement.children enumerateObjectsUsingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL * _Nonnull stop) {
- NSLog(@"%@",element);
- }];
- return array;
- }
并在ViewController.m 中調(diào)用這個方法:
- @implementation ViewController
- - (void)viewDidLoad {
- [super viewDidLoad];
- [Post getNewPosts];
- }
- @end
運(yùn)行后查看 Console, 我們已經(jīng)可以獲取到每篇文章的 HTMl 了,然后我們再來解析每篇文章的具體數(shù)據(jù).
切換到 FireBug,展開其中一篇文章的節(jié)點(diǎn)
文章 HTML 節(jié)點(diǎn).png
我們可以看到<h2 class="title">節(jié)點(diǎn)下的
- <a href="/post/jazzhands/jazzhands-yuan-ma-shi-xian-fen-xi">
- <i class="fa fa-leaf"></i>
- JazzHands 源碼實(shí)現(xiàn)分析</a>
標(biāo)簽中,有文章的具體Url, 和文章標(biāo)題,
<div class="info">節(jié)點(diǎn)下的,
- <span class="date">
- <i class="fa fa-clock-o"></i>
- 2016-03-04 21:39</span>
標(biāo)簽有文章發(fā)布的時間,此時我們可以右鍵點(diǎn)擊節(jié)點(diǎn),復(fù)制文章標(biāo)題,發(fā)布時間等節(jié)點(diǎn)的 XPath,
但這里我們使用相對的 XPath.
每篇文章的 HTML 結(jié)構(gòu)如下:
- 文章標(biāo)題 Url等內(nèi)容
所以我們的
- 文章 Url XPath : “h2/a”
- 文章標(biāo)題 XPath : a 標(biāo)簽的 href 屬性值
- 文章發(fā)布時間 XPath : “div[2]/span[1]”
接下來我們來解析每一篇文章的詳細(xì)數(shù)據(jù)
在 Post.m 中添加方法:
- +(instancetype)postWithHtmlStr:(ONOXMLElement*)element{
- Post *p=[Post new];
- ONOXMLElement *titleElement= [element firstChildWithXPath:@"h2/a"]; // 根據(jù) XPath 獲取含有文章標(biāo)題的 a 標(biāo)簽
- p.postUrl= [titleElement valueForAttribute:@"href"]; //獲取 a 標(biāo)簽的 href 屬性
- p.title= [titleElement stringValue];
- ONOXMLElement *dateElement= [element firstChildWithXPath:@"div[2]/span[1]"]; //根據(jù) XPath 獲取文章發(fā)布時間 span 標(biāo)簽
- 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];
- }
- }];
- ...
最后因?yàn)槲覀兾覀儷@取到的 HTMl 的文章 Url 是相對 Url, 類似
/post/jazzhands/jazzhands-yuan-ma-shi-xian-fen-xi
所以我們在 Setter 方法中拼接域名 , http://BigPi.me
- -(void)setPostUrl:(NSString *)postUrl{
- _postUrl=[kUrlStr stringByAppendingString:postUrl];
- }
我們在下圖位置打斷點(diǎn)查看結(jié)果:

代碼斷點(diǎn).png
運(yùn)行起來,結(jié)果如下:
抓取文章數(shù)據(jù)結(jié)果.png
至此我們已經(jīng)能使用 FireBug + Ono + XPath 來解析 HTML 數(shù)據(jù)
我就使用這個辦法獲取我們學(xué)校教務(wù)管理系統(tǒng) HTML,制作了一個統(tǒng)計(jì)成績,計(jì)算績點(diǎn)的 App.
補(bǔ)充
- FireBug 是一個很強(qiáng)大前端調(diào)試工具.
- 還可以使用 正則表達(dá)式 來解析 HTML 數(shù)據(jù).不過從 StackOverflow 討論 來看 ,并推薦使用正則來解析 HTML 數(shù)據(jù).
- RayWonderLich 有一篇比較老的教程 ,使用類似的技術(shù)解析 HTML
- 最最后,很重要的一點(diǎn),HTML 數(shù)據(jù)可能經(jīng)常會變動,尤其那個網(wǎng)頁還不是我們自己能管理的網(wǎng)頁,所以 XPath 隨時可能解析失敗,
- 如果你一定要使用 XPath 來解析 HTML 數(shù)據(jù),可以在服務(wù)端進(jìn)行這個操作,然后修改成 API 的形式,讓手機(jī)端像以前一樣 GET JSON 數(shù)據(jù).
- 同時,服務(wù)端還可以設(shè)置異常處理,緩存等策略.
本文 Demo 可以在 https://github.com/iShawnWang/BlogDemo/tree/master/ParseHTMLDemo中獲取