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