DE EN ES FR ID JA KO PT RU TH VI ZH

포괄적인 Cheerio 튜토리얼 가이드

가이드 1: Cheerio로 HTML 테이블에서 데이터 추출하는 방법

웹 스크래핑에서 테이블 데이터를 추출하는 것은 HTML 문서 작업 시 가장 일반적인 작업 중 하나입니다. Cheerio를 사용하면 테이블 구조를 파싱하고 의미 있는 데이터를 추출하는 것이 매우 쉬워집니다. 이 가이드는 Cheerio를 사용하여 HTML 테이블에서 데이터를 추출하는 다양한 기술을 안내합니다.

환경 설정

먼저 프로젝트에 Cheerio를 설치하세요:

npm install cheerio

기본 테이블 구조 이해

추출 기술을 살펴보기 전에 일반적인 HTML 테이블 구조를 이해해 봅시다:

<table id="products">
  <thead>
    <tr>
      <th>Product Name</th>
      <th>Price</th>
      <th>Stock</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Laptop</td>
      <td>$999</td>
      <td>15</td>
    </tr>
    <tr>
      <td>Mouse</td>
      <td>$25</td>
      <td>50</td>
    </tr>
  </tbody>
</table>

1단계: HTML 로드 및 기본 테이블 선택

Cheerio로 HTML 콘텐츠를 로드하는 것부터 시작하세요:

import * as cheerio from 'cheerio';

const html = `
<table id="products">
  <thead>
    <tr>
      <th>Product Name</th>
      <th>Price</th>
      <th>Stock</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Laptop</td>
      <td>$999</td>
      <td>15</td>
    </tr>
    <tr>
      <td>Mouse</td>
      <td>$25</td>
      <td>50</td>
    </tr>
  </tbody>
</table>
`;

const $ = cheerio.load(html);

2단계: 테이블 헤더 추출

테이블 헤더는 데이터의 구조를 제공합니다. 먼저 헤더를 추출하세요:

const headers: string[] = [];
$('#products thead th').each((index, element) => {
  headers.push($(element).text().trim());
});

console.log('Headers:', headers);
// Output: ['Product Name', 'Price', 'Stock']

3단계: 행 데이터 추출

이제 실제 테이블 데이터를 추출합니다. 필요에 따라 여러 가지 접근 방법이 있습니다:

방법 1: 행별 추출

const rows: string[][] = [];
$('#products tbody tr').each((index, row) => {
  const rowData: string[] = [];
  $(row).find('td').each((cellIndex, cell) => {
    rowData.push($(cell).text().trim());
  });
  rows.push(rowData);
});

console.log('Rows:', rows);
// Output: [['Laptop', '$999', '15'], ['Mouse', '$25', '50']]

방법 2: 구조화된 객체 생성

interface Product {
  productName: string;
  price: string;
  stock: number;
}

const products: Product[] = [];
$('#products tbody tr').each((index, row) => {
  const cells = $(row).find('td');
  const product: Product = {
    productName: $(cells[0]).text().trim(),
    price: $(cells[1]).text().trim(),
    stock: parseInt($(cells[2]).text().trim())
  };
  products.push(product);
});

console.log('Products:', products);

4단계: 복잡한 테이블 처리

실제 테이블에는 병합된 셀, 중첩 요소 또는 특수 서식이 있는 경우가 많습니다. 이를 처리하는 방법은 다음과 같습니다:

const complexHtml = `
<table class="sales-data">
  <tr>
    <td rowspan="2">Q1</td>
    <td>January</td>
    <td>$<span class="amount">15000</span></td>
    <td class="status success">✓</td>
  </tr>
  <tr>
    <td>February</td>
    <td>$<span class="amount">18000</span></td>
    <td class="status pending">⏳</td>
  </tr>
</table>
`;

const $complex = cheerio.load(complexHtml);

const salesData = [];
$complex('.sales-data tr').each((index, row) => {
  const cells = $complex(row).find('td');
  const record = {
    quarter: cells.length === 4 ? $complex(cells[0]).text() : 'Q1', // Handle rowspan
    month: $complex(cells[cells.length === 4 ? 1 : 0]).text(),
    amount: $complex(cells[cells.length === 4 ? 2 : 1]).find('.amount').text(),
    status: $complex(cells[cells.length === 4 ? 3 : 2]).attr('class')?.includes('success') ? 'Complete' : 'Pending'
  };
  salesData.push(record);
});

5단계: 고급 필터링 및 데이터 처리

Cheerio의 강력한 선택자를 사용하여 데이터를 필터링하고 처리하세요:

// Extract only rows with specific conditions
const highStockProducts = [];
$('#products tbody tr').each((index, row) => {
  const stock = parseInt($(row).find('td:nth-child(3)').text());
  if (stock > 20) {
    highStockProducts.push({
      name: $(row).find('td:first-child').text(),
      stock: stock
    });
  }
});

// Extract data using attribute selectors
$('table[data-category="electronics"] tbody tr').each((index, row) => {
  // Process electronics category tables only
});

완전한 실무 예제

테이블 데이터 추출을 보여주는 포괄적인 예제입니다:

import * as cheerio from 'cheerio';

// Sample HTML with a product catalog table
const html = `
<!DOCTYPE html>
<html>
<body>
  <table id="product-catalog" class="data-table">
    <thead>
      <tr>
        <th>ID</th>
        <th>Product Name</th>
        <th>Category</th>
        <th>Price</th>
        <th>Stock</th>
        <th>Rating</th>
      </tr>
    </thead>
    <tbody>
      <tr data-id="1">
        <td>001</td>
        <td><a href="/laptop-pro">Laptop Pro</a></td>
        <td>Electronics</td>
        <td>$<span class="price">1299</span></td>
        <td class="stock-high">25</td>
        <td><span class="rating" data-score="4.5">★★★★☆</span></td>
      </tr>
      <tr data-id="2">
        <td>002</td>
        <td><a href="/wireless-mouse">Wireless Mouse</a></td>
        <td>Electronics</td>
        <td>$<span class="price">35</span></td>
        <td class="stock-medium">12</td>
        <td><span class="rating" data-score="4.2">★★★★☆</span></td>
      </tr>
      <tr data-id="3">
        <td>003</td>
        <td><a href="/desk-lamp">LED Desk Lamp</a></td>
        <td>Office</td>
        <td>$<span class="price">89</span></td>
        <td class="stock-low">3</td>
        <td><span class="rating" data-score="4.7">★★★★★</span></td>
      </tr>
    </tbody>
  </table>
</body>
</html>
`;

interface Product {
  id: string;
  name: string;
  category: string;
  price: number;
  stock: number;
  rating: number;
  url: string;
  stockLevel: 'low' | 'medium' | 'high';
}

function extractTableData(html: string): Product[] {
  const $ = cheerio.load(html);
  const products: Product[] = [];

  $('#product-catalog tbody tr').each((index, row) => {
    const $row = $(row);
    
    // Extract basic data
    const id = $row.find('td:nth-child(1)').text().trim();
    const nameElement = $row.find('td:nth-child(2) a');
    const name = nameElement.text().trim();
    const url = nameElement.attr('href') || '';
    const category = $row.find('td:nth-child(3)').text().trim();
    
    // Extract and parse price
    const priceText = $row.find('.price').text();
    const price = parseFloat(priceText);
    
    // Extract stock with level detection
    const stockCell = $row.find('td:nth-child(5)');
    const stock = parseInt(stockCell.text().trim());
    let stockLevel: 'low' | 'medium' | 'high' = 'medium';
    
    if (stockCell.hasClass('stock-low')) stockLevel = 'low';
    else if (stockCell.hasClass('stock-high')) stockLevel = 'high';
    
    // Extract rating
    const rating = parseFloat($row.find('.rating').attr('data-score') || '0');
    
    products.push({
      id,
      name,
      category,
      price,
      stock,
      rating,
      url,
      stockLevel
    });
  });

  return products;
}

// Usage
const products = extractTableData(html);
console.log('Extracted Products:', JSON.stringify(products, null, 2));

// Additional processing examples
const highRatedProducts = products.filter(p => p.rating >= 4.5);
const lowStockProducts = products.filter(p => p.stockLevel === 'low');
const expensiveProducts = products.filter(p => p.price > 100);

console.log(`Found ${highRatedProducts.length} high-rated products`);
console.log(`Found ${lowStockProducts.length} low-stock products`);
console.log(`Found ${expensiveProducts.length} expensive products`);

이 포괄적인 접근 방식은 복잡한 테이블 구조를 처리하고, 다양한 데이터 유형을 추출하며, Cheerio를 사용한 모든 테이블 스크래핑 작업의 기초를 제공합니다.


가이드 2: Cheerio로 폼 데이터와 입력값 스크래핑하는 방법

폼은 웹 페이지의 필수 구성 요소로, 사용자 입력, 선택사항, 설정과 같은 중요한 데이터를 포함합니다. 이 가이드는 Cheerio의 강력한 폼 조작 기능을 사용하여 폼 데이터, 입력값, 폼 구조를 추출하는 방법을 설명합니다.

폼 요소 이해

폼에는 서로 다른 추출 접근 방식이 필요한 다양한 입력 유형이 포함되어 있습니다:

<form id="user-registration">
  <input type="text" name="username" value="john_doe">
  <input type="email" name="email" value="john@example.com">
  <input type="password" name="password" value="secret123">
  <input type="checkbox" name="newsletter" checked>
  <select name="country">
    <option value="us" selected>United States</option>
    <option value="ca">Canada</option>
  </select>
  <textarea name="bio">Software developer...</textarea>
</form>

1단계: 폼 데이터 추출 설정

HTML을 로드하고 폼 구조를 이해하는 것부터 시작하세요:

import * as cheerio from 'cheerio';

const formHtml = `
<form id="contact-form" method="post" action="/submit">
  <div class="form-group">
    <label for="name">Full Name:</label>
    <input type="text" id="name" name="fullName" value="Jane Smith" required>
  </div>
  
  <div class="form-group">
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" value="jane@company.com">
  </div>
  
  <div class="form-group">
    <label>Gender:</label>
    <input type="radio" name="gender" value="male" id="male">
    <label for="male">Male</label>
    <input type="radio" name="gender" value="female" id="female" checked>
    <label for="female">Female</label>
  </div>
  
  <div class="form-group">
    <label for="interests">Interests:</label>
    <input type="checkbox" name="interests" value="coding" checked>
    <label>Coding</label>
    <input type="checkbox" name="interests" value="design">
    <label>Design</label>
    <input type="checkbox" name="interests" value="marketing" checked>
    <label>Marketing</label>
  </div>
  
  <div class="form-group">
    <label for="country">Country:</label>
    <select name="country" id="country">
      <option value="">Select Country</option>
      <option value="us" selected>United States</option>
      <option value="uk">United Kingdom</option>
      <option value="ca">Canada</option>
    </select>
  </div>
  
  <div class="form-group">
    <label for="message">Message:</label>
    <textarea name="message" id="message" rows="4">Hello, I'm interested in your services...</textarea>
  </div>
  
  <button type="submit">Submit</button>
</form>
`;

const $ = cheerio.load(formHtml);

2단계: 텍스트 입력값 추출

텍스트 입력, 이메일 필드, 패스워드는 val() 메소드를 사용하여 추출할 수 있습니다:

// Extract individual input values
const fullName = $('input[name="fullName"]').val();
const email = $('input[name="email"]').val();

console.log('Full Name:', fullName); // "Jane Smith"
console.log('Email:', email); // "jane@company.com"

// Extract all text-type inputs at once
const textInputs: Record<string, string> = {};
$('input[type="text"], input[type="email"], input[type="password"]').each((index, element) => {
  const $input = $(element);
  const name = $input.attr('name');
  const value = $input.val() as string;
  
  if (name) {
    textInputs[name] = value || '';
  }
});

console.log('Text Inputs:', textInputs);

3단계: 라디오 버튼 처리

라디오 버튼은 어떤 옵션이 선택되었는지 확인해야 합니다:

// Get selected radio button value
const selectedGender = $('input[name="gender"]:checked').val();
console.log('Selected Gender:', selectedGender); // "female"

// Get all radio button options for a field
const genderOptions: { value: string; label: string; checked: boolean }[] = [];
$('input[name="gender"]').each((index, element) => {
  const $radio = $(element);
  const value = $radio.val() as string;
  const isChecked = $radio.prop('checked') as boolean;
  const label = $radio.next('label').text() || $radio.attr('id') || '';
  
  genderOptions.push({
    value,
    label: label.trim(),
    checked: isChecked
  });
});

console.log('Gender Options:', genderOptions);

4단계: 체크박스 작업

체크박스는 여러 개의 선택된 값을 가질 수 있습니다:

// Get all checked checkbox values for a field
const selectedInterests: string[] = [];
$('input[name="interests"]:checked').each((index, element) => {
  selectedInterests.push($(element).val() as string);
});

console.log('Selected Interests:', selectedInterests); // ["coding", "marketing"]

// Get detailed checkbox information
const interestOptions = [];
$('input[name="interests"]').each((index, element) => {
  const $checkbox = $(element);
  const value = $checkbox.val() as string;
  const isChecked = $checkbox.prop('checked') as boolean;
  const label = $checkbox.next('label').text().trim();
  
  interestOptions.push({
    value,
    label,
    checked: isChecked
  });
});

5단계: 선택 드롭다운 값 추출

Select 요소는 옵션에 대한 특별한 처리가 필요합니다:

// Get selected option value
const selectedCountry = $('select[name="country"]').val();
console.log('Selected Country:', selectedCountry); // "us"

// Get selected option text
const selectedCountryText = $('select[name="country"] option:selected').text();
console.log('Selected Country Text:', selectedCountryText); // "United States"

// Get all select options
const countryOptions: { value: string; text: string; selected: boolean }[] = [];
$('select[name="country"] option').each((index, element) => {
  const $option = $(element);
  const value = $option.val() as string;
  const text = $option.text().trim();
  const selected = $option.prop('selected') as boolean;
  
  countryOptions.push({ value, text, selected });
});

console.log('Country Options:', countryOptions);

6단계: Textarea 요소 처리

Textarea는 value 속성이 아닌 텍스트로 내용을 포함합니다:

// Extract textarea content
const message = $('textarea[name="message"]').text();
console.log('Message:', message); // "Hello, I'm interested in your services..."

// Alternative method using val()
const messageAlt = $('textarea[name="message"]').val();
console.log('Message (alt):', messageAlt);

7단계: 폼 유효성 검사 및 메타데이터 추출

폼 메타데이터와 유효성 검사 규칙을 추출하세요:

interface FormMetadata {
  action: string;
  method: string;
  enctype?: string;
  fieldCount: number;
  requiredFields: string[];
}

function extractFormMetadata(formSelector: string): FormMetadata {
  const $form = $(formSelector);
  
  const action = $form.attr('action') || '';
  const method = ($form.attr('method') || 'get').toLowerCase();
  const enctype = $form.attr('enctype');
  
  // Count form fields
  const fieldCount = $form.find('input, select, textarea').length;
  
  // Find required fields
  const requiredFields: string[] = [];
  $form.find('input[required], select[required], textarea[required]').each((index, element) => {
    const name = $(element).attr('name');
    if (name) requiredFields.push(name);
  });
  
  return {
    action,
    method: method as 'get' | 'post',
    enctype,
    fieldCount,
    requiredFields
  };
}

const formMetadata = extractFormMetadata('#contact-form');
console.log('Form Metadata:', formMetadata);

8단계: 포괄적인 폼 데이터 직렬화

Cheerio의 내장 직렬화 메소드를 사용하세요:

// Using Cheerio's serialize method
const serializedData = $('#contact-form').serialize();
console.log('Serialized Form:', serializedData);

// Using serializeArray for structured data
const formArray = $('#contact-form').serializeArray();
console.log('Form Array:', formArray);

// Convert to object format
const formObject: Record<string, string | string[]> = {};
formArray.forEach(field => {
  if (formObject[field.name]) {
    // Handle multiple values (checkboxes)
    if (Array.isArray(formObject[field.name])) {
      (formObject[field.name] as string[]).push(field.value);
    } else {
      formObject[field.name] = [formObject[field.name] as string, field.value];
    }
  } else {
    formObject[field.name] = field.value;
  }
});

완전한 실무 예제

포괄적인 폼 데이터 추출 솔루션입니다:

import * as cheerio from 'cheerio';

interface FormData {
  textFields: Record<string, string>;
  radioButtons: Record<string, string>;
  checkboxes: Record<string, string[]>;
  selects: Record<string, { value: string; text: string }>;
  textareas: Record<string, string>;
  metadata: {
    action: string;
    method: string;
    fieldCount: number;
    requiredFields: string[];
  };
}

const complexFormHtml = `
<form id="survey-form" action="/submit-survey" method="post">
  <fieldset>
    <legend>Personal Information</legend>
    <input type="text" name="firstName" value="John" required>
    <input type="text" name="lastName" value="Doe" required>
    <input type="email" name="email" value="john.doe@email.com" required>
    <input type="tel" name="phone" value="+1-555-123-4567">
  </fieldset>
  
  <fieldset>
    <legend>Preferences</legend>
    <div>
      <p>Preferred Contact Method:</p>
      <input type="radio" name="contactMethod" value="email" checked>
      <label>Email</label>
      <input type="radio" name="contactMethod" value="phone">
      <label>Phone</label>
      <input type="radio" name="contactMethod" value="mail">
      <label>Mail</label>
    </div>
    
    <div>
      <p>Interests (select all that apply):</p>
      <input type="checkbox" name="interests" value="technology" checked>
      <label>Technology</label>
      <input type="checkbox" name="interests" value="sports">
      <label>Sports</label>
      <input type="checkbox" name="interests" value="music" checked>
      <label>Music</label>
      <input type="checkbox" name="interests" value="travel">
      <label>Travel</label>
    </div>
  </fieldset>
  
  <fieldset>
    <legend>Location</legend>
    <select name="country" required>
      <option value="">Select Country</option>
      <option value="us" selected>United States</option>
      <option value="ca">Canada</option>
      <option value="uk">United Kingdom</option>
      <option value="au">Australia</option>
    </select>
    
    <select name="timezone">
      <option value="est">Eastern Time</option>
      <option value="cst" selected>Central Time</option>
      <option value="mst">Mountain Time</option>
      <option value="pst">Pacific Time</option>
    </select>
  </fieldset>
  
  <fieldset>
    <legend>Additional Information</legend>
    <textarea name="comments" rows="4">Looking forward to hearing from you!</textarea>
    <textarea name="suggestions" placeholder="Any suggestions?"></textarea>
  </fieldset>
  
  <button type="submit">Submit Survey</button>
  <button type="reset">Reset Form</button>
</form>
`;

function extractCompleteFormData(html: string, formSelector: string): FormData {
  const $ = cheerio.load(html);
  const $form = $(formSelector);
  
  // Extract text fields
  const textFields: Record<string, string> = {};
  $form.find('input[type="text"], input[type="email"], input[type="tel"], input[type="password"]').each((_, element) => {
    const $input = $(element);
    const name = $input.attr('name');
    const value = $input.val() as string;
    if (name) textFields[name] = value || '';
  });
  
  // Extract radio buttons
  const radioButtons: Record<string, string> = {};
  const radioGroups = new Set<string>();
  $form.find('input[type="radio"]').each((_, element) => {
    const name = $(element).attr('name');
    if (name) radioGroups.add(name);
  });
  
  radioGroups.forEach(groupName => {
    const selectedValue = $form.find(`input[name="${groupName}"]:checked`).val() as string;
    if (selectedValue) radioButtons[groupName] = selectedValue;
  });
  
  // Extract checkboxes
  const checkboxes: Record<string, string[]> = {};
  const checkboxGroups = new Set<string>();
  $form.find('input[type="checkbox"]').each((_, element) => {
    const name = $(element).attr('name');
    if (name) checkboxGroups.add(name);
  });
  
  checkboxGroups.forEach(groupName => {
    const values: string[] = [];
    $form.find(`input[name="${groupName}"]:checked`).each((_, element) => {
      values.push($(element).val() as string);
    });
    checkboxes[groupName] = values;
  });
  
  // Extract selects
  const selects: Record<string, { value: string; text: string }> = {};
  $form.find('select').each((_, element) => {
    const $select = $(element);
    const name = $select.attr('name');
    if (name) {
      const $selectedOption = $select.find('option:selected');
      selects[name] = {
        value: $selectedOption.val() as string || '',
        text: $selectedOption.text().trim()
      };
    }
  });
  
  // Extract textareas
  const textareas: Record<string, string> = {};
  $form.find('textarea').each((_, element) => {
    const $textarea = $(element);
    const name = $textarea.attr('name');
    if (name) {
      textareas[name] = $textarea.val() as string || '';
    }
  });
  
  // Extract metadata
  const metadata = {
    action: $form.attr('action') || '',
    method: ($form.attr('method') || 'get').toLowerCase(),
    fieldCount: $form.find('input, select, textarea, button').length,
    requiredFields: $form.find('[required]').map((_, el) => $(el).attr('name')).get().filter(Boolean)
  };
  
  return {
    textFields,
    radioButtons,
    checkboxes,
    selects,
    textareas,
    metadata
  };
}

// Usage
const formData = extractCompleteFormData(complexFormHtml, '#survey-form');

console.log('=== EXTRACTED FORM DATA ===');
console.log('Text Fields:', formData.textFields);
console.log('Radio Buttons:', formData.radioButtons);
console.log('Checkboxes:', formData.checkboxes);
console.log('Select Fields:', formData.selects);
console.log('Textareas:', formData.textareas);
console.log('Metadata:', formData.metadata);

// Additional utility functions
function validateFormData(data: FormData): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];
  
  // Check required fields
  data.metadata.requiredFields.forEach(fieldName => {
    if (!data.textFields[fieldName] && 
        !data.radioButtons[fieldName] && 
        !data.selects[fieldName]?.value) {
      errors.push(`Required field '${fieldName}' is missing`);
    }
  });
  
  return {
    isValid: errors.length === 0,
    errors
  };
}

const validation = validateFormData(formData);
console.log('Validation Result:', validation);

이 포괄적인 접근 방식은 모든 일반적인 폼 요소를 처리하고 유효성 검사 기능과 함께 구조화된 데이터 추출을 제공하므로, 폼 분석 및 데이터 처리 작업에 완벽합니다.


가이드 3: Cheerio로 DOM 요소 탐색 및 수정하는 방법

DOM 탐색과 수정은 웹 스크래핑과 HTML 조작의 핵심 측면입니다. 이 가이드는 DOM 트리를 효과적으로 순회하고, 관련 요소를 찾고, Cheerio의 강력한 탐색 메소드를 사용하여 콘텐츠를 수정하는 방법을 설명합니다.

DOM 트리 구조 이해

탐색 기술을 살펴보기 전에 HTML의 계층적 특성을 이해하는 것이 중요합니다:

<div class="container">           <!-- Parent -->
  <header class="main-header">    <!-- First child -->
    <h1>Page Title</h1>           <!-- Grandchild -->
    <nav>Navigation</nav>         <!-- Sibling to h1 -->
  </header>
  <main class="content">          <!-- Next sibling to header -->
    <article>Article content</article>
    <aside>Sidebar</aside>
  </main>
  <footer>Footer content</footer> <!-- Last child -->
</div>

1단계: 기본 요소 선택 및 탐색

기본적인 탐색 메소드부터 시작하세요:

import * as cheerio from 'cheerio';

const html = `
<div class="blog-post" data-id="123">
  <header class="post-header">
    <h1 class="post-title">Understanding Web Scraping</h1>
    <div class="post-meta">
      <span class="author">John Smith</span>
      <time class="published" datetime="2024-01-15">January 15, 2024</time>
      <div class="tags">
        <span class="tag">web-scraping</span>
        <span class="tag">javascript</span>
        <span class="tag">cheerio</span>
      </div>
    </div>
  </header>
  
  <div class="post-content">
    <p class="intro">Web scraping is a powerful technique...</p>
    <p>In this article, we'll explore...</p>
    <blockquote>
      <p>"Data is the new oil of the digital economy."</p>
      <cite>— Industry Expert</cite>
    </blockquote>
    <p class="conclusion">To summarize...</p>
  </div>
  
  <footer class="post-footer">
    <div class="social-share">
      <button class="share-btn twitter" data-url="https://example.com/post/123">Twitter</button>
      <button class="share-btn facebook" data-url="https://example.com/post/123">Facebook</button>
      <button class="share-btn linkedin" data-url="https://example.com/post/123">LinkedIn</button>
    </div>
    <div class="post-actions">
      <button class="like-btn" data-likes="42">❤️ 42</button>
      <button class="comment-btn" data-comments="8">💬 8</button>
    </div>
  </footer>
</div>
`;

const $ = cheerio.load(html);

// Basic selection
const postTitle = $('.post-title').text();
console.log('Post Title:', postTitle);

// Navigate to parent
const postHeader = $('.post-title').parent();
console.log('Parent class:', postHeader.attr('class')); // "post-header"

// Navigate to closest ancestor with specific selector
const blogPostContainer = $('.post-title').closest('.blog-post');
console.log('Container data-id:', blogPostContainer.attr('data-id')); // "123"

2단계: 형제 요소 탐색

형제 요소 간을 효율적으로 탐색하세요:

// Get next sibling
const titleNextSibling = $('.post-title').next();
console.log('Next sibling class:', titleNextSibling.attr('class')); // "post-meta"

// Get all following siblings
const allFollowingSiblings = $('.post-header').nextAll();
console.log('Following siblings count:', allFollowingSiblings.length); // 2

// Get previous sibling
const metaPrevSibling = $('.post-meta').prev();
console.log('Previous sibling tag:', metaPrevSibling.get(0)?.tagName); // "h1"

// Get all preceding siblings
const allPrecedingSiblings = $('.post-footer').prevAll();
console.log('Preceding siblings count:', allPrecedingSiblings.length); // 2

// Get all siblings
const headerSiblings = $('.post-header').siblings();
headerSiblings.each((index, element) => {
  console.log(`Sibling ${index + 1}:`, $(element).attr('class'));
});

3단계: 자식 및 하위 요소 탐색

자식 요소와 깊은 하위 요소를 작업하세요:

// Get direct children
const postContentChildren = $('.post-content').children();
console.log('Direct children count:', postContentChildren.length);

// Get first child
const firstChild = $('.post-content').children().first();
console.log('First child class:', firstChild.attr('class')); // "intro"

// Get last child