cheerio-parsing
Node.jsでCheerioを使ったHTML/XMLパースの専門的なガイダンスを提供し、DOMトラバーサル、データ抽出、効率的なスクレイピングパイプラインのベストプラクティスを網羅します。
description の原文を見る
Expert guidance for HTML/XML parsing using Cheerio in Node.js with best practices for DOM traversal, data extraction, and efficient scraping pipelines.
SKILL.md 本文
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');
// 文字列から読み込み
const $ = cheerio.load('<html><body><h1>Hello</h1></body></html>');
// オプション付きで読み込み
const $ = cheerio.load(html, {
xmlMode: false, // XML として解析
decodeEntities: true, // HTML エンティティをデコード
lowerCaseTags: false, // タグの大文字小文字を保持
lowerCaseAttributeNames: false
});
// フェッチしてパース
async function fetchAndParse(url) {
const response = await axios.get(url);
return cheerio.load(response.data);
}
要素の選択
CSS セレクタ
// タグで選択
$('h1')
// クラスで選択
$('.article')
// ID で選択
$('#main-content')
// 属性で選択
$('[data-id="123"]')
$('a[href^="https://"]') // で始まる
$('a[href$=".pdf"]') // で終わる
$('a[href*="example"]') // を含む
// 組み合わせ
$('div.article > h2') // 直接の子要素
$('div.article h2') // 任意の子孫要素
$('h2 + p') // 隣接する兄弟要素
$('h2 ~ p') // 一般的な兄弟要素
// 疑似セレクタ
$('li:first-child')
$('li:last-child')
$('li:nth-child(2)')
$('li:nth-child(odd)')
$('tr:even')
$('input:not([type="hidden"])')
$('p:contains("specific text")')
複数セレクタ
// 複数の型を選択
$('h1, h2, h3')
// 選択をチェーン
$('.article').find('.title')
データ抽出
テキスト内容
// テキストを取得 (子要素のテキストを含む)
const text = $('h1').text();
// トリミングされたテキストを取得
const text = $('h1').text().trim();
// HTML を取得
const html = $('div.content').html();
// 外側の HTML を取得
const outerHtml = $.html($('div.content'));
属性
// 属性を取得
const href = $('a').attr('href');
const src = $('img').attr('src');
// data 属性を取得
const id = $('div').data('id'); // data-id 属性
// 属性が存在するか確認
const hasClass = $('div').hasClass('active');
複数要素
// 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')
});
});
// 配列にマップ
const titles = $('h2').map((i, el) => $(el).text()).get();
// 要素をフィルタ
const featured = $('.product').filter('.featured');
// 最初/最後
const first = $('li').first();
const last = $('li').last();
// インデックスで取得
const third = $('li').eq(2);
DOM トラバーサル
ナビゲーション
// 親要素
$('span').parent()
$('span').parents() // すべての祖先要素
$('span').parents('.container') // 特定の祖先要素
$('span').closest('.wrapper') // セレクタにマッチする最も近い祖先要素
// 子要素
$('ul').children() // 直接の子要素
$('ul').children('li.active') // フィルタされた子要素
$('div').contents() // テキストノードを含む
// 兄弟要素
$('li').siblings()
$('li').next()
$('li').nextAll()
$('li').prev()
$('li').prevAll()
フィルタリング
// セレクタでフィルタ
$('li').filter('.active')
// 関数でフィルタ
$('li').filter((i, el) => $(el).data('price') > 100)
// 選択範囲内で検索
$('.article').find('img')
// 条件をチェック
$('li').is('.active') // ブール値を返す
$('li').has('span') // セレクタにマッチする子孫を持つ
データ抽出パターン
テーブル抽出
function extractTable(tableSelector) {
const $ = this;
const headers = [];
const rows = [];
// ヘッダーを取得
$(tableSelector).find('th').each((i, el) => {
headers.push($(el).text().trim());
});
// 行を取得
$(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();
}
// 使用例
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('#'));
}
不足データの処理
// デフォルト値での安全な抽出
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;
}
// オプショナルチェーニングパターン
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;
}
}
// 使用例
const baseUrl = 'https://example.com/products/';
$('a').each((i, el) => {
const href = $(el).attr('href');
const absoluteUrl = resolveUrl(baseUrl, href);
console.log(absoluteUrl);
});
パフォーマンス最適化
// 選択をキャッシュ
const $products = $('.product');
$products.each((i, el) => {
const $product = $(el); // 一度ラップ
// $product を複数回使用
});
// パース範囲を限定
const $article = $('.article');
const title = $article.find('.title').text(); // article 内でのみ検索
// 特定のセレクタを使用
// 良い
$('div.product > h2.title')
// 効率が低い
$('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;
}
// エラーハンドリング付き
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 ヘッダーを使用する
- 文字エンコーディングの問題に対応する
- デバッグ用に抽出の失敗をログに記録する
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- mindrally
- リポジトリ
- mindrally/skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/mindrally/skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。