Hogyan hozzunk létre egyéni API-t bármilyen webhelyről a Puppeteer használatával

Gyakran előfordul, hogy rábukkan egy webhelyre, és kénytelen végrehajtani egy sor műveletet, hogy végre megkapja az adatokat. Ezután dilemmával kell szembenéznie: hogyan teszi elérhetővé ezeket az adatokat olyan formában, amelyet az alkalmazása könnyen felhasználhat?

A kaparás ilyenkor segítséget nyújt. A munkához megfelelő eszköz kiválasztása pedig nagyon fontos.

Bábos: Nem csak egy újabb kaparós könyvtár

A Puppeteer egy Node.js könyvtár, amelyet a Google Devtools csapata tart fenn. Alapvetően fej nélküli (vagy konfigurálható) módon futtatja a Chromium vagy a Chrome (talán a jobban felismerhető név) példányt, és egy sor magas szintű API-t tesz elérhetővé.

Hivatalos dokumentációja szerint a bábjátékos általában több folyamathoz is felhasználható, amelyek nem korlátozódnak a következőkre:

  • Képernyőképek és PDF-ek generálása
  • SPA feltérképezése és előre renderelt tartalom létrehozása (azaz szerveroldali megjelenítés)
  • Chrome-bővítmények tesztelése
  • Webinterfészek automatizálási tesztelése
  • A teljesítményproblémák diagnosztizálása olyan technikák segítségével, mint a weboldal idővonalának nyomon követése

Esetünkben képesnek kell lennünk elérni egy weboldalt és feltérképezni az adatokat olyan formában, amelyet alkalmazásunk könnyen felhasználhat.

Egyszerűen hangzik? A megvalósítás sem ilyen összetett. Kezdjük.

Karaktersorozat a kóddal

Az Amazon-termékek iránti szeretetem arra késztet, hogy itt mintaként használjam az egyik terméklistájukat. A felhasználási esetünket két lépésben valósítjuk meg:

  • Bontsa ki az adatokat az oldalról, és térképezze fel könnyen fogyasztható JSON formában
  • Adjunk hozzá egy kis szórást az automatizálásból, hogy egy kicsit megkönnyítsük az életünket

A teljes kódot ebben az adattárban találja meg.

Az adatokat ebből a linkből fogjuk kinyerni: //www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2 (a képen látható legjobban keresett ingek felsorolása) API-kiszolgáló formában.

Mielőtt elkezdenénk a bábjátékos széles körű használatát ebben a szakaszban, meg kell értenünk az általa nyújtott két első osztályt.

  • Böngésző: elindít egy Chrome-példányt, amikor a puppeteer.launchvagy -t használjuk puppeteer.connect. Ez egyszerű böngészőemulációként működik.
  • Oldal: a Chrome böngésző egyetlen lapjára hasonlít. Ez kimerítő módszerkészletet kínál, amelyet egy adott oldalpéldányhoz használhat, és híváskor meghívásra kerül browser.newPage. Ahogy több fület is létrehozhat a böngészőben, ugyanúgy több oldal példányt is létrehozhat egyszerre a bábjátékosban.

Bábjátékos beállítása és navigálás a cél URL-re

A bábjátékos beállítását a mellékelt npm modul használatával kezdjük. A bábjátékos telepítése után létrehozunk egy példányt a böngészőből és az oldalosztályból, és eljutunk a cél URL-hez.

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

Az opció networkidle2értékeként használjuk , waitUntilmiközben az URL-hez navigálunk. Ez biztosítja, hogy az oldal betöltési állapota akkor tekinthető véglegesnek, ha legfeljebb 2 kapcsolat fut legalább 500 ms-ig.

Megjegyzés: A bábművész működéséhez nem kell telepítenie a Chrome-ot vagy annak egy példányát a rendszerére. Már szállít egy egyszerű verzióját a könyvtárral együtt.

Oldalkódok az adatok kinyerésére és feltérképezésére

A DOM már betöltődött a létrehozott oldalpéldányba. Folytatjuk, és felhasználjuk a page.evaluate()módszert a DOM lekérdezésére.

Mielőtt elkezdenénk, ki kell találnunk a pontos adatpontokat, amelyeket ki kell nyernünk. Az aktuális mintában az egyes termékobjektumok ilyennek fognak kinézni.

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

Kidolgoztuk azt a struktúrát, amelyet el akarunk érni. Ideje megvizsgálni a DOM-ot az azonosítók szempontjából. Ellenőrizzük, hogy a leképezésre kerülő elemek között mely szelektorok fordulnak elő. Mi leginkább használni document.querySelector, és document.querySelectorAllaz áthaladó 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; }); ... } ...

// a márka- és terméknevek bejárása

A DOM vizsgálata után azt látjuk, hogy minden felsorolt ​​elem egy elem alá van zárva a választóval div[data-cel-widget^="search_result_"]. Ez a bizonyos választó megkeresi az összes olyan divcímkét az attribútummal data-cel-widget, amelynek kezdőértéke van search_result_.

Hasonlóképpen feltérképezzük a szükséges paraméterek kiválasztóit a felsorolás szerint. Ha többet szeretne megtudni a DOM bejárásáról, akkor nézze meg ezt a Zell informatív cikket.

  • összes felsorolt ​​tétel:div[data-cel-widget^="search_result_"]
  • márka:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base ( ia csomópont számát jelenti total listed items)
  • termék:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base  vagy div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal( ia csomópont számát jelenti total listed items)
  • url:div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal ( ia csomópont számát jelenti total listed items)
  • kép:div[data-cel-widget="search_result_${i}"] .s-image ( ia csomópont számát jelenti total listed items)
  • ár:div[data-cel-widget="search_result_${i}"] span.a-offscreen ( ia csomópont számát jelenti total listed items)
Megjegyzés: A módszer div[data-cel-widget^="search_result_"]használatával megvárjuk, hogy a választó nevű elemek elérhetőek legyenek az oldalon page.waitFor.

Miután a page.evaluatemetódust meghívtuk, láthatjuk a szükséges adatokat naplózva.

Automatizálás hozzáadása az egyszerű áramláshoz

Eddig képesek vagyunk eljutni egy oldalra, kinyerni a szükséges adatokat és átalakítani API-kész formában. Ez minden döcögősen hangzik.

Gondoljon azonban egy pillanatra arra az esetre, amikor bizonyos műveletek végrehajtásával el kell navigálnia az egyik URL-re a másikról - majd megpróbálja kinyerni a szükséges adatokat.

Ettől kicsit trükkösebb lenne az életed? Egyáltalán nem. A Puppeteer könnyen utánozhatja a felhasználói viselkedést. Ideje hozzáadni némi automatizálást a meglévő használati esetünkhöz.

Az előző példával ellentétben a amazon.inkezdőlapra lépünk, és az „Ingek” kifejezésre keresünk. Eljut a terméklistára, és kinyerhetjük a szükséges adatokat a DOM-ból. Könnyű peasy. Nézzük meg a kódot.

... 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.
Megjegyzés: A weboldal lekaparása valahol a jogi elfogadás szürke területein található. Azt javaslom, hogy óvatosan használja, és ellenőrizze a szabályokat, ahol él.
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()); ...
  • A defaultViewport: nullChrome-példány elindításakor találkoztunk, és választhatóként felsoroltam. Ez azért van, mert csak akkor hasznos, ha az indítandó Chrome-példányt nézi. Megakadályozza a weboldal szélességének és magasságának a megjelenítését.
  • A bábjátékos nem a végső megoldás a teljesítmény tekintetében. Fejlesztőként optimalizálnia kell a teljesítmény hatékonyságának növelése érdekében olyan műveletek révén, mint az animációk fojtása a webhelyen, csak az alapvető hálózati hívások engedélyezése stb.
  • 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.