最近有很多朋友跟我说,“爬虫这东西很简单啊,好像还没学就已经会了,没啥深奥的东西哦。看了你之前的教程,不就是一个队列加一些Http请求吗,不就是写写XPath和正则吗,你们还做个神箭手云爬虫出来?我自己上个厕所就写完了啊。”
看来是时候拿出我们压箱底多年的老干妈了,哦不,老干货了。不吓屎你们这群小学生我就不在6年级混了。
废话不多说,所谓爬虫天坑,敢对得起这个名字的一定不能是等闲之辈。起码得是过完年老板给你扔这个任务,你儿童节还在头大的级别。今天第一课,咱们就先找个最难的热热身吧:爬取百度指数的关键词搜索指数。
先贴一个logo让大家跪拜一下
好了,大家平身吧,咱们马上就正式开始了,想上厕所的赶紧去,不然看完这篇文章估计你就忘了怎么上厕所了。
正式开始之前,先插个广告:如果土豪朋友不想写代码或者中途看不下去的,我们将以下代码已经打包成一个完整的应用,大家进入神箭手的云市场搜索百度指数(http://www.shenjianshou.cn/index.php?r=market/product&product_id=500036)就可以看到应用,直接调用既可。
——————————–前方高能预警看也看不完上厕所赶紧去分割线————————————-
咱们正式开始:所谓知己知彼百战不殆,我们要先了解一下我们的对手。咱们打开百度指数
http://index.baidu.com,映入眼帘的是一个简单的输入框。好开心啊,好像不用登录啊,输入一个关键字试一下吧,输入神箭手,回车:
果然百度老司机不会让我们那么开心的。没事没事,不就是登录吗,也不是没做过登录,抓包研究下请求应该不难。我们先找一个账号登录看下。登录之后继续输入神箭手:
出来了。哈哈,不难嘛,这不就直接显示了。然后就按照以前的爬虫的教程,用XPATH获取一下数字就可以了,哈哈哈…哈哈..哈……..
慢着,怎么感觉这个数字看着怪怪。吓得我赶紧掀开被子看看这货到底是啥:
什么?这是图!!!!什么?这还是拼图!!!!什么?这货居然是异步的拼图!!!!
怎么样,感受到天坑的深度没有?
那咱们就一起来看看怎么见招拆招,用神箭手把百度指数搞定的吧。
开始具体的代码之前,我们先在神箭手后台新建三个应用,分别是百度指数API,百度登录爬虫,百度指数图片识别AI。
第一章 登录应用
第一节:咱先搞定登录
模拟登录一直是爬虫的一个老大难问题,虽然我们神箭手提供了智能登录接口login函数,但是遇上复杂一些的登录依然无能为力。当然你可以登录后复制本机Cookie直接用,但这种雕虫小技百度想封你真得比捏死一只蚂蚁还简单。咱们要有不怕苦,迎难而上的精神,死磕登录!算了~还是先去搜一下有没有别人写过。不搜不知道,一搜吓一跳啊。咱就随便找个源码借鉴借鉴。乔布斯老人家说过嘛,greate artist steal。
https://github.com/qiyeboy/baidulogin/blob/master/baidulogin.py
这个不错,逻辑清晰,代码干净,万能的github果然不辜负我的重望。我们steal到神箭手平台上来。
首先我们理清这个流程,根据这个代码我们知道百度的登录流程是这样的:
1.通过请求百度首页或者任意一个百度url获得百度的基础cookie。
2.请求 https://passport.baidu.com/v2/api/?getapi 获得token
3.通过 https://passport.baidu.com/v2/getpublickey 获得密码加密的key
4.通过 https://passport.baidu.com/v2/api/?login 将之前获得到的token,生成的gid,生成的时间戳,用key加密密码,来提交登录。
5.如果返回有验证码,获取codestring并请求 https://passport.baidu.com/cgi-bin/genimage 获得验证码图片并识别。
6.通过 https://passport.baidu.com/v2/?checkvcode 来验证是否识别成功
7.如果不成功通过 https://passport.baidu.com/v2/?reggetcodestr 来切换验证码,在重复前两步。
8.再次提交 https://passport.baidu.com/v2/api/?login 看是否登录成功。
好了,这中间很麻烦的两个地方是
1. 验证码识别 这个神箭手提供了验证码识别的函数,调用方式如下:
var codeUrl = "https://passport.baidu.com/cgi-bin/genimage?" + codeString;
var codeReg = getCaptcha(71, codeUrl);
var imgCaptchaData = JSON.parse(codeReg);
if (imgCaptchaData && imgCaptchaData.ret > 0) {
var result = imgCaptchaData.result;
verifycode = encodeURI(result,"UTF-8");
tt = (new Date()).getTime();
var codeCheck = site.requestUrl("https://passport.baidu.com/v2/?checkvcode&token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&verifycode=" + verifycode + "&codestring=" + codeString + "&callback=");
var checkInfo = JSON.parse(codeCheck);
if (checkInfo.errInfo.no != "0") {
console.log("验证码识别错误");
}
continue;
} else {
console.log("验证码识别失败");
continue;
}
2. RSA加密
当我们获取到key之后需要对密码进行RSA加密,百度是采用的JS的开源RSA加密库,神箭手也提供了RSAEncode的方法,具体代码如下:
var pubkeyJson = site.requestUrl("https://passport.baidu.com/v2/getpublickey?token=" + token + "&tpl=mn&apiver=v3&tt=" + tt + "&gid=" + gid + "&callback=");
var pubkeyInfo = JSON.parse(pubkeyJson.replace(/'/g, "\""));
var pubkey = pubkeyInfo.pubkey;
var rsakey = pubkeyInfo.key;
var crypttype = "";
var rsaPassword="";
if (rsakey != "") {
crypttype = "12";
//加密密码
pubkey = pubkey.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").trim();
rsaPassword = (RSAEncode(password, pubkey));
}
其他的都是一些基础的请求,大家可以参考github中的代码进行编写。
第二节:疯狂登录
完成了第一节的工作之后,你以为登录就没问题了吗?你以为你可以用一个帐号爬到天荒地老吗?有人说限制爬取频率,这当然是一个方法,但却不是最好的解决方案。毕竟缩手缩脚,感觉很受限。最好的方案当然是登录一堆帐号,获取一堆的Cookie,然后从这堆Cookie中每次随机取一个Cookie,再通过这个Cookie去访问。那我们就需要一个新的东西:Cookie池。
我们看下神箭手如何调用Cookie池,首先我们需要新建一个爬虫应用专门用于登录:
var configs = {
shareUserWithKey:"__bindex__",
};
configs.onUserAdded = function (use, psw, site) {
var loginResult = login(use, psw, site);
if( loginResult !="success") {
return false;
}
return true;
}
这里的login方法就是刚刚我们写的百度登录,然后我们再在beforeCrawl的回调函数中反复调用以下方法:
site.addUser(user, password);
当然这里还有一个问题,如果我们一直使用一个IP来登录,也很容易被百度封掉,所以我们最好打开企业代理IP接入。
通过这种形式我们就可以建立一个可共享的Cookie池。然后我们在百度指数API应用(下一章会详细介绍)里通过设置以下代码来共享这个Cookie池:
var configs = {
shareUserWithKey:"__bindex__",
multiUser: true
}
这样在这个应用中会在每次访问一个Url的周期中随机从Cookie池取一个Cookie并请求Url。通过这种形式我们还可以把登录和请求代码解耦合。将来还可以复用登录代码。
第三节:问题来了,帐号从哪来呢?
除了把七大姑八大姨的手机都来注册一遍以外,没什么好办法,除非…(此处省略1000个字)。
第二章:获取指数图片API
第一节:异步请求数据
终于完成了登录,感觉怎么样,是不是有点天坑的意思?哈哈,万里长征咱才走了第一步。下面我们才真正来揭开天坑的核心:数字图片。
然后我们继续掀被子看看这个标签是怎么来的:
貌似不难找,不过看这个URL看着就头大,感觉已经被百度登录伤害过一次之后真的无力再一个一个参数分析,我们直接使用神箭手提供的js渲染页面的接口,直接把页面渲染出来把:
var configs = {
domains: ["index.baidu.com"],
scanUrls: ["http://index.baidu.com/?tpl=trend&word=" + encodeURI(keyword, "GBK")],
enableJS:true
}
这样我们在afterDownloadPage中拿到的就直接是渲染好的页面了,我们再通过正则和XPath取出数字图片的容器标签代码和Css代码(Css代码就是把图片设置成背景的style标签),之所以要拿Style标签是因为两个数字图片共享了一个Style,而这个Style在第一个数字图片的标签中,所以我们必须抽取出这个Style标签,在分别设置给两个不同的数字的容器标签代码。这段代码咱们再下一节中给出。
第二节:渲染数据成图片
我们拿到了数字图片的容器标签代码有什么用呢,当然是要渲染出对应的图片了。那为什么我们要这么大的弯去得到这张图呢。这一点正是百度指数能当选天坑的原因了,我们看一下这个图片是如何拼出来的,我们看下这段HTML代码。
<span class="imgval" style="width:6px;">
<div class="imgtxt" style="margin-left:-8px;"></div>
</span>
<span class="imgval" style="width:18px;">
<div class="imgtxt" style="margin-left:-62px;"></div>
</span>
我们可以看到这里有两个imgval标签,imgval是用来当蒙版的,可以从一张背景图中抠出需要的部分,而imgtxt则是显示图片的,这里又有一个margin-left用来具体调整整张图要显示的位置。
而最最最变态的是,两个标签并不是两个数字,而是三个数字!这就说明我们不可能一个一个数字去识别,必须作为一个整体图片来识别了。
我们知道PhantomJS这类Headless的浏览器都有渲染Html代码成图片的功能,神箭手渲染JS基于PhantomJS当然也支持这个功能,而且我们的调用接口更简单只需要调用site.renderImage方法既可实现将代码渲染成图片的功能,
下面是结合第一节的完整代码如下:
var sevenReg = /class="mtable profWagv">(.+?)<\/table>/;
var sevenMatch = sevenReg.exec(indexInfo);
if(sevenMatch) {
var sevenInfo = extractList(sevenMatch[1],"//*[@class='ftlwhf enc2imgVal']");
var styleReg = /(<style>.+?<\/style>)/;
var styleMatch = styleReg.exec(sevenMatch[1]);
var sevenHtml = '<html><head><base href="http://index.baidu.com/" /></head><body style="background:#fff;"><style>.imgval .imgtxt {display: block;width: 19200px;height: 25px;margin-top: -2px;} .imgval {display: inline-block;height: 12px;margin-bottom: -2px;overflow: hidden;}</style><div class="profWagv">'
if(styleMatch) {
var html = sevenHtml + sevenInfo[0].substring(0,sevenInfo[0].length-7) +styleMatch[1] + "</div></body></html>";
var base64 = site.renderImage(html, 120, 25);
index.week_all_index = base64ToImg(base64,site);
var html2 = sevenHtml + sevenInfo[1].substring(0,sevenInfo[1].length-7) +styleMatch[1] + "</div></body></html>";
var base641 = site.renderImage(html2, 120, 25);
index.week_mobile_index = base64ToImg(base641,site);
}
} else {
system.exit("关键字不存在");
}
这里只展示一下对于7天指数的渲染,30天的几乎一样。可以看到代码中添加了一些定义的Css这是为了渲染这段代码时能够补上网页中本来相关的Css,而不至于显示不正常,我们看一下渲染出来的图片的base64。
iVBORw0KGgoAAAANSUhEUgAAAHgAAAAaCAYAAAB8WJiDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAxklEQVRoge3aWwqDMBBG4Vq6vcxis8HxqVAtmYxtNPhzvjevFA5JVLq4uz8g6zn7B+BcBBZHYHEEFkdgcQQWR2BxBBb3yp5oZpvtWmvznOhYdD1O4AmllO6+z+3oWLQP4w2Zos1sMyJrrV8jFnOkAjOd3ld6DX6L1tkWRvQ8hwLvp+Ij9tcR/BrpNfifuJgnFZi4N5Z51M680kSvSb/cD2Ms7v1/dLTWy9a62vvQwWxwnVRg3BffosURWByBxRFYHIHFEVjcCuhz388wTpcGAAAAAElFTkSuQmCC
把base64转换成图片之后如下图,没毛病。
第三章:识别指数图片
第一节:如何识别?
显然这张渲染出来的图片并不比验证码更复杂,那么干脆我们用验证码识别吧。不过我要提醒同学们,神箭手的验证码识别是要收费的哦。当然啦,土豪无所谓。
对于非土豪的同学继续往下看,这张图片看着并不复杂,甚至简单到我们可以切割后直接对比文件数据的方式来去判断图片,当然这太low了,而且代码写起来其实也不太轻松。有什么其他更有逼格的手段呢?
神箭手作为一个重视逼格胜过重视功能的平台,当然不会只给你这个方案。现在是祭出神箭手最新黑科技-TensorFlow的时候了。不过话说回来了,用TensorFlow来识别这张图片,有点大炮打蚊子的意思。不过本着只选难的,不选烦的工程师本性。
我宁愿写TensorFlow,也不想写像素点对比。当然最重要的是因为我们自己在研发的验证码识别的代码可以直接拿来改改用,那今天就拿出来跟大家共享一下。
第二节:编写机器学习代码
怎么样,感受到天坑牛逼闪闪的光辉了吗?是不是觉得开始看这课的时候是小学生,现在马上就要高中毕业了?
我们打开百度指数图片识别AI这个应用,之前接触过神箭手的同学们肯定对神箭手云爬虫应用不陌生,但是对于这个神箭手AI应用(也就是神箭手封装的TensorFlow)肯定是一头雾水。
首先与神箭手爬虫代码不一样,TensorFlow是Python的(来,咱们再学一个新语言)。但类似于神箭手爬虫,我们依然可以通过自定义输入项来定义输入参数(请求的时候,需要定义一个输入项来接受图片的base64编码)。
有话要说