はじめに
Webスクレイピングって、やり始めると意外と沼なんですよね。
最初は「ページのHTML取得してパースするだけでしょ」と思っていたのに、実際にやってみると、リクエストがブロックされたり、JavaScriptで動的に描画されるコンテンツが取れなかったり、大量のURLを効率よく処理する方法に悩んだり。
個人的にいろいろ試した結果、Node.js/TypeScriptでやるならCrawleeが一番バランス良いという結論に至りました。GitHubのスター数も2万超えてて、実績も十分。今回はこのCrawleeについて紹介していきます。
Crawleeとは
Crawleeは、Apify社が開発しているオープンソースのWebスクレイピング・ブラウザ自動化ライブラリです。「Build reliable web scrapers. Fast.」というコンセプトの通り、信頼性の高いスクレイパーを素早く構築することにフォーカスしています。
ライセンスはApache 2.0で、商用利用も問題なし。Node.js 16以上で動作します。最近はPython版も出ているので、Pythonユーザーも選択肢に入れられますね。
特徴・メリット
1. HTTP/ブラウザの両方に対応
これ、意外と重要なポイントです。
- HTTPクローリング: シンプルなHTMLページなら、Cheerioを使った軽量な処理が可能
- ブラウザクローリング: SPAやJavaScript必須のサイトには、PlaywrightやPuppeteerで対応
同じAPIで両方使い分けられるので、サイトの特性に応じて最適な方法を選べます。
2. 自動的にいい感じにしてくれる機能が多い
正直なところ、スクレイピングで面倒なのは本体のロジックよりも周辺処理なんですよね。Crawleeはその辺をかなり自動化してくれます。
- リクエストキュー管理: URLの重複排除、優先度管理、失敗時の自動リトライ
- プロキシローテーション: IPブロック対策のプロキシ切り替えを自動化
- セッション管理: Cookieやログイン状態の維持
- レート制限: サーバーに負荷をかけすぎないよう自動調整
- ブラウザフィンガープリント: 人間っぽいアクセスに見せかける
3. TypeScriptネイティブ
コードベースの60%以上がTypeScriptで書かれていて、型定義もしっかりしています。IDEの補完が効くのは開発効率的にかなり助かる。
4. データ出力が柔軟
取得したデータはJSON、CSV、XMLなど好きな形式で出力可能。ローカルファイルへの保存はもちろん、Apifyのクラウドプラットフォームとの連携もできます。
インストール方法
CLIを使う方法(おすすめ)
一番手っ取り早いのは、CLIでプロジェクトを生成する方法です。
npx crawlee create my-crawler
対話形式でテンプレートを選べます。Playwright版、Puppeteer版、Cheerio版など、用途に応じて選択できるのが便利。
手動でセットアップする方法
既存プロジェクトに組み込む場合は、必要なパッケージを個別にインストールします。
# Playwrightを使う場合
npm install crawlee playwright
# Puppeteerを使う場合
npm install crawlee puppeteer
# HTTPのみ(Cheerio)の場合
npm install crawlee
基本的な使い方
シンプルなHTTPクローリング
まずは一番シンプルなパターンから。静的なHTMLページからデータを取得する例です。
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ request, $, enqueueLinks, pushData }) {
// ページタイトルを取得
const title = $('title').text();
// h1タグのテキストを取得
const heading = $('h1').text();
// データを保存
await pushData({
url: request.url,
title,
heading,
});
// ページ内のリンクを自動でキューに追加
await enqueueLinks({
globs: ['https://example.com/**'],
});
},
maxRequestsPerCrawl: 100,
});
await crawler.run(['https://example.com']);
$はCheerioのインスタンスなので、jQueryライクなセレクタでDOM操作できます。enqueueLinksで同一ドメインのリンクを自動収集してくれるのが地味に便利。
Playwrightでブラウザクローリング
SPAやJavaScriptで動的に生成されるコンテンツを取得する場合は、PlaywrightCrawlerを使います。
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
async requestHandler({ request, page, enqueueLinks, pushData }) {
// ページの読み込み完了を待つ
await page.waitForSelector('.product-list');
// 商品情報を取得
const products = await page.$$eval('.product-item', (items) =>
items.map((item) => ({
name: item.querySelector('.product-name')?.textContent?.trim(),
price: item.querySelector('.product-price')?.textContent?.trim(),
}))
);
await pushData({
url: request.url,
products,
});
// 次のページへのリンクをキューに追加
await enqueueLinks({
selector: '.pagination a',
});
},
headless: true,
maxRequestsPerCrawl: 50,
});
await crawler.run(['https://example.com/products']);
pageはPlaywrightのPageオブジェクトなので、クリックやスクロール、スクリーンショット取得なども自由自在です。
プロキシを使う
IPブロック対策でプロキシを使いたい場合も簡単です。
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080',
'http://proxy3.example.com:8080',
],
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
async requestHandler({ request, page, pushData }) {
// 処理内容
},
});
await crawler.run(['https://example.com']);
複数のプロキシを登録しておけば、自動でローテーションしてくれます。
実践的なユースケース
ニュースサイトの記事収集
import { CheerioCrawler, Dataset } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ request, $, enqueueLinks, pushData }) {
// 記事ページかどうか判定
if (request.url.includes('/article/')) {
const article = {
url: request.url,
title: $('h1.article-title').text().trim(),
author: $('.author-name').text().trim(),
publishedAt: $('time').attr('datetime'),
content: $('.article-body').text().trim(),
};
await pushData(article);
} else {
// 一覧ページなら記事リンクをキューに追加
await enqueueLinks({
selector: 'a.article-link',
label: 'ARTICLE',
});
}
},
maxConcurrency: 5,
maxRequestsPerCrawl: 1000,
});
await crawler.run(['https://news-site.example.com']);
// 結果をCSVでエクスポート
const dataset = await Dataset.open();
await dataset.exportToCSV('articles');
ECサイトの商品情報収集
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
async requestHandler({ request, page, pushData, enqueueLinks }) {
// 商品詳細ページの場合
if (request.label === 'PRODUCT') {
await page.waitForSelector('.product-detail');
const product = await page.evaluate(() => {
return {
name: document.querySelector('h1')?.textContent?.trim(),
price: document.querySelector('.price')?.textContent?.trim(),
description: document.querySelector('.description')?.textContent?.trim(),
images: Array.from(document.querySelectorAll('.product-image img'))
.map((img) => (img as HTMLImageElement).src),
specs: Array.from(document.querySelectorAll('.spec-item'))
.map((item) => ({
label: item.querySelector('.label')?.textContent?.trim(),
value: item.querySelector('.value')?.textContent?.trim(),
})),
};
});
await pushData({ url: request.url, ...product });
} else {
// カテゴリページなら商品リンクを収集
await enqueueLinks({
selector: '.product-card a',
label: 'PRODUCT',
});
// ページネーション
await enqueueLinks({
selector: '.pagination .next',
});
}
},
maxConcurrency: 3,
});
await crawler.run(['https://ec-site.example.com/category/electronics']);
注意点
スクレイピングを行う際は、以下の点に注意してください。
- robots.txtの確認: サイトのrobots.txtでクローリングが許可されているか確認
- 利用規約の確認: サイトの利用規約でスクレイピングが禁止されていないか確認
- アクセス頻度: サーバーに過度な負荷をかけないよう、適切な間隔を空ける
- 個人情報の取り扱い: 収集したデータに個人情報が含まれる場合は、適切に管理する
CrawleeはmaxConcurrencyやmaxRequestsPerMinuteなどのオプションで制御できるので、しっかり設定しておくのがおすすめです。
まとめ
Crawleeは、Node.js/TypeScriptでWebスクレイピングをやるなら間違いなく有力な選択肢です。
- HTTP/ブラウザ両対応で柔軟
- リトライ、プロキシ、セッション管理などが自動化されている
- TypeScriptネイティブで開発体験が良い
- 2万スター超えの実績
個人的には、以前はPuppeteerを直接使っていたんですが、Crawleeを使い始めてからは周辺処理のコードがかなり減りました。時短になるという意味でも、導入する価値はあると思います。
公式ドキュメントも充実しているので、興味がある方はぜひ触ってみてください。