C# ウェブスクレイピング: 実践ガイド
Expert Network Defense Engineer
TL;DR:
HttpClientと AngleSharp は現代の C# ペアリングです。 AngleSharp は CSS セレクタを受け入れるため、doc.QuerySelectorAll("div.quote")はブラウザコンソールで同じセレクタが動作するように振る舞います。- Scrapeless Universal Scraping API は JSON エンヴェロープで応答します。 マークアップは
dataの中に到着するため、まず JSON を読み、次に HTML をパースします。 - PuppeteerSharp の通常の最初のステップはブラウザをダウンロードします。リモートのブラウザに接続するとそれを完全にスキップします。 すべてのクイックスタートは
BrowserFetcher().DownloadAsync()から始まります;Puppeteer.ConnectAsyncはそれを呼び出しません。測定値: 検証プロジェクトは 7.6 MB で終了し、ディスク上に Chromium はなく、PuppeteerSharp のブラウザキャッシュもありません。 - リモート CDP パスは儀式なしに機能します。
ConnectAsyncはクエリ文字列の資格情報を持つwss://エンドポイントを受け入れ、title=Quotes to Scrapeとquotes on page=10を返しました。 - 無料で始める: Scrapeless ダッシュボード は以下のすべての例で機能するキーを発行します。
必要なもの
下にあるすべては .NET SDK 8.0.129 で quotes.toscrape.com に対して実行されました。このサイトはスクレイピングの練習のために公開されています。
bash
dotnet new console -o scraper
cd scraper
dotnet add package AngleSharp # 1.7.1
dotnet add package PuppeteerSharp # 25.5.0
HttpClient と System.Text.Json はベースクラスライブラリに含まれているため、唯一の依存関係はパーサーと、後にブラウザドライバーです。
パーサーの選択について: HtmlAgilityPack はほとんどの C# スクレイピング資料が手にする名前であり、機能します。AngleSharp は新しいコードには優先すべきです。なぜなら、W3C DOM を実装し、CSS セレクタを直接受け入れるため、ブラウザのデバッギングツールからコピーしたセレクタが変更なしで機能し、XPath に翻訳する必要がないからです。
フェッチとパース
csharp
using AngleSharp.Html.Parser;
using System.Text;
var http = new HttpClient();
http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (compatible; csharp-guide/1.0)");
var res = await http.GetAsync("https://quotes.toscrape.com/");
var html = await res.Content.ReadAsStringAsync();
Console.WriteLine($"http={(int)res.StatusCode} bytes={Encoding.UTF8.GetByteCount(html)}");
var parser = new HtmlParser();
var doc = await parser.ParseDocumentAsync(html);
var quotes = doc.QuerySelectorAll("div.quote");
Console.WriteLine($"quotes={quotes.Length}");
Console.WriteLine($"first_author={quotes[0].QuerySelector("small.author")!.TextContent}");
Console.WriteLine($"first_text={quotes[0].QuerySelector("span.text")!.TextContent[..40]}");
text
http=200 bytes=11064
quotes=10
first_author=Albert Einstein
first_text=“The world as we have created it is a pr
注目すべき詳細が3つあります。
ユーザーエージェントを設定します。HttpClient はデフォルトで何も送信しないため、ユーザーエージェントなしのリクエストはサイトにとって異なる扱いをする最も安価なものの一つです。
Encoding.UTF8.GetByteCount(html) は html.Length ではありません。.NET の文字列は UTF-16 であり、Length はワイヤ上のバイトではなく、UTF-16 コードユニットをカウントします。このページでは、引用符がカールした引用符を使用するため、2つの値は異なります。どちらかを報告しても良いですが、直接フェッチと API 応答を比較する際には混同しないでください。
子クエリを行にスコープします。quotes[0].QuerySelector(...) はその要素の中を検索します; ループ内で doc.QuerySelector(...) を呼び出すと、毎回最初の引用の値を返し、見た目が完全であっても均一に間違ったデータセットを生成します。
プレーン C# が止まるところ
スクリプトはターゲットがサーバーサイドでレンダリングされ、呼び出し元をスクリーニングしないために機能します。それを終わらせる2つの要素: JavaScript が実行された後だけに存在するコンテンツと、素の HTTP クライアントにサービスを提供しないサイト。いずれも .NET の制限ではありません — どの言語の HTTP クライアントもどちらの要素も処理しません。
ここから先は、それぞれ異なるツールが必要になりますが、優れたツールではなく、プレーンな方法は機能するところで最も安価なままです。ブラウザが追加する一般的な形については、ブラウザ自動化の説明 がそのカテゴリーをカバーします。
エスカレーション1: Universal Scraping API
これにより、あなたのコードは普通の HttpClient コーラーのままとなり、難易度がサーバーサイドに移ります。
csharp
using System.Text;
using System.Text.Json;
using AngleSharp.Html.Parser;
var key = Environment.GetEnvironmentVariable("SCRAPELESS_API_KEY")!;
var payload = JsonSerializer.Serialize(new
{
actor = "unlocker.webunlocker",
input = new { url = "https://quotes.toscrape.com/", js_render = false }
});
using var req = new HttpRequestMessage(
HttpMethod.Post, "https://api.scrapeless.com/api/v2/unlocker/request")
{
Content = new StringContent(payload, Encoding.UTF8, "application/json")
};
req.Headers.Add("x-api-token", key);
var apiRes = await http.SendAsync(req);
using var json = JsonDocument.Parse(await apiRes.Content.ReadAsStringAsync());
var keys = string.Join(",", json.RootElement.EnumerateObject().Select(p => p.Name));
Console.WriteLine($"http={(int)apiRes.StatusCode} envelope_keys={keys}");
var apiHtml = json.RootElement.GetProperty("data").GetString()!;
var apiDoc = await new HtmlParser().ParseDocumentAsync(apiHtml);
Console.WriteLine($"quotes={apiDoc.QuerySelectorAll("div.quote").Length}");
text
http=200 envelope_keys=code,data
quotes=10
エンヴェロープは内面化する部分です。レスポンスボディは JSON であり、マークアップは data に存在します。生のボディを ParseDocumentAsync に渡すと、マッチする要素がないドキュメントが生成され、何もスローされません — セレクタは静かにゼロ結果を返します。JSON を読み、data を取得し、パースし、そのアンラップを一つのメソッドで維持します。
req.Headers.Add("x-api-token", key) ではなく Authorization ヘッダーを使用します。これはカスタムヘッダーであるため、HttpRequestMessage.Headers に直接追加されます; 型付きの認証ヘッダーとして表現しようとするのは、ここには適用されない迂回です。
エスカレーション2: ダウンロードせずにリモートブラウザ
ここが C# が通常の指示よりも顕著に優れているところです。すべての PuppeteerSharp クイックスタートは以下のように始まります:
csharp
// The standard first step — downloads a Chromium build before anything runs.
await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
Puppeteer.ConnectAsync はそのすべてを必要としません。あなたは他の場所にすでに存在するブラウザに接続しているため、BrowserFetcher は関与せず、何もディスクに配置されません:
csharp
using PuppeteerSharp;
var key = Environment.GetEnvironmentVariable("SCRAPELESS_API_KEY")!;
await using var browser = await Puppeteer.ConnectAsync(new ConnectOptions
{
BrowserWSEndpoint = $"wss://browser.scrapeless.com/api/v2/browser?token={key}"
});
var page = await browser.NewPageAsync();
await page.GoToAsync("https://quotes.toscrape.com/");
Console.WriteLine($"title={await page.GetTitleAsync()}");
var n = await page.EvaluateExpressionAsync<int>("document.querySelectorAll('.quote').length");
Console.WriteLine($"quotes on page={n}");
text
title=Quotes to Scrape
quotes on page=10
これによって得られる2つの利点があります。
プロジェクトは小さいままです。 この記事のすべての例を実行した後、検証プロジェクトはディスク上で 7.6 MB 計測され、Chromium を含まず、PuppeteerSharp のブラウザキャッシュを残しませんでした。LaunchAsync ワークフローは、コードを実行するすべてのマシンとコンテナイメージにブラウザビルドを追加します。
EvaluateExpressionAsync<T> はあなたのためにデシリアライズします。 <int> を要求すると int が返されるため、アンラップする JsonElement はなく、ブラウザとあなたの変数の間で手動でパースする必要はありません。
知っておくべきこと: ConnectAsync は、追加の設定なしでクエリ文字列に認証情報を含む wss:// エンドポイントを受け入れました。これは言語の CDP クライアント全体には普遍的ではありません — 一部はクエリ文字列を削除したり TLS サーバー名のネゴシエーションを省略したりします — したがって、別のスタックから移行している場合、このレグはあなたが去ったレグよりも簡単である可能性があります。
三つの選択肢からの選択
| アプローチ | 使用時 | コスト |
|---|---|---|
HttpClient + AngleSharp |
サーバー生成された HTML、寛容なターゲット | 最低; 一つのパッケージ |
| ユニバーサル スクレイピング API | ブロックまたはチャレンジ、JS 不要 | 一つの HTTP 呼び出し、アンラップする封筒 |
| PuppeteerSharp + リモートブラウザ | コンテンツに JavaScript 実行が必要 | ジョブごとのセッションですが、ディスク上にブラウザはありません |
上に行くのではなく、リストを下に進めてください。ブラウザが必要に見えるページは、非常に多くの場合 <script> タグにデータを埋め込み、QuerySelector と JsonDocument.Parse の一つがすべての軸でブラウザセッションに勝ります。
結論
C# は快適なスクレイピング言語であり、標準ライブラリはエコシステムのチュートリアルが示唆するよりも多くをカバーしています。HttpClient と AngleSharp は、デブツールから貼り付けられる CSS セレクターを使ってサーバー生成ページを処理し、覚えておく価値のある二つの罠は、バイトを意味しているときに UTF-16 ユニットをカウントすること、そして行を意味しているときにドキュメントをクエリすることです。
ブラウザストーリーはドキュメントが示唆するよりも良好です。すべてのクイックスタートのダウンロードステップはローカルブラウザを起動するプロパティであり、PuppeteerSharp のものではなく、ConnectAsync はディスクフットプリントと共にそれを取り除きます — 実際の Chrome を駆動する 7.6 MB のプロジェクトです。
C# でスクレイピングの準備はできていますか?
Scrapeless ダッシュボード でキーを作成し、すでに収集しているページに対して HttpClient の例を実行してください。それが期待どおりの結果を返すなら、完了です — 一つのパッケージ、ブラウザなし。 ユニバーサル スクレイピング API はそれが提供できないページをカバーし、現在の料金は 料金ページ に掲載されています。
FAQ
Q: AngleSharp と HtmlAgilityPack はどちらが良いですか?
どちらも実世界の HTML をうまくパースします。AngleSharp は W3C DOM を実装し、CSS セレクターを受け入れるため、デブツールのセレクターは変更されずに使えます; HtmlAgilityPack は XPath を優先し、より長い歴史を持っています。新しいコードの場合、AngleSharp は通常、翻訳作業が少なくなります。
Q: PuppeteerSharp を使うために Chromium をダウンロードする必要がありますか?
ローカルブラウザを起動する場合のみです。BrowserFetcher().DownloadAsync() は LaunchAsync パスに属し、ConnectAsync は既存のブラウザに接続して決して触れません。ここでの検証プロジェクトはディスク上にブラウザがない状態で 7.6 MB にとどまっています。
Q: API 呼び出しが成功したときになぜパースが何も返さないのですか?
あなたは封筒をパースしています。レスポンスは data 内のマークアップを持つ JSON であり、AngleSharp は JSON 文字列を受け取り、何も一致せず、何もスローしません。最初に JSON から data を読み取ってください。
Q: なぜバイト数が文字列の長さと異なるのですか?
.NET の文字列は UTF-16 であるため、string.Length はコードユニットをカウントし、Encoding.UTF8.GetByteCount は転送されたバイトをカウントします。非 ASCII 文字を含むページ — タイポグラフィの引用符を含む — は違いを示します。
Q: HttpClient を再利用すべきですか?
はい。ひとつ作成して共有するか、ホステッドアプリケーションで IHttpClientFactory を使用してください。リクエストごとに新しい HttpClient を構築すると、負荷の下でソケットが枯渇します — マイクロソフト独自の HttpClient ガイドライン がその理由を説明しています — これは他の .NET HTTP ワークロードと同じようにスクレイパーにも適用されます。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



