Se întâmplă adesea să întâlniți un site web și să fiți forțați să efectuați un set de acțiuni pentru a obține în cele din urmă niște date. Vă confruntați apoi cu o dilemă: cum faceți ca aceste date să fie disponibile într-un formular care poate fi ușor consumat de aplicația dvs.?

Răzuirea vine în ajutor într-un astfel de caz. Și selectarea instrumentului potrivit pentru job este destul de importantă.

Cum sa creati un API personalizat de pe orice site
API-ul este doar o modalitate de a privi un site web până la urmă (Sursa: XKCD Comics)

Păpușar: nu doar o altă bibliotecă de răzuire

Păpușar este o bibliotecă Node.js întreținută de echipa Chrome Devtools de la Google. Practic, rulează o instanță Chromium sau Chrome (poate numele mai recunoscut) într-un mod fără cap (sau configurabil) și expune un set de API-uri de nivel înalt.

De la documentație oficială, păpușarul este de obicei folosit pentru mai multe procese care nu se limitează la următoarele:

  • Generarea de capturi de ecran și PDF-uri
  • Accesarea cu crawlere a unui SPA și generarea de conținut pre-redat (de exemplu, redarea laterală a serverului)
  • Testarea extensiilor Chrome
  • Testarea automatizării interfețelor web
  • Diagnosticarea problemelor de performanță prin tehnici precum captarea urmelor cronologice ale unui site web

Pentru cazul nostru, trebuie să putem accesa un site web și să mapăm datele într-un formular care poate fi ușor consumat de aplicația noastră.

ad-banner

Sună simplu? Nici implementarea nu este atât de complexă. Să începem.

Încordarea codului de-a lungul

Dragostea mea pentru produsele Amazon mă determină să folosesc una dintre paginile listării produselor lor ca exemplu aici. Vom implementa cazul nostru de utilizare în doi pași:

  • Extrageți date din pagină și mapați-le într-un formular JSON ușor de consumat
  • Adăugați un pic de automatizare pentru a ne ușura viața

Puteți găsi codul complet în aceasta repertoriu.

Vom extrage datele din acest link: https://www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2 (o listă a cămășilor căutate în top, așa cum se arată în imagine) într-un formular care poate fi deservit de API.

1611260171 361 Cum sa creati un API personalizat de pe orice site
Amazon India – Pagina de afișare a cămășilor

Înainte de a începe să folosim extensiv păpușarul în această secțiune, trebuie să înțelegem cele două clase principale oferite de acesta.

  • Browser: lansează o instanță Chrome când folosim puppeteer.launch sau puppeteer.connect . Aceasta funcționează ca o simplă emulare a browserului.
  • Pagină: seamănă cu o singură filă dintr-un browser Chrome. Oferă un set exhaustiv de metode pe care le puteți utiliza cu o anumită instanță de pagină și este invocat atunci când sunăm browser.newPage. La fel cum puteți crea mai multe file în browser, puteți crea în mod similar mai multe instanțe de pagină simultan în păpușar.

Configurarea Puppeteer și navigarea la adresa URL țintă

Începem să configurăm păpușarul folosind modulul npm furnizat. După instalarea puppeteer, creăm o instanță a browserului și a clasei de pagină și navigăm la adresa URL țintă.

const puppeteer = require('puppeteer');

const url="https://www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2";

async function fetchProductList(url) {
    const browser = await puppeteer.launch({ 
        headless: true, // false: enables one to view the Chrome instance in action
        defaultViewport: null, // (optional) useful only in non-headless mode
    });
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2' });
    ...
}

fetchProductList(url);
Lansarea browserului și navigarea către adresa URL țintă (index.js)

Folosim networkidle2 ca valoare pentru waitUntil în timp ce navigați la adresa URL. Acest lucru asigură faptul că starea de încărcare a paginii este considerată finală atunci când are cel mult 2 conexiuni care rulează timp de cel puțin 500 ms.

Notă: Nu trebuie să aveți Chrome sau o instanță a acestuia instalată pe sistemul dvs. pentru ca păpușarul să funcționeze. Se livrează deja cu o versiune simplificată a acestuia la pachet cu biblioteca.

Metode de pagină pentru extragerea și maparea datelor

DOM-ul s-a încărcat deja în instanța de pagină creată. Vom merge mai departe și vom profita de page.evaluate() metoda de interogare a DOM.

Înainte de a începe, trebuie să ne dăm seama exact de punctele de date pe care trebuie să le extragem. În eșantionul actual, fiecare dintre obiectele produsului va arăta cam așa.

{
	brand: 'Brand Name', 
    product: 'Product Name',
    url: 'https://www.amazon.in/url.of.product.com/',
    image: 'https://www.amazon.in/image.jpg',
    price: '₹599',
}
Structura obiectului produsului

Am stabilit structura pe care vrem să o realizăm. Este timpul să începeți inspecția DOM pentru identificatori. Verificăm dacă sunt selectori care apar în toate elementele care trebuie mapate. Vom folosi mai ales document.querySelector și document.querySelectorAll pentru traversarea DOM.

...

async function fetchProductList(url) {
	...
    
    await page.waitFor('div[data-cel-widget^="search_result_"]');

    const result = await page.evaluate(() => {
        // counts total number of products
        let totalSearchResults = Array.from(document.querySelectorAll('div[data-cel-widget^="search_result_"]')).length;

        let productsList = [];

        for (let i = 1; i < totalSearchResults - 1; i++) {
            let product = {
                brand: '',
                product: '',
            };
            let onlyProduct = false;
            let emptyProductMeta = false;
			
            // traverse for brand and product names
            let productNodes = Array.from(document.querySelectorAll(`div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base`));

            if (productNodes.length === 0) {
                // traverse for brand and product names 
				// (in case previous traversal returned empty elements)
                productNodes = Array.from(document.querySelectorAll(`div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal`));
                productNodes.length > 0 ? onlyProduct = true : emptyProductMeta = true;
            }

            let productsDetails = productNodes.map(el => el.innerText);

            if (!emptyProductMeta) {
                product.brand = onlyProduct ? '' : productsDetails[0];
                product.product = onlyProduct ? productsDetails[0] : productsDetails[1];
            }
			
            // traverse for product image
            let rawImage = document.querySelector(`div[data-cel-widget="search_result_${i}"] .s-image`);
            product.image =rawImage ? rawImage.src : '';
			
            // traverse for product url
            let rawUrl = document.querySelector(`div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal`);
            product.url = rawUrl ? rawUrl.href : '';

            // traverse for product price
            let rawPrice = document.querySelector(`div[data-cel-widget="search_result_${i}"] span.a-offscreen`);
            product.price = rawPrice ? rawPrice.innerText : '';

            if (typeof product.product !== 'undefined') {
                !product.product.trim() ? null : productsList = productsList.concat(product);
            }
        }

        return productsList;
    });
    
    ...
}
    
...
Traversarea DOM și logica de mapare a datelor (index.js)

// traversează numele mărcilor și produselor

După investigarea DOM, vedem că fiecare element listat este închis sub un element cu selectorul div[data-cel-widget^="search_result_"] . Acest selector special caută toate div etichete cu atributul data-cel-widget care au o valoare începând cu search_result_.

În mod similar, mapăm selectorii pentru parametrii pe care îi solicităm, conform listei. Dacă doriți să aflați mai multe despre traversarea DOM, puteți consulta aceste informații articol de Zell.

  • total articole enumerate: div[data-cel-widget^="search_result_"]
  • marca: div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base (i reprezintă numărul de nod din total listed items)
  • produs: div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base sau div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal (i reprezintă numărul de nod din total listed items)
  • url: div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal (i reprezintă numărul de nod din total listed items)
  • imagine: div[data-cel-widget="search_result_${i}"] .s-image (i reprezintă numărul de nod din total listed items)
  • Preț: div[data-cel-widget="search_result_${i}"] span.a-offscreen (i reprezintă numărul de nod din total listed items)

Notă: Așteptăm div[data-cel-widget^="search_result_"] elementele numite selector pentru a fi disponibile pe pagină folosind page.waitFor metodă.

Odata ce page.evaluate este invocată metoda, putem vedea datele de care avem nevoie înregistrate.

1611260172 697 Cum sa creati un API personalizat de pe orice site
Functioneaza! Avem datele noastre API gata să servească ceea ce avem nevoie

Adăugarea automatizării la Ease Flow

Până în prezent, putem naviga la o pagină, extragem datele de care avem nevoie și le putem transforma într-un formular pregătit pentru API. Sună totul hunky-dory.

Cu toate acestea, luați în considerare pentru un moment un caz în care trebuie să navigați la o adresă URL de la alta prin efectuarea unor acțiuni – și apoi încercați să extrageți datele de care aveți nevoie.

Oare asta ți-ar face viața puțin mai complicată? Deloc. Păpușarul poate imita cu ușurință comportamentul utilizatorului. Este timpul să adăugați o automatizare la cazul nostru de utilizare existent.

Spre deosebire de exemplul anterior, vom merge la amazon.in pagina principală și căutați „Cămăși”. Ne va duce la pagina de listare a produselor și putem extrage datele solicitate din DOM. Ușor de țăran. Să ne uităm la cod.

...

async function fetchProductList(url, searchTerm) {
	...
	await page.goto(url, { waitUntil: 'networkidle2' });

    await page.waitFor('input[name="field-keywords"]');
    await page.evaluate(val => document.querySelector('input[name="field-keywords"]').value = val, searchTerm);

    await page.click('div.nav-search-submit.nav-sprite');
    
    // DOM traversal and data mapping logic
	// returns a productsList array
    ...
}

fetchProductList('https://amazon.in', 'Shirts');
Automatizarea operațiunii de căutare și navigarea la pagina de produse (index.js)

Putem vedea că așteptăm ca caseta de căutare să fie disponibilă și apoi adăugăm searchTerm trecut folosind page.evaluate. Navigăm apoi la pagina de listare a produselor, emulând acțiunea de clic „butonul de căutare” și expunând DOM-ul.

Complexitatea automatizării variază de la caz de utilizare la caz de utilizare.

Unele Gotchas notabile: un minor se îndreaptă

API-ul Puppeteer este destul de cuprinzător, dar sunt câteva întâmplări pe care le-am întâlnit în timp ce lucram cu el. Amintiți-vă, nu toate aceste gotchas sunt direct legate de păpușar, dar tind să funcționeze mai bine împreună cu el.

  • Puppeteer creează o instanță a browserului Chrome așa cum s-a menționat deja. Cu toate acestea, este probabil ca unele site-uri web existente să blocheze accesul dacă suspectează activitate bot. Există acest pachet numit user-agents care poate fi folosit cu păpușarul pentru a randomiza agentul utilizator pentru browser.

Notă: Răzuirea unui site web se află undeva în zonele gri ale acceptării legale. Aș recomanda utilizarea acestuia cu precauție și verificarea regulilor în care locuiți.

const puppeteer = require('puppeteer');
const userAgent = require('user-agents');

...

const browser = await puppeteer.launch({ headless: true, defaultViewport: null });
const page = await browser.newPage();
await page.setUserAgent(userAgent.toString());

...
utilizarea agenților utilizator
  • Am dat peste defaultViewport: null la lansarea instanței noastre Chrome și o listasem ca opțională. Acest lucru se datorează faptului că este util doar atunci când vizualizați instanța Chrome lansată. Împiedică lățimea și înălțimea site-ului web să fie afectate atunci când este redat.
  • Păpușarul nu este soluția finală atunci când vine vorba de performanță. Tu, ca dezvoltator, va trebui să-l optimizezi pentru a-i crește eficiența performanței prin acțiuni precum limitarea animațiilor pe site, permițând doar apeluri esențiale în rețea etc.
  • Nu uitați să încheiați întotdeauna o sesiune de păpușar închizând instanța browserului folosind browser.close. (Mi s-a întâmplat să-l pierd în prima încercare) Ajută la încheierea unei sesiuni de navigare în desfășurare.
  • Anumite operațiuni JavaScript obișnuite, cum ar fi console.log() nu va funcționa în cadrul metodelor de pagină. Motivul fiind că contextul paginii / contextul browserului diferă de contextul nodului în care rulează aplicația dvs.

Acestea sunt câteva dintre primele pe care le-am observat. Dacă aveți mai multe, nu ezitați să mă contactați cu ei. Mi-ar plăcea să aflu mai multe.

Terminat? Să rulăm aplicația.

Site-ul web către API-ul dvs.: reuniți totul

Aplicația este rulată în modul fără cap, astfel încât să puteți asista la ce se întâmplă exact. Vom automatiza navigarea către pagina de listare a produselor din care obținem datele.

Cum sa creati un API personalizat de pe orice site

Acolo. Aveți configurarea propriilor date consumabile API de pe site-ul web la alegere. Tot ce trebuie să faceți acum este să conectați acest lucru cu un cadru de tip server express și ești bine să pleci.

Concluzie

Puteți face atât de multe lucruri cu Puppeteer. Acesta este doar un caz de utilizare special. Aș recomanda să petreceți ceva timp pentru a citi documentația oficială. Voi face la fel.

Puppeteer este utilizat pe scară largă în unele dintre cele mai mari organizații pentru sarcini de automatizare, cum ar fi testarea și redarea de pe server, printre altele.

Nu există un moment mai bun pentru a începe cu Puppeteer decât acum.

Dacă aveți întrebări sau comentarii, puteți să mă contactați pe LinkedIn sau Stare de nervozitate.

Între timp, continuați să codificați.