基于Casperjs的网页抓取技术【抓取豆瓣信息网络爬虫实战示例】
CasperJS is a navigation scripting & testing utility for the PhantomJS (WebKit) and SlimerJS (Gecko) headless browsers, written in Javascript.
PhantomJS是基于WebKit内核的headless browser
SlimerJS则是基于Gecko内核的headless browser
Headless browser: 无界面显示的浏览器,可以用于自动化测试,网页截图,JS注入,DOM操作等等方面,是一种非常新型的web应用工具。虽然这种浏览器没有任何界面输出,但在很多方面都可以有非常广泛的应用。整篇文章将会介绍使用Casperjs进行网页抓取(网络爬虫)的应用,本文仅仅是起到一个抛砖引玉的作用,实际上headless browser技术的应用会非常广泛,甚至又可能深刻影响web前后端技术的发展。
本文用一个著名的网站【豆瓣网】“开刀”(仅仅是研究学习使用,希望该站不要找我麻烦),来试验一下强大的Headless Browser网页抓取技术的强悍。
第一步,安装Casperjs 打开CasperJS的官网http://casperjs.org/,下载最新稳定版本的CasperJS并安装,官网有非常详细的文档,是学习CasperJS最好的第一手材料。当然了,如果安装了npm,也可以直接通过npm安装。同时,这也是官方推荐的安装方法。关于安装就不多介绍了,官方文档介绍得非常详细。
1 npm install casperjs 2 node_modules/casperjs/bin/casperjs selftest
第二步,分析目标网站的列表页的网页结构 通常内容类网站都是分成列表页面和详细内容页面。豆瓣网也不例外,我们先来看看豆瓣的列表页长什么样。分析以后发现豆瓣电影网的列表页是这样的,首先可以点排序的规则,翻页不是像传统的网站通过页码来翻页,而是点击最后面的加载更多,这样的网页,传统的爬虫程序往往就歇菜了,或者实现起来非常复杂。但是对于headless browser技术,这个都是小Case。通过分析网页就可以看到点击这个【加载更多】这个位置就能够不断得显示跟多影片信息。
第三步,开始写代码获取影片详情页的链接信息 我们就不客气了,模拟点击这个地方,收集超链列表, 下面的代码就是获取链接的代码。引用并创建casperJS对象,如果网页需要插入脚本可以在casper对象生成的时候在ClientScript部分引用要注入网页的脚本,为了加快网页的加载速度,我们禁止下载图片和插件:
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数据库存储,比较适合存放这样的非结构数据,而且性能更优。
作者:Rocky Yang
出处:http://www.cnblogs.com/yangjian2006/
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。感谢各位能够访问我的博客!
Email:Rocky的邮箱
个人博客:Rocky & Sky IT技术和管理