Базовый скрейпинг HTML: первые шаги

23 февр. 2025 г.

Поначалу веб-скрейпинг может казаться пугающим, но, как и любой навык, его лучше всего осваивать на практике. В этих первых трёх главах мы разберём основы извлечения данных из статических HTML-страниц.

Глава 1: HTML-скрейпер Chad

Наше путешествие начинается с самого простого сценария — извлечения текста из базовой HTML-страницы. В этой главе вы познакомитесь с ключевыми понятиями:

  • Выполнение HTTP-запросов для получения веб-страниц
  • Загрузка HTML-содержимого в парсер
  • Базовый выбор элементов DOM с помощью CSS-селекторов

Хотя пример может показаться тривиальным (в конце концов, это всего лишь “hello world”!), он закладывает основу для всего, что будет дальше.

<!-- Example HTML structure -->
<div class="content">
  <p>Some text we want to extract</p>
</div>

Вот небольшое представление о том, с чем мы будем работать:

// Basic concepts (not the solution!)
import * as cheerio from 'cheerio';

// Loading HTML content
const $ = cheerio.load(htmlContent);

// Using CSS selectors
const text = $('p').text();  // Selects all <p> tags
const specific = $('.content p').text();  // More specific selection

Глава 2: Структурированные данные

Дальше становится интереснее: мы переходим к учебной странице товара интернет-магазина. Здесь нам предстоит работать с несколькими элементами схожей структуры:

<!-- Example product structure (simplified) -->
<div class="product">
  <h2>Product Name</h2>
  <span class="price">$99.99</span>
  <div class="specs">
    <ul>
      <li>Size: M</li>
      <li>Color: Blue</li>
    </ul>
  </div>
</div>

Когда вы имеете дело со структурированными данными такого рода, стоит подумать о следующем:

// Conceptual approach (not the solution!)
$('.product').each((index, element) => {
  // For each product, we might want to:
  // 1. Extract the basic info
  const name = $(element).find('h2').text();

  // 2. Parse nested data
  const specs = $(element).find('.specs li');

  // 3. Structure the output
  const data = {
    name,
    specs: specs.map(/* ... */),
  };
});

Совет от профи: прежде чем писать код, уделите время анализу структуры HTML. Ищите закономерности в том, как организованы данные — есть ли повторяющиеся имена классов? Как связаны родительские и дочерние элементы?

Глава 3: Скрейпинг с помощью AI

А вот теперь действительно становится интересно! Если в предыдущих заданиях мы изучали традиционные техники скрейпинга, то в главе 3 появляется современный подход: веб-скрейпинг с помощью AI. Перед нами кошмарный сценарий — непоследовательные структуры HTML, обфусцированные имена классов и вперемешку несколько шаблонов разных фреймворков.

Давайте посмотрим, что делает это задание особенным:

<!-- Traditional product structure -->
<div data-testid="product-container-1" class="_3xj_item">
  <h2 data-qa="name">Red Sneakers</h2>
  <span data-price-current="5999">$59.99</span>
</div>

<!-- React-style component -->
<div class="ProductCard-root-1a2b3c">
  <div class="ProductCard-title-4d5e6f">Pink Walking Shoes</div>
  <div class="ProductCard-pricing-7g8h9i">$84.99</div>
</div>

<!-- Vue-style template -->
<div data-v-abcdef class="product">
  <h2 data-v-abcdef>Navy Boat Shoes</h2>
  <span data-v-abcdef>$79.99</span>
</div>

Традиционные подходы к скрейпингу здесь дают сбой, потому что:

  • Имена классов рандомизированы или привязаны к конкретному фреймворку
  • Структуры данных различаются от товара к товару
  • Разные фреймворки используют разные шаблоны
  • Семантический смысл теряется в разметке

И вот здесь на помощь приходит AI. Вместо того чтобы писать хрупкие селекторы, мы можем описать на естественном языке, что именно нам нужно, а AI возьмёт на себя сопоставление паттернов. Ключевые идеи этой главы включают:

  • Prompt engineering для веб-скрейпинга
  • Использование AI для понимания семантического смысла
  • Обработку непоследовательных структур данных
  • Работу с разметкой, специфичной для разных фреймворков
  • Поддержание согласованности данных между разными шаблонами

AI — не магия, но он отлично справляется с задачами, где нужны распознавание паттернов и адаптация. Именно поэтому он особенно полезен для скрейпинга современных веб-приложений, где нельзя рассчитывать на стабильные паттерны разметки.

Примечание об использовании AI

Помните: помощь AI не означает полностью автоматизированные решения. Наилучшие результаты даёт сочетание:

  • Чёткой постановки задачи
  • Хорошо структурированных промптов
  • Валидации данных
  • Человеческого контроля

Ваша задача — составить такие промпты, которые помогут AI понять и структуру, и смысл того, что вы пытаетесь извлечь.

Готовы объединить знания о традиционном веб-скрейпинге с современными возможностями AI? Давайте разберёмся, как AI может помочь справиться даже с самым хаотичным HTML!

Подсказки

  1. Поэкспериментируйте с разными CSS-селекторами:
// Different ways to select elements
$('.class')           // By class
$('#id')             // By ID
$('div > p')         // Direct children
$('div p')           // All descendants
$('[data-type="x"]') // By attribute
  1. Попробуйте изменить формат вывода
  2. Подумайте об обработке ошибок и пограничных случаях
  3. Подумайте, как ваше решение будет масштабироваться на более крупных наборах данных

Весь код, необходимый для старта, уже есть в репозитории проекта. Клонируйте его, настройте окружение и начинайте скрейпить!

git clone https://github.com/jonaylor89/housefly.git
cd housefly
npm install

Запустите серверы глав локально — каждая глава работает на своём порту (3001–3011):

turbo dev

Затем отредактируйте свой скрейпер в exercises/chapter-NN/starter/src/index.ts и проверьте его:

npm run validate -- <chapter>

Нужны подсказки? Запустите npm run hint -- <chapter> для пошаговых наводок. Исходный HTML для каждого задания доступен в директории apps/chapter{n}/, а эталонные решения находятся в exercises/chapter-NN/solution/src/. Изучайте структуру, продумывайте подход и помните: каждый эксперт когда-то был новичком.

Не забывайте ответственно обращаться со своими запросами:

// Basic error handling example
async function fetchPage(url: string) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.text();
  } catch (error) {
    console.error('Failed to fetch page:', error);
    throw error;
  }
}

Удачного скрейпинга!