9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「ブラウザはインフラである」:スクレイピングによるシステム崩壊を防ぐためのアーキテクチャ

Posted at

現代のウェブにおいて、古典的なスクレイピング手法は死んだも同然だ。静的なHTMLは消え去り、クライアントサイドレンダリングが必須となり、ボット検知がデフォルトで備わっている。

ローカルのスクリプトがRAMを食いつぶし、ランダムにクラッシュする現象にパッチを当て続けること数ヶ月。私はついにその「対症療法」をやめることにした。そして、Chromiumを単なるライブラリとしてではなく、独立したコンピュートサービスとして扱う 「Browserless」アーキテクチャ へと移行した。

本稿では、そのアーキテクチャ、具体的な設定、そしてスケーリングから得られた教訓を共有する。

1. そもそも Browserless とは何か

Browserless は、一言で言えば 「Dockerコンテナ内でステートレスなリモートサービスとして稼働するヘッドレスChromium」 だ。WebSocket経由で操作を行う。

APIサーバー上に直接 Chrome をインストールする(これはゾンビプロセスやメモリリークの温床となる)代わりに、Puppeteer や Playwright をリモート接続させ、処理を実行し、切断するという形をとる。

これにより、使い捨て(Ephemeral)なセッション、クラッシュの隔離、そして Docker による自動クリーンアップの恩恵を受けられる。

最も重要な設定項目

Chrome はクラッシュした際、ゾンビ化したサブプロセスを残しやすい。この修正方法は拍子抜けするほど単純だ。Docker の init プロセスを有効にすることで、死んだ Chrome のサブプロセスを OS が自動的に回収(Reap)できるようにすればいい。

services:
  browserless:
    image: ghcr.io/browserless/chromium:latest
    init: true  # ← これが重要
    shm_size: '1gb'
    environment:
      - MAX_CONCURRENT_SESSIONS=10
      - PREBOOT_CHROME=true

たったこれだけのフラグで、長期稼働時のメモリ肥大化(Memory Creep)は完全に解消された。

2. なぜローカル実行は失敗したのか

問題はスクレイピングのロジックではなく、リソースの競合(Resource Contention) にあった。

ローカルで Puppeteer インスタンスを動かすと、1つあたり約300〜500MBのメモリを消費する。同時実行数が増えると、Chrome が落ちるよりも先にワークフローエンジン本体が OOM (Out of Memory) で死んでしまう。ブラウザのクラッシュが、システム全体のクラッシュを招いていたのだ。

ブラウザはアプリのランタイムの一部ではなく、「使い捨てのワーカー」として切り離す必要があった。

3. アーキテクチャ

責務を以下の3つのコンテナに分離した:

  1. Nginx – ゲートウェイ、SSL終端、レート制限
  2. Packer (Node.js) – キュー管理 + ブラウザのライフサイクル制御
  3. Browserless – 隔離された Chromium ワーカー

ブラウザコンテナは完全に使い捨てとして扱う。メモリがスパイクしたりセッションがハングしたりしても、キューに入っている他のジョブに影響を与えることなく再起動できる。

最小限の docker-compose 構成は以下の通りだ:

services:
  nginx:
    image: nginx:alpine
    depends_on:
      - packer

  packer:
    build: ./browser-packer
    environment:
      - BROWSERLESS_WS=ws://browserless:3000
      - PACKER_MAX_CONCURRENT=10
    depends_on:
      - browserless

  browserless:
    image: ghcr.io/browserless/chromium:latest
    init: true

4. ミドルウェア:ブラウザのための交通整理

Browserless をそのまま(Rawで)使うのは、トラフィックが急増するまではうまくいく。しかし Chromium はキューイングを行わない。死ぬまでプロセスを生成し続けてしまう。

そこで、「Packer」ミドルウェアによって以下を実装し、この問題を解決した:

  • FIFO(先入先出)キュー
  • 同時実行数の上限設定(Concurrency caps)
  • セッションの強制クリーンアップ
const browser = await puppeteer.connect({
  browserWSEndpoint: process.env.BROWSERLESS_WS
});

try {
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'networkidle2' });
} finally {
  // 必ず閉じる
  await browser.close();
}

これにより、1 vCPU あたり約5セッションを安全に実行できている。

5. スピードは「インターネットを遮断する」ことから生まれる

スクレイピングにおける無駄の大部分は、実際には使わないアセット(画像など)のロードにある。

これらがダウンロードされる に、ネットワークレベルで遮断(Intercept)する。

await page.setRequestInterception(true);

page.on('request', (request) => {
  const type = request.resourceType();
  const url = request.url();

  const blockedTypes = ['image','media','font','stylesheet'];
  const blockedDomains = ['google-analytics','doubleclick','facebook','ads','tracking','pixel'];

  if (
    blockedTypes.includes(type) ||
    blockedDomains.some(d => url.includes(d))
  ) return request.abort();

  request.continue();
});

2 vCPU インスタンスでの効果は以下の通り:

  • 平均ロード時間:10秒 → 3〜4秒
  • セッションあたりのCPU使用率:約40%削減
  • 安定した同時実行数:2倍に増加

6. アンチ検知(Anti-Detection):矛盾をなくす

検知エンジンが見ているのは「ボットかどうか」だけではない。「矛盾(Inconsistencies)」 を見ているのだ。

Stealth プラグインを使って、ブラウザ内部の情報の漏れ(Leaks)を修正する:

  • Webdriver フラグの隠蔽
  • WebGLベンダーと User-Agent の整合性確保
  • Navigator プロパティの正規化

ゴールは「人間を装う」ことではなく、「技術的な整合性を保つ」ことにある。

ミドルウェア層での Stealth 注入

Browserless のデフォルト設定だけに頼るのではなく、ブラウザセッションが作られる前のミドルウェア層で Stealth プラグインを直接注入する。

これにより、上流の設定に関わらず、すべての接続が同じ回避能力を持つことが保証される。

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

Puppeteer 層で Chromium にパッチを当てることで、セッション間のフィンガープリントのズレ(Drift)を排除し、パイプライン全体で一貫した検知回避を実現した。

7. 残されたピース:プロキシ

データセンターのIPアドレスを使っている限り、どんなに完璧なフィンガープリントも無意味だ。

次のステップは、WebSocket 接続時に回転(Rotating)する住宅用プロキシ(Residential Proxies)を直接組み込むことだ。

browserWSEndpoint:
'ws://browserless:3000?--proxy-server=http://user:pass@proxy:port'

これにより、IPバンによってキューが詰まる事態を防ぐことができる。

8. 最後に

最大の転換点は、「ブラウザはインフラである」 と認識したことだった。

Chromium を、キュー、制限、再起動、ネットワーク制御を備えた「サービス」として扱った瞬間、スクレイピングの脆弱さは消え去った。スケーリングは予測可能なものになった。

スクリプトはクライアントになり、ブラウザはコンピュート層になった。
この「逆転の発想」こそが、システムを安定させた最大の要因だ。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?