Создание модульного инструментария автоматизации на JavaScript с нуля
Узнайте, как объединить Playwright, Cheerio, SQLite и Commander в многофункциональный движок рабочих процессов Node.js, который может быть использован повторно и превратиться из простого скрипта в коммерческий продукт автоматизации.
1. Регулярно возникающая проблема стала отправной точкой
В какой-то момент вы замечаете, что снова и снова выполняете одни и те же действия.
Загружаете страницу.
Ищете на ней что-то полезное.
Выделяете необходимые элементы.
Храните их где-то для последующего использования.
Переходите к следующей странице.
Снова начинаетесь с цикла.
Ни один из этих шагов сам по себе не является сложным, но их постоянное повторение кажется абсурдным.
Поэтому вместо того, чтобы брать ещё одну фреймворк JavaScript только для создания очередного панели управления, более полезным решением будет создание чего-то, что действительно можно использовать на практике: инструмента автоматизации и сбора данных на JavaScript.
Идея заключается в следующем:
Предоставьте программе задачу → позвольте JavaScript заняться повторяющейся частью → получите структурированные результаты.
Для ранней версии такого инструмента требуется всего несколько технологий:
- Node.js
- Playwright
- Cheerio
- SQLite
- Commander
Этой комбинации достаточно, чтобы превратить небольшой скрипт в что-то, что начинает походить на настоящий продукт.
2. Playwright как первый строительный блок
Цель заключалась в том, чтобы позволить JavaScript управлять настоящим браузером.
Playwright делает это гораздо проще, чем ожидалось.
const { chromium } = require("playwright");
async function visitWebsite(url) {
const browser = await chromium.launch({
headless: false
});
const page = await browser.newPage();
await page.goto(url, {
waitUntil: "domcontentloaded"
});
console.log(
"Page title:",
await page.title()
);
console.log(
"Current URL:",
page.url()
);
await browser.close();
}
visitWebsite(
"https://example.com"
);
Когда вы впервые запускаете что-то подобное, кажется, что всё слишком просто.
JavaScript открывает браузер.
JavaScript переходит на страницу.
JavaScript считывает содержимое страницы.
JavaScript закрывает браузер.
Уже это дает основу, на которой можно развивать функции для тестирования браузеров, мониторинга, выполнения повторяющихся задач и общей автоматизации.
3. Переход от координат к элементам
Одной из вещей, которых стоит избегать с самого начала, является хрупкая автоматизация.
Если сказать программе что-то вроде:
Нажмите именно в этом месте.
это означает, что автоматизация может сломаться при малейшем изменении макета.
Лучший подход — писать код, описывающий то, с чем фактически взаимодействует пользователь, а не то, где на экране расположены элементы.
async function searchPage(page, query) {
await page
.getByRole("textbox")
.fill(query);
await page
.getByRole("button", {
name: "Search"
})
.click();
await page.waitForLoadState(
"domcontentloaded"
);
}
Такой подход гораздо проще в обслуживании со временем.
Код не гласит:
Нажмите на элемент, находящийся по координатам 742, 381.
Он гласит:
Найдите текстовое поле и кнопку поиска.
Это небольшой дизайнерский выбор, но он значительно упрощает автоматизацию в браузере в дальнейшем.
4. Извлечение данных с страницы
Как только браузер сможет самостоятельно навигировать по странице, следующим шагом будет сбор информации.
В качестве примера представьте страницу, заполненную карточками товаров.
async function extractProducts(page) {
return page
.locator(".product-card")
.evaluateAll(cards => {
return cards.map(card => {
const name =
card
.querySelector(".product-name")
?.textContent
?.trim();
const price =
card
.querySelector(".price")
?.textContent
?.trim();
return {
name,
price
};
});
});
}
На этом этапе браузер уже просто не просматривает страницы.
Он преобразует содержимое страницы в объекты JavaScript.
Это позволяет использовать данные для дальнейшей обработки.
const products =
await extractProducts(page);
console.log(
JSON.stringify(
products,
null,
2
)
);
Как только у информации появляется четкая структура, её можно хранить, сравнивать, анализировать или передавать в другую часть приложения.
5. Использование Cheerio для парсинга HTML
Playwright особенно полезен, когда действительно требуется запущенный браузер.
Но часто у вас уже есть HTML, и совсем не нужно запускать Chromium.
Именно здесь и находит применение Cheerio.
const cheerio = require("cheerio");
function parseProducts(html) {
const $ = cheerio.load(html);
const products = [];
$(".product-card").each(
(_, element) => {
const name = $(element)
.find(".product-name")
.text()
.trim();
const price = $(element)
.find(".price")
.text()
.trim();
products.push({
name,
price
});
}
);
return products;
}
Наличие обоих инструментов — это настоящее преимущество.
Playwright отвечает за взаимодействие с браузером.
Cheerio занимается легким парсингом HTML.
Таким образом, полная инстанция браузера используется только тогда, когда это действительно необходимо, а простой парсер справляется со всем остальным.
6. Обеспечение памяти для автоматизации с помощью SQLite
Следующей проблемой, которую нужно было решить, стало хранение данных.
Если скрипт собирает данные сегодня, где они будут находиться завтра?
Решением стало использование SQLite.
const sqlite3 = require("sqlite3").verbose();
const db = new sqlite3.Database(
"automation.db"
);
db.run(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price TEXT,
source TEXT,
created_at DATETIME
DEFAULT CURRENT_TIMESTAMP
)
`);
Далее специальная функция отвечала за сохранение каждого результата.
function saveProduct(product, source) {
return new Promise(
(resolve, reject) => {
db.run(
`
INSERT INTO products
(name, price, source)
VALUES (?, ?, ?)
`,
[
product.name,
product.price,
source
],
error => {
if (error) {
reject(error);
return;
}
resolve();
}
);
}
);
}
Это добавление существенно изменило структуру проекта.
Благодаря наличию базы данных с течением времени можно было накапливать исторические данные. Это открыло возможность отвечать на практические вопросы, такие как:
- Что изменилось?
- Что появилось недавно?
- Что исчезло?
Автоматизация становится гораздо ценнее, когда она может запоминать произошедшее ранее.
7. Создание многоразового движка рабочих процессов
На этом этапе у проекта уже были несколько независимых компонентов: управление браузером, парсинг HTML и сохранение данных в базе. Однако отсутствовала структура — существовала реальная опасность того, что функции окажутся разбросаны повсюду вместо того, чтобы составлять целостную систему.
Чтобы это исправить, был создан класс рабочего процесса, объединивший всё воедино.
class AutomationWorkflow {
constructor(browser) {
this.browser = browser;
this.page = null;
}
async start() {
this.page =
await this.browser.newPage();
}
async visit(url) {
await this.page.goto(url, {
waitUntil: "domcontentloaded"
});
}
async search(query) {
await this.page
.getByRole("textbox")
.fill(query);
await this.page
.getByRole("button", {
name: "Search"
})
.click();
}
async getTitle() {
return this.page.title();
}
async close() {
await this.page.close();
}
}
Благодаря этому классу выполнение полной задачи становится простым и понятным от начала до конца.
const browser =
await chromium.launch({
headless: false
});
const workflow =
new AutomationWorkflow(
browser
);
await workflow.start();
await workflow.visit(
"https://example.com"
);
await workflow.search(
"JavaScript automation"
);
console.log(
await workflow.getTitle()
);
await workflow.close();
await browser.close();
Именно здесь ориентированное на объекты проектирование начинает приносить плоды. Экземпляр класса моделирует сам процесс работы, а его методы представляют отдельные операции. Остальная часть кодовой базы не должна знать, как внутренне реализован каждый шаг.
8. Обработка сбоев с логикой повторных попыток
Автоматизированные скрипты часто работают без ошибок девять раз, а затем сбиваются на десятой попытке. Сеть замедляется, страница не загружается полностью, сервер дает сбои или элемент отрисовывается дольше, чем ожидается.
Чтобы с этим бороться, был введен универсальный помощник для повторных попыток.
async function retry(
operation,
attempts = 3,
delay = 2000
) {
let lastError;
for (
let attempt = 1;
attempt <= attempts;
attempt++
) {
try {
return await operation();
} catch (error) {
lastError = error;
console.log(
`Attempt ${attempt} failed.`
);
if (
attempt < attempts
) {
await new Promise(
resolve =>
setTimeout(
resolve,
delay
)
);
}
}
}
throw lastError;
}
Этот инструмент позволил обернуть критически важные шаги автоматическими повторными попытками.
await retry(
async () => {
await page.goto(
"https://example.com",
{
waitUntil:
"domcontentloaded"
}
);
},
3,
1500
);
Вывод был прост: автоматизация производственного уровня должна учитывать сбои как нормальное явление, а не исключение. Учебные материалы часто исходят из предположения о совершенно стабильной сети. Реальные системы не могут позволить себе такое предположение.
9. Интеграция в интерфейс командной строки
В какой-то момент постоянное открытие исходного файла только для замены URL стало утомительным.
Цель изменилась — сделать инструмент похожим на настоящий утилитарный программный инструмент командной строки.
Commander облегчил достижение этой цели.
const { Command } = require("commander");
const program = new Command();
program
.name("webpilot")
.description(
"JavaScript automation toolkit"
)
.version("1.0.0");
program
.command("visit")
.description(
"Open a webpage"
)
.argument("<url>")
.action(async url => {
const browser =
await chromium.launch({
headless: false
});
const page =
await browser.newPage();
await page.goto(url);
console.log(
await page.title()
);
await browser.close();
});
program.parseAsync();
После этого инструмент можно было запускать непосредственно из терминала.
node webpilot.js visit https://example.com
Это кажется незначительной корректировкой, но она фундаментально меняет способ взаимодействия с программным обеспечением.
Вместо того чтобы каждый раз редактировать программу, когда что-то нужно,
достаточно просто запустить её.
10. Использование ИИ в качестве интерфейса
Это породило новый вопрос:
Зачем пользователю вообще нужно запоминать точную синтаксис команд?
Вместо того чтобы вводить что-то вроде:
node webpilot.js screenshot https://example.com
человек может просто описать свою цель простым языком:
«Сделай скриншот главной страницы».
Слой ИИ может преобразовать этое предложение в структурированный объект задачи.
const task = {
action: "screenshot",
url: "https://example.com",
output: "homepage.png"
};
Однако ИИ никогда не будет иметь права выполнять произвольный JavaScript напрямую.
Каждое запрашиваемое действие сначала проходит через этап проверки.
const allowedActions = new Set([
"visit",
"search",
"screenshot",
"download"
]);
function validateTask(task) {
if (
!allowedActions.has(
task.action
)
) {
throw new Error(
"Unsupported action."
);
}
if (
task.url &&
!task.url.startsWith("https://")
) {
throw new Error(
"Invalid URL."
);
}
return true;
}
Это обеспечивает четкое разделение ответственностей:
ИИ интерпретирует желания пользователя.
Слой JavaScript определяет, что действительно разрешено.
Playwright выполняет только утверждённое действие.
Такой многоуровневый подход гораздо безопаснее, чем предоставление ИИ-модели прямого и неограниченного контроля над устройством.
11. Переосмысление проекта как продукта для продажи
На этом этапе внимание сместилось от базовых библиотек на самого клиента.
Презентация никогда не будет выглядеть так:
"Приложение для автоматизации браузера, созданное с использованием Playwright и JavaScript."
Никто не ищет Playwright специально — они ищут конкретный результат.
Поэтому предлагаемый продукт становится самим результатом. Несколько примеров:
Мониторинг веб-сайтов
Компании могут отслеживать свои веб-сайты и получать уведомления, когда меняются или перестают работать ключевые страницы.
Автоматизированное тестирование качества
Инженерные команды могут выполнять последовательные и воспроизводимые проверки браузера для своих собственных приложений.
Автоматизация внутренних рабочих процессов
Организации могут автоматизировать повторяющиеся задачи, связанные с использованием браузера, в инструментах, к которым у них уже есть доступ.
Автоматизация отчетности
Задание, запланированное на определенное время, может автоматически собирать утвержденные данные, сохранять их и формировать из них отчет.
Автоматизация для агентств
Агентство может разрабатывать индивидуальные автоматизированные цепочки обработки для клиентов и взимать плату за настройку и постоянную поддержку.
Ценообразование может иметь несколько форм:
- Фиксированная плата за однократную настройку
- Регулярные ежемесячные платежи за техническое обслуживание
- Ценообразование по отдельным рабочим процессам
- Лицензирование для команд
- Работа по индивидуальной интеграции
- Доступ по подписке с хостингом
Ключевой момент заключается в том, чтобы сосредоточить внимание на конкретной, определенной проблеме, а не на наборе технологий.
12. Небольшие скрипты могут превратиться в настоящие продукты
В итоге общая архитектура системы выглядела примерно так:
User
│
▼
CLI / AI Input
│
▼
Task Validator
│
▼
Workflow Engine
│
┌────────┼────────┐
▼ ▼ ▼
Playwright Cheerio SQLite
│ │ │
└────────┼────────┘
▼
Result Data
│
▼
Report / API
Именно над этим стоит поработать.
Основная проблема никогда не заключалась в самой автоматизации браузера.
Речь шла о удалении повторяющейся ручной работы.
Библиотеки — Playwright, Cheerio, SQLite, Commander — были лишь инструментами для преобразования этой повторяемости в функциональное программное обеспечение.
Такой подход сейчас определяет способ решения новых задач с использованием JavaScript.
Каждый раз, когда последовательность ручных действий начинает повторяться в двадцатый раз, первая мысль не бывает такой:
«Пора использовать новую фреймворк-систему».
А такой:
«Можно ли превратить этот рабочий процесс в функцию?»
Если ответ «да», это и есть основа для проекта автоматизации.
Когда такая автоматизация экономит кому-то достаточно времени и усилий, она может превратиться в что-то, что стоит продавать.
Связанные материалы
- Проектирование API на Node.js с использованием слоев: от громоздких контроллеров к чистой архитектуре — Узнайте, как переписать API на Node.js с использованием слоев контроллеров, сервисов и доступа к данным для устранения запутанной бизнес-логики, неоднородных ошибок и проблем с масштабированием.