Skärmskärning med Node.js

Du kan ha använt NodeJS som en webbserver, men visste du att du också kan använda den för webbskrapning? I denna handledning granskar vi hur du skrapar statiska webbsidor - och de irriterande med dynamiskt innehåll - med hjälp av NodeJS och några användbara NPM-moduler.



En bit om webbskrapning

Webskrapning har alltid haft en negativ konnotation i webutvecklingsvärlden - och med goda skäl. I modern utveckling finns API-apparater för populäraste tjänster och de bör användas för att hämta data istället för att skrapa. Det inneboende problemet med skrapning är att det bygger på den visuella strukturen på sidan som skrapas. När den HTML ändras - oavsett hur liten förändringen kan vara - kan den helt bryta din kod.

Trots dessa brister är det viktigt att lära sig lite om webbskrapning och några av de verktyg som finns tillgängliga för att hjälpa till med den här uppgiften. När en webbplats inte avslöjar ett API eller ett syndikatflöde (RSS / Atom, etc), är det enda alternativet vi lämnar för att få det innehållet ... att skrapa.

Obs! Om du inte kan få den information du behöver genom ett API eller ett flöde är det ett gott tecken på att ägaren inte vill att informationen ska vara tillgänglig. Det finns emellertid undantag.


Varför använda NodeJS?

Skrapor kan skrivas på vilket språk som helst. Anledningen till att jag tycker om att använda Node är på grund av sin asynkrona natur, vilket innebär att min kod inte blockeras någon gång i processen. Jag är ganska bekant med JavaScript så det är en extra bonus. Slutligen finns det några nya moduler som har skrivits för NodeJS som gör det enkelt att skrapa webbplatser på ett tillförlitligt sätt (bra, så pålitlig som skrapa kan få!). Låt oss börja!


Enkel skrapning med YQL

Låt oss börja med det enkla användningsfallet: statiska webbsidor. Det här är dina vanliga webbsidor på webben. För dessa, Yahoo! Query Language (YQL) ska göra jobbet mycket bra. För dem som är obekanta med YQL, är det en SQL-liknande syntax som kan användas för att fungera med olika API på ett konsekvent sätt.

YQL har några bra tabeller för att hjälpa utvecklare att hämta HTML från en sida. De jag vill lyfta fram är:

  • html
  • data.html.cssselect
  • htmlstring

Låt oss gå igenom var och en och granska hur de ska implementeras i NodeJS.

html tabell

De html Tabellen är det mest grundläggande sättet att skrapa HTML från en webbadress. En vanlig fråga med denna tabell ser så här ut:

välj * från html där url = "http://finance.yahoo.com/q?s=yhoo" och xpath = "// div [@ id =" yfi_headlines "] / div [2] / ul / li / a "

Denna fråga består av två parametrar: "url" och "xpath". Urln är självförklarande. XPath består av en XPath-sträng som berättar YQL vilken del av HTML som ska returneras. Prova denna fråga här.

Ytterligare parametrar som du kan använda inkluderar browser (Boolean), teckenuppsättning (sträng) och compat (sträng). Jag har inte behövt använda dessa parametrar, men hänvisar till dokumentationen om du har särskilda behov.

Inte bekväm med XPath?

Tyvärr är XPath inte ett mycket populärt sätt att korsa HTML-trädstrukturen. Det kan vara svårt att läsa och skriva för nybörjare.

Låt oss titta på nästa tabell, vilket gör samma sak men låter dig använda CSS istället

data.html.cssselect tabell

De data.html.cssselect tabellen är mitt föredragna sätt att skrapa HTML från en sida. Det fungerar på samma sätt som html bord men låter dig CSS istället för XPath. I praktiken omvandlar denna tabell CSS till XPath under huven och ringer sedan html bord, så det är lite långsammare. Skillnaden bör vara försumbar för skrapa behov.

En vanlig fråga med denna tabell ser ut som:

välj * från data.html.cssselect where url = "www.yahoo.com" och css = "# news a"

Som du kan se är det mycket renare. Jag rekommenderar att du försöker den här metoden först när du försöker skrapa HTML med YQL. Prova denna fråga här.

htmlstring tabell

De htmlstring bordet är användbart för fall där du försöker skrapa en stor bit av formaterad text från en webbsida.

Med hjälp av denna tabell kan du hämta hela HTML-innehållet på den sidan i en enda sträng, snarare än som JSON som delas baserat på DOM-strukturen.

Till exempel, ett vanligt JSON-svar som skrapar en taggen ser så här ut:

"resultat": "a": "href": "...", "mål": "_blank", "innehåll": "Chef för Apple Chef för att klättra på ett nytt steg"

Se hur attributen definieras som egenskaper? I stället svaret från htmlstring bordet skulle se ut så här:

"Resultat": "Resultat": "Apple Chef Cook Till Klättra på ett nytt steg

Så, varför skulle du använda det här? Tja, från min erfarenhet kommer det här till stor nytta när du försöker skrapa en stor mängd formaterad text. Tänk på följande avsnitt:

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Proin nec diam magna. Förlust inte en nisi porttitor pharetra et non arcu.

Genom att använda htmlstring tabell, kan du hämta den här HTML-filen som en sträng och använda regex för att ta bort HTML-taggarna, vilket lämnar dig med bara texten. Det här är en enklare uppgift än att iterera genom JSON som har delats in i egenskaper och barnobjekt baserat på sidans DOM-struktur.


Använda YQL med NodeJS

Nu när vi vet lite om några av de tabeller som är tillgängliga för oss i YQL, låt oss implementera en webbskrapa med YQL och NodeJS. Lyckligtvis är detta väldigt enkelt tack vare nod-YQL modul av Derek Gathright.

Vi kan installera modulen med npm:

npm installera yql

Modulen är extremt enkel, bestående av endast en metod: YQL.exec () metod. Det definieras som följande:

funktion exec (strängfråga [, funktion återuppringning] [, objektparametrar] [, objekt httpOptions])

Vi kan använda det genom att kräva det och ringa YQL.exec (). Låt oss till exempel säga att vi vill skrapa rubrikerna från alla inlägg på huvudsidan för Nettut:

var YQL = kräver ("yql"); ny YQL.exec ('välj * från data.html.cssselect där url = "http://net.tutsplus.com/" och css = ". post_title a", funktion (svar) // svaret består av JSON att du kan analysera);

Det fantastiska med YQL är dess förmåga att testa dina frågor och bestämma vilken JSON du kommer tillbaka i realtid. Gå till konsolen för att prova den här frågan eller klicka här för att se den råa JSON.

De params och httpOptions Objekt är valfria. Parametrar kan innehålla egenskaper som env (om du använder en specifik miljö för tabellerna) och formatera (xml eller json). Alla fastigheter gick in i params är URI-kodade och bifogas förfrågningssträngen. De httpOptions objektet skickas in i rubriken för begäran. Här kan du ange om du vill aktivera SSL, till exempel.

JavaScript-filen, namngiven yqlServer.js, innehåller den minsta koden som krävs för att skrapa med YQL. Du kan köra det genom att utfärda följande kommando i din terminal:

nod yqlServer.js

Undantag och andra anmärkningsvärda verktyg

YQL är mitt föredragna val för skrapning av innehåll från statiska webbsidor, eftersom det är lätt att läsa och enkelt att använda. Men YQL kommer att misslyckas om den aktuella webbsidan har en robots.txt fil som avslår ett svar på det. I det här fallet kan du titta på några av de verktyg som nämns nedan eller använda PhantomJS som vi kommer att täcka i följande avsnitt.

Node.io är ett användbart nodverktyg som är särskilt utformat för dataskrapning. Du kan skapa jobb som tar in, bearbetar det och returnerar en viss produktion. Node.io är väl övervakad på Github, och har några användbara exempel för att komma igång.

JSDOM är ett mycket populärt projekt som implementerar W3C DOM i JavaScript. När den levereras HTML kan den konstruera en DOM som du kan interagera med. Kolla in dokumentationen för att se hur du kan använda JSDOM och något JS-bibliotek (till exempel jQuery) tillsammans för att skrapa data från webbsidor.


Skrapningssidor med dynamiskt innehåll

Hittills har vi tittat på några verktyg som kan hjälpa oss att skrapa webbsidor med statiskt innehåll. Med YQL är det relativt enkelt. Tyvärr presenteras vi ofta med sidor som har innehåll som laddas dynamiskt med JavaScript. I dessa fall är sidan ofta tom i början och sedan läggs innehållet efteråt. Hur kan vi hantera denna fråga?

Ett exempel

Låt mig ge ett exempel på vad jag menar; Jag har laddat upp en enkel HTML-fil till min egen webbplats, som lägger till något innehåll via JavaScript, två sekunder efter document.ready () funktion kallas. Du kan kolla sidan här. Så här ser källan ut:

   Testsida med innehåll bifogat efter sidladdning   Innehållet på den här sidan bifogas DOM efter att sidan har laddats. 

Nu ska vi försöka skrapa texten inuti

använder YQL.

var YQL = kräver ("yql"); ny YQL.exec ('välj * från data.html.cssselect där url = "http://tilomitra.com/repository/screenscrape/ajax.html" och css = "# content"', funktion (svar) // Detta kommer att returneras odefinierat! Skrapningen misslyckades! Console.log (response.results););

Du kommer märka att YQL återvänder odefinierad eftersom, när sidan är laddad,

är tom. Innehållet har inte bifogats än. Du kan prova frågan ut för dig själv här.

Låt oss se hur vi kan lösa problemet!

Ange PhantomJS

PhantomJS kan ladda webbsidor och efterlikna en webkitbaserad webbläsare utan GUI.

Min föredragna metod för att skrapa information från dessa platser är att använda PhantomJS. PhantomJS beskriver sig som en "headless Webkit med ett JavaScript-API." Förenklat innebär det att PhantomJS kan ladda webbsidor och efterlikna en webkitbaserad webbläsare utan GUI. Som utvecklare kan vi ringa på specifika metoder som PhantomJS tillhandahåller kör kod på sidan. Eftersom den beter sig som en webbläsare, kör skript på webbsidan som de skulle i en vanlig webbläsare.

För att få data från vår sida ska vi använda PhantomJS-Node, ett bra litet open source-projekt som överbryggar PhantomJS med NodeJS. Under huven går den här modulen PhantomJS som barnprocess.

Installera PhantomJS

Innan du kan installera PhantomJS-Node NPM-modulen måste du installera PhantomJS. Installera och bygga PhantomJS kan dock vara lite knepigt.

Först, gå vidare till PhantomJS.org och hämta den lämpliga versionen för ditt operativsystem. I mitt fall var det Mac OSX.

Efter att ha hämtat, plocka upp den till någonstans, till exempel / Program /. Därefter vill du lägga till det i din VÄG:

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

Byta ut 1.5.0 med din nedladdade version av PhantomJS. Var försiktig att inte alla system kommer att ha / Usr / local / bin /. Vissa system kommer att ha: / Usr / bin /, / Bin /, eller usr / X11 / bin istället.

För Windows-användare, kolla kort handledning här. Du vet att du är upprättad när du öppnar din terminal och skriver phantomjs, och du får inga fel.

Om du är obekväma redigerar din VÄG, notera var du unzipped PhantomJS och jag visar ett annat sätt att ställa in det i nästa avsnitt, även om jag rekommenderar att du redigerar din VÄG.

Installera PhantomJS-nod

Att ställa in PhantomJS-Node är mycket lättare. Förutsatt att du har NodeJS installerat, kan du installera via npm:

npm installera fantom

Om du inte redigerade din VÄG i föregående steg när du installerar PhantomJS kan du gå in i Spöke/ katalog dras ner av npm och redigera denna linje in phantom.js.

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

Ändra sökvägen till:

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

När det är klart kan du testa det genom att köra den här koden:

 var fantom = kräver ("fantom"); phantom.create (funktion (ph) return ph.createPage (funktion (sida) return page.open ("http://www.google.com", funktion (status) console.log ("opened google?" , status), returnera sida.evaluera ((funktion () return document.title;), funktion (resultat) console.log ('Sida titel är + resultat); returnera ph.exit ();); );););

Om du kör detta på kommandoraden bör du ta upp följande:

öppnade google? Successidan är Google

Om du har det här är du redo och redo att gå. Om inte, skicka en kommentar och jag försöker hjälpa dig!

Använda PhantomJS-Node

För att underlätta för dig har jag inkluderat en JS-fil, kallad phantomServer.js i nedladdningen som använder några av PhantomJS API för att ladda en webbsida. Den väntar i 5 sekunder innan du kör JavaScript som skrapar sidan. Du kan köra den genom att navigera till katalogen och utfärda följande kommando i din terminal:

 nod phantomServer.js

Jag ska ge en översikt över hur det fungerar här. För det första kräver vi PhantomJS:

 var fantom = kräver ("fantom");

Därefter implementerar vi några metoder från API: n. Namnlösa: Vi skapar en sida förekomst och sedan ringa öppna() metod:

 phantom.create (funktion (ph) return ph.createPage (funktion (sida) // Härifrån kan vi använda PhantomJS 'API-metoder tillbaka sidan.open ("http://tilomitra.com/repository/screenscrape /ajax.html ", funktion (status) // Sidan är nu öppen console.log (" öppnad webbplats? ", status););););

När sidan är öppen kan vi injicera lite JavaScript på sidan. Låt oss injicera jQuery via page.injectJs () metod:

 phantom.create (funktion (ph) return ph.createPage (funktion (sida) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", funktion (status) console.log ("öppnad webbplats?", status); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', funktion () // jQuery Loaded // Vi kan använda saker som $ ("body"). Html () här.););););

jQuery är nu laddad, men vi vet inte om det dynamiska innehållet på sidan har laddats än. För att ta reda på detta lägger jag vanligtvis min skrapkod inuti a setTimeout () funktion som exekveras efter ett visst tidsintervall. Om du vill ha en mer dynamisk lösning kan du låta och efterlikna vissa händelser i PhantomJS API. Låt oss gå med det enkla fallet:

 setTimeout (funktion () return page.evaluate (function () // Få vad du vill ha från sidan med jQuery. // Ett bra sätt är att fylla ett objekt med alla jQuery-kommandon som du behöver och returnera objektet . var h2Arr = [], // array som håller alla html för h2-element pArr = []; // array som innehåller all HTML för p-element // Populär de två arraysna $ ('h2'). varje (funktion h2Arr.push ($ (this) .html ());); $ ('p'). varje (funktion () pArr.push ($ (this) .html ());); // Returnera denna dataåterkomst h2: h2Arr, p: pArr, funktion (resultat) console.log (result); // Logga ut data. Ph.exit (););, 5000);

Att sätta allt ihop, vårt phantomServer.js filen ser så här ut:

 var fantom = kräver ("fantom"); phantom.create (funktion (ph) return ph.createPage (funktion (sida) return page.open ("http://tilomitra.com/repository/screenscrape/ajax.html", funktion (status) console.log ("öppnad webbplats?", status); page.injectJs ('http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', funktion () // jQuery Loaded . // Vänta lite för att AJAX-innehåll laddas på sidan. Här väntar vi 5 sekunder. SetTimeout (funktion () return page.evaluate (function () // Få vad du vill ha från sidan med jQuery . Ett bra sätt är att fylla ett objekt med alla jQuery-kommandon som du behöver och sedan returnera objektet. Var h2Arr = [], pArr = []; $ ('h2'). Varje (funktion () h2Arr.push ($ (detta) .html ());); $ ('p'). varje (funktion () pArr.push ($ (detta) .html ());); returnera h2: h2Arr p: pArr;, funktion (resultat) console.log (resultat); ph.exit ();;, 5000);););););

Denna implementering är lite rå och oorganiserad, men det gör poängen. Med PhantomJS kan vi skrapa en sida som har dynamiskt innehåll! Din konsol ska mata ut följande:

 → nod phantomServer.js öppnas webbplats? framgång h2: ['Artikel 1', 'Artikel 2', 'Artikel 3'], s: ['Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Ut sed nulla turpis, in faucibus ante. Vivamus ut malesuada est. Det här är bara en gång om du vill ha en tidsbegränsad identitet. "," Curabitur euinmod hendertit quam ut euismod. Ut leo sem, viverra ej gravida nec, tristique nec arcu. ' ]

Slutsats

I denna handledning granskade vi två olika sätt att utföra webbskrapning. Om vi ​​skrapar från en statisk webbsida kan vi dra nytta av YQL, vilket är enkelt att installera och använda. Å andra sidan, för dynamiska platser kan vi utnyttja PhantomJS. Det är lite svårare att sätta upp, men ger fler möjligheter. Kom ihåg att du kan använda PhantomJS för statiska webbplatser!

Om du har några frågor om detta ämne, var god att fråga nedan och jag gör mitt bästa för att hjälpa dig.