Heim BlogWeb-Scraping Web Scraping mit Node.js: So nutzen Sie die Leistungsfähigkeit von JavaScript

Web Scraping mit Node.js: So nutzen Sie die Leistungsfähigkeit von JavaScript

von Kadek

NodeJS ist eine JavaScript-Laufzeitumgebung, die auf der von Google entwickelten V8-JS-Engine aufbaut. Vor allem aber ist Node.js eine Plattform zum Erstellen von Webanwendungen. Wie JavaScript eignet es sich ideal zum Lösen von Webaufgaben.

Es gibt mehrere Web-Scraping-Tools für Node.js: Axios, SuperAgent, Cheerio und Puppeteer mit Headless-Browsern.

Vorteile der Verwendung von Node.js für Web Scraping

Unser Unternehmen verwendet einen JavaScript + NodeJS + MongoDB-Stack in einer Linux-Shell für Web-Scraping. Das verbindende Glied ist NodeJS, das eine Reihe unbestreitbarer Vorteile bietet.

Erstens ist NodeJS als Laufzeitumgebung effizient, da es asynchrone I/O-Vorgänge unterstützt. Dies beschleunigt die Anwendung für HTTP-Anfragen und Datenbankanfragen in Bereichen, in denen der Hauptausführungsthread nicht von den Ergebnissen der E/A abhängt.

Zweitens unterstützt NodeJS die Streaming-Datenübertragung (Stream), was die Verarbeitung großer Dateien (oder Daten) auch bei minimalen Systemanforderungen erleichtert.

Drittens enthält NodeJS viele integrierte Module, die die Interaktion mit dem Betriebssystem und dem Web unterstützen. Zum Beispiel FileSystem und Path für Dateneingabe-/-ausgabeprozeduren auf der Systemfestplatte, URL zum Bearbeiten von Routenparametern und Abfrageparametern in URL, Process und Child-Prozesse – für die Verwaltung von Betriebssystemprozessen, die Crawlern dienen, sowie Utils, Debugger und so weiter .

Viertens enthält das NodeJS-Ökosystem eine große Anzahl von Paketen aus der Entwicklergemeinschaft, die bei der Lösung nahezu jedes Problems helfen können. Für das Scraping gibt es beispielsweise Bibliotheken wie Axios, SuperAgent, Cheerio und Puppeteer. Und wenn Sie Google mithilfe dieser Bibliotheken scrapen möchten, empfehlen wir Ihnen, unseren Artikel zu lesen: „Web Scraping Google mit Node JS“.

HTTP-Anfragen in NodeJS mit Axios

Axios ist ein versprochener HTTP-Client.

Für das Scraping wird von Axios wenig benötigt – die meisten Anfragen werden mit dem gesendet GET Methode.

Verwenden wir:

const axios = require('axios');
const UserAgent = require('user-agents');

const axios = axios.create({
	headers: {
		'user-agent': (new UserAgent()).toString(),
		'cookie': process.env.COOKIE,
		'accept': '*/*',
		'accept-encoding': 'gzip, deflate, br',
	},
	timeout: 10000,
	proxy: {
	  host: process.env.PROXY_HOST,
	  port: process.env.PROXY_PORT,
	  auth: {
		  username: process.env.PROXY_USERNAME,
		  password: process.env.PROXY_PASSWORD,
		}
  },
})

Hier, ein axios Die Instanz wird mit einer Beispielkonfiguration erstellt.

Die Benutzeragentenbibliothek generiert tatsächliche Werte für den gleichnamigen Header, sodass diese Werte nicht manuell eingegeben werden müssen.

Cookies werden über ein separates Skript empfangen, dessen Aufgabe darin besteht, mithilfe der Puppeteer-Bibliothek eine Chromium-Instanz zu starten, sich auf der Zielwebsite zu authentifizieren und den vom Browser zwischengespeicherten Cookie-Wert an die Anwendung zu übergeben.

Im Beispiel ist die Cookie-Eigenschaft an den Wert der Umgebungsvariablen gebunden process.env.COOKIE. Dies impliziert, dass das eigentliche Cookie in einer Umgebungsvariablen platziert wurde, beispielsweise mit dem pm2-Prozessmanager.

Obwohl der Cookie-Wert (der nur eine Zeichenfolge ist) direkt in der Konfiguration oben festgelegt werden kann, indem man ihn aus dem Browser-Entwicklerbereich kopiert.

Es bleibt noch eine http-Anfrage zu senden und den Inhalt abzurufen data Eigentum der Response Objekt. Dies ist in der Regel HTML, kann aber auch jedes andere Format der benötigten Daten sein.

const yourAsyncFunc = async () => {
	const { data } = await axios.get(targetUrl); // data --> <!DOCTYPE html>... and so on

	// some code
}

SuperAgent zum Parsen in NodeJS

Als Alternative zu Axios gibt es einen leichtgewichtigen SuperAgent-HTTP-Client. Man kann ein einfaches machen GET Anfrage wie folgt:

const superagent = require('superagent');

const yourAsyncFunc = async () => {
	const { text } = await superagent.get(targetUrl); // text --> page content

	// some code
}

Es hat einen guten Ruf für die Entwicklung von Webanwendungen, die AJAX verwenden. Eine Funktion des SuperAgent ist die Pipeline-Methode zum Festlegen der Anforderungskonfiguration:

const superagent = require('superagent');

superagent
  .get('origin-url/route')
  .set('User-Agent': '<some UA>')
  .query({ city: 'London' })  
	.end((err, res) => {
    // // Calling the end function will send the request
		const { text } = res; // text --> page content
    // some other code
  });

Mit dem Superagent-Proxy-Wrapper kann man Anfragen einen Proxy-Dienst hinzufügen. Auch SuperAgent unterstützt async/await Syntax.

Strukturtransformation mit Cheerio

Cheerio ist ein unverzichtbares Tool zum Konvertieren von String-HTML-Inhalten in eine Baumstruktur, zum anschließenden Durchlaufen dieser Struktur und zum Extrahieren der erforderlichen Daten.

Beim Scraping werden hauptsächlich die Load-API und die Selectors-API verwendet.

Wird geladen:

const cheerio = require('cheerio');

// somewhere inside asynchronous func...
const { data } = await fetchPageContent(url);

const $ = cheerio.load(data); // now we've got cheerio node tree in '$'

Die nächsten Schritte bestehen darin, Knoten mithilfe von Selektoren auszuwählen und die erforderlichen Daten zu extrahieren.

Hier ist ein Beispiel für die Verwendung von cheerio, das alle Knoten, die einer Kombination von Selektoren entsprechen, in eine Sammlung filtert und die darin enthaltenen Links extrahiert:

const urls = $('.catalog li :nth-child(1) a')
	.filter((i, el) => $(el).attr('href'))
	.map((i, el) => $(el).attr('href'))
	.toArray();

// urls --> ('url1', 'url2', ..., 'urlN')

Das sagt:

  • Finde ein Element mit dem .container Klasse im Markup (auf jeder Verschachtelungsebene);
  • Wählen Sie alle Elemente mit aus li Tag (auf jeder Verschachtelungsebene innerhalb .container);
  • in jedem der li Elemente, wählen Sie das erste untergeordnete Element aus;
  • Wählen Sie im ersten untergeordneten Element alle Elemente mit aus a tag (auf jeder Verschachtelungsebene);
  • Filtern Sie nur diese a Elemente, die enthalten href Attribut;
  • iterieren Sie über die resultierende Sammlung von a Elemente und extrahieren Sie die Werte aus demhref Attribut;
  • Schreiben Sie die empfangenen Links in das JS-Array.

Dieser Code enthält nur 4 kurze Zeilen, weist aber eine hohe Befehlsdichte auf.

Es ist erwähnenswert, dass, wenn Cheerio nicht die erforderlichen Inhaltsfragmente zurückgibt, überprüfen Sie, ob das vom Webserver empfangene HTML-Markup wirklich das enthält, was benötigt wird.

Headless Browser mit Puppeteer

Headless-Browser werden verwendet, um Benutzeraktionen auf einer Seite programmgesteuert zu simulieren, ohne eine GUI-Instanz herunterzuladen.

Die Verwendung eines Headless-Browsers verbraucht auf die eine oder andere Weise Systemressourcen und erhöht die Gesamtlaufzeit der Anwendung. Darüber hinaus muss darauf geachtet werden, dass Prozesse mit Browser-Instanzen nicht im System offen bleiben, da ihr unkontrolliertes Wachstum den gesamten Server zum Absturz bringen würde.

Von den „kopflosen“ Versionen wird am häufigsten Chromium verwendet, das über die Puppeteer-Bibliothek verwaltet wird. Der häufigste Grund für sein Erscheinen im Scraper-Code ist ein Popup-Captcha (oder die Anforderung, vor dem Laden eine Art JS-Code auszuführen). Inhalt). Der Browser erhält die Aufgabe vom Captcha, wartet auf eine Lösung und sendet eine Antwort mit der Lösung an den Webserver. Erst danach erhält der aufrufende Code HTML zum Parsen.

Manchmal wird ein Headless-Browser verwendet, um ein Autorisierungs-Cookie zu empfangen und in sehr seltenen Fällen Inhalte zu laden, indem das Scrollen mit der Maus simuliert wird.

Verwendung:

// puppeteer-service.js
const puppeteer = require('puppeteer');

module.exports = async () => {
try {
	const browser = await puppeteer.launch({
		args: ('--window-size=1920,1080'),
	  headless: process.env.NODE_ENV === 'PROD', // FALSE means you can see all performance during development
	});

	const page = await browser.newPage();
	await page.setUserAgent('any-user-agent-value');
	await page.goto('any-target-url', { waitUntil: ('domcontentloaded') });
	await page.waitForSelector('any-selector');
	await page.type('input.email-input', username);
	await page.type('input.password-input', password);
	await page.click('button(value="Log in")');
	await page.waitForTimeout(3000);

	return page.evaluate(
	  () => document.querySelector('.pagination').innerHTML,
  );
} catch (err) {
		// handling error
} finally {
		browser && await this.browser.close();
	}
}

// somewhere in code
const fetchPagination = require('./puppeteer-service.js');

const $ = cheerio.load(await fetchPagination());
// some useful code next...

Dieses Code-Snippet bietet ein vereinfachtes Beispiel mit grundlegenden Methoden zum Bearbeiten einer Chromium-Seite. Zunächst werden eine Browserinstanz und eine leere Seite instanziiert. Nach dem Festlegen des Headers 'User-Agent' Die Seite fordert Inhalte unter der angegebenen URL an.

Stellen Sie als Nächstes sicher, dass der erforderliche Inhalt geladen wurde (waitForSelector), geben Sie den Benutzernamen und das Passwort in die Berechtigungsfelder ein (type), klicken Sie auf die Schaltfläche „Anmelden“ (click), stellen Sie die Seite so ein, dass sie 3 Sekunden lang wartet (waitForTimeout), bei dem der Inhalt des autorisierten Benutzers geladen wird und schließlich das resultierende HTML-Markup des gewünschten Stücks mit Paginierung an den Anfang zurückgegeben wird.

Verwendung von Javascripts Async zur Geschwindigkeitssteigerung

Die von NodeJS und JavaScript unterstützten asynchronen I/O-Funktionen können zur Beschleunigung der Anwendung genutzt werden. Es gibt zwei Bedingungen: freie Prozessorkerne, auf denen man asynchrone Prozesse getrennt vom Hauptausführungsthread ausführen kann, und Unabhängigkeit des Hauptthreads vom Ergebnis einer asynchronen Operation.

Es ist nicht möglich, die Asynchronität von HTTP-Anfragen zu nutzen, um die Laufzeit des Prozesses zu beschleunigen, da die Fortsetzung des Hauptthreads direkt vom Erhalt einer Antwort auf die HTTP-Anfrage abhängt.

Gleiches gilt für Operationen zum Auslesen von Informationen aus der Datenbank. Bis die Daten empfangen sind, hat der Hauptthread des Scrapers wenig zu tun.

Aber man kann Daten getrennt vom Mainstream in die Datenbank schreiben. Angenommen, irgendwann im Code wird ein Objekt empfangen, das in die Datenbank geschrieben werden soll. Abhängig vom Ergebnis der Aufzeichnung ist es notwendig, weitere Aktionen zu verzweigen und erst dann mit einer weiteren Iteration fortzufahren.

Anstatt an der Stelle, an der die Funktion „Daten schreiben“ aufgerufen wurde, auf das Ergebnis zu warten, kann man einen neuen Prozess erstellen und ihm diese Arbeit zuweisen. Der Hauptthread kann sofort weitergehen.

// in some place of code...

// we've got the object for inserting to database
const data = {
	product: 'Jacket',
	price: 55,
	currency: 'EUR',
};

const { fork } = require('child_process');

// launch the other bind process
const child = fork('path-to-write-data.js');

child.send(JSON.stringify(data));

// do the next code iteration...

Implementierung des Codes für den Kindprozess in einer separaten Datei:

// write-data.js

// inside acync function
process.on('message', (data) => {
	const result = await db.writeDataFunc(JSON.parse(data));

	if (result) {
		// do some impotant things
	} else {
		// do other impotant things 
	}
})

Im Allgemeinen besteht das Merkmal der Fork-Methode darin, dass sie es den übergeordneten und untergeordneten Prozessen ermöglicht, Nachrichten miteinander auszutauschen. In diesem Beispiel wird jedoch zu Demonstrationszwecken die Arbeit an den untergeordneten Prozess delegiert, ohne den übergeordneten Prozess zu benachrichtigen, sodass dieser seinen nächsten Ausführungsthread parallel zum untergeordneten Prozess ausarbeiten kann.

Vermeiden Sie Blockaden beim Schaben

Die meisten Zielwebsites, von denen Daten erfasst werden, wehren sich aktiv gegen diesen Prozess. Natürlich wissen die Entwickler dieser Ressourcen, wie es funktioniert. Das bedeutet, dass es oft nicht ausreicht, nur die richtigen Header zu setzen, um die gesamte Website zu crawlen.

Webserver können die Verbreitung von Inhalten einschränken, nachdem eine bestimmte Anzahl von Anfragen von einer IP pro Zeiteinheit erreicht wurde. Sie können den Zugriff einschränken, wenn sie feststellen, dass die Anfrage von einem Rechenzentrums-Proxy stammt.

Sie können zur Lösung ein Captcha senden, wenn ihnen der Standort der IP-Adresse des Kunden unzuverlässig erscheint. Oder sie bieten möglicherweise an, JS-Code auf der Seite auszuführen, bevor der Hauptinhalt geladen wird.

Der Zweck besteht darin, Webserver-Metriken zu verwenden, um den Eindruck zu erwecken, dass die Anfrage vom Browser des Benutzers und nicht von einem Bot stammt. Wenn es realistisch genug aussieht, sendet der Server den Inhalt so, dass er nicht versehentlich den echten Benutzer und nicht den Bot einschränkt.

Wenn ein so wählerischer Webserver auftaucht, wird das Problem gelöst, indem die Proxy-Adressen korrekt lokalisiert und deren Qualität schrittweise bis hin zu Privatadressen erhöht wird. Der Nachteil dieser Entscheidung sind die erhöhten Kosten für die Datenerfassung.

Fazit und Erkenntnisse

NodeJS und JavaScript eignen sich hervorragend für das Daten-Scraping in allen Teilen des Prozesses. Wenn ein Stack oder Executor benötigt wird, sind NodeJS, JavaScript und MongoDB eine der besten Optionen. Der Einsatz von NodeJS ermöglicht nicht nur die Lösung aller möglichen Probleme im Bereich Scraping, sondern gewährleistet auch den Schutz und die Zuverlässigkeit der Datenextraktion. Und durch den Einsatz von Headless-Browsern wird das Nutzerverhalten nachgeahmt.

Related Posts

Hinterlasse einen Kommentar