cheerio-parsing について
Node.js で Web スクレイピングやデータ抽出パイプラインを作る開発者向けの、Cheerio による HTML 解析ガイドです。セレクター、DOM 操作、安全な抽出、URL 解決、効率化を扱います。
主な機能
- 01CSS セレクターによる要素検索
- 02DOM の走査と絞り込み
- 03テキスト、HTML、属性の抽出
- 04要素がない場合の安全な処理
- 05相対 URL の解決
- 06HTML 解析の効率化
活用シーン
- 01ページから商品情報を抽出
- 02表やリストのデータ収集
- 03ページネーションリンクの検索
- 04再利用可能なスクレイピングツールの構築
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill cheerio-parsingインストール後、Claude Code で/cheerio-parsingを入力して読み込まれたか確認します。
詳細
Cheerio HTML パース
Cheerio、Node.js による HTML パース、DOM 操作、ウェブスクレイピング向けの効率的なデータ抽出パイプライン構築に精通しています。
主な専門分野
- Cheerio API と jQuery に似た構文
- CSS セレクターの最適化
- DOM の走査と操作
- HTML/XML のパース戦略
- HTTP クライアント(axios、got、node-fetch)との統合
- 大きなドキュメントのメモリ効率に優れた処理
- データ抽出のパターンとベストプラクティス
基本原則
- クリーンでモジュール化された抽出関数を記述します
- パースのオーバーヘッドを最小限に抑える効率的なセレクターを使用します
- 不正な形式の HTML を適切に処理します
- 要素が見つからない場合に適切なエラー処理を実装します
- 再利用可能なスクレイピングユーティリティを設計します
- 適切な場合は関数型プログラミングのパターンに従います
基本設定
npm install cheerio axios
HTML の読み込み
const cheerio = require('cheerio');
const axios = require('axios');
// Load from string
const $ = cheerio.load('<html><body><h1>Hello</h1></body></html>');
// Load with options
const $ = cheerio.load(html, {
xmlMode: false, // Parse as XML
decodeEntities: true, // Decode HTML entities
lowerCaseTags: false, // Keep tag case
lowerCaseAttributeNames: false
});
// Fetch and parse
async function fetchAndParse(url) {
const response = await axios.get(url);
return cheerio.load(response.data);
}
要素の選択
CSS セレクター
// By tag
$('h1')
// By class
$('.article')
// By ID
$('#main-content')
// By attribute
$('[data-id="123"]')
$('a[href^="https://"]') // Starts with
$('a[href$=".pdf"]') // Ends with
$('a[href*="example"]') // Contains
// Combinations
$('div.article > h2') // Direct child
$('div.article h2') // Any descendant
$('h2 + p') // Adjacent sibling
$('h2 ~ p') // General sibling
// Pseudo-selectors
$('li:first-child')
$('li:last-child')
$('li:nth-child(2)')
$('li:nth-child(odd)')
$('tr:even')
$('input:not([type="hidden"])')
$('p:contains("specific text")')
複数のセレクター
// Select multiple types
$('h1, h2, h3')
// Chain selections
$('.article').find('.title')
データの抽出
テキストコンテンツ
// Get text (includes child text)
const text = $('h1').text();
// Get trimmed text
const text = $('h1').text().trim();
// Get HTML
const html = $('div.content').html();
// Get outer HTML
const outerHtml = $.html($('div.content'));
属性
// Get attribute
const href = $('a').attr('href');
const src = $('img').attr('src');
// Get data attributes
const id = $('div').data('id'); // data-id attribute
// Check if attribute exists
const hasClass = $('div').hasClass('active');
複数の要素
// Iterate with each
const items = [];
$('.product').each((index, element) => {
items.push({
name: $(element).find('.name').text().trim(),
price: $(element).find('.price').text().trim(),
url: $(element).find('a').attr('href')
});
});
// Map to array
const titles = $('h2').map((i, el) => $(el).text()).get();
// Filter elements
const featured = $('.product').filter('.featured');
// First/Last
const first = $('li').first();
const last = $('li').last();
// Get by index
const third = $('li').eq(2);
DOM の走査
ナビゲーション
// Parent
$('span').parent()
$('span').parents() // All ancestors
$('span').parents('.container') // Specific ancestor
$('span').closest('.wrapper') // Nearest ancestor matching selector
// Children
$('ul').children() // Direct children
$('ul').children('li.active') // Filtered children
$('div').contents() // Including text nodes
// Siblings
$('li').siblings()
$('li').next()
$('li').nextAll()
$('li').prev()
$('li').prevAll()
絞り込み
// Filter by selector
$('li').filter('.active')
// Filter by function
$('li').filter((i, el) => $(el).data('price') > 100)
// Find within selection
$('.article').find('img')
// Check conditions
$('li').is('.active') // Returns boolean
$('li').has('span') // Has descendant matching selector
データ抽出のパターン
テーブルの抽出
function extractTable(tableSelector) {
const $ = this;
const headers = [];
const rows = [];
// Get headers
$(tableSelector).find('th').each((i, el) => {
headers.push($(el).text().trim());
});
// Get rows
$(tableSelector).find('tbody tr').each((i, row) => {
const rowData = {};
$(row).find('td').each((j, cell) => {
rowData[headers[j]] = $(cell).text().trim();
});
rows.push(rowData);
});
return rows;
}
リストの抽出
function extractList(selector, itemExtractor) {
return $(selector).map((i, el) => itemExtractor($(el))).get();
}
// Usage
const products = extractList('.product', ($el) => ({
name: $el.find('.name').text().trim(),
price: parseFloat($el.find('.price').text().replace('$', '')),
image: $el.find('img').attr('src'),
link: $el.find('a').attr('href')
}));
ページネーションリンク
function extractPaginationLinks() {
return $('.pagination a')
.map((i, el) => $(el).attr('href'))
.get()
.filter(href => href && !href.includes('#'));
}
欠損データの処理
// Safe extraction with defaults
function safeText(selector, defaultValue = '') {
const el = $(selector);
return el.length ? el.text().trim() : defaultValue;
}
function safeAttr(selector, attr, defaultValue = null) {
const el = $(selector);
return el.length ? el.attr(attr) : defaultValue;
}
// Optional chaining pattern
const price = $('.price').first().text()?.trim() || 'N/A';
URL の解決
const { URL } = require('url');
function resolveUrl(baseUrl, relativeUrl) {
if (!relativeUrl) return null;
try {
return new URL(relativeUrl, baseUrl).href;
} catch {
return relativeUrl;
}
}
// Usage
const baseUrl = 'https://example.com/products/';
$('a').each((i, el) => {
const href = $(el).attr('href');
const absoluteUrl = resolveUrl(baseUrl, href);
console.log(absoluteUrl);
});
パフォーマンスの最適化
// Cache selections
const $products = $('.product');
$products.each((i, el) => {
const $product = $(el); // Wrap once
// Use $product multiple times
});
// Limit parsing scope
const $article = $('.article');
const title = $article.find('.title').text(); // Searches only within article
// Use specific selectors
// Good
$('div.product > h2.title')
// Less efficient
$('div').find('.product').find('h2').filter('.title')
スクレイピングの完全な例
const cheerio = require('cheerio');
const axios = require('axios');
async function scrapeProducts(url) {
const response = await axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; MyScraper/1.0)'
}
});
const $ = cheerio.load(response.data);
const products = [];
$('.product-card').each((index, element) => {
const $el = $(element);
products.push({
name: $el.find('.product-title').text().trim(),
price: parseFloat(
$el.find('.price').text().replace(/[^0-9.]/g, '')
),
rating: parseFloat($el.find('.rating').attr('data-rating')) || null,
image: $el.find('img').attr('src'),
url: new URL($el.find('a').attr('href'), url).href,
inStock: !$el.find('.out-of-stock').length
});
});
return products;
}
// With error handling
async function safeScrape(url) {
try {
return await scrapeProducts(url);
} catch (error) {
console.error(`Failed to scrape ${url}:`, error.message);
return [];
}
}
主な依存関係
- cheerio
- axios(HTTP クライアント)
- got(代替の HTTP クライアント)
- node-fetch(Node.js 向け Fetch API)
- p-limit(同時実行数の制御)
ベストプラクティス
- 要素が見つからない場合は、常に適切に処理します
- パフォーマンスを高めるため、具体的なセレクターを使用します
- 再利用する jQuery ラッパー要素はキャッシュします
- 抽出したテキストを正規化します(空白をトリミングします)
- 相対 URL を絶対 URL に解決します
- 抽出したデータの型を検証します
- 複数のページをスクレイピングする場合は、レート制限を実装します
- 適切な User-Agent ヘッダーを使用します
- 文字エンコーディングの問題に対処します
- デバッグ用に抽出の失敗を記録します
コメント
Markdown 対応