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


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

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 の書き方は下記ページがわかりやすかったです。

属性値で絞り込む

このへんから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 も見てみたのですが、導入時にエラーが出て、Jiは出なかったので、そのままJiを使いました。(今回は最適なOSSを選択することよりも、とにかく早くやりたいことを達成する方が優先度が高かったため)

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

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

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

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