C#スクレイピング入門|HtmlAgilityPackでWebサイトのデータを取得する方法とサンプルコード
はじめに
C#でスクレイピングを行うと、Webサイト上に公開されているHTMLから必要な情報を取得し、一覧化・保存・集計などの処理を自動化できます。たとえば、ページタイトル、見出し、リンク、表データ、商品情報、ニュース一覧などをプログラムから取得し、CSVやデータベースに保存することが可能です。
C#スクレイピングでよく使われるライブラリのひとつが「HtmlAgilityPack」です。HtmlAgilityPackはHTMLを解析し、XPathを使って必要な要素を取り出せる便利なライブラリです。C#の標準機能だけでもWebページのHTMLを取得できますが、HTMLの中から特定のタグや属性値を抽出するには、HtmlAgilityPackのようなHTMLパーサーを使うと実装がシンプルになります。
この記事では、C#スクレイピングの基本から、HtmlAgilityPackのインストール方法、WebサイトのHTML取得、XPathによる要素指定、実践的なサンプルコード、よくあるエラーへの対処法までを初心者向けに解説します。
1. C#スクレイピングでできることと検索ユーザーの目的
C#スクレイピングを学ぶ目的は、Webサイト上のデータ取得を自動化することです。手作業でページを開き、コピーしてExcelに貼り付ける作業は、件数が増えるほど時間がかかります。C#でスクレイピング処理を作れば、一定のルールに従ってWebページから必要な情報を抽出し、ファイル保存やシステム連携まで自動化できます。
ただし、スクレイピングは便利な一方で、対象サイトの利用規約やアクセス負荷に注意が必要です。取得してよいデータか、APIが提供されていないか、サーバーに負荷をかけないかを確認したうえで実装しましょう。
1-1. C#でWebサイトからデータを取得する基本イメージ
C#スクレイピングの基本的な流れは、次のようになります。
まず、HttpClientを使って対象ページのHTMLを文字列として取得します。次に、取得したHTMLをHtmlAgilityPackのHtmlDocumentに読み込ませます。その後、XPathやタグ名、属性を指定して必要な要素を検索し、InnerTextや属性値を取り出します。
処理のイメージは以下の通りです。
C#using System.Net.Http;
using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var titleNode = doc.DocumentNode.SelectSingleNode("//title");
Console.WriteLine(titleNode?.InnerText);
このように、C#では「HTMLを取得する処理」と「HTMLを解析する処理」を組み合わせてスクレイピングを行います。
1-2. スクレイピングとWeb APIの違い
スクレイピングは、Webページとして表示されるHTMLを取得し、その中から必要なデータを取り出す方法です。一方、Web APIは、システムがデータ取得しやすい形式で提供している公式の入口です。多くの場合、APIではJSONやXML形式でデータを取得できます。
たとえば、スクレイピングでは以下のようなHTMLから商品名を取り出します。
HTML<h2 class="product-name">サンプル商品</h2>
一方、APIでは以下のようなJSONが返ってくる場合があります。
JSON{
"productName": "サンプル商品",
"price": 1200
}
APIが用意されている場合は、基本的にAPIを使う方が安定しています。HTMLはデザイン変更で構造が変わることがあり、XPathやCSSセレクタが動かなくなる可能性があります。C#スクレイピングは、APIが存在しない場合や、HTML上の公開情報を取得したい場合に検討するとよいでしょう。
1-3. C#スクレイピングが向いているケース
C#スクレイピングは、.NET環境で業務アプリケーションや社内ツールを作っている場合に特に向いています。たとえば、Windowsアプリ、バッチ処理、ASP.NET Coreの管理画面、既存のC#システムと連携したい場合などです。
具体的には、次のようなケースで活用できます。
・Webページのタイトルや見出しを取得して一覧化する
・公開されている表データをCSVに保存する
・リンク一覧を取得してサイト構造を確認する
・社内向けの情報収集ツールを作る
・定期的に公開情報を確認して差分を検知する
C#は型安全で保守しやすく、既存の.NET資産とも連携しやすいため、業務用途のスクレイピングにも適しています。
1-4. この記事で作るサンプルの完成イメージ
この記事では、HtmlAgilityPackを使って以下のような処理を実装します。
・Webページのタイトルを取得する
・h1、h2、h3などの見出しタグを一覧取得する
・aタグからリンクテキストとURLを取得する
・tableタグから行と列のデータを取得する
・取得したデータをCSVファイルに保存する
最終的には、C#でWebページにアクセスし、HTMLを解析して必要な情報を取り出す基本パターンが理解できるようになります。
2. C#スクレイピングを始める前に知っておくべき注意点
C#スクレイピングを始める前に、技術面だけでなくルールやマナーも確認しておく必要があります。Web上に公開されている情報であっても、自由に大量取得してよいとは限りません。対象サイトの利用規約、著作権、個人情報、サーバー負荷などに注意しましょう。
2-1. スクレイピングの合法性と利用規約の確認
スクレイピングそのものが常に違法というわけではありません。しかし、対象サイトの利用規約で自動取得が禁止されている場合や、ログイン後の情報を不正に取得する場合、著作物を無断で複製・再配布する場合などは問題になる可能性があります。
実装前に、少なくとも以下を確認しましょう。
・対象サイトの利用規約
・データの利用目的
・取得する情報が公開情報かどうか
・個人情報や機密情報を含まないか
・取得したデータを再公開・商用利用してよいか
特に商用利用や大量取得を行う場合は、必要に応じてサイト運営者に確認することが重要です。
2-2. robots.txtとアクセス頻度への配慮
robots.txtは、検索エンジンなどのクローラーに対して、クロールしてよい範囲や避けるべき範囲を示すファイルです。たとえば、以下のようなURLで確認できます。
https://example.com/robots.txt
robots.txtは法律そのものではありませんが、サイト運営者の意思を示す重要な情報です。スクレイピング対象のパスが明示的に拒否されている場合は、アクセスを避けるのが望ましいです。
また、短時間に大量アクセスすると、サーバーに負荷をかけたり、不正アクセスと判断されたりする可能性があります。C#でループ処理を作る場合は、アクセス間隔を空けるようにしましょう。
2-3. サーバーに負荷をかけないためのマナー
スクレイピングでは、対象サイトのサーバーに配慮することが大切です。特に、複数ページを連続で取得する処理では、アクセス間隔を設定しましょう。
C#foreach (var url in urls)
{
var html = await client.GetStringAsync(url);
// HTML解析処理
await Task.Delay(1000); // 1秒待機
}
また、同じページを何度も取得する必要がない場合は、取得結果をキャッシュする方法も有効です。開発中は何度も同じURLにアクセスしがちなので、ローカルにHTMLを保存してテストするのもよい方法です。
2-4. 取得してはいけないデータの考え方
スクレイピングで取得すべきでないデータには、個人情報、ログインが必要な非公開情報、有料コンテンツ、著作権で保護されたコンテンツ、アクセス制限を回避しないと見られない情報などがあります。
また、技術的に取得できることと、利用してよいことは別です。C#でHTMLを取得できたとしても、そのデータを保存・加工・公開・販売してよいとは限りません。安全に運用するためには、「公開されている情報か」「利用規約に反していないか」「第三者の権利を侵害しないか」を確認しましょう。
3. C#スクレイピングに必要な環境を準備する
C#スクレイピングを始めるには、.NETの開発環境とHtmlAgilityPackが必要です。Visual Studioを使ってもよいですし、.NET CLIを使ってコマンドラインからプロジェクトを作成しても構いません。
3-1. Visual Studioまたは.NET CLIの準備
Visual Studioを使う場合は、「.NETデスクトップ開発」または「ASP.NETとWeb開発」などのワークロードをインストールしておきます。コンソールアプリケーションを作るだけなら、.NET SDKとエディタがあれば十分です。
.NET CLIを使う場合は、以下のコマンドでインストール済みの.NETを確認できます。
Bashdotnet --version
バージョンが表示されれば、C#プロジェクトを作成できます。
3-2. .NETプロジェクトを作成する手順
まず、コンソールアプリケーションのプロジェクトを作成します。
Bashmkdir CSharpScrapingSample
cd CSharpScrapingSample
dotnet new console
作成後、以下のコマンドで実行できます。
Bashdotnet run
最初はProgram.csに「Hello, World!」のようなコードが入っています。ここにスクレイピング用のコードを書いていきます。
3-3. HtmlAgilityPackとは何か
HtmlAgilityPackは、C#でHTMLを解析するためのライブラリです。HTMLが多少崩れていても解析しやすく、XPathを使って目的の要素を取得できます。
HtmlAgilityPackを使うと、以下のような処理を簡単に書けます。
・titleタグのテキストを取得する
・h1やh2などの見出しを取得する
・aタグのhref属性を取得する
・table内のtr、th、tdを取得する
・特定のclassやidを持つ要素を取得する
C#スクレイピングの入門では、まずHtmlAgilityPackで静的HTMLを解析する方法を覚えるのがおすすめです。
3-4. NuGetでHtmlAgilityPackをインストールする方法
.NET CLIを使う場合は、以下のコマンドでHtmlAgilityPackをインストールできます。
Bashdotnet add package HtmlAgilityPack
Visual Studioを使う場合は、ソリューションエクスプローラーでプロジェクトを右クリックし、「NuGetパッケージの管理」からHtmlAgilityPackを検索してインストールします。
インストール後、C#コードの先頭で以下の名前空間を追加します。
C#using HtmlAgilityPack;
これでHtmlAgilityPackを使ったHTML解析ができるようになります。
4. HtmlAgilityPackでWebページのHTMLを取得する基本手順
HtmlAgilityPackでスクレイピングする基本手順は、HTMLの取得、HTMLの読み込み、XPathによる要素指定、テキストや属性値の取り出しです。ここでは、それぞれの流れを順番に確認します。
4-1. HttpClientでWebページのHTMLを取得する
まずはHttpClientを使ってWebページのHTMLを取得します。
C#using System.Net.Http;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
Console.WriteLine(html);
GetStringAsyncは、指定したURLにGETリクエストを送り、レスポンス本文を文字列として取得します。Webページの場合、多くはHTML文字列が返ってきます。
実務では、タイムアウトやUser-Agent、例外処理も設定することが多いです。まずは基本として、URLからHTMLを取得する流れを理解しましょう。
4-2. HtmlDocumentにHTMLを読み込む
取得したHTMLをHtmlAgilityPackで解析するには、HtmlDocumentに読み込みます。
C#using HtmlAgilityPack;
var doc = new HtmlDocument();
doc.LoadHtml(html);
LoadHtmlにHTML文字列を渡すことで、HtmlAgilityPackがHTML構造を解析し、タグや属性をノードとして扱えるようになります。
この状態になれば、doc.DocumentNodeからXPathを使って要素を検索できます。
4-3. XPathで必要な要素を指定する
XPathは、HTMLやXMLの中から特定の要素を指定するための記法です。HtmlAgilityPackでは、SelectSingleNodeやSelectNodesにXPathを指定して要素を取得します。
たとえば、titleタグを取得する場合は以下のように書きます。
C#var titleNode = doc.DocumentNode.SelectSingleNode("//title");
h1タグを取得する場合は以下です。
C#var h1Node = doc.DocumentNode.SelectSingleNode("//h1");
すべてのaタグを取得する場合は以下のように書きます。
C#var linkNodes = doc.DocumentNode.SelectNodes("//a");
//タグ名は、HTML全体から指定したタグを検索する基本的なXPathです。
4-4. InnerTextや属性値を取り出す方法
要素のテキストを取得するにはInnerTextを使います。
C#var title = titleNode?.InnerText;
Console.WriteLine(title);
aタグのhref属性やimgタグのsrc属性を取得するには、GetAttributeValueを使います。
C#var href = linkNode.GetAttributeValue("href", "");
var text = linkNode.InnerText;
第2引数には、属性が存在しない場合のデフォルト値を指定します。属性がない場合に例外を避けられるため、C#スクレイピングではよく使う書き方です。
5. C#スクレイピングのサンプルコード
ここからは、HtmlAgilityPackを使った具体的なC#スクレイピングのサンプルコードを紹介します。サンプルではhttps://example.com/を使っていますが、実際に試す場合は対象サイトの利用規約やアクセス頻度に注意してください。
5-1. ページタイトルを取得するサンプル
以下は、Webページのtitleタグを取得するサンプルです。
C#using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var titleNode = doc.DocumentNode.SelectSingleNode("//title");
var title = titleNode?.InnerText.Trim();
Console.WriteLine($"ページタイトル: {title}");
SelectSingleNode("//title")でtitleタグを1つ取得し、InnerTextでテキストを取り出しています。?.を使っているため、titleタグが存在しない場合でもNullReferenceExceptionを防げます。
5-2. 見出しタグを一覧で取得するサンプル
h1、h2、h3などの見出しタグをまとめて取得する場合は、XPathで複数条件を指定します。
C#using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var headingNodes = doc.DocumentNode.SelectNodes("//h1 | //h2 | //h3");
if (headingNodes != null)
{
foreach (var node in headingNodes)
{
var tagName = node.Name;
var text = node.InnerText.Trim();
Console.WriteLine($"{tagName}: {text}");
}
}
else
{
Console.WriteLine("見出しタグが見つかりませんでした。");
}
//h1 | //h2 | //h3のように|を使うと、複数のXPath条件をまとめて指定できます。SEO調査やページ構造の確認にも使いやすいサンプルです。
5-3. リンクURLとリンクテキストを取得するサンプル
aタグからリンクテキストとURLを取得するには、InnerTextとGetAttributeValueを使います。
C#using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var linkNodes = doc.DocumentNode.SelectNodes("//a[@href]");
if (linkNodes != null)
{
foreach (var link in linkNodes)
{
var text = link.InnerText.Trim();
var href = link.GetAttributeValue("href", "");
Console.WriteLine($"テキスト: {text}");
Console.WriteLine($"URL: {href}");
Console.WriteLine();
}
}
//a[@href]は、href属性を持つaタグを取得するXPathです。リンク切れチェック、サイトマップ作成、内部リンク調査などに応用できます。
相対URLを絶対URLに変換したい場合は、Uriクラスを使います。
C#var baseUri = new Uri(url);
var absoluteUri = new Uri(baseUri, href);
Console.WriteLine(absoluteUri.ToString());
5-4. テーブルデータを取得するサンプル
HTMLのtableタグから行と列を取得する例です。
C#using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var rows = doc.DocumentNode.SelectNodes("//table//tr");
if (rows != null)
{
foreach (var row in rows)
{
var cells = row.SelectNodes("./th | ./td");
if (cells == null)
{
continue;
}
var values = cells
.Select(cell => cell.InnerText.Trim())
.ToArray();
Console.WriteLine(string.Join(", ", values));
}
}
else
{
Console.WriteLine("テーブルが見つかりませんでした。");
}
//table//trでテーブル内の行を取得し、各行の中からthまたはtdを取得しています。./th | ./tdのように先頭に.を付けると、現在の行ノードを基準に検索できます。
5-5. 取得したデータをCSVに保存するサンプル
取得したリンク一覧をCSVに保存するサンプルです。
C#using System.Text;
using HtmlAgilityPack;
var url = "https://example.com/";
using var client = new HttpClient();
var html = await client.GetStringAsync(url);
var doc = new HtmlDocument();
doc.LoadHtml(html);
var linkNodes = doc.DocumentNode.SelectNodes("//a[@href]");
var csvLines = new List<string>();
csvLines.Add("Text,Url");
if (linkNodes != null)
{
foreach (var link in linkNodes)
{
var text = NormalizeText(link.InnerText);
var href = link.GetAttributeValue("href", "");
csvLines.Add($"{EscapeCsv(text)},{EscapeCsv(href)}");
}
}
await File.WriteAllLinesAsync("links.csv", csvLines, new UTF8Encoding(true));
Console.WriteLine("CSVファイルを保存しました。");
static string NormalizeText(string value)
{
return string.Join(" ", value.Split(new[] { ' ', '\r', '\n', '\t' },
StringSplitOptions.RemoveEmptyEntries));
}
static string EscapeCsv(string value)
{
if (value.Contains('"'))
{
value = value.Replace("\"", "\"\"");
}
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
{
value = $"\"{value}\"";
}
return value;
}
CSVでは、カンマやダブルクォート、改行が含まれる場合にエスケープが必要です。日本語をExcelで開く場合は、BOM付きUTF-8で保存すると文字化けを避けやすくなります。
6. 実践でよく使うHtmlAgilityPackの書き方
HtmlAgilityPackを使ったC#スクレイピングでは、要素の取得方法、属性値の取得、空白整形、Null対策が重要です。実務では、HTML構造が想定と違うことも多いため、エラーになりにくい書き方を覚えておきましょう。
6-1. SelectSingleNodeとSelectNodesの使い分け
SelectSingleNodeは、条件に一致する最初の1件だけを取得します。ページタイトルやh1タグなど、1つだけ取得したい場合に使います。
C#var titleNode = doc.DocumentNode.SelectSingleNode("//title");
SelectNodesは、条件に一致する複数の要素を取得します。リンク一覧、見出し一覧、テーブル行など、複数件を処理したい場合に使います。
C#var linkNodes = doc.DocumentNode.SelectNodes("//a[@href]");
注意点として、SelectNodesは要素が見つからない場合に空のコレクションではなくnullを返すことがあります。そのため、if (nodes != null)のような確認が必要です。
6-2. id・class・タグ名を指定して要素を取得する方法
idを指定して要素を取得する場合は、以下のように書きます。
C#var node = doc.DocumentNode.SelectSingleNode("//*[@id='main']");
classを指定する場合は、単純な一致なら以下のように書けます。
C#var nodes = doc.DocumentNode.SelectNodes("//*[@class='item']");
ただし、HTMLのclass属性には複数のクラスが入ることがあります。
HTML<div class="item active"></div>
この場合、完全一致の@class='item'では取得できません。複数クラスに対応するには、以下のように書きます。
C#var nodes = doc.DocumentNode.SelectNodes(
"//*[contains(concat(' ', normalize-space(@class), ' '), ' item ')]"
);
タグ名で取得する場合はシンプルです。
C#var paragraphs = doc.DocumentNode.SelectNodes("//p");
6-3. aタグのhrefやimgタグのsrcを取得する方法
属性値を取得するにはGetAttributeValueを使います。
C#var href = linkNode.GetAttributeValue("href", "");
imgタグのsrc属性を取得する場合は以下です。
C#var imageNodes = doc.DocumentNode.SelectNodes("//img[@src]");
if (imageNodes != null)
{
foreach (var img in imageNodes)
{
var src = img.GetAttributeValue("src", "");
var alt = img.GetAttributeValue("alt", "");
Console.WriteLine($"画像URL: {src}");
Console.WriteLine($"alt: {alt}");
}
}
属性が存在しない場合もあるため、第2引数に空文字を指定しておくと安全です。
6-4. 余分な空白や改行を整形する方法
HTMLから取得したInnerTextには、余分な改行、タブ、連続スペースが含まれることがあります。取得後に整形する関数を用意しておくと便利です。
C#static string NormalizeText(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return "";
}
return string.Join(" ", text.Split(
new[] { ' ', '\r', '\n', '\t' },
StringSplitOptions.RemoveEmptyEntries
));
}
使い方は以下です。
C#var text = NormalizeText(node.InnerText);
スクレイピングしたデータをCSVやデータベースに保存する場合は、空白整形をしておくと後処理が楽になります。
6-5. 要素が存在しない場合のNull対策
WebページのHTML構造は変更されることがあります。昨日まで存在していたタグが、今日なくなることもあります。そのため、C#スクレイピングではNull対策が重要です。
C#var priceNode = doc.DocumentNode.SelectSingleNode("//span[@class='price']");
if (priceNode == null)
{
Console.WriteLine("価格が見つかりませんでした。");
}
else
{
var price = priceNode.InnerText.Trim();
Console.WriteLine(price);
}
Null条件演算子を使う方法もあります。
C#var price = doc.DocumentNode
.SelectSingleNode("//span[@class='price']")
?.InnerText
.Trim() ?? "";
安定したスクレイピング処理を作るには、「取得できて当然」と考えず、「取得できない場合もある」前提で実装しましょう。
7. C#スクレイピングでよくあるエラーと対処法
C#スクレイピングでは、文字化け、403エラー、目的のデータがHTMLに含まれない、XPathが合わない、通信エラーなどがよく発生します。原因を切り分けながら対処することが大切です。
7-1. 文字化けが起きる場合の対処法
HttpClient.GetStringAsyncで取得したHTMLが文字化けする場合、文字コードの判定がうまくいっていない可能性があります。その場合は、バイト配列で取得して適切なエンコーディングで文字列に変換します。
C#using System.Text;
var bytes = await client.GetByteArrayAsync(url);
var html = Encoding.UTF8.GetString(bytes);
対象サイトがShift_JISの場合は、以下のようにエンコーディングプロバイダーを登録してから変換します。
C#using System.Text;
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var bytes = await client.GetByteArrayAsync(url);
var encoding = Encoding.GetEncoding("shift_jis");
var html = encoding.GetString(bytes);
.NETでShift_JISを扱う場合は、必要に応じて以下のパッケージを追加します。
Bashdotnet add package System.Text.Encoding.CodePages
7-2. 403 Forbiddenが返る場合の対処法
403 Forbiddenは、アクセスが拒否されたことを示します。原因としては、User-Agentが未設定、Botアクセスと判断された、ログインが必要、アクセス元が制限されている、利用規約で自動アクセスが禁止されているなどが考えられます。
User-Agentを設定する例は以下です。
C#using var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"Mozilla/5.0 (compatible; CSharpScrapingSample/1.0)"
);
var html = await client.GetStringAsync(url);
ただし、User-Agentを設定すれば必ずアクセスしてよいという意味ではありません。アクセス拒否される場合は、対象サイトの利用規約やAPIの有無を確認しましょう。
7-3. 取得したHTMLに目的のデータが含まれない原因
ブラウザでは表示されているのに、HttpClientで取得したHTMLには目的のデータがないことがあります。この場合、JavaScriptによって後からデータが生成されている可能性があります。
HttpClientとHtmlAgilityPackは、基本的にサーバーから返ってきたHTMLを解析するだけです。ブラウザのようにJavaScriptを実行して画面を完成させるわけではありません。
確認方法としては、ブラウザの「ページのソースを表示」で目的のデータが含まれているかを見るとよいです。ソースに含まれていない場合は、JavaScriptによる動的生成やAPI通信を調べる必要があります。
7-4. XPathで要素が取得できない場合の確認ポイント
XPathで要素が取得できない場合は、以下を確認します。
・対象のHTMLに本当にその要素が存在するか
・class名やid名が正しいか
・class属性が複数指定になっていないか
・検索の基準ノードが間違っていないか
・tbodyなどブラウザが補完した要素を前提にしていないか
・JavaScriptで後から生成される要素ではないか
特に、ブラウザの開発者ツールでコピーしたXPathは、HtmlAgilityPackで取得したHTMLと構造が異なる場合があります。ブラウザが自動補完したDOMと、実際に取得したHTMLは同じとは限りません。
まずは取得したHTMLをファイルに保存して、実際のHTML構造を確認するのがおすすめです。
C#await File.WriteAllTextAsync("debug.html", html);
7-5. タイムアウトや通信エラーへの対応
通信エラーやタイムアウトは、ネットワーク状況やサーバー状態によって発生します。HttpClient.Timeoutを設定し、例外処理を入れておきましょう。
C#using var client = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
try
{
var html = await client.GetStringAsync(url);
Console.WriteLine("取得成功");
}
catch (TaskCanceledException)
{
Console.WriteLine("タイムアウトしました。");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"通信エラー: {ex.Message}");
}
大量のURLを処理する場合は、失敗したURLだけ後で再実行できるようにログへ記録しておくと運用しやすくなります。
8. JavaScriptで生成されるページをスクレイピングする方法
HtmlAgilityPackはHTML解析に便利ですが、JavaScriptを実行する機能はありません。そのため、JavaScriptで生成されるページでは目的のデータを取得できない場合があります。
8-1. HtmlAgilityPackで取得できないページの特徴
HtmlAgilityPackで取得しにくいページには、以下のような特徴があります。
・ブラウザ表示後に商品一覧や記事一覧が出てくる
・無限スクロールで追加データが読み込まれる
・検索結果がJavaScriptで描画される
・HTMLソースに目的のデータが含まれていない
・ログイン後にAPI通信でデータを取得している
このようなページでは、HttpClientで取得したHTMLをHtmlAgilityPackに読み込んでも、必要なデータが存在しないことがあります。
8-2. 動的ページではSeleniumやPlaywrightを検討する
JavaScriptで生成されるページを扱う場合は、SeleniumやPlaywrightのようなブラウザ自動化ツールを検討します。これらは実際のブラウザを操作し、JavaScript実行後のDOMからデータを取得できます。
Playwrightを使う場合のイメージは以下です。
C#using Microsoft.Playwright;
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new()
{
Headless = true
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com/");
var title = await page.TitleAsync();
Console.WriteLine(title);
ただし、ブラウザ自動化はHttpClientよりも重く、実行環境の準備も必要です。静的HTMLで取得できる場合はHtmlAgilityPack、JavaScript実行が必要な場合はPlaywrightやSelenium、という使い分けが基本です。
8-3. API通信を確認してデータ取得する方法
JavaScriptで表示されるデータは、裏側でAPIから取得されていることがあります。ブラウザの開発者ツールで「Network」タブを確認すると、JSON形式のデータを返している通信が見つかる場合があります。
API通信が確認でき、利用規約上問題がない場合は、HTMLを解析するよりもAPIから直接データを取得する方が安定します。
C#var apiUrl = "https://example.com/api/items";
using var client = new HttpClient();
var json = await client.GetStringAsync(apiUrl);
Console.WriteLine(json);
JSONをC#のオブジェクトに変換する場合は、System.Text.Jsonを使えます。
C#using System.Text.Json;
var items = JsonSerializer.Deserialize<List<Item>>(json);
public class Item
{
public string? Name { get; set; }
public int Price { get; set; }
}
ただし、非公開APIや認証が必要なAPIを無断で利用することは避け、必ず利用条件を確認しましょう。
8-4. HtmlAgilityPackとブラウザ自動化の使い分け
HtmlAgilityPackとブラウザ自動化は、目的によって使い分けます。
HtmlAgilityPackが向いているのは、サーバーから返ってくるHTMLに目的のデータが含まれている場合です。軽量で高速に処理でき、コンソールアプリやバッチ処理にも組み込みやすいです。
一方、SeleniumやPlaywrightが向いているのは、JavaScript実行後でないとデータが表示されないページです。ブラウザ操作が必要なため重くなりますが、動的ページに対応できます。
最初にHttpClientでHTMLを取得し、目的のデータが含まれているか確認しましょう。含まれていればHtmlAgilityPack、含まれていなければAPI通信やブラウザ自動化を検討する流れが効率的です。
9. C#スクレイピングを安全に運用するための実装ポイント
C#スクレイピングを実務で運用する場合は、ただデータを取得するだけでなく、安全性と安定性を考慮する必要があります。User-Agent、アクセス間隔、リトライ、ログ、定期実行時の監視などを実装しましょう。
9-1. User-Agentを設定する方法
User-Agentを設定すると、どのようなプログラムからアクセスしているかをサーバー側に伝えられます。
C#using var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(
"CSharpScrapingSample/1.0 (+https://example.com/contact)"
);
業務で運用する場合は、連絡先やツール名が分かるUser-Agentにすることも検討しましょう。ただし、ブラウザを装ってアクセス制限を回避する目的で使うのは避けるべきです。
9-2. アクセス間隔を空ける方法
複数ページを取得する場合は、Task.Delayでアクセス間隔を空けます。
C#foreach (var url in urls)
{
try
{
var html = await client.GetStringAsync(url);
Console.WriteLine($"{url} を取得しました。");
}
catch (Exception ex)
{
Console.WriteLine($"{url} の取得に失敗しました: {ex.Message}");
}
await Task.Delay(TimeSpan.FromSeconds(2));
}
アクセス頻度は対象サイトの規模や利用規約によって調整します。短時間に大量アクセスする処理は避け、必要最小限のアクセスに抑えましょう。
9-3. リトライ処理を入れる方法
一時的な通信エラーに備えて、リトライ処理を入れると安定します。ただし、何度も連続で再試行すると負荷が高くなるため、回数を制限し、待機時間を入れましょう。
C#static async Task<string?> GetHtmlWithRetryAsync(HttpClient client, string url)
{
const int maxRetry = 3;
for (int i = 1; i <= maxRetry; i++)
{
try
{
return await client.GetStringAsync(url);
}
catch (Exception ex)
{
Console.WriteLine($"取得失敗 {i}回目: {ex.Message}");
if (i == maxRetry)
{
return null;
}
await Task.Delay(TimeSpan.FromSeconds(i * 2));
}
}
return null;
}
リトライしても失敗する場合は、対象ページの仕様変更やアクセス制限の可能性があります。
9-4. ログ出力で失敗原因を確認する方法
スクレイピング処理は、対象サイトのHTML変更や通信状況によって失敗することがあります。失敗時にURL、時刻、例外メッセージ、HTTPステータスコードなどをログに残しておくと原因調査がしやすくなります。
簡単なログ出力の例です。
C#static async Task WriteLogAsync(string message)
{
var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\t{message}";
await File.AppendAllTextAsync("scraping.log", line + Environment.NewLine);
}
使い方は以下です。
C#try
{
var html = await client.GetStringAsync(url);
}
catch (Exception ex)
{
await WriteLogAsync($"{url}\t{ex.Message}");
}
本格的な運用では、SerilogやNLogなどのロギングライブラリを使う方法もあります。
9-5. 定期実行する場合の注意点
C#スクレイピングを定期実行する場合は、Windowsタスクスケジューラ、cron、クラウドのジョブ機能などを使います。定期実行では、処理の失敗に気づける仕組みが重要です。
以下の点に注意しましょう。
・取得件数が急に0件になっていないか確認する
・HTML構造の変更を検知できるようにする
・ログを保存して原因を追跡できるようにする
・アクセス頻度を上げすぎない
・同じデータを重複保存しない
・エラー通知の仕組みを用意する
スクレイピングは対象サイト側の変更に影響されるため、一度作って終わりではなく、継続的なメンテナンスが必要です。
10. C#スクレイピングに関するよくある質問
ここでは、C#スクレイピングを始める人が疑問に感じやすい点をまとめます。
10-1. C#でスクレイピングするならHtmlAgilityPackとAngleSharpのどちらがよいか
HtmlAgilityPackは、XPathを使ってHTMLを解析したい場合に便利です。情報量も多く、C#スクレイピングの入門に向いています。
AngleSharpは、よりブラウザのDOMに近い感覚でHTMLを扱えるライブラリです。CSSセレクタで要素を取得したい場合や、HTML/CSSの仕様に近い形で解析したい場合に向いています。
初心者がまずC#スクレイピングを学ぶならHtmlAgilityPackで十分です。CSSセレクタを中心に書きたい場合や、よりDOM操作に近い使い方をしたい場合はAngleSharpも検討するとよいでしょう。
10-2. ログインが必要なサイトはスクレイピングできるか
技術的には、ログイン処理、Cookie、認証ヘッダーなどを扱えば、ログイン後のページを取得できる場合があります。ただし、ログインが必要なサイトは利用規約で自動取得が制限されていることが多く、慎重な判断が必要です。
特に、他人のアカウント情報、個人情報、有料コンテンツ、社内機密情報などを無断で取得することは避けるべきです。ログインが必要なサイトを扱う場合は、公式APIの有無を確認し、必要に応じてサイト運営者の許可を得ましょう。
10-3. 画像やPDFファイルも取得できるか
C#では、画像やPDFファイルもHttpClientでダウンロードできます。HTMLからimgタグやaタグのURLを取得し、そのURLにアクセスしてバイト配列として保存します。
C#var fileUrl = "https://example.com/sample.pdf";
var bytes = await client.GetByteArrayAsync(fileUrl);
await File.WriteAllBytesAsync("sample.pdf", bytes);
画像の場合も同様です。
C#var imageUrl = "https://example.com/image.jpg";
var bytes = await client.GetByteArrayAsync(imageUrl);
await File.WriteAllBytesAsync("image.jpg", bytes);
ただし、画像やPDFにも著作権があります。取得、保存、再配布、商用利用を行う場合は、利用条件を必ず確認しましょう。
10-4. 商用利用しても問題ないか
C#スクレイピングで取得したデータを商用利用できるかどうかは、対象サイトの利用規約、データの種類、利用目的、著作権、個人情報の有無などによって変わります。
公開ページから取得できる情報であっても、商用利用や再配布が禁止されている場合があります。特に、価格情報、レビュー、記事本文、画像、会員向け情報などは注意が必要です。
商用利用を考えている場合は、まず公式APIやデータ提供サービスがないか確認しましょう。不明な場合は、サイト運営者や専門家に確認するのが安全です。
10-5. PythonではなくC#でスクレイピングするメリットは何か
Pythonはスクレイピングで人気の高い言語ですが、C#にもメリットがあります。
C#は.NET環境との相性がよく、Windowsアプリ、業務システム、ASP.NET Core、Azure Functions、バッチ処理などに組み込みやすいです。既存システムがC#で作られている場合、スクレイピング処理もC#で実装すると、保守や連携がしやすくなります。
また、C#は静的型付け言語のため、大規模な業務アプリケーションでもコードの見通しを保ちやすいです。取得したデータをクラスにマッピングし、データベースやCSV、API連携につなげる処理も書きやすいです。
すでに.NETを使っている開発現場であれば、C#スクレイピングは十分に実用的な選択肢です。
まとめ
C#スクレイピングでは、HttpClientでWebページのHTMLを取得し、HtmlAgilityPackでHTMLを解析して必要なデータを取り出します。ページタイトル、見出し、リンク、テーブルデータなどは、XPathを使うことで比較的簡単に取得できます。
基本的な流れは以下です。
1. HttpClientでHTMLを取得する
2. HtmlDocumentにHTMLを読み込む
3. XPathで対象要素を指定する
4. InnerTextや属性値を取り出す
5. 必要に応じてCSVやデータベースに保存する
一方で、C#スクレイピングを行う際は、対象サイトの利用規約、robots.txt、アクセス頻度、著作権、個人情報に十分注意する必要があります。技術的に取得できるデータであっても、利用してよいとは限りません。
HtmlAgilityPackは静的HTMLの解析に向いていますが、JavaScriptで生成されるページでは目的のデータを取得できない場合があります。その場合は、API通信の確認や、Selenium、Playwrightなどのブラウザ自動化ツールを検討しましょう。
まずは小さなサンプルから始め、ページタイトルやリンク一覧の取得を試してみると、C#スクレイピングの基本が理解しやすくなります。安全なアクセスと適切なデータ利用を意識しながら、業務効率化や情報収集の自動化に活用していきましょう。

