C#やVB.NETでWebページをスクレイピングする方法をWeb検索するとHtml Agility Packが見つかることが多いですが、APIはXHTMLやXPATHといったXML技術をベースにしているので、今これを使うのは少々やぼったい印象があります。
じゃあ何がいいのか?ということですが、私はAngleSharpを強くお勧めします。
AngleSharpの利点
AngleSharpのどこがいいのか?は、改めて別の記事を書くつもりですが、ここでは簡単にまとめます。
- HTMLだけでなく、SVG、MathML、CSSもパース可能です。
- HTMLをパースするとW3CのWeb標準に従ったDOMが構築されます。HTML5ベースのため、閉じる必要のないタグ(
<br>
や<img>
など)や閉じタグを省略可能なタグ(<li>
、<dt>
や<dd>
、<tr>
や<td>
など)も正しく理解します。 -
Selectors APIが使えます。
document.QuerySelector()
やdocument.QuerySelectorAll()
という、jQueryのように便利でかつWeb標準に従った機構でDOM要素の検索が可能です。:before
のような疑似要素や:nth-child()
のような擬似クラスだってもちろん使えます。XPathは不要です。 - Browsing contextを実装します。
-
JavaScriptのスクリプティングエンジンを統合可能です。パーサーの初期化時にJavaScriptを有効化すれば、
<script>
タグに記述されたJavaScriptによるDOM操作の結果を取得することもできます。 - LINQサポート(当然ですが)。
- PCL(profile 259)として実装されているので、.NET FrameworkでもMonoでも.NET CoreでもXamarinでも利用できます。
- パフォーマンス測定の結果は、HTML Agility Packよりおおむね高速です。
一言でいえば、「HTML5などの新しいWeb標準に沿ったモダンなHTML環境」といえるでしょう。
コード例1
C#でHtml Agility Packを使ってウェブサイトのタイトルを取得する - 酢ろぐ! のコードをAngleSharpを使ったものに書き換えてみましょう。
// タイトルを取得したいサイトのURL
var urlstring = "http://blog.ch3cooh.jp/";
// 指定したサイトのHTMLをストリームで取得する
var doc = default(IHtmlDocument);
using (var client = new HttpClient())
using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
{
// AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
var parser = new HtmlParser();
doc = await parser.ParseAsync(stream);
}
// HTMLからtitleタグの値(サイトのタイトルとして表示される部分)を取得する
var title = document.Title;
Debug.WriteLine(title);
一番最初に登場する<title>
要素がTitle
プロパティの値として取得されます。
コード例2
C#でHtml Agility Packを使ってYahoo!ファイナンスの現在の株価を取得する - 酢ろぐ! のコードをAngleSharpを使ったものに書き換えてみましょう。
// 株価を取得したいサイトのURL
var code = "7984.T";
var urlstring = $"http://stocks.finance.yahoo.co.jp/stocks/detail/?code={code}";
// 指定したサイトのHTMLをストリームで取得する
var doc = default(IHtmlDocument);
using (var client = new HttpClient())
using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
{
// AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
var parser = new HtmlParser();
doc = await parser.ParseAsync(stream);
}
// クエリーセレクタを指定し株価部分を取得する
var priceElement = doc.QuerySelector("#main td[class=stoksPrice]");
// 取得した株価がstring型なのでint型にパースする
int.TryParse(priceNode.TextContent, NumberStyles.AllowThousands, null, out var price);
Debug.WriteLine("コクヨ(7984.T)の株価: {0}円", price);
オリジナルのコードは要素の階層構造をもっと厳密に見ていますが、せっかくクエリーセレクタが使えるので、少し緩くしています。
コード例3
C#でHtml Agility Packを使って豊橋技科大の休講情報を取得する - 酢ろぐ! のコードをAngleSharpを使ったものに書き換えてみましょう。
// 休講情報を取得したいサイトのURL
var urlstring = "https://www.ead.tut.ac.jp/board/main.aspx";
// 指定したサイトのHTMLをストリームで取得する
var doc = default(IHtmlDocument);
using (var client = new HttpClient())
using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
{
// AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
var parser = new HtmlParser();
doc = await parser.ParseAsync(stream);
}
// クエリーセレクタを指定して休講情報テーブル部分を取得する
var items = doc.QuerySelectorAll("#grvCancel > tr")
.Skip(1)
.Select(item =>
{
// td単位で複数のデータを取得する
var data = item.GetElementsByTagName("td");
// 休講日
var date = data[1].TextContent;
// 時限
var period = data[2].TextContent;
// 授業の名前
var subject = data[3].TextContent;
return new { Date = date, Period = period, Subject = subject };
});
// 取得した休講情報を出力する
items.ToList().ForEach(item =>
{
Debug.WriteLine("${item.Date}({item.Period}) {item.Subject}");
});
元コードで使っていたElementAt()
メソッドはAngleSharpでも使えますが、InnerText
はAngleSharpでは使えません。
コード例4
C#でHtml Agility Packを使って秀和システムの新刊情報を取得する - 酢ろぐ! のコードをAngleSharpを使ったものに書き換えてみましょう。
// 新刊情報を取得したいサイトのURL
var urlstring = "http://www.shuwasystem.co.jp/newbook.html";
// 指定したサイトのHTMLをストリームで取得する
var doc = default(IHtmlDocument);
using (var client = new HttpClient())
using (var stream = await client.GetStreamAsync(new Uri(urlstring)))
{
// AngleSharp.Html.Parser.HtmlParserオブジェクトにHTMLをパースさせる
var parser = new HtmlParser();
doc = await parser.ParseAsync(stream);
}
// 最初のsinkanがコンピュータの関連書籍
var priceElement = doc.GetElementById("sinkan");
// 必要な情報を読み取る
var listItems = priceElement.GetElementsByTagName("dl")
.Select(n =>
{
// 書籍のタイトルを取得する
var title = n.QuerySelector("dt")
.TextContent.Trim();
// 書籍のISBNを取得する
var isbn = n.QuerySelector("dd > p > strong")
.TextContent.Trim();
return new { Title = title, Isbn = isbn };
});
// 結果を出力する
listItems.ToList().ForEach(item =>
{
Debug.WriteLine($"{item.Title} ({item.Isbn})");
});