Kako stvoriti prilagođeni API s bilo koje web stranice pomoću Lutkarstva

Često se dogodi da naiđete na web stranicu i prisiljeni ste izvršiti skup radnji da biste konačno dobili neke podatke. Tada ćete se suočiti s dilemom: kako ove podatke učiniti dostupnima u obliku koji vaša aplikacija može lako potrošiti?

U takvom slučaju u pomoć dolazi struganje. A odabir pravog alata za posao prilično je važan.

Lutkar: Ne samo još jedna biblioteka za struganje

Puppeteer je knjižnica Node.js koju održava Googleov tim za Chrome Devtools. U osnovi pokreće instancu Chromium ili Chrome (možda prepoznatljivije ime) na bezglav (ili podesiv) način i izlaže skup API-ja na visokoj razini.

Iz službene dokumentacije, lutkar se obično koristi za više procesa koji nisu ograničeni na sljedeće:

  • Generiranje snimaka zaslona i PDF-ova
  • Indeksiranje SPA-a i generiranje unaprijed generiranog sadržaja (tj. Generiranje na strani poslužitelja)
  • Testiranje Chromeovih proširenja
  • Ispitivanje automatizacije web sučelja
  • Dijagnosticiranje problema s izvedbom kroz tehnike poput bilježenja vremenskog traga web stranice

U našem slučaju moramo biti u mogućnosti pristupiti web mjestu i mapirati podatke u obliku koji naša aplikacija može lako potrošiti.

Zvuči jednostavno? Ni primjena nije toliko složena. Počnimo.

Nizanje koda

Moja naklonost Amazon proizvodima potiče me da ovdje koristim jednu od njihovih stranica s popisom proizvoda. Primjenu upotrebe primijenit ćemo u dva koraka:

  • Izdvojite podatke sa stranice i mapirajte ih u JSON obliku koji se lako može potrošiti
  • Dodajte malo automatskog posipa kako biste nam malo olakšali život

Kompletni kod možete pronaći u ovom spremištu.

Podatke ćemo izdvojiti s ove veze: //www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2 (popis najtraženijih košulja kao što je prikazano na slici) u API obliku koji se može poslužiti.

Prije nego što započnemo opsežno koristiti lutkar u ovom odjeljku, moramo razumjeti dva osnovna razreda koja nudi.

  • Preglednik: pokreće instancu Chromea kada koristimo puppeteer.launchili puppeteer.connect. Ovo djeluje kao jednostavna emulacija preglednika.
  • Stranica: sliči jednoj kartici u pregledniku Chrome. Pruža iscrpan skup metoda koje možete koristiti s određenom instancom stranice i poziva se na njih kada zovemo browser.newPage. Baš kao što možete stvoriti više kartica u pregledniku, na sličan način u lutkaru možete istovremeno stvoriti više instanci stranica.

Postavljanje lutkara i navigacija do ciljanog URL-a

Počinjemo postavljati lutkare koristeći ponuđeni npm modul. Nakon instalacije lutkarstva, kreiramo instancu preglednika i klase stranice i idemo do ciljanog URL-a.

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); 

Koristimo networkidle2kao vrijednost za waitUntilopciju tijekom kretanja do URL-a. To osigurava da se stanje učitavanja stranice smatra konačnim kada nema najmanje dvije veze koje se izvode tijekom najmanje 500 ms.

Napomena: Da bi lutkar mogao raditi, na vašem sustavu ne trebate instalirati Chrome niti njegovu instancu. Već se isporučuje s osnovnom verzijom u paketu s knjižnicom.

Metode stranica za izdvajanje i mapiranje podataka

DOM se već učitao u stvorenoj instanci stranice. Ići ćemo naprijed i koristiti page.evaluate()metodu za postavljanje upita za DOM.

Prije nego što započnemo, moramo shvatiti točne podatkovne točke koje moramo izvući. U trenutnom uzorku, svaki od predmeta proizvoda izgledat će otprilike ovako.

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

Izložili smo strukturu koju želimo postići. Vrijeme je da započnemo s pregledom DOM-a radi identifikatora. Provjeravamo postoje li selektori koji se javljaju u svim stavkama koje se mapiraju. Uglavnom ćemo koristiti document.querySelectori document.querySelectorAllza prolazak DOM-om.

... 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; }); ... } ...

// prijelaz za nazive marki i proizvoda

Nakon istrage DOM-a vidimo da je svaka navedena stavka zatvorena ispod elementa s biračem div[data-cel-widget^="search_result_"]. Ovaj određeni selektor traži sve divoznake s atributom data-cel-widgetkoje imaju vrijednost koja počinje s search_result_.

Slično tome, mapiramo selektore za parametre koji su nam potrebni kako su navedeni. Ako želite saznati više o prelasku DOM-a, možete pogledati ovaj informativni članak tvrtke Zell.

  • ukupno navedene stavke:div[data-cel-widget^="search_result_"]
  • marka:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base ( ioznačava broj čvora u total listed items)
  • proizvod:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base  ili div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal( ioznačava broj čvora u total listed items)
  • url:div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal ( ioznačava broj čvora u total listed items)
  • slika:div[data-cel-widget="search_result_${i}"] .s-image ( ioznačava broj čvora u total listed items)
  • cijena:div[data-cel-widget="search_result_${i}"] span.a-offscreen ( ioznačava broj čvora u total listed items)
Napomena: Čekamo da div[data-cel-widget^="search_result_"]elementi s imenom selektora budu dostupni na stranici pomoću page.waitFormetode.

Jednom kada se page.evaluatemetoda pozove, možemo vidjeti podatke koje trebamo evidentirati.

Dodavanje automatizacije za olakšavanje protoka

Do sada smo u mogućnosti doći do stranice, izvući podatke koji su nam potrebni i transformirati ih u obrazac spreman za API. To zvuči skroz smešno.

Međutim, na trenutak razmotrite slučaj kada morate doći do jednog URL-a s drugog izvršavajući neke radnje - a zatim pokušajte izdvojiti podatke koji su vam potrebni.

Bi li to vaš život učinilo malo zamršenijim? Nikako. Lutkar može lako oponašati ponašanje korisnika. Vrijeme je da dodamo malo automatizacije našem postojećem slučaju korištenja.

Za razliku od prethodnog primjera, ići ćemo na amazon.inpočetnu stranicu i tražiti 'Košulje'. Odvest će nas na stranicu s popisom proizvoda i podatke koji su potrebni možemo izvući iz DOM-a. Lako graškast. Pogledajmo kod.

... 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.
Napomena: Struzanje web mjesta leži negdje u sivim područjima pravnog prihvaćanja. Preporučio bih ga oprezno i ​​provjeravao pravila u kojima živite.
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()); ...
  • Naišli smo defaultViewport: nullprilikom pokretanja Chromeove instance i naveo sam je kao neobaveznu. To je zato što vam dobro dođe samo kada gledate Chromeovu instancu koja se pokreće. Sprječava utjecaj na širinu i visinu web stranice prilikom prikazivanja.
  • Lutkar nije krajnje rješenje kada su u pitanju performanse. Vi ćete ga kao programer morati optimizirati kako biste povećali učinkovitost izvedbe radnjama poput prigušivanja animacija na web mjestu, dopuštajući samo bitne mrežne pozive itd.
  • 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.

There. You have your own API consumable data setup from the website of your choice. All you need to do now is to wire this up with a server side framework like express and you are good to go.

Conclusion

There is so much you can do with Puppeteer. This is just one particular use case. I would recommend that you spend some time to read the official documentation. I will be doing the same.

Puppeteer is used extensively in some of the largest organizations for automation tasks like testing and server side rendering, among others.

There is no better time to get started with Puppeteer than now.

If you have any questions or comments, you can reach out to me on LinkedIn or Twitter.

In the meantime, keep coding.