酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

Xamarin.iOSでHtml Agility Packを使ってHTMLをスクレイピングする

この記事は「Xamarinのカレンダー | Advent Calendar 2014 - Qiita」の10日目です。

Xamarin Advent Calendarは、Xamarinを使ったアプリ開発のTipsが1日1つずつ紹介されています。この記事はXamarin.iOSについて書いていますが、他の方はXamarin.AndroidやXamarin.Formsなどの記事を書かれており大変有益なTipsが集まっています。

この記事では、HTMLを取得して解析して特定のデータを取得する方法を考えてみましょう。

ウェブスクレイピングとは

さて、本題に入る前にスクレイピングとは何でしょうか。うーん……悩んだときにはWikipedia先生に聞いてみましょう。

ウェブスクレイピングは多くの検索エンジンによって採用されている、ボットを利用してウェブ上の情報にインデックス付けを行うウェブインデクシングと密接な関係がある。(中略)ウェブスクレイピングの用途は、オンラインでの価格比較、気象データ監視、ウェブサイトの変更検出、研究、ウェブマッシュアップやウェブデータの統合等である。

なるほど。スクレイピングとは非構造化データ(HTML)から有益な情報を取り出して利用するということですね。

他にも触れていないことも沢山ありますが、ウェブスクレイピングについてはもっと詳しく知りたい方は「http://bugrammer.hateblo.jp/entry/2014/08/29/141926」をご参照ください。

本記事で紹介すること

本記事で紹介することは以下の通りです。

  1. Xamarin Studioでサンプルアプリ用のプロジェクトを作る(前準備)
  2. Html Agility PackをXamarin.iOSで使えるようにする
  3. スクレイピング処理を実装する

さぁ、Xamarin.iOSでスクレイピング処理するアプリを作ってみましょう。

前準備

前準備ではスクレイピング処理とは関係ない部分に関してさっくりと紹介してしまいます。Xamarin Studioで新しいプロジェクトを作成する方法はこちらをご覧ください。

まずは、Xamarin Studioを起動してプロジェクトを適当に作成します。ここでは、新しいソリューションダイアログの名前(プロジェクト名)欄に「KabukaApp」と入力しました。

f:id:ch3cooh393:20141205210400p:plain

[OKボタン]をクリックするとテンプレートプロジェクトが生成されました。

ボタンとラベルを配置します

Xamarin Studioには、Xcodeを起動しなくてもStoryboardを編集することのできる「iOS Designer」が搭載されています。

左ペインにあるソリューションエクスプローラーからMainStoryboard.storyboardをダブルクリックしてiOS Designerで表示します。Single View Applicationを選択して生成したプロジェクトのRoot View Controllerは白紙の状態ですので、スクレイピング処理を開始するトリガーとなるUIButton(ボタン)スクレイピングした結果を表示するUILabel(ラベル)をそれぞれ配置しましょう。

右ペインのツールボックスからUIButtonとUILabelをドラッグアンドドロップします。適当な位置にViewを配置して、Viewのサイズを調整します。

f:id:ch3cooh393:20141205211935p:plain

ラベルに名前をつけます。右ペインにプロパティウィンドウが表示されているので、WidgetタブのName欄にViewの名前を入力します。名前を入力するとKabukaAppViewController.designer.csにプロパティが追加されます。

f:id:ch3cooh393:20141205213741p:plain

ボタンは既に追加していると思います。スクレイピング処理を開始するトリガーとなるボタンをタップした時のイベントを追加しましょう。プロパティウィンドウのEventタブを選択します。Up InsideにTappedStartという名前を入力します。

f:id:ch3cooh393:20141205213808p:plain

KabukaAppViewController.designer.csを見てみましょう。.designer.csファイルは基本的にXamarin Studioによって自動生成されたコードなので普段は見る必要のないファイルです。Objective-Cを使ってiOSアプリを開発されていた方にはご存知だと思いますが、IBOutletIBActionなどの修飾子は、Xamarin(C#コード)上では属性として定義されています。

// WARNING
//
// This file has been generated automatically by Xamarin Studio from the outlets and
// actions declared in your storyboard file.
// Manual changes to this file will not be maintained.
//
using System;
using Foundation;
using UIKit;
using System.CodeDom.Compiler;

namespace KabukaApp
{
  [Register ("KabukaAppViewController")]
  partial class KabukaAppViewController
  {
    [Outlet]
    [GeneratedCode ("iOS Designer", "1.0")]
    UILabel OutputLabel { get; set; }

    [Action ("TappedStart:")]
    [GeneratedCode ("iOS Designer", "1.0")]
    partial void TappedStart (UIButton sender);

    void ReleaseDesignerOutlets ()
    {
      if (OutputLabel != null) {
        OutputLabel.Dispose ();
        OutputLabel = null;
      }
    }
  }
}

TappedStart(UIButton)メソッドがpartialで宣言されていますので、実体を実装するためにKabukaAppViewController.csを開きます。

using System;
using System.Drawing;
using Foundation;
using UIKit;

namespace KabukaApp
{
  public partial class KabukaAppViewController : UIViewController
  {
    public KabukaAppViewController (IntPtr handle) : base (handle)
    {
    }

    // ボタンを押したらこのメソッドが呼ばれる
    partial void TappedStart (UIButton sender)
    {
      this.OutputLabel.Text = "ボタンが押されました";
    }

// .....

ここまで実装できれば一度iPhoneシミュレータで実行してプロジェクトを正常にビルドすることが可能か確認します。実行するiPhoneシミュレータのデバイスを選択します。特に理由はありませんが、ここではiPhone 4s iOS 8.1を選択します。

f:id:ch3cooh393:20141205214717p:plain

Xamarin Studioのウィンドウ左上にある▶ボタン(実行ボタン)をクリックします。少し時間はかかりますがiPhoneシミュレータが起動してアプリがデプロイされます。アプリが起動されたらボタンをタップします。

f:id:ch3cooh393:20141205214724p:plain

ラベル部分に「ボタンが押されました」と表示されると思います。はい、うまいこと行きましたね。

ウェブスクレイピングをおこなうC#ライブラリ

Html Agility Pack」という.NET Framework向けに書かれたHTMLパーサーライブラリがあります。

このライブラリはC#から利用することができます。元々はデスクトップPC用として作られていたライブラリなのですが、この記事ではDeathspikeさんによってPortable Class Library(PCL)用に移植された「HtmlAgilityPack-PCL」を利用することにします。本家の方と比べメソッドがいくつか存在しませんが機能的には十分です。

HtmlAgilityPack-PCLは、NuGet経由でインストールすることができます。しかし、残念なことに実行時にエラーが発生してしまうため僕の環境では利用できませんでした*1 *2

HtmlAgilityPack-PCLを導入する

Xamarin.iOS (Unified API)プロジェクトから利用しやすいように、iOS用のライブラリプロジェクトを作成して、HtmlAgilityPack-PCLのソースファイルを追加して みましょう。*3

GitHubからソースコード一式を任意のフォルダへダウンロードします。

f:id:ch3cooh393:20141208171540p:plain

.zipファイルを解凍します。ソースファイルは下図の通りです。.csprojファイルや.slnファイルなどのプロジェクト関係以外のファイルをそのまま利用したいと思います。

f:id:ch3cooh393:20141208171618p:plain

左ペインのKabukaAppを選択して右クリックしてコンテキストメニューを表示します。[追加]、[新しいプロジェクトを追加...]の順に選択して、新しいプロジェクトを作成します。

f:id:ch3cooh393:20141208171650p:plain

新しいプロジェクトダイアログが表示されるので、Unified APIのiOS Library Projectを選択します。プロジェクト名はなんでも良いですが、ここではHtmlAgilityPack-PCLとつけてみました。

f:id:ch3cooh393:20141208171727p:plain

先ほど解凍したソースファイルを追加します。

f:id:ch3cooh393:20141208171802p:plain

対話ウィンドウが表示され、追加の仕方を選択します。Copyを選択して[OK]ボタンをクリックします。

f:id:ch3cooh393:20141208171916p:plain

そうそう、PCLフォルダ直下のファイルもプロジェクトへ追加することを忘れないでください。

f:id:ch3cooh393:20141208171833p:plain

実際にビルドして、コンパイル時にエラーが発生しないことを確認します。

f:id:ch3cooh393:20141208171941p:plain

これでXamarin.iOS (Unified API)プロジェクトから利用するためのライブラリプロジェクトへのHtmlAgilityPack-PCLの移植は完了です。

参照の追加

アプリプロジェクトであるKabukaAppプロジェクトから、ライブラリへの参照を追加します。

f:id:ch3cooh393:20141208172026p:plain

以上で、HtmlAgilityPack-PCLの導入が完了しました。

Yahoo!ファイナンスから現在の株価を取得する

@kazuakixと株の話をしている時に欠かせないのはイオン(8267.T)です。以前Tipsを紹介したことがあるYahoo!ファイナンスの株価を表示させてみましょう。前述したTappedStartメソッドの処理を書き換えてしまいましょう。

// ボタンを押したらこのメソッドが呼ばれる
partial void TappedStart (UIButton sender)
{
    // 株価を取得したいサイトのURL
    var code = "8267.T";
    var urlstring = string.Format("http://stocks.finance.yahoo.co.jp/stocks/detail/?code={0}", code);

    // 指定したサイトのHTMLをストリームで取得する
    var doc = new HtmlAgilityPack.HtmlDocument();
    using (var client = new System.Net.WebClient())
    {
        var html = client.DownloadString(new Uri(urlstring));

        // HtmlAgilityPack.HtmlDocumentオブジェクトにHTMLを読み込ませる
        doc.LoadHtml(html);
    }

    // XPathを指定し株価部分を取得する
    var priceNode = doc.DocumentNode.Descendants("td")
        .Single(node => node.GetAttributeValue("class", "") == "stoksPrice");

    // 取得した株価がstring型なのでint型にパースする
    var price = int.Parse(priceNode.InnerText,
        NumberStyles.Integer | NumberStyles.AllowThousands, 
        NumberFormatInfo.CurrentInfo);

    this.OutputLabel.Text = string.Format("イオン(8267.T)の株価: {0}円", price);
}

上記のサンプルコードを実行します。いかがでしょうか?

f:id:ch3cooh393:20141209225840p:plain

まとめ

Xamarin.iOSでHtml Agility Packを使ってHTMLをスクレイピングする方法をご紹介しました。Objective-Cで同様のコードを書く場合には、若干骨が折れる作業になるのでそれよりかは簡単に導入できたのではないでしょうか(当社比)。

このブログでは12月に入ってからチマチマとHtml Agility Packを使ったTipsを書いています。一通りの使い方はこの記事で紹介しきってしまった感がありますが、もし「ほかのシーンではどのように使えばよいのか?」といった疑問があれば参考にしていただければと思います。

明日の「Xamarinのカレンダー | Advent Calendar 2014 - Qiita」の担当は鈴木章太郎(shosuz)さんです。鈴木さんはコミュニティ勉強会等でAzure Mobile Servicesについてお話していたのを何度か聞いたことがあります。明日はどんな記事が公開されるのか今から楽しみですね❤️

追記(2014/12/17)

PCLではなくて、本家の「Html Agility Pack」をXamarin.Androidでビルドする方法が紹介されました!こっちのが簡単!

関連記事

Xamarin.iOSを使ったアプリを開発中に気付いたことや調べたことをメモしています。Xamarin.iOSを使ってアプリ開発する際に逆引きとしてお使いください。

このブログでHtml Agility Packについて取り扱った記事をピックアップしました。

*1:dllが見つからないのか実行しようとするとFileNotFoundExceptionでアプリがクラッシュする

*2:先週に試した時には、NuGet経由でHtmlAgilityPack-PCLをプロジェクトへ導入できることを確認してたと思ったんだけど、週明けてスクリーンショットを取ってる時には使えなくなってました。Xamarin Studioをアップデートしたせいか、そもそも僕の記憶違いか……。一度、NuGetを使って導入する記事を書いてたのですが、一部削除して自前でライブラリをビルドする方法に書き直しました

*3:元々、PCL向けにビルドされているものなのですが、.slnプロジェクトを開いてもターゲットが古いのでビルドを通すのが面倒くさそうなので断念しました