手軽に使えるSwift製HTMLパーサ「Ji」

Wikipedia をスクレイピングするのに利用しました。

- [honghaoz/Ji](https://github.com/honghaoz/Ji)（戟）

HTMLをパースするOSSを（Swiftに限らず）他に使ったことないので比較はできないのですが、**サクッと導入できてとくにトラブルなくやりたいことができた**ので、導入方法や使い方を紹介してみます。



##導入手順

###CocoaPods 利用

Podfile

```
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'Ji', '~> 1.1.1'
```

で、`$ pod install`

###Carthage 利用

Cartfile

```
github "honghaoz/Ji" >= 1.1.1
```


###マニュアルインストール

今回は成果物を渡す相手が非iOS開発者だったので、実際には上記の CocoaPods / Carthage は自分では試しておらず（すみません）、下記の手順で手動導入しました。

- "Linked Frameworks and Libraries" から libxml2.dylib を追加
- "Header Search Paths" に `$(SDKROOT)/usr/include/libxml2` を追加
- Bridging Header に以下を追加

```
#import <libxml/tree.h>
#import <libxml/parser.h>
#import <libxml/HTMLtree.h>
#import <libxml/HTMLparser.h>
#import <libxml/xpath.h>
#import <libxml/xpathInternals.h>
#import <libxml/xmlerror.h>
```

- Souce フォルダ配下にある `Ji.swift`, `JiHelper.swift`, `JiNode.swift` をプロジェクトに追加


##使い方

READMEに普通にわかりやすくいろいろなパターンが書いてあるので、ここでは、自分が **Wikipediaをスクレイピングする際に使った機能** を順番に紹介していきます。


###URLからHTMLを取得（→ Jiオブジェクトを生成）

URLを渡すだけ。

```
let url = NSURL(string: "https://en.wikipedia.org/wiki/xxxx")
let jiDoc = Ji(htmlURL: url!)
```

HTMLの文字列を渡して生成することもできます。

```
let jiDoc = Ji(htmlString: html!)
```

###xPath でツリー構造をたどって特定の要素を抽出する

`xPath:` メソッドでHTMLタグのツリー構造をたどることができます。

bodyタグの中身を抽出する場合はこんな感じ。

```
let bodyNode = jiDoc?.xPath("//body")!.first!
```

戻り値の型は `[JiNode]?` です。（body は必ず1つ存在するだろう、という前提のもとに上記のようにしてます）

XPath の書き方は下記ページがわかりやすかったです。

- [XML/XPath/XPathの書き方](http://yakinikunotare.boo.jp/orebase/index.php?XML%2FXPath%2FXPath%A4%CE%BD%F1%A4%AD%CA%FD)

###属性値で絞り込む

このへんからWikipediaのページ依存、というか見ていたページにしかあてはまらないかもしれませんが、実際のコンテンツが、

```
<div id="content" class="mw-body" role="main">
   .
   .
   .
   <div id="bodyContent" class="mw-body-content">
       .
       .
       .
       <div id="mw-content-text" lang="en" dir="ltr" class="mw-content-ltr">
```

と div をいくつかたどった中に入っていた（Chrome の Developer Tools で確認）ので、次のようにコンテンツが入っているdivを抽出しました。


```
let contentDivNode = bodyNode.xPath("div[@id='content']/div[@id='bodyContent']/div[@id='mw-content-text']").first
```

[@xxxx='xxxxxxx'] は XPath で**属性値によるノード絞り込み**を行うための記法です。



###子ノードのリストを取得する

`children` メソッドで取得できます。

```
for childNode in contentDivNode!.children {
    // ...
}
```



###タグ名を取得する

`tag` プロパティを使うだけ。たとえば h2 タグを判定する際に次のように使いました。

```
if childNode.tag == "h2" {
    // ...
}
```

###JiNode オブジェクトの中身を文字列で取得する

`content` メソッドで取得できます。

```
let spanNode = childNode.xPath("span[@class='mw-headline']")        
let title = spanNode.first?.content!
```

戻り値の型は `String?` です。


###

以上の機能をやりくりして、あんまり構造化されてない Wikipedia のHTMLコンテンツをパースして、Dictionary / Arrayで構造化したオブジェクトとして抽出する、という目的を達成できました。



##おまけ：Jiを選択した理由

- GitHubで（Swift製HTMLパーサを検索した中では）Star数が最も多かった
  - 2015.8.30現在で341
- アクティブに更新されている
  - 2015.8.30現在で直近のコミットは3日前
  - **Swift 2.0 のブランチもあり**

国産の [Kanna](https://github.com/tid-kijyun/Kanna) も見てみたのですが、導入時にエラーが出て、Jiは出なかったので、そのままJiを使いました。（今回は最適なOSSを選択することよりも、とにかく早くやりたいことを達成する方が優先度が高かったため）


##追記：CSSセレクタでも要素抽出できる「Kanna」

KannaもSwift製のHTMLパーサです。作者の方よりコメントいただき、マニュアルセットアップの手順も追記されたとのこと。

また、紹介記事も書かれていました。

- [Swift製HTMLパーサ「Kanna」](http://qiita.com/_tid_/items/c228b1931cd9b23d52d3)

「CSSセレクタでも要素抽出ができる」という違いがあるそうです。次の機会にはKannaを使ってみます！