Schermschrapen met Node.js

Mogelijk hebt u NodeJS als een webserver gebruikt, maar wist u dat u het ook voor webschrapen kunt gebruiken? In deze zelfstudie zullen we bekijken hoe u statische webpagina's - en die vervelende met dynamische inhoud - schraapt met de hulp van NodeJS en enkele nuttige NPM-modules.



Een beetje over webschrapen

Webscraping heeft altijd een negatieve bijklank gehad in de wereld van webontwikkeling - en niet zonder reden. In de moderne ontwikkeling zijn API's aanwezig voor de meeste populaire services en deze moeten worden gebruikt om gegevens op te halen in plaats van te schrapen. Het inherente probleem met schrapen is dat het vertrouwt op de visuele structuur van de pagina die wordt geschraapt. Wanneer die HTML verandert - hoe klein de wijziging ook is - kan deze uw code volledig breken.

Ondanks deze tekortkomingen, is het belangrijk om iets te leren over webschrapen en enkele hulpmiddelen die beschikbaar zijn om met deze taak te helpen. Wanneer een site geen API of een syndicatie-feed (RSS / Atom, enz.) Onthult, is de enige optie die overblijft om die inhoud te krijgen ... is schaven.

Opmerking: als u de informatie die u nodig hebt niet kunt krijgen via een API of een feed, is dit een goed teken dat de eigenaar niet wil dat die informatie toegankelijk is. Er zijn echter uitzonderingen.


Waarom NodeJS gebruiken?

Schrapers kunnen eigenlijk in elke taal worden geschreven. De reden waarom ik geniet van het gebruik van Node is vanwege het asynchrone karakter ervan, wat betekent dat mijn code op geen enkel moment in het proces wordt geblokkeerd. Ik ken JavaScript redelijk, dus dat is een extra bonus. Ten slotte zijn er enkele nieuwe modules die zijn geschreven voor NodeJS, waardoor het eenvoudig is om websites op een betrouwbare manier te schrapen (goed, zo betrouwbaar als schrapen kan krijgen!). Laten we beginnen!


Eenvoudig schrapen met YQL

Laten we beginnen met de eenvoudige use-case: statische webpagina's. Dit zijn uw standaard gangbare webpagina's. Voor deze, Yahoo! Query Language (YQL) zou het werk zeer goed moeten doen. Voor degenen die niet vertrouwd zijn met YQL, is het een SQL-achtige syntaxis die kan worden gebruikt om op een consistente manier met verschillende API's te werken.

YQL heeft een aantal geweldige tabellen om ontwikkelaars te helpen HTML van een pagina te krijgen. Degenen die ik wil benadrukken zijn:

  • html
  • data.html.cssselect
  • htmlstring

Laten we ze allemaal doornemen en bekijken hoe ze in NodeJS kunnen worden geïmplementeerd.

html tafel

De html tabel is de meest eenvoudige manier om HTML van een URL te verwijderen. Een gewone zoekopdracht met deze tabel ziet er als volgt uit:

selecteer * uit html waar url = "http://finance.yahoo.com/q?s=yhoo" en xpath = "// div [@ id =" yfi_headlines "] / div [2] / ul / li / a "

Deze query bestaat uit twee parameters: de "url" en de "xpath". De URL spreekt voor zich. De XPath bestaat uit een XPath-string die aan YQL vertelt welk deel van de HTML moet worden geretourneerd. Probeer deze vraag hier.

Aanvullende parameters die u kunt gebruiken, zijn onder meer browser (Boolean), charset (string), en compat (draad). Ik heb deze parameters niet hoeven gebruiken, maar raadpleeg de documentatie als u specifieke behoeften hebt.

Niet comfortabel met XPath?

Helaas is XPath geen erg populaire manier om de HTML-boomstructuur te doorlopen. Het kan ingewikkeld zijn om voor beginners te lezen en te schrijven.

Laten we naar de volgende tabel kijken, die hetzelfde doet, maar u in plaats daarvan CSS laat gebruiken

data.html.cssselect tafel

De data.html.cssselect tabel is mijn geprefereerde manier om HTML van een pagina te schrapen. Het werkt op dezelfde manier als het html tabel maar kunt u CSS in plaats van XPath. In de praktijk converteert deze tabel de CSS naar XPath onder de motorkap en roept vervolgens de html tafel, dus het is een beetje langzamer. Het verschil zou te verwaarlozen moeten zijn voor het schrapen van behoeften.

Een gewone zoekopdracht met deze tabel ziet er als volgt uit:

selecteer * from data.html.cssselecteer waar url = "www.yahoo.com" en css = "# news a"

Zoals u kunt zien, is het veel schoner. Ik raad aan dat u deze methode eerst probeert wanneer u HTML probeert te schrapen met YQL. Probeer deze vraag hier.

htmlstring tafel

De htmlstring tabel is handig voor gevallen waarin u probeert een groot deel van de opgemaakte tekst van een webpagina te schrapen.

Met behulp van deze tabel kunt u de volledige HTML-inhoud van die pagina ophalen in een enkele tekenreeks, in plaats van als JSON die is gesplitst op basis van de DOM-structuur.

Bijvoorbeeld een standaard JSON-antwoord dat een scrapt tag ziet er zo uit:

"resultaten": "a": "href": "...", "target": "_blank", "content": "Apple Chief Executive Cook to Climb in New Stage"

Zie je hoe de kenmerken worden gedefinieerd als eigenschappen? In plaats daarvan is het antwoord van de htmlstring tabel ziet er als volgt uit:

"resultaten": "result": "Apple Chief Executive Cook to Climb in een nieuw stadium

Dus waarom zou je dit gebruiken? Welnu, vanuit mijn ervaring komt dit heel goed van pas als je een groot aantal opgemaakte tekst probeert te schrapen. Neem bijvoorbeeld het volgende fragment:

Lorem ipsum dolor zit amet, consectetur adipiscing elit.

Proin nec diam magna. Sed non lorem a nisi porttitor pharetra et non arcu.

Door de htmlstring tabel, kunt u deze HTML als een tekenreeks krijgen en regex gebruiken om de HTML-tags te verwijderen, waardoor u alleen de tekst krijgt. Dit is een eenvoudigere taak dan itereren via JSON die is opgesplitst in eigenschappen en onderliggende objecten op basis van de DOM-structuur van de pagina.


YQL gebruiken met NodeJS

Nu we iets weten over enkele van de tabellen die beschikbaar zijn in YQL, laten we een webschraper implementeren met YQL en NodeJS. Gelukkig is dit heel eenvoudig, dankzij de knooppunt-YQL module door Derek Gathright.

We kunnen de module installeren met NPM:

npm yql installeren

De module is uiterst eenvoudig, bestaande uit slechts één methode: de YQL.exec () methode. Het wordt gedefinieerd als het volgende:

function exec (string query [, functie callback] [, objectparams] [, object httpOptions])

We kunnen het gebruiken door het te eisen en te bellen YQL.exec (). Laten we bijvoorbeeld zeggen dat we de koppen van alle berichten op de hoofdpagina van Nettuts willen schrapen:

var YQL = require ("yql"); nieuwe YQL.exec ('select * from data.html.cssselect where url = "http://net.tutsplus.com/" en css = ". post_title a"', functie (antwoord) // antwoord bestaat uit JSON die je kunt ontleden);

Het mooie van YQL is de mogelijkheid om uw zoekopdrachten te testen en te bepalen wat JSON u in real time terugkrijgt. Ga naar de console om deze query uit te proberen, of klik hier om de onbewerkte JSON te bekijken.

De params en httpOptions objecten zijn optioneel. Parameters kunnen eigenschappen bevatten zoals env (of u een specifieke omgeving voor de tabellen gebruikt) en formaat (xml of json). Alle eigenschappen zijn ingevoerd params zijn URI-gecodeerd en toegevoegd aan de querystring. De httpOptions object wordt doorgegeven aan de header van het verzoek. Hier kunt u bijvoorbeeld aangeven of u SSL wilt inschakelen.

Het JavaScript-bestand, genaamd yqlServer.js, bevat de minimale code die vereist is om te schrapen met YQL. U kunt het uitvoeren door de volgende opdracht in uw terminal op te geven:

knooppunt yqlServer.js

Uitzonderingen en andere opmerkelijke hulpmiddelen

YQL is mijn favoriete keuze voor het verwijderen van inhoud van statische webpagina's, omdat het gemakkelijk te lezen en gebruiksvriendelijk is. YQL mislukt echter als de betreffende webpagina een robots.txt bestand dat een reactie daarop weigert. In dit geval kunt u enkele van de hieronder genoemde hulpprogramma's bekijken of PhantomJS gebruiken, die we in het volgende gedeelte zullen bespreken.

Node.io is een handig hulpprogramma Node dat specifiek is ontworpen voor gegevensschrapen. U kunt taken maken die invoer uitvoeren, deze verwerken en een aantal uitvoer retourneren. Node.io wordt goed bekeken op Github en heeft enkele handige voorbeelden om u op weg te helpen.

JSDOM is een zeer populair project dat de W3C DOM implementeert in JavaScript. Bij geleverde HTML kan het een DOM construeren waarmee u kunt communiceren. Bekijk de documentatie om te zien hoe u JSDOM en elke JS-bibliotheek (zoals jQuery) samen kunt gebruiken om gegevens van webpagina's te schrapen.


Pagina's schrapen met dynamische inhoud

Tot nu toe hebben we gekeken naar enkele hulpmiddelen die ons kunnen helpen webpagina's te schrapen met statische inhoud. Met YQL is het relatief eenvoudig. Helaas worden we vaak geconfronteerd met pagina's met inhoud die dynamisch wordt geladen met JavaScript. In deze gevallen is de pagina in eerste instantie vaak leeg en wordt de inhoud achteraf toegevoegd. Hoe kunnen we dit probleem aanpakken??

Een voorbeeld

Laat me een voorbeeld geven van wat ik bedoel; Ik heb een eenvoudig HTML-bestand geüpload naar mijn eigen website, die inhoud toevoegt via JavaScript, twee seconden na de document.ready () functie wordt aangeroepen. U kunt hier de pagina bekijken. Dit is hoe de bron eruit ziet:

   Testpagina met inhoud toegevoegd na laden van de pagina   Inhoud op deze pagina wordt toegevoegd aan de DOM nadat de pagina is geladen. 

Laten we nu proberen de tekst in de. Te schrapen

met behulp van YQL.

var YQL = require ("yql"); nieuwe YQL.exec ('select * from data.html.cssselect where url = "http://tilomitra.com/repository/screenscrape/ajax.html" en css = "# content"', functie (antwoord) // Dit zal undefined terugkeren! Het schrapen was niet succesvol! Console.log (response.results););

Je zult merken dat YQL terugkomt onbepaald omdat, wanneer de pagina is geladen, de

is leeg. De inhoud is nog niet toegevoegd. U kunt de vraag hier zelf uitproberen.

Laten we kijken naar hoe we dit probleem kunnen omzeilen!

Voer PhantomJS in

PhantomJS kan webpagina's laden en een op Webkit gebaseerde browser nabootsen zonder de GUI.

Mijn voorkeursmethode voor het schrapen van informatie van deze sites is om PhantomJS te gebruiken. PhantomJS beschrijft zichzelf als een "headless-webkit met een JavaScript-API." In simplistische termen betekent dit dat PhantomJS webpagina's kan laden en een op Webkit gebaseerde browser kan nabootsen zonder de GUI. Als ontwikkelaar kunnen we een beroep doen op specifieke methoden die PhantomJS biedt voor voer code uit op de pagina. Omdat het zich gedraagt ​​als een browser, lopen scripts op de webpagina zoals ze zouden doen in een gewone browser.

Om gegevens van onze pagina te halen, gaan we PhantomJS-Node gebruiken, een geweldig klein open-sourceproject dat PhantomJS met NodeJS overbrugt. Onder de motorkap draait deze module PhantomJS als een kindproces.

Installeren van PhantomJS

Voordat u de PhantomJS-Node NPM-module kunt installeren, moet u PhantomJS installeren. Installeren en bouwen van PhantomJS kan echter een beetje lastig zijn.

Ga eerst naar PhantomJS.org en download de juiste versie voor uw besturingssysteem. In mijn geval was het Mac OSX.

Na het downloaden, unzip het naar ergens zoals / Applications /. Vervolgens wil je het aan je toevoegen PAD:

sudo ln -s / Applications/phantomjs-1.5.0/bin/phantomjs / usr / local / bin /

Vervangen 1.5.0 met je gedownloade versie van PhantomJS. Houd er rekening mee dat niet alle systemen zullen hebben / Usr / local / bin /. Sommige systemen hebben: / Usr / bin /, / Bin /, of usr / X11 / bin in plaats daarvan.

Voor Windows-gebruikers, bekijk de korte tutorial hier. Je weet dat je helemaal klaar bent als je je Terminal opent en schrijft phantomjs, en je krijgt geen fouten.

Als u zich ongemakkelijk voelt bij het bewerken van uw PAD, noteer waar je PhantomJS hebt uitgepakt en ik zal een andere manier laten zien om het in te stellen in de volgende sectie, hoewel ik je aanraad om je PAD.

Installeren van PhantomJS-Node

Het instellen van PhantomJS-Node is veel eenvoudiger. Mits je NodeJS hebt geïnstalleerd, kun je dit via npm installeren:

npm phantom installeren

Als je je niet hebt bewerkt PAD in de vorige stap bij het installeren van PhantomJS, kunt u naar de phantom / map naar beneden getrokken door npm en bewerk deze regel in phantom.js.

ps = child.spawn ('phantomjs', args.concat ([__ dirname + '/shim.js', port]));

Verander het pad naar:

ps = child.spawn ('/ path / to / phantomjs-1.5.0 / bin / phantomjs', args.concat ([__ dirname + '/shim.js', port]));

Zodra dat is gebeurd, kunt u het uittesten door deze code uit te voeren:

 var phantom = require ('phantom'); phantom.create (functie (ph) return ph.createPage (functie (pagina) return page.open ("http://www.google.com", functie (status) console.log ("google geopend?" , status); return page.evaluate ((function () return document.title;), function (result) console.log ('Paginatitel is' + result); return ph.exit ();); );););

Dit uitvoeren op de opdrachtregel moet het volgende weergeven:

google geopend? succes De paginatitel is Google

Als je dit hebt, ben je helemaal klaar en klaar om te gaan. Zo niet, plaats dan een reactie en ik zal proberen u te helpen!

PhantomJS-Node gebruiken

Om het u gemakkelijker te maken, heb ik een JS-bestand met de naam phantomServer.js in de download die een deel van de API van PhantomJS gebruikt om een ​​webpagina te laden. Het wacht 5 seconden voordat JavaScript wordt uitgevoerd dat de pagina schraapt. U kunt het uitvoeren door naar de map te gaan en de volgende opdracht in uw terminal op te geven:

 knooppunt phantomServer.js

Ik zal een overzicht geven van hoe het hier werkt. Ten eerste hebben we PhantomJS nodig:

 var phantom = require ('phantom');

Vervolgens implementeren we enkele methoden uit de API. We maken namelijk een pagina-instantie en bellen vervolgens de Open() methode:

 phantom.create (functie (ph) return ph.createPage (functie (pagina) // Vanaf hier kunnen we de API-methoden van PhantomJS gebruiken return page.open ("http://tilomitra.com/repository/screenscrape /ajax.html ", functie (status) // De pagina is nu geopend console.log (" geopende site? ", status););););

Als de pagina eenmaal is geopend, kunnen we wat JavaScript in de pagina injecteren. Laten we jQuery injecteren via de page.injectJs () methode:

 phantom.create (functie (ph) return ph.createPage (functie (pagina) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", functie (status) console.log ("geopende site?", status); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function () // jQuery Loaded // We kunnen hier dingen als $ ("body"). Html () gebruiken.););););

jQuery is nu geladen, maar we weten niet of de dynamische inhoud van de pagina al is geladen. Om dit te verklaren, plaats ik mijn schrapende code meestal in een setTimeout () functie die na een bepaald tijdsinterval wordt uitgevoerd. Als u een meer dynamische oplossing wilt, kunt u met de PhantomJS API bepaalde gebeurtenissen beluisteren en emuleren. Laten we gaan met de eenvoudige case:

 setTimeout (function () return page.evaluate (function () // Krijg wat je zoekt op de pagina met jQuery. // Een goede manier is om een ​​object te vullen met alle jQuery-opdrachten die je nodig hebt en het object vervolgens terug te sturen . var h2Arr = [], // array die alle html voor h2-elementen bevat pArr = []; // array die alle html voor p-elementen bevat // Vul de twee arrays $ ('h2'). each (function () h2Arr.push ($ (this) .html ());); $ ('p'). each (function () pArr.push ($ (this) .html ());); // Retourneer deze gegevens retour h2: h2Arr, p: pArr, function (result) console.log (result); // Log uit de gegevens ph.exit (););, 5000);

Alles bij elkaar, onze phantomServer.js bestand ziet er als volgt uit:

 var phantom = require ('phantom'); phantom.create (functie (ph) return ph.createPage (functie (pagina) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", functie (status) console.log ("geopende site?", status); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', function () // jQuery Loaded . // Wacht een beetje totdat AJAX-inhoud op de pagina wordt geladen. Hier wachten we 5 seconden. SetTimeout (function () return page.evaluate (function () // Krijg wat je wilt van de pagina met jQuery Een goede manier is om een ​​object te vullen met alle jQuery-opdrachten die u nodig hebt en vervolgens het object te retourneren var h2Arr = [], pArr = []; $ ('h2'). Each (function () h2Arr.push ($ (this) .html ());); $ ('p'). each (function () pArr.push ($ (this) .html ());); return h2: h2Arr, p: pArr;, function (result) console.log (result); ph.exit (););, 5000);););););

Deze implementatie is een beetje grof en ongeorganiseerd, maar het maakt het punt. Met PhantomJS kunnen we een pagina schrapen met dynamische inhoud! Uw console zou het volgende moeten uitvoeren:

 → node phantomServer.js geopende site? succes h2: ['Artikel 1', 'Artikel 2', 'Artikel 3'], p: ['Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Ut sed nulla turpis, in faucibus ante. Vivamus ut malesuada est. Curabitur vel enim eget purus pharetra tempor id in tellus. ',' Curabitur euismod hendrerit quam ut euismod. Ut leo sem, viverra nec gravida nec, tristique nec arcu. ' ]

Conclusie

In deze zelfstudie hebben we twee verschillende manieren besproken voor het uitvoeren van webschrapen. Als we scrapen vanaf een statische webpagina, kunnen we profiteren van YQL, dat eenvoudig is in te stellen en te gebruiken. Aan de andere kant kunnen we voor dynamische sites gebruikmaken van PhantomJS. Het is iets moeilijker om in te stellen, maar biedt meer mogelijkheden. Denk eraan: u kunt PhantomJS ook voor statische sites gebruiken!

Als u vragen over dit onderwerp heeft, kunt u hieronder vragen stellen en ik zal mijn best doen om u te helpen.