• 基于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前后端技术的发展。

    本文用一个著名的网站【豆瓣网】“开刀”(仅仅是研究学习使用,希望该站不要找我麻烦Smile),来试验一下强大的Headless Browser网页抓取技术的强悍。

    第一步,安装Casperjs 打开CasperJS的官网http://casperjs.org/,下载最新稳定版本的CasperJS并安装,官网有非常详细的文档,是学习CasperJS最好的第一手材料。当然了,如果安装了npm,也可以直接通过npm安装。同时,这也是官方推荐的安装方法。关于安装就不多介绍了,官方文档介绍得非常详细。

      1 npm install casperjs
      2 node_modules/casperjs/bin/casperjs selftest
    View Code

    第二步,分析目标网站的列表页的网页结构 通常内容类网站都是分成列表页面和详细内容页面。豆瓣网也不例外,我们先来看看豆瓣的列表页长什么样。分析以后发现豆瓣电影网的列表页是这样的,首先可以点排序的规则,翻页不是像传统的网站通过页码来翻页,而是点击最后面的加载更多,这样的网页,传统的爬虫程序往往就歇菜了,或者实现起来非常复杂。但是对于headless browser技术,这个都是小Case。通过分析网页就可以看到点击这个【加载更多】这个位置就能够不断得显示跟多影片信息。

    doubanlist

    第三步,开始写代码获取影片详情页的链接信息 我们就不客气了,模拟点击这个地方,收集超链列表, 下面的代码就是获取链接的代码。引用并创建casperJS对象,如果网页需要插入脚本可以在casper对象生成的时候在ClientScript部分引用要注入网页的脚本,为了加快网页的加载速度,我们禁止下载图片和插件:

      1  pageSettings: {
      2         loadImages:  false,        // The WebPage instance used by Casper will
      3         loadPlugins: false         // use these settings
      4     },
    View Code


    完整的获取详情页链接的代码,这里模拟点击【加载更多】并循环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();
    GetDoubanList.js

    我使用了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 + "
    " + 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 
    GetAllUrls
    引用的RecordUrl模块,存MongoDB这一部分没有写,大家可以自行完成。
      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 };
    RecordUrl

    第四步,分析详情页面并编写详情页面抓取程序

    到这一步大家就已经获得了要抓取的详情页面的列表了,现在我们打开一个电影详情页来看看结构如何,分析下各个信息如何抓取。对于信息的抓取必须要综合使用DOM,文本处理和JS脚本等技术。我想获得这部分的信息,包括导演,编剧,评分等等。在本文就不重复了,这里仅抽取几个信息项例子演示。

    doubanDetail

    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();
    GetDirectors

    2. 抓取制片国家和地区,这个信息使用CSS selector抓取会有困难,原因分析网页后就可以发现,首先这个信息不是放在一个<span>标签里面, 而且“美国”这个文本直接在<div id=’info’>这个高层级的元素里。对于这样的信息我们采用另外一种方式,文本分析和截取,首先映入String模块var S = require("string"); 这个模块也是要另外安装的。然后抓取整块的信息,然后用文本截取:

    info

      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("制片国家/地区:","
    "));
    GetCountry

    其他信息可以类似获取。

    第五步,将抓取到的信息存储并作为分析的源,推荐使用MongoDB这类NoSql数据库存储,比较适合存放这样的非结构数据,而且性能更优。

  • 相关阅读:
    vue-cli构建项目 npm run build后应该怎么运行在本地查看效果
    解析JSON数组
    Lambda
    keytool不是内部或外部命令
    XML布局
    HTML5本地存储IndexedDB基础使用
    Vue 2.0基础
    cs231n__3. LostFunction
    cs231n__2. K-nearest Neighbors
    week_Last
  • 原文地址:https://www.cnblogs.com/yangjian2006/p/6341355.html
Copyright © 2020-2023  润新知