Crawlee が便利なのは、HTTP での解析とブラウザ実行の両方を、クロールの制御フローとしてまとめて扱えるからです。つまり、同じ URL でも、選ぶクローラーや「読み取り準備が整った」とみなす条件によって、返ってくる結果が変わります。
公開されている Quotes to Scrape JS ページでは、CheerioCrawler は対象の quote を 0 件しか取得できませんでしたが、PlaywrightCrawler は .quote が表示されるまで待機したあと 10 件を取得できました。クロールの流れ自体は似ていますが、これは単なるクラス名の置き換えではありません。Cheerio 側のハンドラは $ を使い、Playwright 側は page、明示的な待機、そしてブラウザ上での抽出を使っていました。
Crawlee とは何か
Crawlee(apify/crawlee プロジェクト、バージョン 3.17.0)は、Node.js と TypeScript 向けの Web スクレイピング/ブラウザ自動化ライブラリです。Cheerio や JSDOM を使った HTTP クロールと、Playwright や Puppeteer を使ったブラウザクロールの両方に対応しています。ライセンスは Apache-2.0 なので、配布時には notice や帰属表示の要件を確認してください。
ここで押さえるべき考え方は、「ページを取得すること」と「ページを読むこと」を分けることです。片方は生の HTML をダウンロードするだけで JavaScript は実行しません。もう片方は Chromium を起動してページのスクリプトを実行できますが、それでも適切な準備完了条件が必要で、ユーザー操作待ち・遅延読み込み・shadow DOM・API 失敗・ボット対策などで欠ける情報は取りこぼすことがあります。Crawlee は、こうした経路ごとに対応するライフサイクル概念を提供しており、DOM の部品をそのまま入れ替えるような作りではありません。
これを理解してからセレクタを書くのが大事です。というのも、その 2 つのエンジンのどちらを選ぶかで、あるサイトでスクレイパーがデータを返すか、何も返さないかが決まるからです。
主な特徴: 2 つのエンジン、1 つの API 面

CheerioCrawler は HTML を取得して Cheerio で解析します。PlaywrightCrawler は Chromium を操作し、スクリーンショットも撮れます。どちらも requestHandler を使い、run() を公開し、キューやリンク発見といったクロールの概念を共有しています。とはいえ、ハンドラの文脈は違います。今回のテストでは、Cheerio 側は $ 経由で抽出し、ブラウザ側は page、waitForSelector、$$eval を使いました。キューやライフサイクル周りは共通のままでも、抽出コードはアダプタ化するか、書き換えが必要になることがあります。
その裏で、Crawlee は実運用のクロールに必要な土台をしっかり用意しています。RequestQueue は巡回対象 URL の前線を管理し、重複を除外し、処理済みを追跡します。enqueueLinks はセレクタと同一ホスト制限つきで新しい URL を見つけてキューに追加するので、クロールを自動で広げられます。Dataset は取得したレコードをエクスポート用に集めます。デフォルトでは、Crawlee はこれらをディスク上のローカル storage/ ディレクトリに保存します。再開には便利ですが、プロジェクト内に意図せず storage/ フォルダができて少し驚くこともあります(今回の検証環境では、テストをクリーンに保つため一時ディレクトリに向け、永続化も無効化しました)。
それぞれの要素は単体では特別なものではありません。大事なのは、これらが両エンジンで共通していることです。HTTP でクロールする場合もブラウザ経由でクロールする場合も、キュー、リンク発見、データセットの扱いは同じように動きます。1 つの API を覚えれば、2 つの取得方式を使い分けられるわけです。
セットアップ: ブラウザは別途インストールが必要
今回の検証環境では、パッケージを入れただけでは Chromium の実行ファイルは含まれていませんでした。

npm install crawlee playwright は問題なく完了しました。85 パッケージ、脆弱性 0 件で、特に波乱はありません。ここで止めて CheerioCrawler を動かすだけなら、HTTP クロールなのでブラウザは不要なため、そのまま動きます。
この環境では、PlaywrightCrawler を使うには npx playwright install chromium で Chromium を別途入れる必要がありました。これがないと起動に失敗しました。観測されたブラウザのペイロードはおおよそ 82 MiB でしたが、元のメモにはそれが転送サイズだったのかディスク上のサイズだったのかが残っていません。これは機械ごとのセットアップ観測であって、固定的な製品仕様ではありません。ドキュメントの導線やパッケージの挙動は変わりうるので、この記事ではその不足が常に起こるとも、ずっと未記載だとも言いません。
今回のセットアップは、Node パッケージのインストールと、Playwright 側で使うブラウザのインストール、という 2 段階で考えるのがよいでしょう。実際に導入するバージョンとプラットフォームについては、Crawlee と Playwright の最新の手順を確認してください。
実践: 同じページなのに、答えがまったく違う

中心となるテストでは、JavaScript で描画される同じフィクスチャを両方のクローラーに通しました。URL と対象フィールドは共通ですが、抽出に使う部品は共通ではありません。
ローカルのフィクスチャでは、CheerioCrawler は生 HTML に対象カードが存在しないため 0 件を返しました。PlaywrightCrawler は #dynamic-products article.product-card を待ってから、期待どおり 8 件のカードを返し、スクリーンショットも取得しました。これは、その待機条件のあとに選択したフィールドについて、フィクスチャ上の完全性が確認できたという意味です。ただし、ブラウザがあらゆるページ状態を見られるという意味ではありません。生ファイルと スクリーンショット はベンチマークのリポジトリにあります。

公開されている Quotes to Scrape JS ページ では、CheerioCrawler は対象 quote を 0 件しか見つけられず、PlaywrightCrawler は .quote が現れるまで待ってから 10 件を抽出しました。これで、公開対象でも HTTP とブラウザの境界が同じように現れることが確認できます。ただし、クローラークラス、ハンドラの文脈、待機条件、抽出方法は両者で異なります。
ここから得られる実用的な結論は、もっと限定的です。まず HTTP 経路で必要フィールドを検証し、足りなければブラウザクローラーに切り替える、ということです。ブラウザ側のハンドラも、そのフィールドに紐づいた条件を待つ必要があります。
HTTP 経路は、管理下にある静的カタログと記事フィクスチャでは期待どおりのレコードをすべて返し、直接 JSON 応答からは 8 件すべてを復号し、11 ページの制限付きグラフを巡回し、1 件の 500 応答を failedRequestHandler に振り分けました。これらは 1 つの精度スコアではなく、個別の能力チェックです。公開されている Books to Scrape ページでは、設定したセレクタで 20 件の product を取得でき、スモークテストとしては十分でした。
| テスト | エンジン | 結果 |
|---|---|---|
| 静的抽出: カタログ + ページネーション | CheerioCrawler | 期待された商品 12/12 件 |
| 記事抽出 | CheerioCrawler | title + 段落 3/3 件 |
| 転送: 直接 JSON 応答 | CheerioCrawler | 期待された商品 8/8 件 |
| 巡回: 内部リンクグラフ | CheerioCrawler | 11 ページ、深さ {0:1, 1:3, 2:7} |
| 障害分岐: HTTP 500 | CheerioCrawler | ステータスが failure handler に到達 |
| レンダリング: ローカルフィクスチャ | CheerioCrawler | 生 HTML では対象カード 0 件 |
| レンダリング: ローカルフィクスチャ | PlaywrightCrawler | 対象セレクタ待機後 8/8 件 |
| レンダリング: Quotes JS | CheerioCrawler | 生 HTML では対象 quote 0 件 |
| レンダリング: Quotes JS | PlaywrightCrawler | 対象セレクタ待機後 10 件 |
全体の所要時間と各テストの数値は results/crawlee-test-summary.json にあります。
ここで正直な注意点も挙げておきます。単一マシンで 1 回ずつ実行しただけなので、限界があるのは当然です。これはベンチマークではなく計測メモです。したがって、ブラウザ経路のページあたりコストが高いことは、「Cheerio のサブ秒実行より明らかに遅い」と読むべきで、公式な数値として扱うべきではありません。また、この時点で試していないことも多くあります。プロキシローテーション、セッションプール、数百〜数千ページ規模の実行、RequestQueue の永続化とクラッシュ後の再開、Puppeteer エンジン、Dataset/KeyValueStore のエクスポートの使い勝手などです(今回はエクスポートを手動で書きました)。2 エンジンの使い分けと、フィクスチャレベルの正確性については確認できましたが、スケールやブロック回避の挙動までは保証できないので、そこは保証しません。
共有されるもの、変えるべきもの

共通しているのはクロールのオーケストレーションです。どちらのクローラークラスも requestHandler を受け取り、run() を公開します。キュー、リクエストメタデータ、リンク発見、失敗時のフック、保存の考え方は、どちらの実行経路でも一貫して組み立てられます。これにより、1 つの対象だけブラウザが必要になったときでも、チームが覚え直す基盤の量を減らせます。
一方で、ページへのアクセス面は共通ではありません。CheerioCrawler のハンドラは $ のような Cheerio 向けのアクセスを受け取り、ブラウザなしでレスポンス本体を扱えます。今回検証した PlaywrightCrawler のハンドラは page を受け取り、セレクタを待って、ブラウザ DOM を評価します。最終的に同じレコード形式を出力したとしても、そこに至る API は異なります。再利用可能なアダプタでこの差分の一部は隠せるかもしれませんが、今回のハーネスでは実装も実証もしていません。
この違いは見積もりに直結します。クローラークラスを変えても、キュー、データセット、URL ポリシーは維持できるかもしれませんが、セレクタ、準備完了条件、スクリーンショット、操作手順、エラー処理は変わりえます。したがってこの記事では、「共通のクロール基盤」が確認できたメリットとして扱い、「1 行で移行できる」という主張は根拠のない約束として退けます。
実用的なエンジン選択フロー
返ってくる HTML や直接 JSON 応答に必要フィールドが含まれているなら、まず HTTP 経路を使ってください。必須キー、最小件数、対象セレクタなどの完全性契約を定義し、満たされない場合は明示的に失敗させます。空配列は「そのページにデータがない」証拠ではありません。今回の 2 つの JavaScript ケースでは、選んだ表現に対象要素がなかった、という意味でした。
| 対象条件 | まず使うもの | 切り替える条件 |
|---|---|---|
| 必須フィールドが返却 HTML にある | CheerioCrawler | 必須セレクタまたはフィールドがない |
| 再現可能な JSON 応答にデータがある | CheerioCrawler | リクエストがブラウザ専用状態に依存する |
| ページが実行後に対象要素を挿入する | PlaywrightCrawler | 該当なし。対象専用の準備完了条件を定義する |
| 対象の種類が不明 | まず HTTP + 完全性検証 | 検証が "representation incomplete" の型付き結果で失敗する |
実行が必要になったら、その型付き失敗をブラウザハンドラへエスカレーションします。このハーネスでは、ローカルページは #dynamic-products article.product-card を待ち、公開 quotes ページは .quote を待ちました。これらの条件は抽出契約の一部です。一般的な load イベントだけでは、アプリケーションデータが到着したことは証明できませんし、このテストからは万能の待機ルールも導けません。
ブラウザへ切り替えた後も、DOM の部品は違っていても出力スキーマは安定させてください。どのエンジンが結果を出したか、どの準備完了条件が通ったか、必須フィールドの検証が成功したかを記録します。こうしておけば、HTTP からブラウザへのフォールバックが、黙って欠損を受け入れるのではなく、観測可能になります。
最後に、ブラウザのインストールと実行コストはデプロイ時の入力値として扱ってください。ここで観測された約 82 MiB は、あくまでローカルでの概算にすぎません。実際のブラウザビルド、プラットフォーム、キャッシュ挙動、イメージへの影響は、自分の環境で測るべきです。プロキシローテーション、セッション、永続化、クラッシュ復旧、継続的な同時実行も、本番規模の選択に使う前に個別テストが必要です。
長所と短所
長所:
- HTTP とブラウザのクローラーが、エンジン固有の抽出文脈を持ちながらライフサイクル概念を共有している。
- 静的カタログ、記事、JSON API では 100% の HTTP 抽出精度を確認できた。
RequestQueue、深さ制御つきのenqueueLinks、Datasetなど、両エンジンで共通の基盤を使える。- ブラウザ経路はフィクスチャのスクリプトを実行し、2 つの JavaScript レンダリングテストで期待された対象項目をすべて取得できた。
- エラー処理が明確で、HTTP 500 はクラッシュせずに表面化した。
- Apache-2.0 ライセンス。利用者は notice と帰属表示の要件を確認すべき。
短所:
- 検証環境ではブラウザエンジンに別途 Chromium のインストールが必要で、それがないと
PlaywrightCrawlerは起動しなかった。 - HTTP 経路では、生 HTML にない対象要素は取得できない。完全性検証がなければ、正しく空の結果に見えてしまう。
- ブラウザ経路は追加のバイナリを伴い、今回の実行ではページあたりコストも高かった。サイズや時間はビルドとプラットフォームで変わる。
- デフォルト実行では
storage/ディレクトリがディスクに残る。 - Node/TypeScript 専用なので、Python スタックにはそのままでは役立たない。
向いている人・向いていない人
Crawlee は、HTTP とブラウザのクロールを共通のキュー/ライフサイクル概念で扱いたい Node または TypeScript チームに向いています。実践的には、まず HTTP クローラーを試し、必要フィールドを検証し、型付きの完全性失敗を対象専用の準備完了条件を持つブラウザハンドラへ渡す、という流れがよいでしょう。ハンドラの DOM アクセスコードはエンジン固有ですが、キューやリンク発見の土台は共有できます。
もし Python 中心の組織なら期待値を下げるか、別の選択肢を探してください(Crawlee は Node/TS 向けです。別の Python ポートはありますが、この検証では Node のライブラリを試しました)。また、対象がすべて静的で、より軽量な単機能 HTTP スクレイパーで十分なら、Crawlee にこだわる必要はありません。さらに、プロキシローテーション、セッションプール、クラッシュ後の再開といった大規模運用での実績が必要なら、今回のハンズオンではそこまで見ていないので、別途検証が必要です。PlaywrightCrawler を使うなら、まず Chromium を入れてください。入れなければ動きません。
代替案と、Thunderbit の位置づけ
Crawlee は、自分で運用・保守するオープンソースソフトウェアです。ベンダーへの per-call 課金はありませんが、ブラウザの計算資源、帯域、プロキシ、保存領域、可観測性、そしてエンジニアリングのコストは運用負担として残ります。クローラーの選択、ブラウザバイナリ、保存状態、準備完了ロジックは、すべて自分で管理します。
関連レビュー: scrapy-playwright review。
マネージド型の抽出サービスなら、取得とスキーマ整形の責任をベンダーに移せます。私たちは Thunderbit を提供していますが、このフィクスチャでは実行していないため、品質、レイテンシ、機能の互換性、コストを比較するものではありません。重要なのは、チームが Crawlee のプロセス内制御を望むのか、それとも per-call のサービス境界を望むのか、という点です。
関連ベンチマークレビュー: open-source scraper の総合比較、同じページでの Playwright vs Puppeteer、Scrapy の no-browser request-replay review。
結論
Crawlee は、HTTP とブラウザ実行の両方で共通のクロール制御を使いたい Node/TypeScript チームにとって、有力な候補です。今回検証したハンドラは互換ではなく、Playwright に移るには page、対象セレクタの待機、ブラウザ側抽出が必要でした。プロキシ、セッション、永続化、再開、大規模運用の挙動は未解決です。
Thunderbit で Web データ抽出を試す Get Started Free
よくある質問
Crawlee の 2 つのクローラーは、実際には何が違うのですか?
CheerioCrawler は HTTP 経由で HTML を取得し、JavaScript は実行しません。PlaywrightCrawler は Chromium を操作し、ページスクリプトを実行したりスクリーンショットを撮ったりできますが、その分ローカルでのページあたりコストは高くなります。ライフサイクルの概念は共有していますが、ハンドラの文脈は同一ではありません。このハーネスでは、HTTP 側で $ を使い、Playwright 側で page、対象セレクタの待機、ブラウザ側評価を使いました。
Crawlee を入れたのに PlaywrightCrawler が動かないのはなぜですか?
今回の環境では、パッケージのインストールだけではブラウザ実行ファイルは含まれていませんでした。npx playwright install chromium で Chromium を追加すると起動失敗は解消しました。観測されたペイロードは約 82 MiB でしたが、元の計測では転送サイズかディスクサイズかが残っていないため、自分のプラットフォームとビルドで再計測してください。
CheerioCrawler で JavaScript レンダリングページは取得できますか?
ページの JavaScript 自体は実行できません。ただし、クライアントが使うアクセス可能な JSON エンドポイントを取得することはできます。直接レスポンスのフィクスチャがその例です。必要なデータがブラウザ実行後にしか出ないなら、ブラウザクローラーと、そのフィールドに結びついた準備完了条件を使ってください。
Crawlee は通常の静的抽出でも正確ですか? 管理下のフィクスチャでは、期待されたカタログ商品 12/12 件、記事段落 3/3 件、直接 JSON アイテム 8/8 件を取得できました。これはフィクスチャの完全性チェックであり、未検証サイト全般に対する総合精度スコアではありません。
Crawlee は商用利用できますか? Apache-2.0 で公開されています。現在のライセンスは repository で確認し、配布時の notice と帰属表示の要件も見直してください。
本番導入の前には、このフィクスチャで未確認の部分をテストしてください。代表的なページでの繰り返し同時実行、プロキシとセッションの挙動、中断後の永続キュー復旧、ブラウザプロセスのクリーンアップ、障害時のデータセット出力などです。結果と一緒に、解決したブラウザのバージョンとインストールパスも残しておきましょう。2 つのクローラークラスはオーケストレーションの差を減らしますが、エンジン固有の準備完了条件、リソース予算、運用上の障害対応を不要にはしません。


