Галоўная / Артыкулы / Стварэнне модульнага інструментарыя для аўтаматызаціі на JavaScript з нуля

Стварэнне модульнага інструментарыя для аўтаматызаціі на JavaScript з нуля

Выучыце, як саюзаваць Playwright, Cheerio, SQLite і Commander у аднойчыны можна выкарыстоўваець двухвыконвы механізм роботы для Node.js, які пераходзіць з простага скрыпта ў продукт аўтоматызаціі, які можна продаць.

2080 слоў

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.

    Калі последовнасць ручных крокаў пачынае павтарацца ў двадзяты раз, інстынкт не ёсць:

    "Час знайсці новы фрэймворк."

    Це ёсць:

    «Чы можна ператворыць гэты процес на функцыю?»

    Якщо адказ «так», то гэта ўжо пачатак проекта з автоматызацыі.

    А калі такая автоматызацыя заўсёды зарабляе кому-небудзь час і зусілля, яна можа ператворыцца на ўтрамбаваны продукт, які можна продаць.

    Супаўзяменыя матэрыялы

  • Напады на ланцуг поставок npm: якія ўсе способы ўражэння та як захістыць Node.js — Інформуе пра тое, як функціонуют напады на ланцуг поставок npm, такія як захопленне адначакоў, тайпоскуатінг і плутанне залежнасцяў, а таксама прыводзіць конкрэтныя крокі для зміцнення інсталяцый Node.js.