Help us understand the problem. What is going on with this article?

C#でモダンにスクレイピングするならAngleSharp

More than 3 years have passed since last update.

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.Parser.Html.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.Parser.Html.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.Parser.Html.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.Parser.Html.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})");
});
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした