<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Парсинг и SEO-анализ с Playwright: автоматизация для разработчиков]]></title><description><![CDATA[<p dir="auto"><img src="/assets/uploads/files/91/bb/5f/1773906787975-generated_1773906761018-resized.webp" alt="Обложка: Как автоматизировать парсинг и SEO-анализ сайтов с Playwright и TypeScript: DIY-скрипт для full-stack разработчиков" class=" img-fluid img-markdown" /></p>
<p dir="auto">Ребят, сколько раз я видел, как разработчики вручную собирают данные с сайтов, копируют мета-теги, проверяют структуру  просто задолбалось смотреть на эту рутину. Playwright + TypeScript решают эту проблему за пару часов кодинга. Это не просто инструмент для тестирования, а мощная апишка для автоматизации парсинга и SEO-анализа, которая работает быстро и надежно.</p>
<p dir="auto">Делюсь сегодня реальным подходом: как накидать DIY-скрипт, который будет стягивать данные с любого сайта, анализировать SEO-метрики, собирать заголовки и структуру страниц. Никакого сложного Scrapy или дорогих сервисов — просто Node.js, Playwright и немного TypeScript.</p>
<h2>Почему именно Playwright для парсинга</h2>
<p dir="auto">Праграйт — это браузерная апишка, которая позволяет управлять Chromium, Firefox и WebKit прямо из кода. В отличие от простых HTTP-клиентов, он <strong>рендерит JavaScript</strong>, работает с динамическим контентом и легко имитирует действия юзера. Если сайт грузит данные через AJAX или использует React, Vue или Angular — Playwright справится с этим на раз.</p>
<p dir="auto">Ключевое преимущество: <strong>скорость разработки</strong>. За час ты напишешь парсер, который будет стягивать данные, анализировать HTML-структуру, собирать метатеги и генерировать отчеты. Для full-stack разработчика это значит, что ты не зависишь от отдельных парсеров или апишек — всё на одном стеке.</p>
<p dir="auto">Вот что умеет Playwright из коробки:</p>
<ul>
<li><strong>Навигация и ожидание</strong>: <code>page.goto()</code> переводит на нужный URL, <code>waitForSelector</code> дожидается загрузки элементов</li>
<li><strong>Извлечение данных</strong>: <code>page.$eval()</code> для одного элемента, <code>page.$$eval()</code> для массива</li>
<li><strong>Имитация действий</strong>: клики, ввод текста, скролл, перехват сетевых запросов</li>
<li><strong>Скриншоты и PDF</strong>: для визуальной проверки и архивирования</li>
<li><strong>Параллельная работа</strong>: можно запустить несколько браузеров одновременно</li>
</ul>
<h2>Парсим страницы и собираем SEO-данные</h2>
<p dir="auto">Начнём с базового скрипта, который открывает сайт, вытягивает основные SEO-метрики и сохраняет результаты в JSON. Это фундамент, на котором можно строить что угодно.</p>
<p dir="auto">Представим, что нам нужно проанализировать e-commerce сайт: собрать названия товаров, цены, описания, метатеги для SEO. Вот как это выглядит:</p>
<pre><code class="language-js">import { chromium } from 'playwright';
import * as fs from 'fs';

interface SEOData {
  title: string;
  description: string;
  h1: string[];
  h2: string[];
  links: { text: string; href: string }[];
  images: { alt: string; src: string }[];
}

interface Product {
  name: string;
  price: string;
  description: string;
}

async function scrapeSEOData(url: string): Promise&lt;SEOData&gt; {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto(url);

  const seoData = await page.evaluate(() =&gt; {
    const title = document.querySelector('title')?.innerText || '';
    const description =
      document.querySelector('meta[name="description"]')?.getAttribute('content') || '';
    const h1 = Array.from(document.querySelectorAll('h1')).map(el =&gt; el.innerText);
    const h2 = Array.from(document.querySelectorAll('h2')).map(el =&gt; el.innerText);
    const links = Array.from(document.querySelectorAll('a')).map(el =&gt; ({
      text: el.innerText,
      href: el.getAttribute('href') || ''
    }));
    const images = Array.from(document.querySelectorAll('img')).map(el =&gt; ({
      alt: el.getAttribute('alt') || '',
      src: el.getAttribute('src') || ''
    }));

    return { title, description, h1, h2, links, images };
  });

  await browser.close();
  return seoData;
}

// Использование
(async () =&gt; {
  const data = await scrapeSEOData('https://danube-web.shop/');
  fs.writeFileSync('seo-data.json', JSON.stringify(data, null, 2));
  console.log('SEO-данные сохранены в seo-data.json');
})();
</code></pre>
<p dir="auto">Видишь, как просто? За несколько строк кода ты получил все основные SEO-метрики. Теперь усложним: давай собирать товары с e-commerce сайта.</p>
<pre><code class="language-js">async function scrapeProducts(url: string): Promise&lt;Product[]&gt; {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(url);

  // Ждём загрузки карточек товара
  await page.waitForSelector('.preview');

  const products = await page.$$eval('.preview', nodes =&gt;
    nodes.map(node =&gt; ({
      name: node.querySelector('.preview-title')?.innerText || '',
      price: node.querySelector('.preview-price')?.innerText || '',
      description: node.querySelector('.preview-author')?.innerText || ''
    }))
  );

  await browser.close();
  return products;
}
</code></pre>
<p dir="auto">Делюсь ещё одним лайфхаком: если нужно собрать данные со страницы, где много AJAX и динамической подгрузки, используй <code>waitForSelector</code>. Это гарантирует, что элементы уже в DOM, прежде чем ты попытаешься их вытянуть.</p>
<h2>Имитация действий пользователя и обход блокировок</h2>
<p dir="auto">Не все данные лежат на поверхности. Иногда нужно нажать кнопку, заполнить форму, скролить страницу — и только потом собирать информацию. Playwright это делает как родное.</p>
<p dir="auto">Например, ты хочешь открыть поисковую форму, ввести запрос и собрать результаты. Вот как это выглядит в реальности:</p>
<pre><code class="language-js">async function searchAndScrape(searchQuery: string): Promise&lt;string[]&gt; {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('https://twitch.tv/directory/game/Art');

  // Находим и кликаем на кнопку поиска
  await page.click('button[aria-label="Search"]');

  // Вводим текст в поле поиска
  await page.fill('input[type="search"]', searchQuery);

  // Нажимаем Enter
  await page.press('input[type="search"]', 'Enter');

  // Ждём результаты
  await page.waitForSelector('.search-result');

  // Собираем данные
  const results = await page.$$eval('.search-result', nodes =&gt;
    nodes.map(node =&gt; node.innerText)
  );

  await browser.close();
  return results;
}
</code></pre>
<p dir="auto">Если сайт пытается заблокировать парсер (проверяет User-Agent, отправляет капчу), ты можешь перехватывать сетевые запросы и блокировать ненужные ресурсы. Это <strong>ускоряет парсинг</strong> в разы:</p>
<pre><code class="language-js">await page.route('**/*', route =&gt; {
  const request = route.request();

  // Блокируем картинки, стили, шрифты
  if (
    request.resourceType() === 'image' ||
    request.resourceType() === 'stylesheet' ||
    request.resourceType() === 'font'
  ) {
    route.abort();
  } else {
    route.continue();
  }
});

await page.goto(url);
</code></pre>
<p dir="auto">В результате парсинг работает быстрее, и сервер видит, что ты не качаешь всё подряд — это снижает риск блокировки.</p>
<h2>TypeScript и параметризация тестов</h2>
<p dir="auto">Если ты разрабатываешь для production, TypeScript — обязателен. Типы помогают избежать ошибок, дают автодополнение в IDE и делают код поддерживаемым. Плюс, с TypeScript ты можешь параметризовать тесты и парсеры, чтобы легко добавлять новые сценарии.</p>
<p dir="auto">Давай соберём всё вместе: создадим конфигурацию для парсинга разных сайтов, напишем типы для данных и реализуем параметризованные скрипты.</p>
<pre><code class="language-js">interface ScrapeConfig {
  name: string;
  url: string;
  selectors: {
    productCard: string;
    title: string;
    price: string;
    image: string;
  };
}

const configs: ScrapeConfig[] = [
  {
    name: 'Danube Shop',
    url: 'https://danube-web.shop/',
    selectors: {
      productCard: '.preview',
      title: '.preview-title',
      price: '.preview-price',
      image: 'img'
    }
  },
  {
    name: 'Another Shop',
    url: 'https://example-shop.com/',
    selectors: {
      productCard: '.product',
      title: '.product-name',
      price: '.product-price',
      image: '.product-img'
    }
  }
];

async function scrapeWithConfig(config: ScrapeConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto(config.url);

  await page.waitForSelector(config.selectors.productCard);

  const products = await page.$$eval(config.selectors.productCard, (nodes, selectors) =&gt; {
    return nodes.map(node =&gt; ({
      title: node.querySelector(selectors.title)?.innerText || '',
      price: node.querySelector(selectors.price)?.innerText || '',
      image: node.querySelector(selectors.image)?.getAttribute('src') || ''
    }));
  }, config.selectors);

  await browser.close();
  return products;
}

// Запускаем парсинг для всех конфигов
(async () =&gt; {
  for (const config of configs) {
    console.log(`Парсим ${config.name}...`);
    const products = await scrapeWithConfig(config);
    fs.writeFileSync(`${config.name}.json`, JSON.stringify(products, null, 2));
    console.log(`Сохранено ${products.length} товаров`);
  }
})();
</code></pre>
<p dir="auto">Видишь идею? Ты описываешь конфигурацию сайта один раз, а потом просто добавляешь новые сайты в массив. Скрипт сам разбирается, как их парсить. Это <strong>масштабируемо и удобно</strong>.</p>
<h2>Параллельный парсинг и оптимизация</h2>
<p dir="auto">Если нужно собрать данные с сотни ссылок, парсить их по одной — пустая трата времени. Playwright легко запускает несколько браузеров параллельно. С помощью Promise.all() ты можешь обработать сразу несколько страниц.</p>
<pre><code class="language-js">async function scrapeMultiplePages(urls: string[]): Promise&lt;any[]&gt; {
  const browser = await chromium.launch();
  const allData: any[] = [];

  // Батчим по 5 страниц одновременно
  const batchSize = 5;
  for (let i = 0; i &lt; urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);
    const promises = batch.map(async url =&gt; {
      const page = await browser.newPage();
      await page.goto(url);
      const data = await page.evaluate(() =&gt; ({
        title: document.querySelector('title')?.innerText || '',
        url: window.location.href
      }));
      await page.close();
      return data;
    });

    const batchResults = await Promise.all(promises);
    allData.push(...batchResults);
    console.log(`Обработано ${Math.min(i + batchSize, urls.length)} из ${urls.length}`);
  }

  await browser.close();
  return allData;
}
</code></pre>
<p dir="auto">Это позволяет парсить в <strong>100+ раз быстрее</strong>, чем последовательный обход. Батчируем по 5-10 страниц одновременно — и сервер не заподозрит, что его парсят.</p>
<h2>Автоматизация SEO-аудита и отчеты</h2>
<p dir="auto">Делюсь ещё одной фишкой: автоматизированный SEO-аудит. Собираешь SEO-метрики, анализируешь их, генерируешь отчёт. Это может быть полезно для агентств, которые проверяют сайты клиентов.</p>
<pre><code class="language-js">interface SEOAudit {
  url: string;
  hasTitle: boolean;
  hasDescription: boolean;
  h1Count: number;
  brokenLinks: string[];
  missingAltText: number;
  pageLoadTime: number;
}

async function auditSEO(url: string): Promise&lt;SEOAudit&gt; {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  const startTime = Date.now();
  await page.goto(url);
  const pageLoadTime = Date.now() - startTime;

  const audit = await page.evaluate(() =&gt; {
    const hasTitle = !!document.querySelector('title')?.innerText;
    const hasDescription = !!document.querySelector('meta[name="description"]');
    const h1Count = document.querySelectorAll('h1').length;
    const links = Array.from(document.querySelectorAll('a'));
    const brokenLinks = links
      .filter((el: HTMLAnchorElement) =&gt; !el.href || el.href.startsWith('javascript:'))
      .map((el: HTMLAnchorElement) =&gt; el.href);
    const missingAltText = Array.from(document.querySelectorAll('img')).filter(
      (el: HTMLImageElement) =&gt; !el.alt
    ).length;

    return {
      hasTitle,
      hasDescription,
      h1Count,
      brokenLinks,
      missingAltText
    };
  });

  await browser.close();

  return {
    url,
    ...audit,
    pageLoadTime
  };
}

// Использование
(async () =&gt; {
  const audit = await auditSEO('https://example.com');
  console.log('SEO-аудит:', audit);
  fs.writeFileSync('seo-audit.json', JSON.stringify(audit, null, 2));
})();
</code></pre>
<p dir="auto">Такой скрипт за минуту проверит все основные SEO-метрики и даст тебе список проблем. Добавь в Cronjob — и получишь <strong>автоматический мониторинг SEO</strong> прямо в своей инфраструктуре.</p>
<hr />
<h2>Когда Playwright  это не лучший выбор</h2>
<p dir="auto">Практичный совет: Playwright — мощный инструмент, но не универсальный. Если тебе нужно парсить тысячи страниц на непрерывной основе, <strong>headless браузер может быть медленным</strong>. В таких случаях имеет смысл рассмотреть гибридный подход.</p>
<p dir="auto">Когда Playwright оправдывает себя:</p>
<ul>
<li>Работа с JavaScript-интенсивными сайтами (React, Vue, Angular)</li>
<li>Имитация действий пользователя (клики, скролл, ввод)</li>
<li>Относительно небольшой объём данных (до 10K страниц в день)</li>
<li>Нужна надежность и воспроизводимость</li>
<li>Ты уже используешь Node.js/TypeScript в проекте</li>
</ul>
<p dir="auto">Когда стоит подумать о альтернативах:</p>
<ul>
<li>Парсинг статических HTML-сайтов (используй cheerio, jsdom или Beautiful Soup)</li>
<li>Большой объём данных, нужна масштабируемость (Scrapy, Selenium Grid)</li>
<li>Работа с API (запрашивай данные напрямую, без браузера)</li>
<li>Сложная обработка данных (используй Python + pandas)</li>
</ul>
<p dir="auto">Оптимальный подход: <strong>гибридный</strong>. Твой pipeline может выглядеть так: Playwright собирает динамические данные, параллельно простой HTTP-клиент стягивает статику, всё сохраняется в БД, потом Python обрабатывает и анализирует.</p>
<h2>И что мы сделали?</h2>
<p dir="auto">Покрыли основы: от простого парсинга до параллельной обработки и SEO-аудита. Но в реальных проектах ты столкнёшься с куда более сложными сценариями.</p>
<p dir="auto">Дальше имеет смысл разобраться с обработкой ошибок (retry-логика, таймауты, graceful shutdown), кешированием результатов и интеграцией с очередями задач (BullMQ, Redis). Если парсишь большой объём — настрой логирование и мониторинг, чтобы видеть, что происходит на сервере. И главное: всегда проверяй <code>robots.txt</code> и условия использования сайта — парсинг может быть нарушением ToS.</p>
]]></description><link>https://forum.exlends.ru/topic/1750/parsing-i-seo-analiz-s-playwright-avtomatizaciya-dlya-razrabotchikov</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:59:14 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1750.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 19 Mar 2026 07:53:08 GMT</pubDate><ttl>60</ttl></channel></rss>