テーブルからのデータ抽出と CSV 化

概要

Web スクレイピングやデータマイグレーションの現場では、HTML テーブルからデータを取得し、CSV ファイルに書き出して表計算ソフトやデータ分析ツールで扱いやすくするケースがよくあります。HtmlAgilityPack (HAP) は HTML 文書を DOM ツリーとして解析できるため、セル要素を柔軟に抽出し、.NET 標準ライブラリや CsvHelper などと組み合わせて簡潔に CSV 化を実装できます。以下では代表的なワークフローと実装上の勘所を解説します。


1. テーブルの特定と読み込み

手順 ポイント
1-1 HTML の取得 HtmlWebHttpClient で取得した文字列/ストリームを HtmlDocument.LoadHtml() または HtmlDocument.Load() でパースします。
1-2 テーブルノードの検索 – XPath: doc.DocumentNode.SelectSingleNode("//table[@id='sales']")
– CSS 拡張ライブラリ: doc.QuerySelector("table#sales")
– 複数テーブルがある場合は SelectNodes() でコレクション取得

ヒント: テーブル内に入れ子テーブルがある場合、//table//tr だと子テーブルの行まで拾うため、親テーブルノードを変数に保持し、その .SelectNodes(".//tr") のようにドット付き XPath で相対探索すると意図しない行を除外できます。


2. ヘッダー行と本文行の抽出

csharp
var headerCells = table.SelectSingleNode(".//tr[1]") .SelectNodes("./th|./td"); var headers = headerCells.Select(th => th.InnerText.Trim()).ToList(); var rows = table.SelectNodes(".//tr[position()>1]") ?? new HtmlNodeCollection(null); foreach (var row in rows) { var cells = row.SelectNodes("./th|./td") ?? new HtmlNodeCollection(null); var values = cells.Select(c => c.InnerText.Trim()).ToList(); // 後述の CSV ビルダーへ渡す }

rowspan / colspan への対処

  • 単純化できる場合: rowspan は上位行を複製、colspan は空セルを挿入し横幅を合わせる。

  • 複雑な表: 表計算や pandas へ直接投げるほうが保守しやすいこともあります。


3. CSV 文字列の生成

3-1 StringBuilder で手書き

csharp
var sb = new StringBuilder(); // ヘッダー sb.AppendLine(string.Join(",", headers.Select(EscapeCsv))); // データ行 foreach (var record in records) sb.AppendLine(string.Join(",", record.Select(EscapeCsv))); string csv = sb.ToString(); File.WriteAllText("sales.csv", csv, Encoding.UTF8);
csharp
static string EscapeCsv(string field) { // 「"」と改行を含む場合は RFC4180 に従って二重引用符で囲み、内部の「"」をエスケープ if (field.Contains('"') || field.Contains(',') || field.Contains('\n')) field = "\"" + field.Replace("\"", "\"\"") + "\""; return field; }

3-2 CsvHelper を利用

csharp
using var writer = new StreamWriter("sales.csv", false, Encoding.UTF8); using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture); // 任意: ヘッダー自動書出し csv.WriteField("商品"); csv.WriteField("数量"); csv.WriteField("金額"); csv.NextRecord(); foreach (var record in records) { foreach (var value in record) csv.WriteField(value); csv.NextRecord(); }

文字コード

  • Windows の Excel に直接渡すなら Encoding.GetEncoding("Shift_JIS") を指定し BOM なしで保存すると文字化けを防げます。

  • UTF-8+BOM でも近年の Excel では問題ありませんが、大量データで BOM が不要なら new UTF8Encoding(false) を明示。


4. 大量テーブル/複数ページ対応

課題 対策
行数 10 万超 StringBuilder ではなく一行ずつ StreamWriter.WriteLine() へ直接書込む。
複数ページ async で並列ダウンロード → 各ページの recordsConcurrentQueue に蓄積 → 最終的に書出し。
メモリ使用量 行単位で即書出すストリーミング方式を採用し、リスト保持を避ける。

5. テストと検証

  1. ユースケースごとの HTML サンプルを用意

    • 正常系・セル結合あり・変則ヘッダ・空セル・エンコーディング違い。

  2. NUnit / xUnit で Snapshot 比較

    • 生成した CSV を事前のゴールデンマスターと文字列比較。

  3. 特殊文字の回帰試験

    • "," や改行を含むセルをモックし、RFC 4180 逸脱の有無を確認。


6. よくある落とし穴とベストプラクティス

落とし穴 回避策
InnerText で余分な空白・改行が入る InnerText.Trim() 後に Regex.Replace で連続空白を単一空白化。
JavaScript で生成される表 HAP は実行後 DOM を取得できないため、Playwright などで prerender してから HTML を渡す。
表ヘッダーが複数行 2 段ヘッダー → フラット化ルールを決めて JOIN / CONCAT。
colspan と Excel 列合計 Excel で意図した列揃えとならない場合があるため、ダミー列挿入を実装。
大容量出力時のメモリ不足 行 flush 方式 & async / await を徹底。

まとめ

  • 抽出: HAP の XPath/CSS クエリで th / td ノードを簡潔に取得。

  • 整形: 空白処理・セル結合補正・エスケープを行い、2 次利用しやすい整形済みデータを得る。

  • CSV 化: StringBuilder もしくは CsvHelper で RFC 4180 準拠の安全なファイルを生成。

  • 運用: エンコーディングとストリーミング書出しで Excel 互換性と大規模データ性能を確保。

この流れを押さえておけば、Web サイトの集計表や社内システムの HTML レポートを高品質に CSV へ変換し、後段の BI ツールや pandas 解析へスムーズに受け渡すことができます。

ChatGPT4o 生成日:2025/06/23