基本のHTMLスクレイピング:最初の一歩

2025年2月23日

Webスクレイピングは最初こそ難しそうに感じるかもしれませんが、どんなスキルでもそうであるように、実際に手を動かしながら学ぶのがいちばんです。最初の3つの章では、静的なHTMLページからデータを抽出するための基礎を見ていきます。

第1章:HTMLスクレイパー・チャド

最初の旅は、できるかぎりシンプルなケースから始まります。基本的なHTMLページからテキストを取り出すことです。この章では、次のような中核となる概念を紹介します。

  • Webページを取得するためのHTTPリクエスト
  • HTMLコンテンツをパーサーに読み込む方法
  • CSSセレクタを使った基本的なDOM選択

例自体は些細に見えるかもしれません(しょせん “hello world” ですから!)が、この先に続くすべての土台になります。

<!-- Example HTML structure -->
<div class="content">
  <p>Some text we want to extract</p>
</div>

扱う内容の雰囲気はこんな感じです。

// Basic concepts (not the solution!)
import * as cheerio from 'cheerio';

// Loading HTML content
const $ = cheerio.load(htmlContent);

// Using CSS selectors
const text = $('p').text();  // Selects all <p> tags
const specific = $('.content p').text();  // More specific selection

第2章:構造化データ

次は、モックのeコマース商品ページに入り、ぐっと面白くなってきます。ここでは、似たような構造を持つ複数の要素を相手にすることになります。

<!-- Example product structure (simplified) -->
<div class="product">
  <h2>Product Name</h2>
  <span class="price">$99.99</span>
  <div class="specs">
    <ul>
      <li>Size: M</li>
      <li>Color: Blue</li>
    </ul>
  </div>
</div>

このような構造化データを扱うときは、次のような考え方が重要です。

// Conceptual approach (not the solution!)
$('.product').each((index, element) => {
  // For each product, we might want to:
  // 1. Extract the basic info
  const name = $(element).find('h2').text();

  // 2. Parse nested data
  const specs = $(element).find('.specs li');

  // 3. Structure the output
  const data = {
    name,
    specs: specs.map(/* ... */),
  };
});

プロのコツ:コードを書き始める前に、HTML構造をじっくり確認しましょう。データがどのように整理されているか、そのパターンを探してみてください。クラス名には一貫性があるか? 親要素と子要素はどのような関係になっているか?

第3章:AI支援スクレイピング

ここから一気に面白くなります! これまでのチャレンジでは従来のスクレイピング手法を学んできましたが、第3章では現代的なアプローチ、つまりAI支援によるWebスクレイピングを導入します。今回直面するのは悪夢のような状況です。HTML構造は一貫せず、難読化されたクラス名が並び、複数のフレームワーク由来のパターンが混在しています。

まずは、このチャレンジが特別な理由を見てみましょう。

<!-- Traditional product structure -->
<div data-testid="product-container-1" class="_3xj_item">
  <h2 data-qa="name">Red Sneakers</h2>
  <span data-price-current="5999">$59.99</span>
</div>

<!-- React-style component -->
<div class="ProductCard-root-1a2b3c">
  <div class="ProductCard-title-4d5e6f">Pink Walking Shoes</div>
  <div class="ProductCard-pricing-7g8h9i">$84.99</div>
</div>

<!-- Vue-style template -->
<div data-v-abcdef class="product">
  <h2 data-v-abcdef>Navy Boat Shoes</h2>
  <span data-v-abcdef>$79.99</span>
</div>

従来のスクレイピング手法では、ここで苦戦しがちです。なぜなら:

  • クラス名がランダム化されている、またはフレームワーク固有である
  • 商品ごとにデータ構造が異なる
  • フレームワークごとに使うパターンが異なる
  • マークアップの中で意味的な情報が失われている

そこで力を発揮するのがAIです。壊れやすいセレクタを書く代わりに、欲しい情報を自然言語で説明し、パターンマッチングはAIに任せることができます。この章で扱う重要な概念は次のとおりです。

  • Webスクレイピングのためのプロンプトエンジニアリング
  • 意味的な内容をAIに理解させる方法
  • 一貫しないデータ構造への対処
  • フレームワーク固有のマークアップへの対応
  • 異なるパターン間でのデータ整合性の維持

AIは魔法ではありませんが、パターン認識や適応が必要な作業には非常に優れています。そのため、一貫したマークアップパターンを前提にできない現代的なWebアプリケーションのスクレイピングでは、特に大きな価値があります。

AI利用についてひとこと

AIの支援があるからといって、完全に自動化された解決策になるわけではありません。最良の結果は、次の組み合わせから生まれます。

  • 明確な問題定義
  • よく構造化されたプロンプト
  • データの検証
  • 人による監督

このチャレンジでのあなたの課題は、抽出したい内容の「構造」と「意図」の両方をAIが理解できるようなプロンプトを組み立てることです。

従来のWebスクレイピングの知識と、現代的なAIの力を組み合わせる準備はできましたか? どんなに散らかったHTMLでも、AIがどう役立つのかを一緒に見つけていきましょう!

ヒント

  1. さまざまなCSSセレクタを試してみましょう:
// Different ways to select elements
$('.class')           // By class
$('#id')             // By ID
$('div > p')         // Direct children
$('div p')           // All descendants
$('[data-type="x"]') // By attribute
  1. 出力形式を変更してみましょう
  2. エラーハンドリングや境界ケースについて考えてみましょう
  3. データセットが大きくなったときに、あなたの解決策がどうスケールするかを考えてみましょう

始めるのに必要なコードは、すべてプロジェクトのリポジトリに入っています。Cloneして環境を整え、スクレイピングを始めましょう!

git clone https://github.com/jonaylor89/housefly.git
cd housefly
npm install

章ごとのサーバーをローカルで起動します — 各章はそれぞれ独自のポート(3001–3011)で動作します:

turbo dev

次に、exercises/chapter-NN/starter/src/index.ts にあるスクレイパーを編集し、検証します:

npm run validate -- <chapter>

ヒントが欲しいですか? npm run hint -- <chapter> を実行すると、段階的なガイダンスが得られます。各チャレンジの元HTMLは apps/chapter{n}/ ディレクトリにあり、参考となる解答は exercises/chapter-NN/solution/src/ にあります。構造を確認し、方針を立て、そして忘れないでください — どんなエキスパートも、最初は初心者でした。

リクエストは責任を持って扱うようにしましょう:

// Basic error handling example
async function fetchPage(url: string) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.text();
  } catch (error) {
    console.error('Failed to fetch page:', error);
    throw error;
  }
}

楽しいスクレイピングを!