メインコンテンツまでスキップ

Cursor での Web スクレイピング:完全ガイド

Web scraping in Cursor

Web スクレイピングは、Web サイトからプログラムでデータを抽出するプロセスです。価格監視、リードジェネレーション、ニュース集約、市場調査、機械学習用のデータ収集を可能にします。Node.js エコシステムには3つの主要なツールがあります。ヘッドレス Chrome 自動化のための Puppeteer、クロスブラウザ自動化のための Playwright、そして高速な HTML 解析のための Cheerio です。Cursor は、統合された AI アシスタントと組み込みの TypeScript 言語サーバーにより、スクレイピングプロジェクトに最適なエディタです。ブラウザ API の IntelliSense を提供し、セレクタチェーンを自動補完し、Web ページの自然言語記述から抽出ロジックを生成できます。

このガイドでは、Cursor での完全な Web スクレイピングワークフローをカバーします:適切なツールの選択、Puppeteer と Playwright のセットアップ、Cheerio による HTML 解析、抽出パターンの実装、アンチボット対策の回避、AI 支援抽出の活用。

適切なツールの選択​

各スクレイピングツールには独自の強みがあります。それぞれをいつ使用するかを理解することが、効果的なスクレイピング戦略の基盤です。

ツール最適ブラウザ速度複雑さ
Cheerio静的 HTML、高速解析なし非常に速い低
Puppeteer動的 JS レンダリングページChrome/Chromium中中
Playwrightクロスブラウザ、モダン SPAChrome、Firefox、WebKit中中高
Puppeteer + CheerioハイブリッドアプローチChrome速い中
ヒント

Cursor に依頼:「Web サイトをスクレイピングする必要があります。必要なデータはページ読み込み後に JavaScript でレンダリングされます。どのツールを使うべきで、その理由は?」Cursor が Puppeteer または Playwright を推奨し、Cheerio 単独では動的にレンダリングされたコンテンツをキャプチャできない理由を説明します。

Cursor でのスクレイピングプロジェクトのセットアップ​

プロジェクトの初期化​

mkdir web-scraper && cd web-scraper
npm init -y
npm install typescript tsx @types/node --save-dev
npx tsc --init
ヒント

tsx を使用して、別のビルドステップなしで TypeScript スクレイピングスクリプトを直接実行できます。Cursor の統合ターミナル(Ctrl+`)から npx tsx scraper.ts を実行してスクリプトをすぐにテストできます。

Puppeteer のインストール​

npm install puppeteer

Puppeteer はデフォルトでバンドルされた Chromium をダウンロードします。インストールを確認:

// test-puppeteer.ts
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();

Playwright のインストール​

npm install playwright
npx playwright install # Downloads browser binaries

確認:

// test-playwright.ts
import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();

Cheerio のインストール​

npm install cheerio

Cheerio はブラウザなしで HTML 文字列を解析し、静的ページで最も速いオプションです:

// test-cheerio.ts
import * as cheerio from 'cheerio';

const html = '<div class="item"><h2>Title</h2><span class="price">$9.99</span></div>';
const $ = cheerio.load(html);
console.log($('.item h2').text()); // "Title"
console.log($('.item .price').text()); // "$9.99"
警告

Cheerio は JavaScript を実行できません。対象ページがクライアントサイド JS(React、Vue など)でコンテンツを動的にレンダリングする場合、Cheerio は初期 HTML スケルトンのみを確認します。これらのサイトには Puppeteer または Playwright を使用してください。

Puppeteer でのスクレイピング​

Puppeteer は Chrome または Chromium を制御するための高レベル API を提供します。JavaScript レンダリングページのスクレイピング、ログインの処理、ページ要素とのインタラクションに最適です。

基本的なページスクレイピング​

// scrape-products-puppeteer.ts
import puppeteer from 'puppeteer';
import * as cheerio from 'cheerio';

interface Product {
name: string;
price: string;
rating: string;
url: string;
}

async function scrapeProducts(): Promise<Product[]> {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

try {
const page = await browser.newPage();

// Set a realistic viewport
await page.setViewport({ width: 1920, height: 1080 });

// Set a realistic user agent
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36'
);

// Navigate and wait for the product grid to render
await page.goto('https://example-store.com/products', {
waitUntil: 'networkidle2',
timeout: 30000,
});

// Wait for the product cards to appear
await page.waitForSelector('.product-card', { timeout: 10000 });

// Scroll to trigger lazy-loaded content
await autoScroll(page);

// Extract data from the page
const products = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product-card')).map((card) => ({
name: card.querySelector('.product-name')?.textContent?.trim() ?? '',
price: card.querySelector('.product-price')?.textContent?.trim() ?? '',
rating: card.querySelector('.product-rating')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);

return products;
} finally {
await browser.close();
}
}

async function autoScroll(page: puppeteer.Page) {
await page.evaluate(async () => {
await new Promise<void>((resolve) => {
let totalHeight = 0;
const distance = 100;
const timer = setInterval(() => {
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= document.body.scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
}

const products = await scrapeProducts();
console.log(`Scraped ${products.length} products`);
console.log(products[0]);
ヒント

autoScroll 関数は無限スクロールページに不可欠です。多くのサイトはスクロール時に遅延ロードで製品を読み込みます。スクロールしないと、Puppeteer は初期ロードされたアイテムのみをキャプチャします。

ページネーションの処理​

async function scrapeAllPages(baseUrl: string): Promise<Product[]> {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
const allProducts: Product[] = [];
let currentPage = 1;
let hasNextPage = true;

try {
while (hasNextPage) {
await page.goto(`${baseUrl}?page=${currentPage}`, { waitUntil: 'networkidle2' });
await page.waitForSelector('.product-card', { timeout: 10000 });

const products = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product-card')).map((card) => ({
name: card.querySelector('.product-name')?.textContent?.trim() ?? '',
price: card.querySelector('.product-price')?.textContent?.trim() ?? '',
rating: card.querySelector('.product-rating')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);
allProducts.push(...products);

hasNextPage = await page.evaluate(
() => document.querySelector('.pagination .next:not(.disabled)') !== null
);
currentPage++;
await new Promise((r) => setTimeout(r, 2000));
}
} finally {
await browser.close();
}
return allProducts;
}
警告

ページリクエスト間には常に遅延を追加してください。速すぎるスクレイピングはレート制限、IP ブロック、CAPTCHA チャレンジを引き起こす可能性があります。リクエスト間の 1-3 秒の遅延が良い出発点です。より大規模なクロールには、プロキシローテーションサービスを使用してください。

ログインが必要なページの処理​

async function scrapeLoggedInPage(email: string, password: string) {
const browser = await puppeteer.launch({ headless: false }); // Show browser for debugging
const page = await browser.newPage();

try {
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
await page.type('#email', email, { delay: 50 });
await page.type('#password', password, { delay: 50 });

await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle2' }),
page.click('#submit'),
]);

// Verify login success
const isLoggedIn = await page.evaluate(() => !document.querySelector('.login-error'));
if (!isLoggedIn) throw new Error('Login failed');

// Scrape the protected page
await page.goto('https://example.com/dashboard', { waitUntil: 'networkidle2' });
return await page.evaluate(() => document.querySelector('.dashboard-data')?.textContent);
} finally {
await browser.close();
}
}

Playwright でのスクレイピング​

Playwright はマイクロソフトのクロスブラウザ自動化ツールです。Chromium、Firefox、WebKit をサポートし、Puppeteer よりモダンな API を持っています。

基本的な Playwright スクレイピング​

// scrape-playwright.ts
import { chromium } from 'playwright';

interface Article {
title: string;
author: string;
date: string;
summary: string;
url: string;
}

async function scrapeArticles(): Promise<Article[]> {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
});

const page = await context.newPage();

try {
await page.goto('https://example-blog.com', {
waitUntil: 'domcontentloaded',
timeout: 30000,
});

// Wait for articles to load
await page.waitForSelector('.article-card', { timeout: 10000 });

const articles = await page.$$eval('.article-card', (cards) =>
cards.map((card) => ({
title: card.querySelector('.article-title')?.textContent?.trim() ?? '',
author: card.querySelector('.article-author')?.textContent?.trim() ?? '',
date: card.querySelector('.article-date')?.textContent?.trim() ?? '',
summary: card.querySelector('.article-summary')?.textContent?.trim() ?? '',
url: card.querySelector('a')?.href ?? '',
}))
);

return articles;
} finally {
await browser.close();
}
}

ネットワークインターセプションによる SPA スクレイピング​

Playwright はネットワークリクエストをインターセプトして API レスポンスを直接キャプチャでき、レンダリングされた HTML を解析する必要がありません:

async function scrapeViaAPI() {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();

const apiResponses: any[] = [];

// Intercept API responses
page.on('response', async (response) => {
if (response.url().includes('/api/products') && response.status() === 200) {
const json = await response.json();
apiResponses.push(json);
}
});

await page.goto('https://example-store.com', { waitUntil: 'networkidle' });

await browser.close();

// Now you have the raw JSON data, no HTML parsing needed
return apiResponses;
}
ヒント

ネットワークインターセプションはモダン SPA をスクレイピングする最も信頼性の高い方法です。レンダリングされた HTML を解析する代わりに、ページが取得する JSON API レスポンスをキャプチャします。Cursor に依頼:「このページの XHR リクエストをインターセプトして、API レスポンスから製品データを抽出して。」Cursor が Playwright レスポンスリスナーを生成します。

Cheerio による HTML 解析​

Cheerio は HTML 文字列を解析するための jQuery ライクな API を提供します。ページをレンダリングしたり JavaScript を実行したりしないため、ブラウザベースのスクレイピングより 10-100 倍高速です。

フェッチと解析​

// scrape-cheerio.ts
import * as cheerio from 'cheerio';

interface Book {
title: string;
price: number;
availability: string;
rating: number;
}

async function scrapeBooks(): Promise<Book[]> {
const response = await fetch('https://books.toscrape.com/', {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
},
});

const html = await response.text();
const $ = cheerio.load(html);

const books: Book[] = [];

$('.product_pod').each((_, element) => {
const $el = $(element);
const title = $el.find('h3 a').attr('title') ?? '';
const priceText = $el.find('.price_color').text();
const price = parseFloat(priceText.replace(/[^0-9.]/g, ''));
const availability = $el.find('.instock').text().trim();

// Parse rating from class name (e.g., "star-rating Three" -> 3)
const ratingClass = $el.find('.star-rating').attr('class') ?? '';
const ratingMap: Record<string, number> = {
One: 1, Two: 2, Three: 3, Four: 4, Five: 5,
};
const ratingWord = ratingClass.split(' ')[1];
const rating = ratingMap[ratingWord] ?? 0;

books.push({ title, price, availability, rating });
});

return books;
}

Cheerio セレクタパターン​

Cheerio は jQuery ライクなセレクタを使用します:クラスには $('.class')、ID には $('#id')、属性マッチングには $('a[href*="/product/"]')、DOM トラバーサルと抽出には .parent()、.siblings()、.children()、.text()、.html()、.attr() などのメソッドを使用します。

ヒント

jQuery セレクタを知っていれば、Cheerio を知っています。Cursor に依頼:「この HTML 構造からすべての製品名、価格、画像 URL を抽出する Cheerio セレクタチェーンを書いて」と HTML を貼り付けてください。Cursor が正しいセレクタを生成します。

データ抽出パターン​

テーブルからの構造化データ抽出​

async function scrapeTable(): Promise<Record<string, string>[]> {
const response = await fetch('https://example.com/data-table');
const html = await response.text();
const $ = cheerio.load(html);

// Get headers
const headers: string[] = [];
$('table thead th').each((_, el) => {
headers.push($(el).text().trim().toLowerCase().replace(/\s+/g, '_'));
});

// Extract rows
const rows: Record<string, string>[] = [];
$('table tbody tr').each((_, rowEl) => {
const cells: string[] = [];
$(rowEl).find('td').each((_, cellEl) => {
cells.push($(cellEl).text().trim());
});

const row: Record<string, string> = {};
headers.forEach((header, index) => {
row[header] = cells[index] ?? '';
});
rows.push(row);
});

return rows;
}

JSON-LD データの抽出​

多くの Web サイトは JSON-LD 形式で構造化データを埋め込んでいます。これは利用可能な場合、最も信頼性の高い抽出方法です:

import * as cheerio from 'cheerio';

async function scrapeJsonLd(url: string) {
const response = await fetch(url);
const html = await response.text();
const $ = cheerio.load(html);

const jsonLdData: any[] = [];

$('script[type="application/ld+json"]').each((_, el) => {
try {
const data = JSON.parse($(el).contents().text());
if (Array.isArray(data)) {
jsonLdData.push(...data);
} else {
jsonLdData.push(data);
}
} catch (e) {
console.warn('Failed to parse JSON-LD:', e);
}
});

return jsonLdData;
}

// Usage
const structuredData = await scrapeJsonLd('https://example.com/product/123');
// structuredData[0] might contain: { "@type": "Product", "name": "...", "offers": { "price": "..." } }
ヒント

JSON-LD は構造化データを抽出する最も簡単で信頼性の高い方法です。Cursor に依頼:「このページに JSON-LD 構造化データがあるか確認し、製品情報を抽出して。」サイトが schema.org マークアップを使用している場合、Cursor は脆弱な CSS セレクタなしで解析します。

抽出データの保存​

import { writeFileSync } from 'fs';

// Save to JSON
function saveToJson(data: any[], filename: string) {
writeFileSync(filename, JSON.stringify(data, null, 2));
console.log(`Saved ${data.length} records to ${filename}`);
}

// Save to CSV
function saveToCsv(data: Record<string, any>[], filename: string) {
if (data.length === 0) return;
const headers = Object.keys(data[0]);
const csvLines = [
headers.join(','),
...data.map((row) =>
headers.map((h) => `"${String(row[h] ?? '').replace(/"/g, '""')}"`).join(',')
),
];
writeFileSync(filename, csvLines.join('\n'));
}

アンチボット回避技術​

モダンな Web サイトは自動化されたスクレイピングを検出・ブロックするためにアンチボット保護(Cloudflare、reCAPTCHA、Datadome、PerimeterX)を展開しています。以下は一般的な保護を回避する戦略です。

リアルなヘッダーの設定​

const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
};

const response = await fetch(url, { headers });

Puppeteer-Stealth の使用​

npm install puppeteer-extra puppeteer-extra-plugin-stealth
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();

// The stealth plugin patches common detection vectors:
// - navigator.webdriver = false
// - chrome.runtime object
// - permissions API
// - plugins array
// - languages array
// - WebGL vendor/renderer

await page.goto('https://bot-protected-site.com');
警告

ステルスプラグインは役立ちますが、万能ではありません。Cloudflare Turnstile のような高度なアンチボットサービスは、単純な navigator プロパティチェックを超えた TLS フィンガープリンティング、行動分析、チャレンジ・レスポンスメカニズムを使用します。重度に保護されたサイトの本番スクレイピングには、CAPTCHA 解決組み込みの商用プロキシサービスを検討してください。

プロキシの使用​

// Puppeteer with proxy
const browser = await puppeteer.launch({
headless: true,
args: [
`--proxy-server=http://${proxyHost}:${proxyPort}`,
'--no-sandbox',
],
});

await page.authenticate({
username: proxyUsername,
password: proxyPassword,
});

// Playwright with proxy
const browser = await chromium.launch({
headless: true,
proxy: {
server: `http://${proxyHost}:${proxyPort}`,
username: proxyUsername,
password: proxyPassword,
},
});

User Agent のローテーション​

const userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
];

const randomUA = userAgents[Math.floor(Math.random() * userAgents.length)];
await page.setUserAgent(randomUA);

遅延と人間らしい振る舞いの処理​

async function humanLikeScroll(page: puppeteer.Page) {
const totalHeight = await page.evaluate(() => document.body.scrollHeight);
let scrolled = 0;
while (scrolled < totalHeight) {
const scrollAmount = Math.floor(Math.random() * 200) + 100;
await page.evaluate((y) => window.scrollBy(0, y), scrollAmount);
scrolled += scrollAmount;
await new Promise((r) => setTimeout(r, Math.random() * 1500 + 500));
}
}

async function randomMouseMovement(page: puppeteer.Page) {
await page.mouse.move(Math.random() * 800, Math.random() * 600);
await new Promise((r) => setTimeout(r, Math.random() * 500 + 200));
await page.mouse.move(Math.random() * 800, Math.random() * 600);
}
ヒント

Cursor に依頼:「Puppeteer スクレイパーをより人間らしく振る舞わせて。ランダムなマウス移動、可変スクロール速度、アクション間のランダムな遅延を追加して。」Cursor が行動模倣関数を生成し、スクレイピングフローに統合します。

Cursor での AI 支援抽出​

Cursor の AI アシスタントは、ページの記述からスクレイピングロジックを生成し、HTML スニペットからセレクタを特定し、失敗した抽出をデバッグできます。

HTML からのセレクタ生成​

Cursor Chat(Ctrl+L)を開いて HTML スニペットを貼り付け:

Here is a product card's HTML structure:

<div class="product-item" data-id="123">
<img src="/img/product.jpg" alt="Product Name" />
<h3 class="product-title">Product Name</h3>
<span class="price-current">$29.99</span>
<span class="price-original">$49.99</span>
<span class="rating" data-rating="4.5">4.5 stars</span>
<span class="stock">In Stock</span>
</div>

Generate a Cheerio extraction function with a typed interface.
Include: productId, name, currentPrice, originalPrice, rating, stockStatus, imageUrl.

Cursor が生成:

interface ScrapedProduct {
productId: string;
name: string;
currentPrice: number;
originalPrice: number;
rating: number;
stockStatus: string;
imageUrl: string;
}

function extractProduct($: cheerio.CheerioAPI, element: any): ScrapedProduct {
const $el = $(element);
const parsePrice = (sel: string) =>
parseFloat($el.find(sel).text().replace(/[^0-9.]/g, '')) || 0;

return {
productId: $el.attr('data-id') ?? '',
name: $el.find('.product-title').text().trim(),
currentPrice: parsePrice('.price-current'),
originalPrice: parsePrice('.price-original'),
rating: parseFloat($el.find('.rating').attr('data-rating') ?? '0') || 0,
stockStatus: $el.find('.stock').text().trim(),
imageUrl: $el.find('img').attr('src') ?? '',
};
}
ヒント

Cursor にセレクタの生成を依頼する際は、常に代表的な HTML サンプルを貼り付けてください。これによりセレクタが推測ではなく実際の DOM 構造に一致します。Cursor はオプションフィールドの欠落などのエッジケースも特定し、フォールバックを追加します。

AI 支援デバッグ​

スクレイパーが空の結果を返す場合、Cursor に助けを求めてください:

My Puppeteer scraper is returning an empty array. The selector
".product-card" should match elements but page.evaluate returns [].
Here is my code. The page loads successfully and I can see the elements
in the browser. What could be wrong?

Cursor が一般的な問題を特定:

  1. セレクタが Shadow DOM 内にある可能性がある。
  2. コンテンツが iframe 内にある可能性がある。
  3. 最近の再設計後にページが異なるセレクタを使用している可能性がある。
  4. waitUntil オプションが不十分な可能性がある。
警告

Web サイトは HTML 構造を頻繁に変更します。今日動作するスクレイパーが明日壊れる可能性があります。防御的にスクレイパーを構築してください。複数のフォールバックセレクタを使用し、抽出失敗をログに記録し、セレクタが一致しなくなったことを検出するモニタリングを設定してください。

ベストプラクティスと倫理​

robots.txt の尊重​

async function checkRobotsTxt(url: string): Promise<boolean> {
const robotsUrl = `${new URL(url).origin}/robots.txt`;
try {
const robotsTxt = await (await fetch(robotsUrl)).text();
const path = new URL(url).pathname;
for (const line of robotsTxt.split('\n')) {
if (line.trim().toLowerCase().startsWith('disallow:')) {
const disallowed = line.split(':')[1].trim();
if (disallowed && path.startsWith(disallowed)) return false;
}
}
return true;
} catch {
return true; // If robots.txt doesn't exist, assume allowed
}
}
警告

スクレイピング前に常に robots.txt を確認してください。これは Web サイトがどのページをスクレイピングできるかを宣言するための標準プロトコルです。robots.txt の違反は IP ブロックや法的問題につながる可能性があります。スクレイピング操作の前に上記の checkRobotsTxt 関数を使用してください。

レート制限​

async function rateLimitedDelay() {
const delay = Math.random() * 2000 + 1000; // 1-3 second delay
await new Promise((r) => setTimeout(r, delay));
}

// Usage: call between each page request
await rateLimitedDelay();

まとめ​

Cursor での Web スクレイピングは、Puppeteer、Playwright、Cheerio の力と AI 支援コード生成を組み合わせます。Cheerio は高速な静的 HTML 解析の頼れる選択肢、Puppeteer は Chrome 自動化で JavaScript レンダリングページを処理し、Playwright はモダン SPA のためのクロスブラウザサポートとネットワークインターセプションを提供します。アンチボット技術(ステルスプラグイン、プロキシローテーション、人間らしい振る舞い)は保護されたサイトへのアクセスを助け、Cursor の AI アシスタントは HTML スニペットからセレクタを生成し、失敗した抽出をデバッグし、自然言語の記述から完全なスクレイピングパイプラインを記述します。対象に適したツールの選択から始め、フォールバックセレクタとレート制限で防御的に構築し、常に robots.txt を尊重し、Cursor に抽出ロジックを生成させて、データ品質とパイプラインの信頼性に集中しましょう。