Створення модульного інструментарію автоматизації на 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 на шари контролерів, сервісів та доступу до даних, щоб усунути заплутану бізнес-логіку, непослідовні помилки та проблеми з масштабуванням.