PhantomJS是基於WebKit內核的headless browsernode
SlimerJS則是基於Gecko內核的headless browserjquery
Headless browser: 無界面顯示的瀏覽器,能夠用於自動化測試,網頁截圖,JS注入,DOM操做等等方面,是一種很是新型的web應用工具。雖然這種瀏覽器沒有任何界面輸出,但在不少方面均可以有很是普遍的應用。整篇文章將會介紹使用Casperjs進行網頁抓取(網絡爬蟲)的應用,本文僅僅是起到一個拋磚引玉的做用,實際上headless browser技術的應用會很是普遍,甚至又可能深入影響web先後端技術的發展。web
本文用一個著名的網站【豆瓣網】「開刀」(僅僅是研究學習使用,但願該站不要找我麻煩),來試驗一下強大的Headless Browser網頁抓取技術的強悍。sql
第一步,安裝Casperjs 打開CasperJS的官網http://casperjs.org/,下載最新穩定版本的CasperJS並安裝,官網有很是詳細的文檔,是學習CasperJS最好的第一手材料。固然了,若是安裝了npm,也能夠直接經過npm安裝。同時,這也是官方推薦的安裝方法。關於安裝就很少介紹了,官方文檔介紹得很是詳細。mongodb
1 npm install casperjs 2 node_modules/casperjs/bin/casperjs selftest
第二步,分析目標網站的列表頁的網頁結構 一般內容類網站都是分紅列表頁面和詳細內容頁面。豆瓣網也不例外,咱們先來看看豆瓣的列表頁長什麼樣。分析之後發現豆瓣電影網的列表頁是這樣的,首先能夠點排序的規則,翻頁不是像傳統的網站經過頁碼來翻頁,而是點擊最後面的加載更多,這樣的網頁,傳統的爬蟲程序每每就歇菜了,或者實現起來很是複雜。可是對於headless browser技術,這個都是小Case。經過分析網頁就能夠看到點擊這個【加載更多】這個位置就可以不斷得顯示跟多影片信息。數據庫
第三步,開始寫代碼獲取影片詳情頁的連接信息 咱們就不客氣了,模擬點擊這個地方,收集超鏈列表, 下面的代碼就是獲取連接的代碼。引用並建立casperJS對象,若是網頁須要插入腳本能夠在casper對象生成的時候在ClientScript部分引用要注入網頁的腳本,爲了加快網頁的加載速度,咱們禁止下載圖片和插件:npm
1 pageSettings: { 2 loadImages: false, // The WebPage instance used by Casper will 3 loadPlugins: false // use these settings 4 },
)
完整的獲取詳情頁連接的代碼,這裏模擬點擊【加載更多】並循環50次。其實循環能夠進行改進,【判斷 while(沒有」加載更多」) then( stop)】,得到後用require('utils').dump(….)輸出連接列表。保存下面的代碼爲getDoubanList.js, 而後運行 casperjs getDoubanList.js 就可以得到並輸出該分類下全部的詳情頁連接。後端
1 1 phantom.outputEncoding="uft8"; 2 var casper = require('casper').create({ 3 // clientScripts: [ 4 // 'includes/jquery.js', // These two scripts will be injected in remote 5 // 'includes/underscore.js' // DOM on every request 6 // ], 7 pageSettings: { 8 loadImages: false, // The WebPage instance used by Casper will 9 loadPlugins: false // use these settings 10 }, 11 logLevel: "info", // Only "info" level messages will be logged 12 verbose: false // log messages will be printed out to the console 13 }); 14 15 casper.start("https://movie.douban.com/explore#!type=movie&tag=%E7%BB%8F%E5%85%B8&sort=recommend&page_limit=20&page_start=0", function () { 16 this.capture("1.png"); 17 }); 18 19 casper.then(function () { 20 this.click("a.more",10,10); 21 var i = 0; 22 do 23 { 24 i ++; 25 casper.waitForText('加載更多', function() { 26 this.click("a.more",10,10);//this.capture("2.png"); // read data from popup 27 }); 28 } 29 while (i<50); 30 }); 31 32 33 casper.then(function () { 34 require('utils').dump(this.getElementsAttribute('div.list-wp div.list a.item', 'href')); 35 'href'))); 36 }); 37 casper.waitForText('加載更多', function() { 38 this.capture("3.png"); // read data from popup 39 }); 40 casper.run();
我使用了Nodejs來調用casperjs(用其餘的語言好比Python,Java調用也是能夠的,CasperJS並非一個完整的系統,因此多線程,文本處理,數據庫仍是須要依賴其餘的語言或者工具),並把結果輸出到文件裏保存,固然把結果放到數據庫裏也沒有問題,可是這裏爲了簡化,就不展開了(實際的應用中我是用的MongoDB)。Nosql數據庫很是適合存放抓取下來的非結構化數據存儲。瀏覽器
1 //var fs = require("fs"); 2 //var S = require("string"); 3 var url = 'mongodb://localhost:27017/test'; 4 //var trim = require('trim.js'); 5 //include recode url module 6 var record = require('./RecordUrl'); 7 8 9 ///Program running block///////////////////////////////////////////////////////////////////// 10 const spawn = require('child_process').spawn; 11 const urllist = spawn('casperjs', ['casper3_more.js']); 12 var strUrls = ""; 13 14 urllist.stdout.on('data', (data) => { 15 console.log(data.toString()); 16 strUrls = strUrls + data.toString(); 17 18 }); 19 20 urllist.stderr.on('data', (data) => { 21 console.log(data); 22 }); 23 24 urllist.on('exit', (code) => { 25 console.log(`Child exited with code ${code}`); 26 var urlData = JSON.parse(strUrls); 27 var content2 = ""; 28 for(var key in urlData){ 29 if (content2 != "") { 30 content2 = content2 + "\r\n" + urlData[key]; 31 } 32 else { 33 content2 = urlData[key]; 34 } 35 } 36 var recordurl = new record.RecordAllUrl(); 37 recordurl.RecordUrlInText(content2); 38 console.log(content2); 39 }); 40
1 exports.RecordAllUrl = RecordUrl; 2 var fs = require('fs'); 3 function RecordUrl() { 4 var file = "d:/urllog.txt"; 5 var RecordUrlInFile = function(theurl) { 6 9 fs.appendFile(file, theurl, function(err){ 10 if(err) 11 console.log("fail " + err); 12 else 13 console.log("寫入文件ok"); 14 }); 15 }; 16 var RecordUrlInMongo = function() { 17 console.log('Hello ' + name); 18 }; 19 return { 20 RecordUrlInDB: RecordUrlInMongo, 21 RecordUrlInText: RecordUrlInFile 22 } ; 23 };
第四步,分析詳情頁面並編寫詳情頁面抓取程序網絡
到這一步你們就已經得到了要抓取的詳情頁面的列表了,如今咱們打開一個電影詳情頁來看看結構如何,分析下各個信息如何抓取。對於信息的抓取必需要綜合使用DOM,文本處理和JS腳本等技術。我想得到這部分的信息,包括導演,編劇,評分等等。在本文就不重複了,這裏僅抽取幾個信息項例子演示。
1. 抓取導演列表:導演列表的DOM CSS selector 'div#info span:nth-child(1) span.attrs a' , 咱們使用了function getTextContent(strRule, strMesg) 這個方法去抓取內容。
1 phantom.outputEncoding="GBK"; 2 var S = require("string"); 3 var casper = require('casper').create({ 4 clientScripts: [ 5 'includes/jquery.js', // These two scripts will be injected in remote 6 'includes/underscore.js' // DOM on every request 7 ], 8 pageSettings: { 9 loadImages: false, // The WebPage instance used by Casper will 10 loadPlugins: false // use these settings 11 }, 12 logLevel: "info", // Only "info" level messages will be logged 13 verbose: false // log messages will be printed out to the console 14 }); 15 16 //casper.echo(casper.cli.get(0)); 17 var fetchUrl='https://movie.douban.com/subject/25662329/', fetchNumber; 18 if(casper.cli.has('url')) 19 fetchUrl = casper.cli.get('url'); 20 else if(casper.cli.has('number')) 21 fetchNumber = casper.cli.get('number'); 22 casper.echo(fetchUrl); 23 24 casper.start(fetchUrl, function () { 25 this.capture("1.png"); 26 //this.echo("啓動程序...."); 27 //this.echo(this.getHTML('div#info span:nth-child(3) a')); 28 //this.echo(this.fetchText('div#info span:nth-child(1) a')); 29 30 //抓取導演 31 getTextContent('div#info span:nth-child(1) span.attrs a','抓取導演'); 32 33 34 }); 35 36 //get the text content of tag 37 function getTextContent(strRule, strMesg) 38 { 39 //給evaluate傳入參數 40 var textinfo = casper.evaluate(function(rule) { 41 var valArr = ''; 42 $(rule).each(function(index,item){ 43 valArr = valArr + $(this).text() + ','; 44 }); 45 return valArr.substring(0,valArr.length-1); 46 }, strRule); 47 casper.echo(strMesg); 48 require('utils').dump(textinfo.split(',')); 49 return textinfo.split(','); 50 }; 51 52 //get the attribute content of tag 53 function getAttrContent(strRule, strMesg, Attr) 54 { 55 //給evaluate傳入參數 56 var textinfo = casper.evaluate(function(rule, attrname) { 57 var valArr = ''; 58 $(rule).each(function(index,item){ 59 valArr = valArr + $(this).attr(attrname) + ','; 60 }); 61 return valArr.substring(0,valArr.length-1); 62 }, strRule, Attr); 63 casper.echo(strMesg); 64 require('utils').dump(textinfo.split(',')); 65 return textinfo.split(','); 66 }; 67 68 casper.run();
2. 抓取製片國家和地區,這個信息使用CSS selector抓取會有困難,緣由分析網頁後就能夠發現,首先這個信息不是放在一個<span>標籤裏面, 並且「美國」這個文本直接在<div id=’info’>這個高層級的元素裏。對於這樣的信息咱們採用另一種方式,文本分析和截取,首先映入String模塊var S = require("string"); 這個模塊也是要另外安裝的。而後抓取整塊的信息,而後用文本截取:
1 //影片信息全文字抓取 2 nameCount = casper.evaluate(function() { 3 var valArr = ''; 4 $('div#info').each(function(index,item){ 5 valArr = valArr + $(this).text() + ','; 6 }); 7 return valArr.substring(0,valArr.length-1); 8 }); 9 this.echo("影片信息全文字抓取"); 10 this.echo(nameCount); 11 //this.echo(nameCount.indexOf("製片國家/地區:")); 12 13 //抓取國家 14 this.echo(S(nameCount).between("製片國家/地區:","\n"));
其餘信息能夠相似獲取。
第五步,將抓取到的信息存儲並做爲分析的源,推薦使用MongoDB這類NoSql數據庫存儲,比較適合存放這樣的非結構數據,並且性能更優。