Swift製HTMLパーサ「Kanna」


@shu223 さんのpost ”手軽に使えるSwift製HTMLパーサ「Ji」” に名前が出ていたので
Kannaも紹介記事を書いてみます。

Github: Kanna(鉋)

追記:2015/9/2
1. バージョンアップしました(0.1.3 => 0.1.4)
   手元のバージョンで説明を書いてしまっていたため一部コードが動かない状態でした・・・。

2. マニュアルセットアップに方法2を追記しました。
   手動でframeworkをビルドする方法です。
   マニュアルセットアップするならコチラの方が楽です。

Kannaとは?

Swift製のHTML/XMLパーサです。
他パーサとの違いは CSSセレクタでも要素抽出 ができるぐらいです。

元々はSwift-HTML-Parserという名前でした。
"SwiftでObjCコードを書き換えてみる(Objective-C-HTML-Parser)"
最近RubyのNokogiriを知って、似せる形で書き換えたのでついでに名前も変えました。

導入手順

※Swift2の場合はREADME.mdを参照してください。

CocoaPods

Podfile
use_frameworks!
pod 'Kanna'

$ pod install

Carthage

Cartfile
github "tid-kijyun/Kanna" >= 0.1.0

$ carthage update

マニュアルセットアップ(方法1)

  1. プロジェクトを選択して"Linked Frameworks and Libraries" に libxml2.dylib を追加
  2. ”Header Search Paths" に $(SDKROOT)/usr/include/libxml2 を追加
  3. Bridging Headerに下記を追加
  #import <libxml/HTMLtree.h>
  #import <libxml/xpathInternals.h>

Bridging Headerが無い場合は作成してください。
4. Souceフォルダ配下の下記ファイルをプロジェクトに追加

   Kanna.swift
   CSS.swift
   libxml/libxmlHTMLDocument.swift
   libxml/libxmlHTMLNode.swift
   libxml/libxmlParserOption.swift

マニュアルセットアップ(方法2)

出力される場所を見つけられるなら手動でframeworkをビルドする方法もあります。

  1. Kanna.xcworkspace をXcodeで開いてビルド
  2. 生成された Release-iphoneos ディレクトリ内の Kanna.framework をプロジェクトに登録

参考:Github Issue #28 Use in Projects

使い方

インポート

// マニュアルセットアップの場合は不要です。
import Kanna

Kannaオブジェクトを作成

HTMLは別途引っ張ってくる必要があります。(だいたいAlamofire使うので入れていません・・・)

let url = NSURL(string: "https://en.wikipedia.org/wiki/cat")
let data = NSData(contentsOfURL: url!)
let doc = HTML(html: data!, encoding: NSUTF8StringEncoding)

文字列から作成することもできます。

let html = "<html>...</html>"
let doc = HTML(html: html, encoding: NSUTF8StringEncoding)

CSSセレクタで特定の要素を抽出する

// aタグ と linkタグ を抽出
nodes = doc?.css("a, link")

実際使うとこんな感じです。

// HTML内のリンク(URL)を抜き出す
for node in doc!.css("a, link") {
    println(node["href"])  // href属性に設定されている文字列を出力
}

Xpathで特定の要素を抽出する(結果はCSSセレクタの場合と同じ)

// aタグ と linkタグ を抽出
nodes = doc?.xpath("//a | //link")
// HTML内のリンク(URL)を抜き出す
for node in doc!.xpath("//a | //link") {
    println(node["href"])  // href属性に設定されている文字列を出力
}

属性値で絞り込む

Wikipediaのコンテンツをスクレイピングする場合・・・

// cssセレクタ ver
let contentDivNode = doc?.body?.css("div#content div#bodyContent div#mw-content-text")

// xpath ver
let contentDivNode = doc?.body?.xpath("div[@id='content']/div[@id='bodyContent']/div[@id='mw-content-text']")

こう書いてもOKです(bodyを省略)

// cssセレクタ ver
let contentDivNode = doc?.css("div#content div#bodyContent div#mw-content-text")

// xpath ver
let contentDivNode = doc?.xpath("//div[@id='content']/div[@id='bodyContent']/div[@id='mw-content-text']")

細かい使い方

タイトルを取得する

doc?.title      //=> Optional("hoge title")

headノードを取得する

head: メソッドで取得できます

let head = doc?.head

// 下記でも同じ
let head = doc?.css("head").first

bodyノードを取得する

body: メソッドで取得できます

let body = doc?.body

// 下記でも同じ
let body = doc?.css("body").first

タグ名を取得する

tagName で取得できます。

let node = doc?.css("div").first
node?.tagName   //=> Optional("div")

属性値を取得する

["ATTR_NAME"] で取得できます。

let node = doc?.css("a[href]").first
node?["href"]   //=> Optional("http://...")

HTMLを取得する

toHTML で取得できます。

let node = doc?.css("div").first
node?.toHTML    //=> Optional("<div...</div>")

タグの中身を取得する

innerHTML で取得できます。

let node = doc?.css("div").first
node?.innerHTML    //=> Optional("...")

その他

ご質問・ご指摘ありましたらお願いします。
プルリクも大歓迎です!