Sådan oprettes en brugerdefineret API fra ethvert websted ved hjælp af dukketeater

Det sker ofte, at du støder på et websted og er tvunget til at udføre et sæt handlinger for endelig at få nogle data. Derefter står du over for et dilemma: hvordan gør du disse data tilgængelige i en form, der let kan forbruges af din applikation?

Skrabning kommer til undsætning i et sådant tilfælde. Og det er ret vigtigt at vælge det rigtige værktøj til jobbet.

Puppeteer: Ikke bare endnu et skrabebibliotek

Puppeteer er et Node.js-bibliotek, der vedligeholdes af Chrome Devtools Team hos Google. Det kører grundlæggende en Chromium eller Chrome (måske det mere genkendelige navn) forekomst på en hovedløs (eller konfigurerbar) måde og udsætter et sæt API'er på højt niveau.

Fra sin officielle dokumentation udnyttes dukketeater normalt til flere processer, som ikke er begrænset til følgende:

  • Generere skærmbilleder og PDF-filer
  • Gennemgang af en SPA og generering af præ-gengivet indhold (dvs. Server Side Rendering)
  • Test af Chrome-udvidelser
  • Automatiseringstest af webgrænseflader
  • Diagnose af præstationsproblemer ved hjælp af teknikker som at registrere tidslinjespor på et websted

For vores sag skal vi have adgang til et websted og kortlægge dataene i en form, der let kan forbruges af vores applikation.

Det lyder simpelt? Implementeringen er heller ikke så kompleks. Lad os begynde.

At stramme koden sammen

Min kærlighed til Amazon-produkter beder mig om at bruge en af ​​deres produktlisteside som en prøve her. Vi implementerer vores brugssag i to trin:

  • Uddrag data fra siden og kortlæg dem i en JSON-form, der er let forbrugt
  • Tilføj et lille stænk automatisering for at gøre vores liv lidt lettere

Du kan finde den komplette kode i dette lager.

Vi udtrækker dataene fra dette link: //www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2 (en liste over de mest søgte skjorter som vist på billedet) i en API-betjenbar form.

Før vi kommer i gang med at bruge dukketeater i dette afsnit, er vi nødt til at forstå de to primære klasser, der leveres af den.

  • Browser: starter en Chrome-forekomst, når vi bruger puppeteer.launcheller puppeteer.connect. Dette fungerer som en simpel browseremulering.
  • Side: ligner en enkelt fane i en Chrome-browser. Det giver et udtømmende sæt metoder, du kan bruge med en bestemt sideinstans, og kaldes på, når vi ringer browser.newPage. Ligesom du kan oprette flere faner i browseren, kan du på samme måde oprette flere sider forekomster på én gang i dukketeater.

Opsætning af dukketeater og navigering til mål-URL

Vi begynder at opsætte dukkeudøver ved hjælp af det medfølgende npm-modul. Efter installation af dukketeater opretter vi en forekomst af browseren og sideklassen og navigerer til mål-URL'en.

const puppeteer = require('puppeteer'); const url = '//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); 

Vi bruger networkidle2som værdien for waitUntilindstillingen, mens vi navigerer til URL'en. Dette sikrer, at sideindlæsningstilstanden betragtes som endelig, når den ikke har mere end 2 forbindelser, der kører i mindst 500 ms.

Bemærk: Du behøver ikke at have Chrome eller en forekomst af det installeret på dit system for at marionetmænd fungerer. Den leveres allerede med en lite version af den, der følger med biblioteket.

Sidemetoder til udpakning og kortdata

DOM er allerede indlæst i den oprettede sideinstans. Vi vil fortsætte og udnytte page.evaluate()metoden til at forespørge DOM.

Før vi starter, skal vi finde ud af de nøjagtige datapunkter, vi skal udtrække. I den aktuelle prøve vil hvert af produktobjekterne se sådan ud.

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

Vi har lagt den struktur, vi ønsker at opnå. Tid til at begynde at inspicere DOM for identifikatorerne. Vi kontrollerer for de vælgere, der forekommer i de varer, der skal kortlægges. Vi bruger mest document.querySelectorog document.querySelectorAlltil at krydse 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  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; }); ... } ...

// gennemgå mærke- og produktnavne

Efter at have undersøgt DOM ser vi, at hvert anført emne er lukket under et element med vælgeren div[data-cel-widget^="search_result_"]. Denne særlige vælger søger alle divtags med attributten, data-cel-widgetder har en værdi, der starter med search_result_.

På samme måde kortlægger vi vælgerne for de parametre, vi har brug for, som angivet. Hvis du vil lære mere om DOM-traversal, kan du tjekke denne informative artikel af Zell.

  • i alt listede varer:div[data-cel-widget^="search_result_"]
  • brand:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base ( istår for nodenummeret i total listed items)
  • produkt:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base  eller div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal( istår for nodenummeret i total listed items)
  • url:div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal ( istår for nodenummeret i total listed items)
  • billede:div[data-cel-widget="search_result_${i}"] .s-image ( istår for nodenummeret i total listed items)
  • pris:div[data-cel-widget="search_result_${i}"] span.a-offscreen ( istår for nodenummeret i total listed items)
Bemærk: Vi venter på, at div[data-cel-widget^="search_result_"]selektor-navngivne elementer er tilgængelige på siden ved hjælp af page.waitFormetoden.

Når page.evaluatemetoden er påberåbt, kan vi se de data, vi har brug for, logget.

Tilføjelse af automatisering for at lette flow

Indtil videre er vi i stand til at navigere til en side, udtrække de data, vi har brug for, og omdanne dem til en API-klar form. Det lyder alt sammen uhyggeligt.

Overvej dog et øjeblik et tilfælde, hvor du skal navigere til en URL fra en anden ved at udføre nogle handlinger - og prøv derefter at udtrække de data, du har brug for.

Ville det gøre dit liv lidt vanskeligere? Slet ikke. Puppeteer kan let efterligne brugeradfærd. Tid til at tilføje nogle automatiseringer til vores eksisterende brugssag.

I modsætning til i det foregående eksempel vil vi gå til amazon.inhjemmesiden og søge efter 'Skjorter'. Det fører os til listen over produkter, og vi kan udtrække de krævede data fra DOM. Let peasy. Lad os se på koden.

... 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('//amazon.in', 'Shirts'); 

We can see that we wait for the search box to be available and then we add the searchTerm passed using page.evaluate. We then navigate to the products listing page by emulating the 'search button' click action and exposing the DOM.

The complexity of automation varies from use case to use case.

Some Notable Gotchas: A Minor Heads Up

Puppeteer's API is pretty comprehensive but there are a few gotchas I came across while working with it. Remember, not all of these gotchas are directly related to puppeteer but tend to work better along with it.

  • Puppeteer creates a Chrome browser instance as already mentioned. However, it is likely that some existing websites might block access if they suspect bot activity. There is this package called user-agents which can be used with puppeteer to randomize the user-agent for the browser.
Bemærk: Skrabning af et websted ligger et eller andet sted i de grå områder af juridisk accept. Jeg vil anbefale at bruge det med forsigtighed og kontrollere reglerne, hvor du bor.
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()); ...
  • Vi stødte på, defaultViewport: nullda vi startede vores Chrome-forekomst, og jeg havde opført den som valgfri. Dette skyldes, at det kun er praktisk, når du ser Chrome-forekomsten, der startes. Det forhindrer webstedets bredde og højde i at blive påvirket, når det gengives.
  • Puppeteer er ikke den ultimative løsning, når det kommer til ydeevne. Du, som udvikler, bliver nødt til at optimere den for at øge dens effektivitet gennem handlinger som kvælningsanimationer på webstedet og kun tillader vigtige netværksopkald osv.
  • Remember to always end a puppeteer session by closing the Browser instance by using browser.close. (I happened to miss out on it in the first try) It helps end a running Browser Session.
  • Certain common JavaScript operations like console.log() will not work within the scope of the page methods. The reason being that the page context/browser context differs from the node context in which your application is running.

These are some of the gotchas I noticed. If you have more, feel free to reach out to me with them. I would love to learn more.

Done? Let's run the application.

Website to Your API: Bringing it All Together

The application is run in non-headless mode so you can witness what exactly happens. We will automate the navigation to the product listing page from which we obtain the data.

Der. Du har din egen API-forbrugsdataopsætning fra det valgte websted. Alt hvad du skal gøre nu er at koble dette op med en serverside som f.eks. expressOg du er god at gå.

Konklusion

Der er så meget, du kan gøre med Puppeteer. Dette er kun en bestemt brugssag. Jeg vil anbefale, at du bruger lidt tid på at læse den officielle dokumentation. Jeg vil gøre det samme.

Puppeteer bruges i vid udstrækning i nogle af de største organisationer til blandt andet automatiseringsopgaver som test og serversidesætning.

Der er ikke noget bedre tidspunkt at komme i gang med Puppeteer end nu.

Hvis du har spørgsmål eller kommentarer, kan du kontakte mig på LinkedIn eller Twitter.

I mellemtiden skal du fortsætte med at kode.