Πώς να δημιουργήσετε ένα προσαρμοσμένο API από οποιονδήποτε ιστότοπο χρησιμοποιώντας το Puppeteer

Συχνά συμβαίνει να συναντήσετε έναν ιστότοπο και αναγκάζεστε να εκτελέσετε ένα σύνολο ενεργειών για να λάβετε τελικά κάποια δεδομένα. Τότε αντιμετωπίζετε ένα δίλημμα: πώς κάνετε αυτά τα δεδομένα διαθέσιμα σε μια μορφή που μπορεί εύκολα να καταναλωθεί από την εφαρμογή σας;

Η απόξεση έρχεται στη διάσωση σε μια τέτοια περίπτωση. Και η επιλογή του σωστού εργαλείου για την εργασία είναι πολύ σημαντική.

Puppeteer: Όχι απλώς μια άλλη βιβλιοθήκη ξύσματος

Το Puppeteer είναι μια βιβλιοθήκη Node.js που διατηρείται από την ομάδα του Chrome Devtools στο Google. Εκτελεί βασικά μια παρουσία Chromium ή Chrome (ίσως το πιο αναγνωρίσιμο όνομα) με τρόπο χωρίς κεφαλή (ή ρυθμιζόμενο) και εκθέτει ένα σύνολο API υψηλού επιπέδου.

Από την επίσημη τεκμηρίωσή του, ο κουκλοπαίκτης συνήθως αξιοποιείται για πολλαπλές διαδικασίες που δεν περιορίζονται στα ακόλουθα:

  • Δημιουργία στιγμιότυπων οθόνης και PDF
  • Ανίχνευση SPA και δημιουργία περιεχομένου προ-αποδίδεται (π.χ. Server Side Rendering)
  • Δοκιμή επεκτάσεων Chrome
  • Έλεγχος αυτοματισμού διεπαφών Ιστού
  • Διάγνωση ζητημάτων απόδοσης μέσω τεχνικών όπως η καταγραφή του ίχνους χρονοδιαγράμματος ενός ιστότοπου

Για την περίπτωσή μας, πρέπει να είμαστε σε θέση να αποκτήσουμε πρόσβαση σε έναν ιστότοπο και να χαρτογραφήσουμε τα δεδομένα σε μια φόρμα που μπορεί εύκολα να καταναλωθεί από την εφαρμογή μας.

Ακούγεται απλό; Η εφαρμογή δεν είναι ούτε τόσο περίπλοκη. Ας αρχίσουμε.

Συμβολοσειρά του κώδικα

Η αγάπη μου για τα προϊόντα Amazon με κάνει να χρησιμοποιήσω μια από τις σελίδες καταχώρισης προϊόντων ως δείγμα εδώ. Θα εφαρμόσουμε τη θήκη χρήσης μας σε δύο βήματα:

  • Εξαγάγετε δεδομένα από τη σελίδα και χαρτογράψτε τα σε μια εύκολα αναλώσιμη μορφή JSON
  • Προσθέστε λίγο πασπάλισμα αυτοματοποίησης για να κάνετε τη ζωή μας λίγο πιο εύκολη

Μπορείτε να βρείτε τον πλήρη κώδικα σε αυτό το αποθετήριο.

Θα εξαγάγουμε τα δεδομένα από αυτόν τον σύνδεσμο: //www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2 (μια λίστα με τα κορυφαία πουκάμισα που αναζητήθηκαν όπως φαίνεται στην εικόνα) σε μια μορφή που εξυπηρετείται από το API.

Πριν αρχίσουμε να χρησιμοποιούμε εκτενώς το puppeteer σε αυτήν την ενότητα, πρέπει να κατανοήσουμε τις δύο κύριες τάξεις που παρέχει.

  • Πρόγραμμα περιήγησης: εκκινεί μια παρουσία Chrome όταν χρησιμοποιούμε puppeteer.launchή puppeteer.connect. Αυτό λειτουργεί ως απλή εξομοίωση του προγράμματος περιήγησης.
  • Σελίδα: μοιάζει με μία καρτέλα σε πρόγραμμα περιήγησης Chrome. Παρέχει ένα εξαντλητικό σύνολο μεθόδων που μπορείτε να χρησιμοποιήσετε με μια συγκεκριμένη παρουσία σελίδας και καλείται όταν καλούμε browser.newPage. Ακριβώς όπως μπορείτε να δημιουργήσετε πολλές καρτέλες στο πρόγραμμα περιήγησης, μπορείτε επίσης να δημιουργήσετε πολλές παρουσίες σελίδων ταυτόχρονα στο κουκλοπαίκτη.

Ρύθμιση Puppeteer και πλοήγηση στη διεύθυνση URL προορισμού

Αρχίζουμε να δημιουργούμε puppeteer χρησιμοποιώντας την παρεχόμενη ενότητα npm. Μετά την εγκατάσταση του puppeteer, δημιουργούμε μια παρουσία του προγράμματος περιήγησης και της κλάσης σελίδων και μεταβαίνουμε στη διεύθυνση URL προορισμού.

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

Χρησιμοποιούμε networkidle2ως τιμή για την waitUntilεπιλογή κατά την πλοήγηση στη διεύθυνση URL. Αυτό διασφαλίζει ότι η κατάσταση φόρτωσης σελίδας θεωρείται οριστική όταν δεν εκτελεί περισσότερες από 2 συνδέσεις για τουλάχιστον 500ms.

Σημείωση: Δεν χρειάζεται να έχετε εγκατεστημένο το Chrome ή μια παρουσία του στο σύστημά σας για να λειτουργεί το κουκλοθέατρο. Στέλνει ήδη με μια lite έκδοση μαζί με τη βιβλιοθήκη.

Μέθοδοι σελίδας για εξαγωγή και χαρτογράφηση δεδομένων

Το DOM έχει ήδη φορτωθεί στην παρουσία σελίδας που δημιουργήθηκε. Θα προχωρήσουμε και θα αξιοποιήσουμε τη page.evaluate()μέθοδο για το ερώτημα του DOM.

Πριν ξεκινήσουμε, πρέπει να καταλάβουμε τα ακριβή σημεία δεδομένων που πρέπει να εξαγάγουμε. Στο τρέχον δείγμα, κάθε αντικείμενο του προϊόντος θα μοιάζει με αυτό.

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

Έχουμε καθορίσει τη δομή που θέλουμε να επιτύχουμε. Ώρα να ξεκινήσετε τον έλεγχο του DOM για τα αναγνωριστικά. Ελέγχουμε για τους επιλογείς που εμφανίζονται σε όλα τα στοιχεία που θα χαρτογραφηθούν Θα χρησιμοποιήσουμε ως επί το πλείστον document.querySelectorκαι document.querySelectorAllγια να διασχίσουμε το 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; }); ... } ...

// διασχίστε τις επωνυμίες και τα ονόματα προϊόντων

Αφού ερευνήσουμε το DOM, βλέπουμε ότι κάθε αναφερόμενο στοιχείο περικλείεται κάτω από ένα στοιχείο με τον επιλογέα div[data-cel-widget^="search_result_"]. Αυτός ο συγκεκριμένος επιλογέας αναζητά όλες τις divετικέτες με το χαρακτηριστικό data-cel-widgetπου έχει τιμή ξεκινώντας από search_result_.

Ομοίως, χαρτογραφούμε τους επιλογείς για τις παραμέτρους που απαιτούνται όπως αναφέρονται. Αν θέλετε να μάθετε περισσότερα σχετικά με το DOM traversal, μπορείτε να δείτε αυτό το ενημερωτικό άρθρο της Zell.

  • σύνολο καταχωρισμένων αντικειμένων:div[data-cel-widget^="search_result_"]
  • μάρκα:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base ( iσημαίνει τον αριθμό κόμβου σε total listed items)
  • προϊόν:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base  ή div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal( iσημαίνει τον αριθμό κόμβου σε total listed items)
  • url:div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal ( iσημαίνει τον αριθμό κόμβου σε total listed items)
  • εικόνα:div[data-cel-widget="search_result_${i}"] .s-image ( iσημαίνει τον αριθμό κόμβου στο total listed items)
  • τιμή:div[data-cel-widget="search_result_${i}"] span.a-offscreen ( iσημαίνει τον αριθμό κόμβου σε total listed items)
Σημείωση: Περιμένουμε div[data-cel-widget^="search_result_"]να είναι διαθέσιμα στοιχεία επιλογής με όνομα στη σελίδα χρησιμοποιώντας τη page.waitForμέθοδο.

Μόλις page.evaluateγίνει επίκληση της μεθόδου, μπορούμε να δούμε τα δεδομένα που απαιτούνται για καταγραφή.

Προσθήκη αυτοματισμού για διευκόλυνση της ροής

Μέχρι στιγμής είμαστε σε θέση να πλοηγηθούμε σε μια σελίδα, να εξαγάγουμε τα δεδομένα που χρειαζόμαστε και να τα μετατρέψουμε σε μορφή έτοιμη για API. Ακούγεται πολύ χάνκι-Ντόρι.

Ωστόσο, σκεφτείτε για μια στιγμή μια περίπτωση όπου πρέπει να πλοηγηθείτε σε μια διεύθυνση URL από την άλλη εκτελώντας ορισμένες ενέργειες - και στη συνέχεια προσπαθήστε να εξαγάγετε τα δεδομένα που χρειάζεστε.

Αυτό θα έκανε τη ζωή σας λίγο πιο δύσκολη; Καθόλου. Το Puppeteer μπορεί εύκολα να μιμηθεί τη συμπεριφορά των χρηστών. Ώρα να προσθέσουμε κάποιο αυτοματισμό στην υπάρχουσα θήκη χρήσης μας.

Σε αντίθεση με το προηγούμενο παράδειγμα, θα μεταβούμε στην amazon.inαρχική σελίδα και θα αναζητήσουμε "Πουκάμισα". Θα μας μεταφέρει στη σελίδα καταχώρισης προϊόντων και μπορούμε να εξαγάγουμε τα δεδομένα που απαιτούνται από το DOM. Πανεύκολο. Ας δούμε τον κώδικα.

... 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.
Σημείωση: Η απόσυρση ενός ιστότοπου βρίσκεται κάπου στις γκρίζες περιοχές νομικής αποδοχής. Θα το συνιστούσα να το χρησιμοποιείτε με προσοχή και να ελέγχετε τους κανόνες όπου ζείτε.
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()); ...
  • Αντιμετωπίσαμε defaultViewport: nullκατά την εκκίνηση της παρουσίας Chrome και το είχα αναφέρει ως προαιρετικό. Αυτό συμβαίνει επειδή είναι χρήσιμο μόνο όταν βλέπετε την παρουσία του Chrome που ξεκινά. Αποτρέπει το πλάτος και το ύψος της ιστοσελίδας να επηρεαστεί κατά την απόδοση.
  • Το Puppeteer δεν είναι η απόλυτη λύση όσον αφορά την απόδοση. Εσείς, ως προγραμματιστής, θα πρέπει να το βελτιστοποιήσετε για να αυξήσετε την αποδοτικότητα της απόδοσής του μέσω ενεργειών, όπως περιορισμός κινούμενων σχεδίων στον ιστότοπο, επιτρέποντας μόνο βασικές κλήσεις δικτύου κ.λπ.
  • 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.

Εκεί. Έχετε τη δική σας ρύθμιση αναλώσιμων δεδομένων API από τον ιστότοπο της επιλογής σας. Το μόνο που χρειάζεται να κάνετε τώρα είναι να το συνδέσετε με ένα πλαίσιο διακομιστή όπως expressκαι είστε έτοιμοι.

συμπέρασμα

Υπάρχουν τόσα πολλά που μπορείτε να κάνετε με το Puppeteer. Αυτή είναι μόνο μια συγκεκριμένη περίπτωση χρήσης. Θα συνιστούσα να αφιερώσετε λίγο χρόνο για να διαβάσετε την επίσημη τεκμηρίωση. Θα κάνω το ίδιο.

Το Puppeteer χρησιμοποιείται εκτενώς σε ορισμένους από τους μεγαλύτερους οργανισμούς για εργασίες αυτοματοποίησης, όπως δοκιμές και απόδοση από πλευράς διακομιστή, μεταξύ άλλων.

Δεν υπάρχει καλύτερη στιγμή για να ξεκινήσετε με το Puppeteer από τώρα.

Εάν έχετε οποιεσδήποτε ερωτήσεις ή σχόλια, μπορείτε να επικοινωνήσετε μαζί μου στο LinkedIn ή στο Twitter.

Εν τω μεταξύ, συνεχίστε την κωδικοποίηση.