回调函数

回调函数是在神箭手应用爬取并处理网页的过程中设置的一些系统钩子, 通过这些钩子可以完成一些特殊的处理逻辑.

回调函数需要设置到configs对象中才起作用

下图是采集爬虫爬取并处理网页的流程图, 矩形方框中标识了采集爬虫运行过程中所使用的重要回调函数:

注意: 实时API爬取并处理网页的流程与采集爬虫类似, 但实时API不会发现新网页url, 因此不会调用onProcessScanPage, onProcessHelperPageonProcessContentPage三个回调函数.

采集爬虫回调函数流程图

beforeCrawl(site)

神箭手应用初始化时调用, 用来进行一些爬取前的操作, 栗如, 给所有HTTP请求添加Headers等

@param site site对象. 点此查看site对象详解

beforeCrawl回调函数中可调用addHeadersite对象的方法

在神箭手应用的代码中均可使用

通用栗子:

在采集爬虫初始化时给其所有的HTTP请求添加一个Header

configs.beforeCrawl = function(site) {
site.addHeader("Referer", "http://www.demo.cn/");
};

beforeDownloadPage(page, site)

在一个网页下载或JS渲染开始之前调用, 主要用来处理网页url

@param page 网页对象. page.raw值为空, 点此查看page对象详解

@param site site对象. 点此查看site对象详解

@return 处理后的网页对象

在神箭手应用的代码中均可使用

通用栗子:

下载某个网页之前, 如果网页url中未包含&page=, 则给网页url添加该字符串, 处理过程如下:

configs.beforeDownloadPage = function(page, site) {
if (page.url.indexOf("&page=") == -1) {
page.url = page.url + "&page=1";
}
return page;
};

onChangeProxy(site, page)

在神箭手应用切换代理IP后调用, 主要用来给HTTP请求添加Header和Cookie等数据

@param site site对象. 点此查看site对象详解

@param page 网页对象. 点此查看page对象详解

必备条件: 开启代理IP切换

切换代理IP后, 先前HTTP请求添加的Cookie会被清除, 若打算继续使用Cookie, 强烈建议在onChangeProxy回调函数中添加Cookie

在神箭手应用的代码中均可使用

通用栗子:

默认已经开启代理IP切换, 在神箭手应用切换代理IP后, 调用site.requestUrl后请求网页返回的Cookie会添加到网页域名中, 供后续符合该网页域名的HTTP请求使用.

var configs = {
// configs的成员
...
};
configs.onChangeProxy = function(site, page) {
site.requestUrl("http://www.demo.cn/");
};

点此查看“代理IP切换”详解

isAntiSpider(url, content, page)

判断访问网页时是否被目标网站屏蔽, 如果判断被屏蔽了, 神箭手会切换一次代理IP后自动重新爬取(前提: 开启代理IP切换)

@param url 网页url, String类型

@param content 返回的网页内容, String类型

@param page 网页对象. 点此查看page对象详解

@return 判断是否被目标网站屏蔽的标识, 布尔类型. 如果判断被目标网站屏蔽, 返回true; 如果判断未被目标网站屏蔽, 返回false

在神箭手应用的代码中均可使用

通用栗子:

var configs = {
// configs的成员
...
};
configs.isAntiSpider = function(url, content, page) {
// 判断返回的网页内容是否包含"404页面不存在"
if (content.indexOf("404页面不存在") !== -1) {
// 返回"true",
// 表示判断此时被目标网站屏蔽了,
// 神箭手会切换一次代理IP后自动重新爬取
return true;
}
// 默认返回"false",
// 判断未被目标网站屏蔽
return false;
};

注意:

1. 默认情况下, 如果发送HTTP请求返回的状态码是403, 神箭手会自动判断为被目标网站屏蔽;

2. 点此查看“代理IP切换”详解

afterDownloadPage(page, site)

在一个网页下载或JS渲染完成之后调用, 主要用来处理网页

@param page 网页对象. 点此查看page对象详解

@param site site对象. 点此查看site对象详解

@return 处理后的网页对象

在神箭手应用的代码中均可使用

通用栗子:

下载了某个网页, 希望向网页的body中添加HTML代码, 处理过程如下:

configs.afterDownloadPage = function(page, site) {
var pageHtml = '<div id="num"><span>5</span></div>';
var index = page.raw.indexOf("</body>");
page.raw = page.raw.substring(0, index) + pageHtml
+ page.raw.substring(index);
return page;
};

onProcessScanPage(page, content, site)

在下载入口页内容之后, 发现并添加新url到待爬队列之前调用. 主要用来手动添加需要爬取的新url到待爬队列中

@param page 入口页对象. 点此查看page对象详解

@param content 入口页的内容, String类型

@param site site对象. 点此查看site对象详解

@return 是否需要在入口页内容中自动发现新url, 布尔类型. 默认返回true, 表示需要, 返回false, 表示不需要

onProcessScanPage回调函数中可调用site对象的addScanUrl函数将新入口页url添加到待爬队列中

在采集爬虫代码中可使用

采集爬虫栗子1:

实现这个回调函数并返回false, 表示采集爬虫在处理这个入口页url的时候, 不会从网页中自动发现新url

configs.onProcessScanPage = function(page, content, site) {
return false;
};

采集爬虫栗子2:

根据入口页的内容生成需爬取的列表页url, 添加到待爬队列中, 并通知采集爬虫不再从当前网页中自动发现新url

configs.onProcessScanPage = function(page, content, site) {
var jsonObj = JSON.parse(page.raw);
for (var i = 0, n = jsonObj.data.length; i < n; i++) {
var item = jsonObj.data[i];
var lastid = item._id;
// 生成待爬取的第一个列表页url
var url = page.url + lastid;
// 将新url添加到待爬队列中
site.addUrl(url);
}
// 不再从当前网页中自动发现新url
return false;
};

注意:

1. 从入口页内容中自动发现并添加到待爬队列的包括列表页内容页url

onProcessHelperPage(page, content, site)

在下载列表页内容之后, 发现并添加新url到待爬队列之前调用. 主要用来手动添加需要爬取的新url到待爬队列中

@param page 列表页对象. 点此查看page对象详解

@param content 列表页的内容, String类型

@param site site对象. 点此查看site对象详解

@return 是否需要在列表页内容中自动发现新url, 布尔类型. 默认返回true, 表示需要, 返回false, 表示不需要

onProcessHelperPage回调函数中可调用site对象的addUrl函数将新列表页和内容页url添加到待爬队列中

在采集爬虫代码中可使用

采集爬虫栗子1:

实现这个回调函数并返回false, 表示采集爬虫在处理这个列表页url的时候, 不会从网页中自动发现新url

configs.onProcessHelperPage = function(page, content, site) {
return false;
};

采集爬虫栗子2:

根据列表页的内容生成需爬取的内容页url, 添加到待爬队列中, 并通知采集爬虫不再从当前网页中自动发现新url

configs.onProcessHelperPage = function(page, content, site) {
for (var i = 1; i <= 100; i++) {
// 将拼出的新url添加到待爬队列中
site.addUrl("http://www.demo.com/pageNum=" + i);
}
// 不再从当前网页中自动发现新url
return false;
};

注意:

1. 从列表页内容中自动发现并添加到待爬队列的包括其他列表页内容页url

onProcessContentPage(page, content, site)

在下载内容页内容之后, 发现并添加新url到待爬队列之前调用. 主要用来手动添加需要爬取的新url到待爬队列中

@param page 内容页对象. 点此查看page对象详解

@param content 内容页的内容, String类型

@param site site对象. 点此查看site对象详解

@return 是否需要在内容页内容中自动发现新url, 布尔类型. 默认返回true, 表示需要, 返回false, 表示不需要

onProcessContentPage回调函数中可调用site对象的addUrl函数将新列表页和内容页url添加到待爬队列中

在采集爬虫代码中可使用

采集爬虫栗子1:

实现这个回调函数并返回false, 表示采集爬虫在处理这个内容页url的时候, 不会从网页中自动发现新url

configs.onProcessContentPage = function(page, content, site) {
return false;
};

采集爬虫栗子2:

onProcessContentPage回调函数中, 如果发现内容页中包含404 ERROR!, 则跳过当前内容页url, 并通知采集爬虫不再从当前网页中自动发现新url

configs.onProcessContentPage = function(page, content, site) {
// 判断page.raw中是否包含"404 ERROR!"
if (page.raw.indexOf("404 ERROR!") !== -1) {
// 跳过当前爬取的内容页
page.skip();
}
// 不再从当前网页中自动发现新url
return false;
};

注意:

1. 从内容页内容中可以自动发现列表页url并添加到待爬队列中

afterDownloadAttachedPage(page, site)

在一个attachedUrl对应的网页下载或JS渲染完之后调用, 主要用来处理网页

@param page 网页对象. 点此查看page对象详解

@param site site对象. 点此查看site对象详解

@return 处理后的网页对象

在神箭手应用的代码中均可使用

通用栗子:

下载的网页需去掉前后两边的中括号, 并将处理后的网页对象返回, 处理过程如下:

configs.afterDownloadAttachedPage = function(page, site) {
var data = page.raw;
var start = data.indexOf("[") + 1;
var end = data.lastIndexOf("]");
page.raw = data.substring(start, end);
return page;
};

beforeHandleImg(fieldName, img)

从内容页中抽取到一个抽取项的值之后调用, 对其中包含的img标签进行处理

@param fieldName 抽取项的名字, String类型. 子抽取项的名字会带着父抽取项的名字, 通过.连接, 如, images.image_url

@param img img标签, String类型

@return 处理后的img标签, String类型

很多网站对图片设置了延迟加载, 这时就需要在beforeHandleImg回调函数中处理

在神箭手应用的代码中均可使用

通用栗子:

汽车之家论坛帖子的图片大部分是延迟加载的, 默认会使用http://x.autoimg.cn/club/lazyload.png图片url, 需要获取真实的图片url并替换, 具体实现如下:

configs.beforeHandleImg = function(fieldName, img) {
if (fieldName == "detail") {
// 通过正则匹配"img"中的"src9"属性获取真实图片url
var m = /src9=\"(https?[:\/]+.*?)\"/.exec(img);
if (m) {
// 默认图片url
var url = "http://x.autoimg.cn/club/lazyload.png";
// 真实图片url
var newUrl = m[1];
// 将默认图片url替换成真实图片url
img = img.replace(url, newUrl);
}
}
return img;
};

beforeCacheImg(fieldName, url)

在对爬取到的图片url进行托管处理之前调用, 主要对托管的图片url进行处理

@param fieldName 抽取项的名字, String类型. 子抽取项的名字会带着父抽取项的名字, 通过.连接, 如, images.image_url

@param url 图片url, String类型

@return 处理后的图片url, String类型

在神箭手应用的代码中均可使用

通用栗子:

知乎问答页面, 用户的头像url是这样的: https://pic3.zhimg.com/xxxxxx_s.jpg

研究一下可以发现, 大一点的头像url是这样的: https://pic3.zhimg.com/xxxxxx_l.jpg

configs = {
// configs的其他成员
...
fields: [
// 其他"field"
...
{
name: "answers",
selector: "//div[contains(@class,'answers')]",
repeated: true,
children: [
{
name: "avatar",
selector: "//div[contains(@class,'answer-avatar')]"
}
]
}
]
};
configs.beforeCacheImage = function(fieldName, url) {
if (fieldName == "answers.avatar") {
// 对url进行字符串替换, 得到较大图片的url, 并返回
return url.replace("_s.jpg", "_l.jpg");
}
// 返回未处理的图片url
return url;
};

afterExtractField(fieldName, data, page, site)

从内容页中抽取到一个抽取项的值后进行的回调, 在此回调中可以对该抽取项的值进行处理并返回

@param fieldName 抽取项的名字, String类型. 子抽取项的名字会带着父抽取项的名字, 通过.连接, 如, images.image_url

@param data 抽取结果, 即抽取项的值

@param page 内容页对象. 点此查看page对象详解

@param site site对象. 点此查看site对象详解

@return 处理后的抽取结果, 数据类型请和处理前”data”的数据类型保持一致; 否则, 神箭手会自动强制转换成”data”的数据类型, 如果转换失败, 会返回空值

在神箭手应用的代码中均可使用

通用栗子:

configs = {
// configs的其他成员
...
fields: [
{
name: "interests",
repeated: true
},
{
name: "gender",
selector: "XXX"
},
{
name: "time"
}
]
};
configs.afterExtractField = function(fieldName, data, page, site) {
if (fieldName == "interests") {
data = ["足球", "看书", "健身"];
// 由于"interests"抽取项是数组类型, 返回值是数组类型
return data;
}
if (fieldName == "gender") {
// 由于"gender"抽取项是String类型, 返回值是String类型
if (data == "male") return "男";
else if (data == "female") return "女";
else return "未知";
}
if (fieldName == "time") {
// 获取到当前时间的秒级时间戳并赋值给"data",
// "data"是"float"类型,
data = new Date().getTime()/1000;
// 返回整型时间戳
return parseInt(data);
}
return data;
};

afterExtractPage(page, data, site)

在内容页的所有抽取项抽取完成之后, 在此回调函数中对抽取项再进行一次处理或进行其他操作

@param page 内容页对象. 点此查看page对象详解

@param data 内容页的抽取结果, JS对象

@param site site对象. 点此查看site对象详解

@return 处理后的data对象

在神箭手应用的代码中均可使用

通用栗子:

var configs = {
// configs的其他成员
...
fields: [
{
name: "title",
selector: "XXX"
},
{
name: "time",
selector: "XXX"
},
{
name: "tags",
repeated: true
},
{
name: "time"
}
]
};
configs.afterExtractPage = function(page, data, site) {
// 得到字符串"t"
var t = "[" + data.time + "] " + data.title;
// 将"t"赋值给"title"抽取项
// 由于"title"抽取项是String类型, "data.title"也必须是String类型
data.title = t;
// 由于"tags"抽取项是数组类型, "data.tags"也必须是数组类型
data.tags = ["新闻", "人物"];
// 将当前时间转换成秒级时间戳赋值给"time"抽取项
data.time = parseInt(new Date().getTime()/1000);
// 返回处理后的"data"对象
return data;
};