为应用程序收集数据有时候是一件困难和费力的事。一个急需的API可能会丢失,或者可能有太多的数据需要处理。有时候,只是有时候,你需要通过网络抓取信息。
不用说,这可以是一个合法的雷区,所以你应该确保在版权法的范围内。
有很多工具可以帮助你抓取内容,比如Import.io,但是有时候这些工具并不能让你一直顺利。或者,你只是和我一样好奇,想看看它真的有多容易。
让我们先从一个简单的挑战开始——一个网络爬虫,可以从Techmeme获得当天最热门的故事列表!
注意:我会在这里使用DZone,但我遇到了捕获页面的问题。稍后再详细介绍
你需要先安装一些东西。假设你已经安装了Node.js(我的意思是,谁会没安装!)。即使我们没有直接使用PhantomJS,你仍然需要安装它。版本2.0.1现在可用 —— 你可以从网站下载,也可以使用homebrew软件或同等功能的软件包管理器。
如果你使用安装homebrew软件的Mac,则可以用以下命令安装PhantomJS
brew install phantomjs
下载完毕后,你需要以类似的方式安装CasperJS。你可以将CasperJS视为PhantomJS的伴侣。它实际上给你一个更简单的API来处理网页。虽然它就像PhantomJS一样,被设计用来测试网页,但是还有很多功能可以使它适用于抓取内容。
CasperJS允许我们用JavaScript编写我们的脚本。你可以测试它是否已正确安装,并且通过在终端键入casperjs
测试它是否在你的安装路径(PATH)上。
接下来创建一个新的包含你脚本的JavaScript文件。在我的例子中,我将其命名为index.js。你需要做的第一件事是在你的代码中创建一个casper实例,但需要模块并传入一些基本参数
var casper = require("casper").create({
waitTimeout: 10000,
stepTimeout: 10000,
verbose: true,
pageSettings: {
webSecurityEnabled: false
},
onWaitTimeout: function() {
this.echo('** Wait-TimeOut **');
},
onStepTimeout: function() {
this.echo('** Step-TimeOut **');
}
});
上面的onWaitTimeout回调将在你等待某个元素可见时调用,例如点击一个按钮后,并且waitTimeout已超过限度。
现在你可以启动casper实例并将其指向我们要检查的页面。
casper.start();
casper.open("http://techmeme.com");
Casper使用承诺框架来帮助你以有序的方式运行所有内容。首先,你需要使用then函数。
casper.then(function() {
//logic here
// 函数逻辑
});
//start your script
//运行你的脚本
casper.run();
要让casper打开网页并运行你的逻辑,你需要调用run函数。
当抓取一个网页时,假设有一个特定的结构。在编写脚本之前,你已经查看了页面源代码,或者你可能会使用开发人员工具根据某些操作观察页面的变化。
所以,让我们从一个简单的逻辑开始吧。使用CasperJS断言系统在继续之前确保某个元素已经到位。如果元素不存在,脚本将会失败,但至少你会知道为什么。这种断言行为对于注意过去成功抓取的页面中的更改是非常重要的,但自上次查找以来可能会有新的结构。
如果你检查Techmeme首页上的元素,你会注意到最新消息部分位于id为 topcol1 的div上
让我们使用断言功能来确保此元素存在:
casper.then(function() {
this.test.assertExists("#topcol1");
如果元素不存在,测试(即我们的脚本)将会失败,否则它将继续。
你也可以使用waitForSelector函数以更详细的方式实现相同的结果:
this.waitForSelector("#topcol1",
function pass () {
console.log("Continue");
},
function fail () {
// 无法加载元素...出现错误
this.die("Did not load element... something is wrong");
}
);
使用这个函数的好处是它允许页面在执行之前加载元素并等待。你在初始配置中指定的waitTimeout将用于决定在发生故障前要等待多长时间。
注意:有时你可能无法使用CasperJS查找元素。要获取CasperJS可以看到的图片(字面意义上的!),请使用capture()函数来保存屏幕截图this.capture('screener.png');
接下来,我们来看看如何从这个页面找到标题,以及链接到这些文章。首先,找到包含你要查找的内容的元素。在我们的案例中,它是与第二类相关的div。
CasperJS附带一个评估(evaluate)函数,它允许你从页面内运行JavaScript,并且可以让该函数返回一个变量以供进一步处理。
如何编写这个JavaScript并没有什么特别之处。在本例中,你会注意到我使用的是普通的旧DOM方法而不是jQuery,不过如果你希望在evaluate函数中使用jQuery,则可以使用jQuery:
var links = this.evaluate(function(){
var results = [];
var elts = document.getElementsByClassName("ii");
for(var i = 0; i < elts.length; i++){
var link = elts[i].getElementsByTagName("a")[0].getAttribute("href");
var headline = elts[i].firstChild.textContent;
results.push({link: link, headline: headline});
}
return results;
});
如果你要在evaluate函数中使用console.log语句,那么它们将通过remote.message处理程序输出到你自己的控制台,如下一节所述。
评估完成后,结果将返回供你使用。你可以将它们写入文件系统,或者将它们打印到屏幕上:
console.log("There were " + links.length + " stories");
for(var i = 0; i < links.length; i++){
console.log(links[i].headline);
}
这将带来像这样的输出:
有时候,你可能在执行的JavaScript中存在错误,或者你正在抓取的页面可能存在问题。在这些情况下,你可以捕获错误并使用'remote.message'和'page.error'事件将其打印到控制台
casper.on('remote.message', function(msg) {
this.echo('remote message caught: ' + msg);
});
casper.on('page.error', function(msg, trace) {
this.echo('Error: ' + msg, 'ERROR');
});
你还可以监控正在请求的资源以及使用resource.error和resource.received事件加载的资源:
casper.on('resource.error', function(msg) {
this.echo('resource error: ' + msg);
});
casper.on('resource.received', function(resource) {
console.log(resource.url);
});
这篇文章只能说明你可以用CasperJS做什么。该项目的文档是没有错误的,所以一定要检查一下API,看看你还能做什么。
在本系列的下一篇文章中,我将研究如何从网页下载图像,并且还将讨论如何使用CasperJS中内置的文件系统函数,这些函数比你将习惯使用来自Node.js的函数更加受限.