私が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>
これをパースする手順は以下のようになります。
- <div class="topicsindex">を探す。
- <ul class="emphasis">を探す。
- <a>を探してリンク対象の文字を読み取る。
- </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の内部実装も同じ原理を文字単位に適用して処理しています。