Dockerを使用してDigitalOcean App PlatformでPuppeteer Webスクレイパーを構築する

ウルトラマラソン愛好者として、私はよくある課題に直面することがあります。それは、まだ挑戦したことのないより長いレースの完走時間をどのように見積もるかということです。コーチと話し合ったとき、彼は実用的なアプローチを提案してくれました―私が完走したレースと私が目指しているレースの両方を完走したランナーを見るというものです。この相関関係は、潜在的な完走時間に関する貴重な洞察を提供してくれるかもしれません。しかし、レース結果を手作業で検索するのは非常に時間がかかります。

このことが私を導いたのが、レースタイムインサイトというツールの開発です。このアプリケーションは、UltraSignupやPacific Multisportsなどのプラットフォームからレース結果をスクレイピングし、ランナーが2つのレースのURLを入力して他のアスリートが両方のイベントでどのようにパフォーマンスを発揮したかを見ることができます。

このツールを作成することで、DigitalOceanのApp Platformの強力さを実感しました。PuppeteerとヘッドレスChromeをDockerコンテナーで使用することで、ランナーの問題解決に集中し、App Platformがすべてのインフラの複雑さを処理してくれました。その結果、ランニングコミュニティがレース目標についてデータに基づいた判断をするのに役立つ堅牢でスケーラブルなソリューションが生まれました。

レースタイムインサイトを構築した後、同じ技術―Puppeteer、Dockerコンテナー、DigitalOcean App Platform―を活用する方法を他の開発者に示すガイドを作成したいと考えました。もちろん、外部データを扱う際には、レート制限や利用規約などを考慮する必要があります。

プロジェクト・グーテンベルクに入力します。パブリック・ドメインの膨大な本のコレクションと明確な利用条件を持つため、これらの技術を実証するための理想的な候補です。この投稿では、Puppeteerを使用してDockerコンテナ内に書籍検索アプリケーションを構築し、App Platformに展開する方法を探ります。外部データアクセスのためのベストプラクティスに従いながら。

私は、プロジェクト・グーテンベルクから書籍情報を適切にスクレイピングするWebアプリケーションを構築し、共有しました。このアプリケーションは、GitHubリポジトリで見つけることができ、ユーザーが数千冊のパブリック・ドメインの本を検索し、各本の詳細情報を表示し、さまざまなダウンロード形式にアクセスできるようにします。特に興味深いのは、これが責任あるWebスクレイピングの実践を示しながら、ユーザーに実質的な価値を提供している点です。

良いデジタル市民であること

Webスクレイパーを構築する際には、良い慣行に従い、技術的および法的な境界を尊重することが重要です。プロジェクト・グーテンベルクは、これらの原則を学ぶための優れた例です。なぜなら:

  1. 明確な利用条件を持っています
  2. robots.txtガイドラインを提供します。
  3. その内容は明示的にパブリックドメインです。
  4. リソースへのアクセスが増加するメリットがあります。

当社の実装にはいくつかのベストプラクティスが含まれています:

レート制限

デモンストレーション目的で、少なくともリクエスト間に1秒を保証する単純なレート制限機能を実装しています:

// 単純なレート制限の実装
const rateLimiter = {
    lastRequest: 0,
    minDelay: 1000, // リクエスト間の1秒
    async wait() {
        const now = Date.now();
        const timeToWait = Math.max(0, this.lastRequest + this.minDelay - now);
        if (timeToWait > 0) {
            await new Promise(resolve => setTimeout(resolve, timeToWait));
        }
        this.lastRequest = Date.now();
    }
};

この実装は意図的に例として簡略化されています。単一のアプリケーションインスタンスを想定し、状態をメモリに保存しますが、本番環境で使用するには適していません。より堅牢なソリューションでは、Redisを使用した分散レート制限やスケーラビリティを向上させるためのキューベースのシステムを実装することがあります。

このレートリミッターはProject Gutenbergへのすべてのリクエストの前に使用されます:

async searchBooks(query, page = 1) {
    await this.initialize();
    await rateLimiter.wait();  // レート制限を強制
    // ... 検索ロジックの残り
}

async getBookDetails(bookUrl) {
    await this.initialize();
    await rateLimiter.wait();  // レート制限を強制
    // ... 詳細ロジックの残り
}

ボットの識別をクリア

カスタムユーザーエージェントは、ウェブサイトの管理者が誰がサイトにアクセスしているのか、なぜアクセスしているのかを理解するのに役立ちます。この透明性により、彼らは次のことができます:

  1. 問題がある場合は連絡する
  2. ボットのトラフィックを人間のユーザーとは別に監視および分析する
  3. 合法的なスクレイパーに対してより良いアクセスやサポートを提供する可能性があります
await browserPage.setUserAgent('GutenbergScraper/1.0 (Educational Project)');

効率的なリソース管理

Chromeは、複数のインスタンスを実行している場合などにメモリを大量に消費する可能性があります。使用後に適切にブラウザのページを閉じることにより、メモリリークを防ぎ、アプリケーションが効率的に動作することを保証します:

try {
    // ... スクレイピングロジック
} finally {
    await browserPage.close();  // メモリとシステムリソースを解放する
}

これらの方法に従うことで、アクセスするリソースに対して効果的でありながら、リソースを尊重するスクレイパーを作成します。特に、Project Gutenbergのような貴重な公共リソースと連携する場合には特に重要です。

クラウド上のウェブスクレイピング

このアプリケーションは、DigitalOceanのApp Platformを介してモダンなクラウドアーキテクチャとコンテナ化を活用しています。このアプローチにより、開発の簡素さと本番環境の信頼性との完璧なバランスが実現されています。

App Platformのパワー

App Platformは次のようなデプロイメントプロセスを効率化します:

  • Webサーバーの構成
  • SSL証明書の管理
  • セキュリティのアップデート
  • 負荷分散
  • リソースモニタリング

これにより、App Platformがインフラを管理する間、私たちはアプリケーションコードに集中できます。

コンテナ内のヘッドレスChrome

私たちのスクレイピング機能のコアは、Chromeをプログラムで制御するための高レベルAPIを提供するPuppeteerを使用しています。アプリケーションでPuppeteerを設定および使用する方法は次のとおりです:

const puppeteer = require('puppeteer');

class BookService {
    constructor() {
        this.baseUrl = 'https://www.gutenberg.org';
        this.browser = null;
    }

    async initialize() {
        if (!this.browser) {
            // デバッグ用の環境情報ログを追加
            console.log('Environment details:', {
                PUPPETEER_EXECUTABLE_PATH: process.env.PUPPETEER_EXECUTABLE_PATH,
                CHROME_PATH: process.env.CHROME_PATH,
                NODE_ENV: process.env.NODE_ENV
            });

            const options = {
                headless: 'new',
                args: [
                    '--no-sandbox',
                    '--disable-setuid-sandbox',
                    '--disable-dev-shm-usage',
                    '--disable-gpu',
                    '--disable-extensions',
                    '--disable-software-rasterizer',
                    '--window-size=1280,800',
                    '--user-agent=GutenbergScraper/1.0 (+https://github.com/wadewegner/doappplat-puppeteer-sample) Chromium/120.0.0.0'
                ],
                executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium-browser',
                defaultViewport: {
                    width: 1280,
                    height: 800
                }
            };

            this.browser = await puppeteer.launch(options);
        }
    }

    // Puppeteerを使用したスクレイピングの例
    async searchBooks(query, page = 1) {
        await this.initialize();
        await rateLimiter.wait();

        const browserPage = await this.browser.newPage();
        try {
            // 実際のブラウザを模倣し、ボットを識別するためのヘッダーを設定
            await browserPage.setExtraHTTPHeaders({
                'Accept-Language': 'en-US,en;q=0.9',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Connection': 'keep-alive',
                'Upgrade-Insecure-Requests': '1',
                'X-Bot-Info': 'GutenbergScraper - A tool for searching Project Gutenberg'
            });

            const searchUrl = `${this.baseUrl}/ebooks/search/?query=${encodeURIComponent(query)}&start_index=${(page - 1) * 24}`;
            await browserPage.goto(searchUrl, { waitUntil: 'networkidle0' });
            
            // ... 残りの検索ロジック
        } finally {
            await browserPage.close();  // 常にクリーンアップする
        }
    }
}

この設定により、次のことが可能になります:

  • ヘッドレスモードでChromeを実行(GUIは不要)
  • WebページのコンテキストでJavaScriptを実行
  • ブラウザリソースを安全に管理
  • コンテナ環境で信頼性の高い作業を行う

この設定には、コンテナ環境で実行するためのいくつかの重要な構成も含まれています:

  1. 適切なChrome引数:コンテナ内で実行するための--no-sandbox--disable-dev-shm-usageなどの重要なフラグ
  2. 環境に適したパス:環境変数から正しいChromeバイナリパスを使用
  3. リソース管理:ビューポートサイズを設定し、不要な機能を無効にする
  4. プロフェッショナルなボット識別:スクレイパーを識別する明確なユーザーエージェントとHTTPヘッダー
  5. エラー処理: メモリリークを防ぐためのブラウザページの適切なクリーンアップ

PuppeteerはChromeをプログラムで制御することを容易にしますが、コンテナで実行するには適切なシステム依存関係と構成が必要です。この記事では、Docker環境でこれをどのように設定するかを見てみましょう。

Docker: 一貫性のある環境を確保する

Webスクレイピングツールを展開する際の最大の課題の1つは、開発環境と本番環境で同じように機能することを確認することです。スクレイパーがローカルマシンで完璧に動作するかもしれませんが、クラウドでは依存関係の不足や異なるシステム構成のために失敗するかもしれません。Dockerは、Node.jsからChrome自体まで、アプリケーションが必要とするすべてを1つのコンテナにパッケージ化し、どこでも同じように実行できるようにします。

私たちのDockerfileはこの一貫性のある環境を設定します:

FROM node:18-alpine

# Chromiumと依存関係のインストール
RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    dumb-init

# 環境変数の設定
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \
    PUPPETEER_DISABLE_DEV_SHM_USAGE=true

Alpineベースのイメージは、全ての必要な依存関係を含みながらコンテナを軽量に保ちます。このコンテナを実行すると、あなたのノートパソコンやDigitalOceanのApp Platformであっても、ヘッドレスChromeを実行するための正しいバージョンと構成が全て揃ったまったく同じ環境を得ることができます。

開発から展開まで

このプロジェクトを始動させる手順を見ていきましょう:

1. ローカル開発

まずは、例のリポジトリをGitHubアカウントにフォークしてください。これにより、自分専用のコピーを取得し、展開することができます。次に、フォークしたリポジトリをローカルにクローンしてください:

# フォークをクローン
git clone https://github.com/YOUR-USERNAME/doappplat-puppeteer-sample.git
cd doappplat-puppeteer-sample

# Dockerでビルドして実行
docker build -t gutenberg-scraper .
docker run -p 8080:8080 gutenberg-scraper

2. コードの理解

このアプリケーションは、3つの主要コンポーネントを中心に構成されています。

  1. ブックサービス: ウェブスクレイピングとデータ抽出を処理します

    async searchBooks(query, page = 1) {
     await this.initialize();
     await rateLimiter.wait();
    
     const itemsPerPage = 24;
     const searchUrl = `${this.baseUrl}/ebooks/search/?query=${encodeURIComponent(query)}&start_index=${(page - 1) * itemsPerPage}`;
     
     // ... スクレイピングロジック
    }
    
  2. Express サーバー: ルートを管理しテンプレートをレンダリングします

    app.get('/book/:url(*)', async (req, res) => {
     try {
         const bookUrl = req.params.url;
         const bookDetails = await bookService.getBookDetails(bookUrl);
         res.render('book', { book: bookDetails, error: null });
     } catch (error) {
         // エラー処理
     }
    });
    
  3. フロントエンドビュー: Bootstrapを使用したクリーンでレスポンシブなUI

    <div class="card book-card h-100">
     <div class="card-body">
         <span class="badge bg-secondary downloads-badge">
             <%= book.downloads.toLocaleString() %> ダウンロード
         </span>
         <h5 class="card-title"><%= book.title %></h5>
         <!-- ... 他のUI要素 ... -->
     </div>
    </div>
    

3. DigitalOceanへのデプロイ

リポジトリのフォークができたら、DigitalOcean App Platformへのデプロイは簡単です:

  1. 新しいApp Platformアプリケーションを作成します
  2. フォークされたリポジトリに接続します
  3. リソースで、2番目のリソース(Dockerfileではないもの)を削除します。これはApp Platformによって自動生成され、不要です
  4. 「リソースを作成」をクリックしてデプロイします

アプリケーションは自動的にビルドされデプロイされ、App Platformがすべてのインフラの詳細を処理します。

結論

このProject Gutenbergスクレイパーは、最新のクラウドテクノロジーを使用して実用的なWebアプリケーションを構築する方法を示しています。WebスクレイピングのためのPuppeteer、コンテナ化のためのDocker、そしてデプロイメントのためのDigitalOceanのApp Platformを組み合わせることで、堅牢でメンテナンスしやすいソリューションを作成しました。

このプロジェクトは、あなた自身のWebスクレイピングアプリケーションのテンプレートとして機能し、ブラウザの自動化の処理方法、リソースの効率的な管理、クラウドへのデプロイについて示しています。データ収集ツールを構築しているか、コンテナ化されたアプリケーションについて学んでいる場合でも、この例は基盤となる堅固な基盤を提供します。

詳細や自分自身のインスタンスをデプロイする方法については、GitHubでのプロジェクトをチェックしてください!

Source:
https://www.digitalocean.com/community/tutorials/build-a-puppeteer-web-scrapper-with-docker-and-app-platform