【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

HTMLをパース

私がHTMLをパースするのに使用している独自パーサーXmlParserをご紹介します。.NET標準のXmlReaderから妥当性チェックなどを省いて単純化したものです。

構造は単純で、XmlParserという1つのクラスだけで構成されています。C#/VB10のソースを以下にパブリックドメインで置いておきます。

使用例

基本的な考え方はXmlReaderと同じです。DOMを組み立てずにフラットにパースします。考え方はテキストの検索に近いですが、タグを認識するため、処理はタグ単位で行います。

Yahooのトップページからトピックスを抜き出す処理を見てみます。ソースを見ると以下の構造になっていることが分かります。

<div class="topicsindex">
<em>xxx更新</em>
<ul class="emphasis"><li><a href="xxx">xxx</a></li><li><a href="xxx">xxx</a></li></ul>
(略)
</div>

これをパースする手順は以下のようになります。

  1. <div class="topicsindex">を探す。
  2. <ul class="emphasis">を探す。
  3. <a>を探してリンク対象の文字を読み取る。
  4. </ul>が出てくるまで3.を繰り返す。

これをコードにすると以下のようになります。

var wc = new WebClient { Encoding = Encoding.UTF8 };
wc.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows XP)");
var html = wc.DownloadString("http://www.yahoo.co.jp/");
using (var xp = new XmlParser(html))
{
  while (xp.Read())
  {
    if (xp.Tag == "div" && xp["class"] == "topicsindex")
    {
      while (xp.Read())
      {
        if (xp.Tag == "ul" && xp["class"] == "emphasis")
        {
          while (xp.Read())
          {
            if (xp.Tag == "/ul")
              break;
            else if (xp.Tag == "a" && xp.Read())
              listBox1.Items.Add(xp.Text);
          }
          break;
        }
      }
      break;
    }
  }
}

あまりに冗長なので、クロージャで簡略化します。

var wc = new WebClient { Encoding = Encoding.UTF8 };
wc.Headers.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows XP)");
var html = wc.DownloadString("http://www.yahoo.co.jp/");
using (var xp = new XmlParser(html))
  xp.Search("div", () => xp["class"] == "topicsindex", () =>
    xp.Search("ul", () => xp["class"] == "emphasis", () =>
      xp.SearchEach("a", () => true, () =>
      {
        xp.Read();
        listBox1.Items.Add(xp.Text);
      })));

タグの構造がそのままコードに反映されていることに注目すれば理解しやすいと思います。ちなみにXmlParserの内部実装も同じ原理を文字単位に適用して処理しています。

サンプル

VC# 2010のプロジェクト一式を置いておきます。C#だけでなくVB.NETに変換したソースも同梱しています。