一个js爬虫

第一个demo

爬虫采用JavaScript编写, 下面以糗事百科为例, 来看一下我们的爬虫长什么样子:

var configs = {
    // configs对象的成员domains, scanUrls, contentUrlRegexes和fields
    domains: ["www.qiushibaike.com"],
    scanUrls: ["http://www.qiushibaike.com/"],
    contentUrlRegexes: ["http://www\\.qiushibaike\\.com/article/\\d+"], fields: [ { name: "content", // fields成员中第一个field对象的name selector: "//*[@id='single-next-link']", // fields成员中第一个field对象的selector required: true // fields成员中第一个field对象的required }, { name: "author", // fields成员中第二个field对象的name selector: "//div[contains(@class,'author')]//h2" // fields成员中第二个field对象的selector } ] }; // 向爬虫任务中添加configs配置,并启动爬虫 var crawler = new Crawler(configs); crawler.start(); 

爬虫的整体框架就是这样, 首先定义了一个configs对象, 里面设置了待爬网站的一些信息, 然后通过调用var crawler = new Crawler(configs);crawler.start();来配置并启动爬虫.

configs对象如何定义, 后面会作详细介绍.

 

configs详解——之成员

爬虫的整体框架是这样:首先定义了一个configs对象, 里面设置了待爬网站的一些信息, 然后通过调用var crawler = new Crawler(configs);crawler.start();来配置并启动爬虫.

configs对象中可以定义下面这些成员

domains

定义爬虫爬取哪些域名下的网页, 非域名下的url会被忽略以提高爬取速度

数组类型 不能为空

举个栗子:

domains: ["wallstreetcn.com"],
domains: ["zhihu.sogou.com", "zhihu.com"],

scanUrls

定义爬虫的入口链接, 爬虫从这些链接开始爬取, 
同时这些链接也是监控爬虫所要监控的链接

数组类型 不能为空

举个栗子:

scanUrls: ["http://wallstreetcn.com/news"],
scanUrls: ["http://club2011.auto.163.com/board/biaozhi308/r0a0t0g0bpeo0-n1.html", "http://club2011.auto.163.com/board/fengshen/r0a0t0g0bpeo0-n1.html"],

contentUrlRegexes

定义内容页url的规则
内容页是指包含要爬取内容的网页 比如http://www.qiushibaike.com/article/115878724 就是糗事百科的一个内容页

数组类型 正则表达式 最好填写以提高爬取效率

举个栗子:

contentUrlRegexes: ["http://wallstreetcn\\.com/node/\\d+"],
contentUrlRegexes: ["http://club2011\\.auto\\.163\\.com/post/\\d+\\.html.*"],

特别需要注意的是,正则表达式中.?都是需要转义的。

helperUrlRegexes

定义列表页url的规则
对于有列表页的网站, 使用此配置可以大幅提高爬虫的爬取速率
列表页是指包含内容页列表的网页 比如http://www.qiushibaike.com/8hr/page/2/?s=4867046 就是糗事百科的一个列表页

数组类型 正则表达式

举个栗子:

helperUrlRegexes: ["http://wallstreetcn\\.com/news(\\?/page=\\d+)?"],
helperUrlRegexes: ["http://club2011\\.auto\\.163\\.com/board/biaozhi308/r0a0t0g0bpeo0\\-n\\d+\\.html.*", "http://club2011\\.auto\\.163\\.com/board/fengshen/r0a0t0g0bpeo0\\-n\\d+\\.html.*"],

fields

定义内容页的抽取规则
规则由一个个field组成, 一个field代表一个数据抽取项

数组类型 不能为空

举个栗子:

fields: [
    {
        name: "content",
        selector: "//*[@id='single-next-link']",
        required: true
    },
    {
        name: "author",
        selector: "//div[contains(@class,'author')]//h2" } ] 

上面的例子从网页中抽取内容和作者, 抽取规则是针对糗事百科的内容页写的

fieldconfigs详解——之field中作详细介绍。

enableProxy

是否使用代理
如果爬取的网站根据IP做了反爬虫, 可以设置此项为true

布尔类型 可选设置

举个栗子:

enableProxy: true,






configs详解——之field

field定义一个抽取项, 一个field可以定义下面这些东西

name

给此项数据起个变量名
变量名中不能包含.
如果抓取到的数据想要以文章或者问答的形式发布到网站(WeCenterWordPressDiscuz!等),field的命名请参考两个完整demo中的命名, 否则无法发布成功

String类型 不能为空

举个栗子:
field起了个名字叫question

{
    name: "question",
    selector: "//*[@id='zh-question-title']/h2"
}

selector

定义抽取规则, 默认使用XPath
如果使用其他类型的, 需要指定selectorType

String类型 不能为空

举个栗子:
使用XPath来抽取知乎问答网页的问题,selector的值就是问题的XPath

{
    name: "question",
    selector: "//*[@id='zh-question-title']/h2"
}

selectorType

抽取规则的类型
目前可用SelectorType.XPathSelectorType.JsonPathSelectorType.Regex
默认SelectorType.XPath

枚举类型

栗子1:
selector默认使用XPath

{
    name: "question",
    selector: "//*[@id='zh-question-title']/h2" // XPath抽取规则
}

栗子2:
selector使用JsonPath,如果内容是Json数据格式,则使用JsonPath抽取数据比较方便

{
    name: "question_answer_content",
    selectorType: SelectorType.JsonPath,
    selector: "$.comment.content", // JsonPath抽取规则
    required: true
}

栗子3:
除了XPath和JsonPath之外,神箭手还支持使用正则表达式来抽取数据

{
    name: "title",
    selectorType: SelectorType.Regex,
    selector: '<div\\sid=\\"page\\"><h1>[^\\/]+<\\/h1>' // Regex抽取规则
}

required

定义该field的值是否必须, 默认false
赋值为true的话, 如果该field没有抽取到内容, 该field对应的整条数据都将被丢弃

布尔类型

举个栗子:

{
    name: "article_title",
    selector: "//div[contains(@class,'location')]/text()[3]",
    required: true
}

repeated

定义该field抽取到的内容是否是有多项, 默认false
赋值为true的话, 无论该field是否真的是有多项, 抽取到的结果都是数组结构

布尔类型

举个栗子:
爬取的网页中包含多条评论,所以抽取评论的时候要将repeated赋值为true

{
    name: "comments",
    selector: "//*[@id='zh-single-question-page']//a[contains(@class,'zm-item-tag')]",
    repeated: true
}

children

为此field定义子项
子项的定义仍然是一个fields结构, 即一个field对象的数组
没错, 这是一个树形结构

数组类型

举个栗子:
抓取知乎问答网页的回答,每个回答爬取了内容,作者,赞同数

{
    name: "answers",
    selector: "//*[@id=\"zh-question-answer-wrap\"]/div",
    repeated: true,
    children: [
        {
            name: "content",
            selector: "//div[contains(@class,\"zm-editable-content\")]", required: true }, { name: "author", selector: "//a[@class=\"author-link\"]" }, { name: "agree_count", selector: "//button[@class=\"up\"]/span[@class=\"count\"]" } ] } 

sourceType

field的数据源, 默认从当前的网页中抽取数据
选择SourceType.AttachedUrl可以发起一个新的请求, 然后从请求返回的数据中抽取
选择SourceType.UrlContext可以从当前网页的url附加数据中抽取
url附加数据后面会作介绍

枚举类型

attachedUrl

sourceType设置为SourceType.AttachedUrl时, 定义新请求的url

String类型

举个栗子:
当爬取的网页中某些内容需要异步加载请求时,就需要使用attachedUrl,比如,抓取知乎回答中的评论部分,就是通过AJAX异步请求的数据

{
    name: "comment_id",
    selector: "//div/@data-aid",
},
{
    name: "comments",
    sourceType: SourceType.AttachedUrl,
    // "comments"是从发送"attachedUrl"这个异步请求返回的数据中抽取的
    // "attachedUrl"支持引用上下文中的抓取到的"field", 这里就引用了上面抓取的"comment_id" attachedUrl: "https://www.zhihu.com/r/answers/{comment_id}/comments", selectorType: SelectorType.JsonPath, selector: "$.data", repeated: true, children: [ ... ] } 

UrlContext

sourceType赋值为SourceType.UrlContext时, 表示从内容页中的附加数据(是开发者自定义的一段代码,例如,html代码)中抽取数据

String类型

举个栗子:
将自定义数据附加到内容页中,然后再提取到field

var configs = {
    // configs中的其他成员
    ...
    fields: [
        {
            name: "extra_data",
            // 这里是从开发者附加的一段html代码中抽取的数据
            sourceType: SourceType.UrlContext,
            selector: "//span[contains(@class,'shenjianshou')]", } ] }; configs.onProcessHelperPage = function(page, content, site) { // 定义附加数据 var extraData = '<div><span class="shenjianshou">100</span></div>'; // 将extraData附加到contentUrl对应的网页中,将contentUrl添加到待爬队列中 site.addUrl(contentUrl, "get", null, extraData); ... return false; }



configs详解——之sitepageconsole

site

site表示当前正在爬取的网站的对象,下面介绍了可以调用的函数

site.addHeader(key, value)

一般在beforeCrawl回调函数(在configs详解——之回调函数中会详细描述)中调用, 用来添加一些HTTP请求的Header

@param key Header的key, 如User-Agent,Referer
@param value Header的值

举个栗子:
Referer是HTTP请求Header的一个属性,http://buluo.qq.com/p/index.htmlReferer的值

configs.beforeCrawl = function(site) {
    site.addHeader("Referer", "http://buluo.qq.com/p/index.html"); } 

site.addCookie(key, value)

一般在beforeCrawl回调函数(在configs详解——之回调函数中会详细描述)中调用, 用来添加一些HTTP请求的Cookie

@param key Cookie的key
@param value Cookie的值

举个栗子:
cookie是由键-值对组成的,BAIDUID是cookie的key,FEE96299191CB0F11954F3A0060FB470:FG=1则是cookie的值

configs.beforeCrawl = function(site) {
    site.addCookie("BAIDUID", "FEE96299191CB0F11954F3A0060FB470:FG=1"); } 

site.addCookies(cookies)

一般在beforeCrawl回调函数(在configs详解——之回调函数中会详细描述)中调用, 用来添加一些HTTP请求的Cookie

@param cookies 多个Cookie组成的字符串

举个栗子:
cookies是多个cookie的键-值对组成的字符串,用;分隔。BAIDUIDBIDUPSID是cookie的key,FEE96299191CB0F11954F3A0060FB470:FG=1FEE96299191CB0F11954F3A0060FB470是cookie的值,键-值对用=相连

configs.beforeCrawl = function(site) {
    site.addCookies("BAIDUID=FEE96299191CB0F11954F3A0060FB470:FG=1; BIDUPSID=FEE96299191CB0F11954F3A0060FB470;");
}

site.addUrl(url, method, data, contextData)

一般在onProcessScanPageonProcessHelperPage回调函数(在configs详解——之回调函数中会详细描述)中调用, 用来往待爬队列中添加url

@param url 待添加的url
@param method 默认为GET请求,也支持POST请求
@param data 发送请求时需添加的参数,可以为空
@param contextData 此url附加的数据, 可以为空

栗子1:

configs.onProcessHelperPage = function(page, content, site) {
    var regex = /https?:\\\/\\\/www\.jiemian\.com\\\/article\\\/\d+\.html/g; var urls = []; urls = content.match(regex); if (urls != "") { for (var i = 0, n = urls.length; i < n; i++) { urls[i] = urls[i].replace(/\\/g, ""); // get请求,不需要添加参数,也不需要附加数据 site.addUrl(urls[i]); } } ... return false; } 

栗子2:

configs.onProcessHelperPage = function(page, content, site) {
    ...
    var nextUrl = page.url.replace("page="+currentPage, "page="+page); // 定义了发送POST请求所需参数 var param = new Object(); param.page = page + ""; param.size = "18"; // 标明该请求是POST请求,并添加了所需参数 site.addUrl(nextUrl, "POST", param); return false; } 

site.requestUrl(url, method, data)

一般在beforeCrawlafterDownloadPageonProcessScanPageonProcessHelperPage回调函数(在configs详解——之回调函数中会详细描述)中调用, 下载网页, 得到网页内容

@param url 待下载的url
@param method 默认为GET请求,也支持POST请求
@param data 发送请求时需添加的参数,可以为空

举个栗子:

configs.afterDownloadPage = function(page, site) {
    // 定义了发送POST请求所需参数
    var data = new Object(); data.sno = 'FK1QPNCEGRYD'; data.CSRFToken = result[1]; var url = "https://checkcoverage.apple.com/cn/zh/?sn=FK1QPNCEGRYD"; // 通过发送带参数的POST请求,下载网页,并将网页内容赋值给result var result = site.requestUrl(url, "PSOT", data); ... return page; } 

page

page表示当前正在爬取的网页页面的对象,下面介绍了可以调用的变量和函数

page.url

当前正在爬取的网页页面的url

举个栗子:
afterExtractPage回调函数(在configs详解——之回调函数中会详细描述)中,将url值赋给名叫article_contentfield

configs.afterExtractPage = function(page, data) {
    data.article_content = page.url + "";
    return data; } 

page.raw

当前网页原始内容

举个栗子:
onProcessScanPage回调函数(在configs详解——之回调函数中会详细描述)中,通过page.raw得到网页原始内容,然后转换成Json对象

configs.onProcessScanPage = function(page, content, site) {
    var jsonObj = JSON.parse(page.raw); ... return false; } 

page.skip(fieldName)

一般在afterExtractField回调函数(在configs详解——之回调函数中会详细描述)中使用, 用来忽略当前网页的抽取结果或当前上下文的一条结果

@param fieldName 根据fieldName来确定忽略范围,可以为空。当为空时,忽略当前网页的抽取结果

栗子1:
例如爬取知乎问答的时候, 想只爬取话题中包含经济学的问答, 可以这样过滤:

configs.afterExtractField = function(fieldName, data) {
    if (fieldName == "topics") { // fieldName是topics if (data.indexOf("经济学") == -1) { // data中不包含"经济学" page.skip(); // 跳过当前爬取的网页 } } return data; } 

栗子2:
例如爬取知乎问答的时候, 想要只爬取赞同数大于10的回答, 可以这样过滤:

configs = {
    // configs中的其他成员
    ...
    fields: [
        {
            name: "answers",
            selector: "//*[@id=\"zh-question-answer-wrap\"]/div",
            repeated: true,
            children: [
                {
                    name: "agree_count", selector: "//button[@class=\"up\"]/span[@class=\"count\"]" }, ... ] } ] }; configs.afterExtractField = function(fieldName, data, page) { if (fieldName == "answers.agree_count") { // fieldName是answers数组中的agree_count元素名 if (parseInt(data) < 10) { // 回答的点赞数小于10 page.skip("answers"); // 跳过这个回答 } } return data; } 

console

console对象提供不同级别的日志打印

console.log(message)

打印日志, 调试时使用

举个栗子:

  console.log("测试log日志");

console.debug(message)

打印出错级别日志, 调试时使用

举个栗子:

  console.debug("正在提取文章标题数据");

console.info(message)

打印普通日志

举个栗子:

  console.info("成功处理一个页面");

console.warn(message)

打印警告情况的日志

举个栗子:

  console.warn("XX文件解析失败");

console.error(message)

打印错误情况的日志

举个栗子:

  console.error("XPath错误");




configs详解——之回调函数

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

回调函数需要设置到configs参数里面才起作用

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

beforeCrawl(site)

爬虫初始化时调用, 用来指定一些爬取前的操作

@param site 当前正在爬取的网站的对象

在函数中可以调用site.addHeader(key, value)site.addCookie(key, value)site.addCookies(cookies)

举个栗子:
在爬虫开始爬取之前给所有待爬网页添加一个Header

configs.beforeCrawl = function(site) {
    site.addHeader("Referer", "http://buluo.qq.com/p/index.html"); } 

isAntiSpider(url, content)

判断当前网页是否被反爬虫了, 需要开发者实现

@param url 当前网页的url
@param content 当前网页内容
@return 如果被反爬虫了, 返回true, 否则返回false

举个栗子:

configs.isAntiSpider = function(url, content) {
    if (content.indexOf("404页面不存在") > -1) { // content中包含"404页面不存在"字符串 return true; // 返回当前网页被反爬虫了 } return false; } 

afterDownloadPage(page, site)

在一个网页下载完成之后调用. 主要用来对下载的网页进行处理.

@param page 当前下载的网页页面的对象,调用page.url可获取当前网页的url,调用page.raw可获取当前网页内容
@param site 当前正在爬取的网站的对象
@return 返回处理后的网页

举个栗子:
比如下载了某个网页,希望向网页的body中添加html标签,处理过程如下:

configs.afterDownloadPage = function(page, site) {
    var pageHtml = "<div id=\"comment-pages\"><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到待爬队列之前调用. 主要用来发现新的待爬url, 并且能给新发现的url附加数据.

@param page 当前正在爬取的网页页面的对象,调用page.url可获取当前网页的url,调用page.raw可获取当前网页内容,调用page.skip()便不再爬取当前网页
@param content 当前网页内容
@param site 当前正在爬取的网站的对象
@return 返回false表示不需要再从此网页中发现待爬url

此函数中通过调用site.addUrl(url)来添加新的url到待爬队列。

栗子1:
实现这个回调函数并返回false,表示爬虫在处理这个scanUrl的时候,不会从中提取待爬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; } 

onProcessHelperPage(page, content, site)

在爬取到列表页url的内容之后, 添加新的url到待爬队列之前调用. 主要用来发现新的待爬url, 并且能给新发现的url附加数据.

@param page 当前正在爬取的网页页面的对象,调用page.url可获取当前网页的url,调用page.raw可获取当前网页内容,调用page.skip()便不再爬取当前网页
@param content 当前网页内容
@param site 当前正在爬取的网站的对象
@return 返回false表示不需要再从此网页中发现待爬url

此函数中通过调用site.addUrl(url)来添加新的url到待爬队列

栗子1:
实现这个回调函数并返回false,表示爬虫在处理这个helperUrl的时候,不会从中提取待爬url

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

栗子2:
onProcessHelperPage回调函数中,生成新的contentUrl并添加到待爬队列中,并通知爬虫不再从当前网页中发现待爬url

configs.onProcessHelperPage = function(page, content, site) {
    var jsonObj = JSON.parse(content); var id = 0; for (var i = 0, n = jsonObj.data.length; i < n; i++) { var item = jsonObj.data[i]; id = item._id; // 将新的url插入待爬队列中 site.addUrl("http://service.chanjet.com/api/v1/message/"+id); } // 通知爬虫不再从当前网页中发现待爬url return false; } 

beforeHandleImg(fieldName, img)

在抽取到field内容之后调用, 对其中包含的img标签进行回调处理

@param fieldName 当前fieldname. 注意: 子fieldname会带着父fieldname, 通过.连接.
@param img 整个img标签的内容
@return 返回处理后的img标签的内容

很多网站对图片作了延迟加载, 这时候就需要在这个函数里面来处理

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

configs.beforeHandleImg = function(fieldName, img) {
    // 通过正则判断img中的src属性是不是真实图片url,如果是,则直接返回img,如果不是,继续执行
    var regex = /src="(https?:\/\/.*?)"/; var rs = regex.exec(img); if (!rs) return img; var url = rs[1]; if (url == "http://x.autoimg.cn/club/lazyload.png") { var regex2 = /src9="(https?:\/\/.*?)"/; rs = regex2.exec(img); // 替换成真实图片url if (rs) { var newUrl = rs[1]; img = img.replace(url, newUrl); } } return img; } 

beforeCacheImg(fieldName, url)

由于很多网站都有图片防盗链限制, 所以神箭手会缓存爬到的图片, 在缓存之前, 可以对图片作处理

@param fieldName 当前fieldname. 注意: 子fieldname会带着父fieldname, 通过.连接.
@param url 图片的url
@return 处理后的图片url

举个栗子:
知乎问答页面, 用户的头像链接是这样的: https://pic3.zhimg.com/xxxxxx_s.jpg
研究一下可以发现, 大一点的头像是这样的: https://pic3.zhimg.com/xxxxxx_l.jpg

configs.beforeCacheImage = function(fieldName, url) {
    if (fieldName == "answers.avatar") { return url.replace("_s.jpg", "_l.jpg"); // 对url进行字符串替换,得到较大图片的url } return url; // 返回图片url } 

afterExtractField(fieldName, data, page)

当一个field的内容被抽取到后进行的回调, 在此回调中可以对网页中抽取的内容作进一步处理

@param fieldName 当前fieldname. 注意: 子fieldname会带着父fieldname, 通过.连接.
@param data 当前field抽取到的数据. 如果该fieldrepeateddata为数组类型, 否则是String
@param page 当前正在爬取的网页页面的对象,调用page.url可获取当前网页的url,调用page.skip()便不再爬取当前网页
@return 返回处理后的数据, 注意数据类型需要跟传进来的data类型匹配

举个栗子:
比如爬取知乎用户的性别信息, 相关网页源码如下:

<span class="item gender" ><i class="icon icon-profile-male"></i></span> 

那么可以这样写:

configs = {
    // configs的其他成员
    ...
    fields: [
        {
            name: "gender",
            selector: "//span[contains(@class, 'gender')]/i/@class"
        }
    ]
    ...
};

configs.afterExtractField = function(fieldName, data, page) { if (fieldName == "gender") { if (data.indexOf("icon-profile-male") > -1) { // data中包含"icon-profile-male",说明当前知乎用户是男性 return "男"; } else if (data.indexOf("icon-profile-female") > -1) { // data中包含"icon-profile-female",说明当前知乎用户是女性 return "女"; } else { // data未匹配到上面的两个字符串,无法判断用户性别 return "未知"; } } return data; } 

afterExtractPage(page, data)

在一个网页的所有field抽取完成之后, 可能需要对field进一步处理, 以发布到自己的网站

@param page 当前正在爬取的网页页面的对象,调用page.url可获取当前网页的url,调用page.skip()便不再爬取当前网页
@param data 当前网页抽取出来的所有field的数据, JavaScript对象
@return 返回处理后的数据, 注意数据类型不能变

举个栗子:
比如从某网页中得到timetitle两个field抽取项, 现在希望把time的值添加中括号后拼凑到title中,处理过程如下:

configs.afterExtractPage = function(page, data) {
    var title = "[" + data.time + "] " + data.title; data.title = title; return data; }





爬虫进阶开发——之内置函数

本节介绍常用的内置js函数

extract(data, xpath)

使用xpathdata中抽取单条数据

@param data html代码片段
@param xpath XPath表达式
@return 返回符合XPath表达式的第一条html元素

举个栗子:
假设data是:

<span class="location item" title="加州"><a href="/topic/19581783" title="加州" class="topic-link" data-token="19581783" data-topicid="10505">加州</a></span> <span class="business item" title="互联网"><a href="/topic/19550517" title="互联网" class="topic-link" data-token="19550517" data-topicid="99">互联网</a></span> <span class="item gender"><i class="icon icon-profile-male"></i></span> 

执行以下代码:

configs.afterExtractField = function(fieldName, data, page) {
    if (fieldName == "content") { data = extract(data, "//a"); } return data; } 

返回的data结果是:

<a href="/topic/19581783" title="加州" class="topic-link" data-token="19581783" data-topicid="10505">加州</a> 

extractList(data, xpath)

使用xpathdata中抽取多条数据

@param data html代码片段
@param xpath XPath表达式
@return 返回符合XPath表达式的html元素数组

举个栗子:
仍然使用上例中data,执行以下代码:

configs.afterExtractField = function(fieldName, data, page) {
    if (fieldName == "content") { var contents = []; contents = extractList(data, "//a"); } ... } 

contents数组的内容是:

['<a href="/topic/19581783" title="加州" class="topic-link" data-token="19581783" data-topicid="10505">加州</a>', '<a href="/topic/19550517" title="互联网" class="topic-link" data-token="19550517" data-topicid="99">互联网</a>'] 

exclude(data, xpath)

data中去除符合xpath的html元素

@param data html代码片段
@param xpath 要去除的元素的XPath表达式
@return 返回去除之后的html代码片段

举个栗子:
仍然使用上例中data,执行以下代码:

configs.afterExtractField = function(fieldName, data, page) {
    if (fieldName == "content") { data = exclude(data, "//a"); } return data; } 

data的结果是:

<span class="location item" title="加州"></span> <span class="business item" title="互联网"></span> <span class="item gender"><i class="icon icon-profile-male"></i></span> 

encodeURIencodeURIComponent

js自带的这两个函数在处理汉字时有问题, 系统重新定义了这两个函数

getCaptcha(type, url)

验证码识别的函数,通过验证码图片获取识别的验证码

@param type 验证码类型,详情见爬虫进阶开发——之验证码识别中的验证码价目表
@param url 验证码图片url
@return 返回Json格式的验证码数据,格式为:{"result":"UNEL","ret":0}(result是识别的验证码结果;ret是返回码,识别成功时值为0,失败时值为非零负数)

举个栗子:
要处理某网站登陆界面的验证码,需调用getCaptcha函数,执行以下代码:

configs.afterDownloadPage = function(page, site) {
    // 验证码图片url
    var url = "https://account.guokr.com/captcha/117654328/"; // type是52(1-4位不定长数字英文混合),将得到的验证码Json数据赋值给"imgCaptchaData" var imgCaptchaData = getCaptcha(52, url); ... return page; }; 

getCaptchaFromBase64(type, imgBase64)

验证码识别的函数,通过Base64编码的验证码图片获取识别的验证码

@param type 验证码类型,详情见爬虫进阶开发——之验证码识别中的验证码识别价目表
@param imgBase64 验证码图片的Base64编码,不包含图片Base64的头:" data:image/jpeg;base64,"
@return 返回Json格式的验证码数据,格式为:{"result":"UNEL","ret":0}(result是识别的验证码结果;ret是返回码,识别成功时值为0,失败时值为非零负数)

举个栗子:
要处理某网站登陆界面Base64编码的验证码图片,需调用getCaptchaFromBase64函数,执行以下代码:

configs.afterDownloadPage = function(page, site) {
    // 验证码图片的Base64编码
    var imgBase64 = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQ..."; // type是53(1-5位不定长数字英文混合),将得到的验证码Json数据赋值给"imgCaptchaData" var imgCaptchaData = getCaptchaFromBase64(53, imgBase64); ... return page; };



爬虫进阶开发——之模板化

添加“输入框”

格式如下:

var keywords = "苹果产品,Apple Pay";//@input(keywords, 搜索关键字, 只采集此关键字在zhihu.sogou.com中的搜索结果,多个关键字用逗号分隔)

效果图如下:

输入框中输入的信息,可作为变量的值,比如,根据关键字爬取知乎,可在输入框中输入需爬取的关键字,在爬虫代码中对数据加以处理,即可爬到相应的数据。

添加“选择框”

格式如下:

var filter = true;//@input(filter, 结果过滤, 标题或主题中必须包含搜索关键字)

效果图如下:

选择框的信息,可作为判断条件,对数据进行操作,比如,现在要爬取某文章类网站,并添加是否过滤文章图片的功能,就需要将这个变量作为判断依据,并在爬虫代码中对不同情况进行相应处理。





爬虫进阶开发——之图片云托管

当我们需要爬取一个多图网站的时候,最直接的方法是直接爬取网站中的img标签,这种方式常常会导致采集到的图片无法正常显示或显示防盗链图。这种情况,选择图片云托管是最明智的。

那如何使用图片云托管呢?

最简单的方法,在神箭手后台配置爬虫任务的时候,勾选“自动缓存图片”,在任务运行时,就会帮你将采集到的所有图片托管到神箭手云服务器上。但这种方式只能帮你托管后缀名是jpgjpegpnggif的图片。

这样做当然无法满足开发者的需求,为此,我们提供了cacheImg内置函数,目前cacheImg内置函数只能在afterExtractField回调函数中使用,可以处理用户头像、文章封面图和内容中的图片。

举个栗子:
afterExtractField回调函数中,通过cacheImg函数将非标准的图片url托管到神箭手云服务器上

configs.afterExtractField = function(fieldName, data, page) {
    if (fieldName == "article_cover_img") { return cacheImg(data); // data是图片url,返回可被神箭手云服务器处理的url } return data; }



爬虫进阶开发——之自动IP代理

当爬取大众点评时会出现爬取不到数据的情况,这是怎么回事呢?其实,是我们的爬虫IP被网站封掉了,这个问题,选择我们神箭手提供的自动IP代理就能解决。

举个栗子:
在configs对象中添加成员enableProxy并设置为true,就可使用自动IP代理

var configs = {
    // configs的其他成员
    ...
    enableProxy: true, // 开启自动IP代理
    ...
};
 
 

爬虫进阶开发——之验证码识别(内测版)

当需要爬取的网页需要需要输入验证码才能访问的时候,该怎么办呢?这个问题,使用神箭手提供的验证码识别服务就能解决。

验证码识别是付费功能,在使用前请先确保你的账户有足够的余额,否则会影响使用。

验证码识别的调用函数请参考内置函数getCaptchagetCaptchaFromBase64

举个栗子:
当输入Base64编码的验证码才能爬取到某网页的数据时,先调用getCaptchaFromBase64函数得到验证码数据,再通过发送POST请求得到网页数据,执行以下代码:

configs.afterDownloadPage = function(page, site) {
    var imgBase64 = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQ..."; // 得到Base64编码的验证码数据,数据是Json格式,验证码类型是53(1-5位不定长数字英文混合) var imgCaptchaData = getCaptchaFromBase64(53, imgBase64); var imgCaptcha = JSON.parse(imgCaptchaData); if (imgCaptcha == null) { return page; } // 判断是否成功得到验证码数据 if (imgCaptcha.ret < 0) { return page; } var reg = /csrfToken: \"([0-9a-zA-Z-]+)"/; var result = reg.exec(page.raw); var data = new Object(); data.sno = 'FK1QPNCEGRYD'; // 将验证码赋值给"data.ans" data.ans = imgCaptcha.result; data.captchaMode = "image"; data.CSRFToken = result[1]; page.url = "https://checkcoverage.apple.com/cn/zh/?sn=FK1QPNCEGRYD"; // 发送带参数的"POSt"请求得到网页数据 page.raw = site.requestUrl('https://checkcoverage.apple.com/cn/zh/?sn=FK1QPNCEGRYD', 'post', data); return page; } 

验证码识别价目表(成功识别一次的价格)

验证码类型(Type)价格(分)类型描述
111位数字
212位数字
313位数字
414位数字
515位数字
616位数字
727位数字
828位数字
939位数字
10410位数字
1111-2位不定长数字
1211-3位不定长数字
1311-4位不定长数字
1411-5位不定长数字
1511-6位不定长数字
1621-7位不定长数字
1721-8位不定长数字
1831-9位不定长数字
1941-10位不定长数字
2011位英文字符
2112位英文字符
2213位英文字符
2314位英文字符
2415位英文字符
2516位英文字符
2627位英文字符
2728位英文字符
2829位英文字符
29210位英文字符
121211位英文字符
122212位英文字符
123213位英文字符
124214位英文字符
125215位英文字符
126216位英文字符
127217位英文字符
128218位英文字符
129219位英文字符
130220位英文字符
3011-2位不定长字符
3111-3位不定长字符
3211-4位不定长字符
3311-5位不定长字符
3411-6位不定长字符
3521-7位不定长字符
3621-8位不定长字符
3731-9位不定长字符
3841-10位不定长字符
11141-11位不定长字符
11241-12位不定长字符
11351-13位不定长字符
11451-14位不定长字符
11551-15位不定长字符
11651-16位不定长字符
11751-17位不定长字符
11861-18位不定长字符
11961-19位不定长字符
12061-20位不定长字符
28161-20位不定长字符,输入斜体部分单词
3011-2位不定长字符
3111-3位不定长字符
3211-4位不定长字符
3311-5位不定长字符
3411-6位不定长字符
3521-7位不定长字符
3621-8位不定长字符
3731-9位不定长字符
3841-10位不定长字符
11141-11位不定长字符
11241-12位不定长字符
11351-13位不定长字符
11451-14位不定长字符
11551-15位不定长字符
11651-16位不定长字符
11761-17位不定长字符
11861-18位不定长字符
11961-19位不定长字符
12061-20位不定长字符
28161-20位不定长字符,输入斜体部分单词
5011-2位不定长数字英文混合
5111-3位不定长数字英文混合
5211-4位不定长数字英文混合
5311-5位不定长数字英文混合
5411-6位不定长数字英文混合
5521-7位不定长数字英文混合
5621-8位不定长数字英文混合
5731-9位不定长数字英文混合
5841-10位不定长数字英文混合
13151-11位不定长数字英文混合
13251-12位不定长数字英文混合
13351-13位不定长数字英文混合
13451-14位不定长数字英文混合
13551-15位不定长数字英文混合
13661-16位不定长数字英文混合
13761-17位不定长数字英文混合
13861-18位不定长数字英文混合
13961-19位不定长数字英文混合
5911位中文
6022位中文
6133位中文
6244位中文
6355位中文
6456位中文
6567位中文
6678位中文
6789位中文
68910位中文
2591011位中文
2601112位中文
2611213位中文
2621314位中文
2631415位中文
2641516位中文
2651617位中文
2661718位中文
2671819位中文
2681920位中文
6921-2位不定长中文
7031-3位不定长中文
7141-4位不定长中文
7251-5位不定长中文
7351-6位不定长中文
7461-7位不定长中文
7571-8位不定长中文
7681-9位不定长中文
7791-10位不定长中文
269101-11位不定长中文
270111-12位不定长中文
271121-13位不定长中文
272131-14位不定长中文
273141-15位不定长中文
274151-16位不定长中文
275161-17位不定长中文
276171-18位不定长中文
277181-19位不定长中文
278191-20位不定长中文
7912位纯英文或纯数字
8013位纯英文或纯数字
8114位纯英文或纯数字
8215位纯英文或纯数字
8326位纯英文或纯数字
8427位纯英文或纯数字
8528位纯英文或纯数字
8639位纯英文或纯数字
87410位纯英文或纯数字
8811-2位不定长纯英文或纯数字
8911-3位不定长纯英文或纯数字
9011-4位不定长纯英文或纯数字
9111-5位不定长纯英文或纯数字
9211-6位不定长纯英文或纯数字
9321-7位不定长纯英文或纯数字
9421-8位不定长纯英文或纯数字
9531-9位不定长纯英文或纯数字
9641-10位不定长纯英文或纯数字
14061-20位不定长字符




爬虫进阶开发——之自动JS渲染

当需要爬取js动态生成的数据时,该怎么处理呢?神箭手为你提供了一个核武器,只需设置一个参数就可让爬虫爬取这类动态网页。

开启自动JS渲染后,爬取速度会变慢,请耐心等待。

举个栗子:
在configs对象中添加成员enableJS并设置为true,就开启了自动JS渲染,然后就可以像爬取普通网页一样爬取动态网页了

var configs = {
    // configs的其他成员
    ...
    enableJS: true, // 开启JS渲染
    ...
};



爬虫进阶开发——之技巧篇

本节的八篇文章是开发爬虫模板时需要了解的技巧。包括,在爬取网站过程中经常遇到的问题,回调函数和内置函数的使用技巧等。

如何实现模拟登陆?

通过模拟登陆,可以解决登陆后才能爬取某些网站数据的问题。接下来给你介绍模拟登陆的实现。

举个栗子:
在beforeCrawl回调函数(在configs详解——之回调函数中会详细描述)中,调用site.requestURl函数发送带参数的POST请求,就可以实现模拟登陆:

configs.beforeCrawl = function(site) {
    // 登陆页url
    var loginUrl = "http://www.waduanzi.com/login?url=http%3A%2F%2Fwww.waduanzi.com%2F"; // 提交的参数 var urlParam = {"LoginForm[returnUrl]":"http%3A%2F%2Fwww.waduanzi.com%2F", "LoginForm[username]":"用户名", "LoginForm[password]":"密码", "yt0":"登录"}; // 发送登陆请求 site.requestUrl(loginUrl, "post", urlParam); }
figs的其他成员
    ...
    scanUrls: ["http://wallstreetcn.com/news?status=published&type=news&order=-created_at&limit=30&page=1"],
    helperUrlRegexes: ["http://wallstreetcn\\.com/news(\\?[^\\s]+&page=\\d+)?"],
    ...
};

var currentPage = 1; configs.onProcessHelperPage = function(page, content, site) { // 得到当前列表页的页码 currentPage = parseInt(page.url.substring(page.url.indexOf("&page=") + 6)); // 定义下一个列表页的页码 var pageNum = currentPage + 1; // 通过对当前列表页url得到下一个列表页url var nextUrl = page.url.replace("&page=" + currentPage, "&page=" + pageNum); // 将下一个列表页url插入待爬队列 site.addUrl(nextUrl); return false; }

如何爬取AJAX动态生成的数据?

比如,百度百家文章的列表页是通过AJAX动态生成的,再比如,兴趣部落的评论也是通过AJAX动态生成的,对于这两种情况,我们都提供了相应接口来处理。

以爬取百度百家的文章为栗:
因为百度百家文章的列表页是通过AJAX动态生成的, 所以解决方案是在onProcessHelperPage回调函数中,根据当前的列表页生成新的列表页url并添加到待爬队列中,具体实现如下:

configs.onProcessHelperPage = function(page, content, site) {
    var i = page.url.indexOf("page="); if (i < 0) return false; // 得到当前列表页页码 var currentPage = parseInt(page.url.substring(i + 5)); var pageNum = currentPage + 1; // 通过当前列表页页码,生成新的列表页url var nextUrl = page.url.replace("page=" + currentPage, "page=" + pageNum); // 将生成的列表页url添加到待爬队列中 site.addUrl(nextUrl); return false; } 

以爬取兴趣部落的问答为栗:
这种情况的解决方案是使用attachedUrl发送请求:

{
    name: "question_answer_bid",
    selector: "//a[contains(@class,'bid')]"
},
{
    name: "question_answer_pid",
    selector: "//a[contains(@class,'pid')]"
},
{
    name: "question_answer", sourceType: SourceType.AttachedUrl, // "question_answer"是从发送"attachedUrl"这个异步请求返回的数据中抽取的 // "attachedUrl"支持引用上下文中的抓取到的"field", 这里就引用了上面抓取的"question_answer_bid"和"question_answer_pid" attachedUrl: "http://buluo.qq.com/cgi-bin/bar/post/get_comment_by_page_v2?bid={$.question_answer_bid}&pid={$.question_answer_pid}&num=20&start=0&barlevel=1&r=" + Math.random() + "&bkn=", selectorType: SelectorType.JsonPath, selector: "$.result.comments", repeated: true, children: [ ... ] }
 
 

通过Post或Get请求获取网页数据

一般的网站是通过Get请求来得到网页数据,但有些网站比较特殊,需要使用Post请求或在Headers中添加参数才可得到网页数据,这类情况需要调用addUrlPostaddHeader等函数来处理。

举个HTTP POST请求的栗子:

configs.onProcessHelperPage = function(page, content, site) {
    // 获取下一个列表页url并插入待爬队列
    var currentPage = parseInt(page.url.substring(page.url.indexOf("page=") + 5, page.url.indexOf("&size="))); var pageNum = currentPage + 1; var nextUrl = page.url.replace("page=" + currentPage, "page=" + pageNum); var param = new Object(); param.page = page + ""; param.size = "18"; site.addUrl(nextUrl, "Post", param); return false; } 

举个HTTP GET请求的栗子:
在Get请求的Headers中添加Referer属性

configs.beforeCrawl = function(site) {
    // Referer是Headers的属性,"http://buluo.qq.com/p/index.html"是Referer对应的值
    site.addHeader("Referer", "http://buluo.qq.com/p/index.html"); }

如何爬取列表页中的数据?

一般情况下,我们只需爬取内容页的数据即可,不过有时候列表网页中也会有需要爬取的数据,那想要爬取这部分数据,就要用到addUrl函数。

举个栗子:
在爬取爱游网的时候,除了基本的内容页信息外,还需要爬取浏览次数(或阅读量),但是这些数据是在列表页中,这就需要在onProcessHelperPage回调函数中做处理

var configs = {
    // configs的其他成员
    ...
    fields: [
        {
            name: "question_view_count",
            // 从内容页的数据中提取浏览次数(或阅读量)
            sourceType: SourceType.UrlContext,
            selector: "//a[contains(@class,'shenjianshou')]" } ] }; configs.onProcessHelperPage = function(page, content, site) { // 从列表页中找到浏览次数(或阅读量) var pageView = '<div><a class="shenjianshou">5000</a></div>'; // 将找到的浏览次数(或阅读量)附加到内容页的数据中 site.addUrl(contentUrl, "get", null, pageView); ... return false; }

如何去掉网页中的广告?

当成功爬取到的网页数据中有很多不相干的html广告标签时,你是否会感到无可奈何,有时候即使将XPath的效果发挥到极致,也无法去掉顽固的html广告标签,咋整呢?

本节给你介绍通过excludeextract内置函数去除html广告标签的方法,可提取有用数据或清理无用数据。

举个栗子:
在爬取某论坛问答帖时,发现有很多html广告标签以及一些无用数据,就需要在afterExtractField回调函数中调用exclude内置函数了

configs.afterExtractField = function(fieldName, data, page) {
    if (fieldName == "question_detail") { // 将data中符合XPath:"//div[contains(@class,'a_pr')]"的数据去掉 data = exclude(data, "//div[contains(@class,'a_pr')]"); return data; } return data; } 

有时,如果无用数据太多,最好调用extract内置函数直接将有用的数据提取出来,这么做会比调用exclude内置函数方便。

 
 

如果内容页有分页,该如何爬取到完整数据?

如果要爬取的某个内容页中有多个分页,该如何爬取这个内容页的完整数据呢?这里就无法使用onProcessHelperPage回调函数了,而需要使用field中的attachedUrl来请求其他分页的数据。

举个栗子:
爬取某网站文章时,发现有些文章有多个内容页面,处理过程如下:

var configs = {
    // configs的其他成员
    ...
    fields: [
        {
            name: "contents",
            selector: "//ul[contains(@class,'pagination')]//@href",
            repeated: true, children: [ { // 抽取出其他分页的url待用 name: "content_page_url", selector: "//text()" }, { // 抽取其他分页的内容 name: "page_content", sourceType: SourceType.AttachedUrl, // 发送"attachedUrl"请求获取其他的分页数据 // "attachedUrl"使用了上面抓取的"content_page_url" attachedUrl: "{content_page_url}", selector: "//*[@id='article_page']" } ] } ] }; 

在爬取到所有的分页数据之后,可以在afterExtractPage回调函数中将这些数据组合成完整的数据

configs.afterExtractPage = function(page, data) {
    var contents = data.contents;
    if (contents != null) { data.article_content = ""; for (var i = 0, n = contents.length; i < n; i++) { data.article_content += contents[i].page_content; } } data.article_content = data.content_cover_img + data.article_content; return data; }
 
 

文章和问答demo

本节给出两个完整的抓取爬虫, 一个是文章类型, 一个是问答类型

如果抓取到的数据想要以文章或者问答的形式发布到网站(WeCenterWordPressDiscuz!等),field的命名请参考这两个demo的命名, 否则无法发布成功

文章采集爬虫demo

如果爬取的内容想要以文章形式通过我们的发布器发布到您的网站, 那么fieldname请与下面demo的命名一致

爬取华尔街见闻的全球资讯, 爬虫如下:

var configs = {
    domains: ["wallstreetcn.com"],
    scanUrls: ["http://wallstreetcn.com/news"],
    contentUrlRegex: "http://wallstreetcn\\.com/node/\\d+",
    helperUrlRegexes: ["http://wallstreetcn\\.com/news(\\?[^\\s]+&page=\\d+)?"], fields: [ { // 文章标题 name: "article_title", selector: "//*[@id='main']/article/h1", required: true }, { // 文章内容 name: "article_content", selector: "//*[@id='main']/article/div[contains(@class,'article-content')]", required: true }, { // 文章作者 name: "article_author", selector: "//*[@id='main']/article/div[contains(@class,'meta')]//a", }, { // 文章发布时间 name: "article_publish_time", selector: "//*[@id='main']/article/div[contains(@class,'meta')]/span[contains(@class,'time')]", } ] }; configs.afterExtractField = function(fieldName, data, page) { if (fieldName == "article_publish_time") { var timestamp = Date.parse(data.replace("年","-").replace("月","-").replace("日","")); return isNaN(timestamp) ? "0" : timestamp/1000 + ""; } return data; }; var crawler = new Crawler(configs); crawler.start();
r: "//*[@id='bbs_show_top']//h3",
            required: true
        },
        {
            // 问题作者
            name: "question_author",
            selector: "//*[@id='f1']/a[2]" }, { // 问题作者头像 name: "question_author_avator", selector: "//*[@id='f1']/a[1]/img" }, { // 问题详情 name: "question_detail", selector: "//*[@id='mainPost']//div[contains(@class,'bbs_show_main')]/div[contains(@class,'bbs_show_content')]" }, { // 问题提问时间 name: "question_publish_time", selector: "//*[@id='mainPost']//small", required: true }, { // 问题答案 name: "question_answer", selector: "//*[@id='uc_wrap']/div[5]//div[contains(@class,'bbs_show_item')]", repeated: true, children: [ { // 问题答案内容 name: "question_answer_content", selector: "//div[contains(@class,'bbs_show_main')]/div[contains(@class,'bbs_show_content')]", required: true }, { // 问题答案作者 name: "question_answer_author", selector: "//div[contains(@class,'bbs_user_head')]//a[2]" }, { // 问题答案作者头像 name: "question_answer_author_avatar", selector: "//div[contains(@class,'bbs_user_head')]/a[1]/img" }, { // 问题答案回答时间 name: "question_answer_publish_time", selector: "//small" } ] } ] }; configs.afterExtractField = function(fieldName, data, page) { if (fieldName == "question_publish_time" || fieldName == "question_answer.question_answer_publish_time") { data = data.substring(5); var timestamp = Date.parse(data); return isNaN(timestamp) ? "0" : timestamp/1000 + ""; } else if (fieldName == "question_detail" || fieldName == "question_answer.question_answer_content") { data = exclude(data, "//div[contains(@class,'image_note')]"); return data; } return data; } var crawler = new Crawler(configs); crawler.start();
 
 

开发神箭手爬虫的常用工具

“工欲善其事,必先利其器”,开发神箭手爬虫,起码得有几件顺手的工具才行吧,接下来给你逐个介绍。

谷歌Chrome浏览器

说起谷歌的Chrome浏览器(以下简称Chrome),相信大家都耳熟能详了吧,不仅使用流畅,而且功能强大,对开发神箭手爬虫非常有帮助。

我们主要使用的是Chrome的开发者工具,如下图所示:

或者可以直接在网页上点击鼠标右键,选择“检查”,也可打开开发者工具。

开发者工具顶部有ElementsConsoleNetwork等八个栏目。常用的有三个:Elements,用来查看需爬取字段的HTML标签信息;Console,可以检测你的JS代码;Network,用来分析HTTP请求。

XPath Helper

XPath Helper是Chrome浏览器的插件,可以在Chrome应用商店安装下载,主要用来分析当前网页信息的XPath,并将其精简化。具体操作步骤如下:

1、在Chrome浏览器上,选择抽取的html字段并右击,点击“检查”,即可弹出开发者工具;右击已选字段,点击Copy XPath即可将该字段的XPath保存到浏览器剪贴板上,如下图所示:

2、打开XPath Helper插件,将得到的XPath复制进去,最好进行简化修改后再使用,如下图所示:

3、在XPath中,如果使用class属性来定位元素,最好使用contains函数,因为元素可能含有多个class

{
    name: "article_publish_time",
    selector: "//span[contains(@class,'date')]/span"
},

DHC REST

DHC REST也是Chrome浏览器的插件,可以在Chrome应用商店安装下载,主要用来模拟HTTP客户端发送测试数据到服务器。HTTP Get请求在开发中比较常用。

正则表达式测试工具

推荐使用站长工具中的正则表达式测试工具,链接如下: http://tool.chinaz.com/regex/

 
 

常见问题

Q:为什么要在configs成员中添加domains

A:domains定义爬虫爬取指定域名下的网页,非域名下的url会被忽略,可提高爬取速度。

值得注意,如果scanUrlscontentUrlshelperUrls不在domains包含的域名范围内,那么爬虫将无法爬取到数据。

Q:为什么contentUrlRegexeshelperUrlRegexes正则表达式中的转义字符要加双反斜杠\\

A:因为contentUrlRegexeshelperUrlRegexes的每个正则表达式都是字符串类型,如下所示:

contentUrlRegexes: ["http://wallstreetcn\\.com/node/\\d+"],
helperUrlRegexes: ["http://wallstreetcn\\.com/news(\\?/page=\\d+)?"],

在JS语法中规定若正则表达式以字符串形式表示,其中的转义字符需加双反斜杠。

Q:为什么我的爬虫爬取到的图片都是裂图或网站默认图?

A:首先,在神箭手后台检查你的任务配置中是否勾选了“自动缓存图片”,我们建议勾选上,因为这样做,无论你要爬取的网站图片是否加了“防盗链”,我们的系统都会帮你把图片缓存到神箭手云服务器上;

然后,判断需要爬取的图片url的后缀名是不是jpgjpegpnggif。如果不是,请加神箭手开发者QQ群咨询,群号:342953471;如果是,请耐心等待几分钟,因为我们的图片缓存服务器还在努力下载图片中。

Q:为什么我一定要将爬取到的文章发布时间转换成Unix时间戳?

A:当发布文章或问答到网站时,需要通过我们提供的发布插件进行发布,而我们的发布插件对发布时间有特殊要求,需要Unix时间戳。

值得注意,如果爬取的数据不需要发布到网站,则不用对时间做处理。

Q:我想按网站分类爬取文章,为什么爬到的数据中总是包含其他分类的文章?

A:爬取到的文章不够精准,说明你的contentUrlRegexeshelperUrlRegexes中的正则表达式有问题,匹配了其他分类的contentUrl(内容页url)或helperUrl(列表页url),请仔细检查并进行修改。

Q:为什么我爬取的网页数据中有很多无用html标签,怎么处理?

A:爬取的数据中包含无用html标签,原因可能是,你写的XPath不够精确,抽取的内容中包含了很多无用html标签。如果无法通过XPath来解决,请考如何去掉网页中的广告?

Q:在神箭手上写的爬虫容易调试吗?

A:很容易,我们向开发者提供了用于打印日志信息的console对象,可以调用console.log(message)console.debug(message)等方法来分级打印你需要的日志。详情见configs详解——之site, page和console

Q:如何爬一个有反爬虫的网站?

A:在configs对象中添加enableProxy: true,就能使用我们平台提供的IP代理服务了。

Q:为什么我写的爬虫可以爬取到数据却提示发布失败?

A:遇到这种情况,最好先检查一下你写的爬虫代码,确保每个field对象的name两个完整demo中的命名一致。如果不一致,请参照demo将命名改掉;如果一致,可能是发布的问题,请直接联系客服,客服QQ:2879835984。

Q:神箭手云爬虫是不是可以爬所有网站?

A:理论上是可以的,为了兼容更多用户的爬取需求,我们的平台也在不断完善中。










  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值