Парсинг и SEO-анализ с Playwright: автоматизация для разработчиков
-

Ребят, сколько раз я видел, как разработчики вручную собирают данные с сайтов, копируют мета-теги, проверяют структуру просто задолбалось смотреть на эту рутину. Playwright + TypeScript решают эту проблему за пару часов кодинга. Это не просто инструмент для тестирования, а мощная апишка для автоматизации парсинга и SEO-анализа, которая работает быстро и надежно.
Делюсь сегодня реальным подходом: как накидать DIY-скрипт, который будет стягивать данные с любого сайта, анализировать SEO-метрики, собирать заголовки и структуру страниц. Никакого сложного Scrapy или дорогих сервисов — просто Node.js, Playwright и немного TypeScript.
Почему именно Playwright для парсинга
Праграйт — это браузерная апишка, которая позволяет управлять Chromium, Firefox и WebKit прямо из кода. В отличие от простых HTTP-клиентов, он рендерит JavaScript, работает с динамическим контентом и легко имитирует действия юзера. Если сайт грузит данные через AJAX или использует React, Vue или Angular — Playwright справится с этим на раз.
Ключевое преимущество: скорость разработки. За час ты напишешь парсер, который будет стягивать данные, анализировать HTML-структуру, собирать метатеги и генерировать отчеты. Для full-stack разработчика это значит, что ты не зависишь от отдельных парсеров или апишек — всё на одном стеке.
Вот что умеет Playwright из коробки:
- Навигация и ожидание:
page.goto()переводит на нужный URL,waitForSelectorдожидается загрузки элементов - Извлечение данных:
page.$eval()для одного элемента,page.$$eval()для массива - Имитация действий: клики, ввод текста, скролл, перехват сетевых запросов
- Скриншоты и PDF: для визуальной проверки и архивирования
- Параллельная работа: можно запустить несколько браузеров одновременно
Парсим страницы и собираем SEO-данные
Начнём с базового скрипта, который открывает сайт, вытягивает основные SEO-метрики и сохраняет результаты в JSON. Это фундамент, на котором можно строить что угодно.
Представим, что нам нужно проанализировать e-commerce сайт: собрать названия товаров, цены, описания, метатеги для SEO. Вот как это выглядит:
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<SEOData> { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto(url); const seoData = await page.evaluate(() => { const title = document.querySelector('title')?.innerText || ''; const description = document.querySelector('meta[name="description"]')?.getAttribute('content') || ''; const h1 = Array.from(document.querySelectorAll('h1')).map(el => el.innerText); const h2 = Array.from(document.querySelectorAll('h2')).map(el => el.innerText); const links = Array.from(document.querySelectorAll('a')).map(el => ({ text: el.innerText, href: el.getAttribute('href') || '' })); const images = Array.from(document.querySelectorAll('img')).map(el => ({ alt: el.getAttribute('alt') || '', src: el.getAttribute('src') || '' })); return { title, description, h1, h2, links, images }; }); await browser.close(); return seoData; } // Использование (async () => { const data = await scrapeSEOData('https://danube-web.shop/'); fs.writeFileSync('seo-data.json', JSON.stringify(data, null, 2)); console.log('SEO-данные сохранены в seo-data.json'); })();Видишь, как просто? За несколько строк кода ты получил все основные SEO-метрики. Теперь усложним: давай собирать товары с e-commerce сайта.
async function scrapeProducts(url: string): Promise<Product[]> { 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 => nodes.map(node => ({ name: node.querySelector('.preview-title')?.innerText || '', price: node.querySelector('.preview-price')?.innerText || '', description: node.querySelector('.preview-author')?.innerText || '' })) ); await browser.close(); return products; }Делюсь ещё одним лайфхаком: если нужно собрать данные со страницы, где много AJAX и динамической подгрузки, используй
waitForSelector. Это гарантирует, что элементы уже в DOM, прежде чем ты попытаешься их вытянуть.Имитация действий пользователя и обход блокировок
Не все данные лежат на поверхности. Иногда нужно нажать кнопку, заполнить форму, скролить страницу — и только потом собирать информацию. Playwright это делает как родное.
Например, ты хочешь открыть поисковую форму, ввести запрос и собрать результаты. Вот как это выглядит в реальности:
async function searchAndScrape(searchQuery: string): Promise<string[]> { 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 => nodes.map(node => node.innerText) ); await browser.close(); return results; }Если сайт пытается заблокировать парсер (проверяет User-Agent, отправляет капчу), ты можешь перехватывать сетевые запросы и блокировать ненужные ресурсы. Это ускоряет парсинг в разы:
await page.route('**/*', route => { const request = route.request(); // Блокируем картинки, стили, шрифты if ( request.resourceType() === 'image' || request.resourceType() === 'stylesheet' || request.resourceType() === 'font' ) { route.abort(); } else { route.continue(); } }); await page.goto(url);В результате парсинг работает быстрее, и сервер видит, что ты не качаешь всё подряд — это снижает риск блокировки.
TypeScript и параметризация тестов
Если ты разрабатываешь для production, TypeScript — обязателен. Типы помогают избежать ошибок, дают автодополнение в IDE и делают код поддерживаемым. Плюс, с TypeScript ты можешь параметризовать тесты и парсеры, чтобы легко добавлять новые сценарии.
Давай соберём всё вместе: создадим конфигурацию для парсинга разных сайтов, напишем типы для данных и реализуем параметризованные скрипты.
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) => { return nodes.map(node => ({ 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 () => { 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} товаров`); } })();Видишь идею? Ты описываешь конфигурацию сайта один раз, а потом просто добавляешь новые сайты в массив. Скрипт сам разбирается, как их парсить. Это масштабируемо и удобно.
Параллельный парсинг и оптимизация
Если нужно собрать данные с сотни ссылок, парсить их по одной — пустая трата времени. Playwright легко запускает несколько браузеров параллельно. С помощью Promise.all() ты можешь обработать сразу несколько страниц.
async function scrapeMultiplePages(urls: string[]): Promise<any[]> { const browser = await chromium.launch(); const allData: any[] = []; // Батчим по 5 страниц одновременно const batchSize = 5; for (let i = 0; i < urls.length; i += batchSize) { const batch = urls.slice(i, i + batchSize); const promises = batch.map(async url => { const page = await browser.newPage(); await page.goto(url); const data = await page.evaluate(() => ({ 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; }Это позволяет парсить в 100+ раз быстрее, чем последовательный обход. Батчируем по 5-10 страниц одновременно — и сервер не заподозрит, что его парсят.
Автоматизация SEO-аудита и отчеты
Делюсь ещё одной фишкой: автоматизированный SEO-аудит. Собираешь SEO-метрики, анализируешь их, генерируешь отчёт. Это может быть полезно для агентств, которые проверяют сайты клиентов.
interface SEOAudit { url: string; hasTitle: boolean; hasDescription: boolean; h1Count: number; brokenLinks: string[]; missingAltText: number; pageLoadTime: number; } async function auditSEO(url: string): Promise<SEOAudit> { 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(() => { 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) => !el.href || el.href.startsWith('javascript:')) .map((el: HTMLAnchorElement) => el.href); const missingAltText = Array.from(document.querySelectorAll('img')).filter( (el: HTMLImageElement) => !el.alt ).length; return { hasTitle, hasDescription, h1Count, brokenLinks, missingAltText }; }); await browser.close(); return { url, ...audit, pageLoadTime }; } // Использование (async () => { const audit = await auditSEO('https://example.com'); console.log('SEO-аудит:', audit); fs.writeFileSync('seo-audit.json', JSON.stringify(audit, null, 2)); })();Такой скрипт за минуту проверит все основные SEO-метрики и даст тебе список проблем. Добавь в Cronjob — и получишь автоматический мониторинг SEO прямо в своей инфраструктуре.
Когда Playwright это не лучший выбор
Практичный совет: Playwright — мощный инструмент, но не универсальный. Если тебе нужно парсить тысячи страниц на непрерывной основе, headless браузер может быть медленным. В таких случаях имеет смысл рассмотреть гибридный подход.
Когда Playwright оправдывает себя:
- Работа с JavaScript-интенсивными сайтами (React, Vue, Angular)
- Имитация действий пользователя (клики, скролл, ввод)
- Относительно небольшой объём данных (до 10K страниц в день)
- Нужна надежность и воспроизводимость
- Ты уже используешь Node.js/TypeScript в проекте
Когда стоит подумать о альтернативах:
- Парсинг статических HTML-сайтов (используй cheerio, jsdom или Beautiful Soup)
- Большой объём данных, нужна масштабируемость (Scrapy, Selenium Grid)
- Работа с API (запрашивай данные напрямую, без браузера)
- Сложная обработка данных (используй Python + pandas)
Оптимальный подход: гибридный. Твой pipeline может выглядеть так: Playwright собирает динамические данные, параллельно простой HTTP-клиент стягивает статику, всё сохраняется в БД, потом Python обрабатывает и анализирует.
И что мы сделали?
Покрыли основы: от простого парсинга до параллельной обработки и SEO-аудита. Но в реальных проектах ты столкнёшься с куда более сложными сценариями.
Дальше имеет смысл разобраться с обработкой ошибок (retry-логика, таймауты, graceful shutdown), кешированием результатов и интеграцией с очередями задач (BullMQ, Redis). Если парсишь большой объём — настрой логирование и мониторинг, чтобы видеть, что происходит на сервере. И главное: всегда проверяй
robots.txtи условия использования сайта — парсинг может быть нарушением ToS. - Навигация и ожидание:
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.