前言
之前搞过几次模拟登录,都是模拟 post 后取到 cookie,之后便能用这个 cookie 愉快玩耍。这次碰到了验证码,其实只需手动登录一次,手动取到 cookie 后也能玩耍,不过 cookie 有效期不长,经常要换,十分麻烦。然后看到了这个库 tesseract.js,由于我们的验证码机器识别难度并不是很高,试了下出现 good case 概率还是非常高的,决定试下自动识别验证码登录。
验证码是用 PHP 生成的,指向一个 PHP 地址。原理不是很难,PHP 生成验证码图片,开启 Session(所以每次点击验证码切换图片,都会是一个新的 Session),然后把验证码数字记在 Session 数组中,当用户提交表单后,check 输入的验证码数字是否存在于 Session 数组中。
用的语言是 Node。之前想的非常简单,首先将验证码 down 下来,然后调用 tesseract.js 识别,最后模拟 post 后取到后续操作需要的 key cookie。
POST 中文参数问题
出现该问题需要满足两个条件:
- 页面编码 GBK
- POST 携带中文参数
模拟 post 有个参数 username 用的是中文,如果页面是 utf-8 编码应该没这个问题,就直接发送中文参数了,但是看实际请求中文参数被编码了,查了下 urlencode 这个包可以做类似的编码。将这个被编码后的参数填入发起模拟请求,报用户名错误,然后查到了 这个帖子,场景类似,同样是 gbk 编码页面的 post 携带了中文参数,我猜测原因也可能相同,被二次编码了。
但把转换过的字符串传到 request 的 form 中去后,request 又会对其中的 % 编码为 %25
用帖子里的方法可以解决:
var request = require('request');
var iconv = require('iconv-lite');
var post_request = request
.post({
url: 'https://xx.com',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': 'PHPSESSID=xx'
},
encoding: null // 关键代码
}, function (err, sres, body) {
// 解决返回页面乱码(gbk)
var html = iconv.decode(body, 'gb2312');
console.log(html);
});
// form data
post_request.write("a=xx&b=xx&c=xx");
请求 gbk 页面会中文乱码,这里用了 iconv-lite 这个包,其他解决方法可以参考 node爬虫之gbk网页中文乱码解决方案
因为一直用的是 superAgent 这个模块,所以希望能在 superAgent 基础上解决这个问题,然后在 cnode 提了个 问题 ,最后也是解决了。
为了避免返回信息中文乱码,用了 superagent-charset 这个包:
var superagent = require('superagent-charset');
var req = superagent
.post('https://xx.com')
.set("Content-Type", "application/x-www-form-urlencoded")
.set("Cookie", "PHPSESSID=xxx")
.charset('gbk');
// form data
req.write("username=xx&password=xx&checkcode=xx");
req.end(function(err, sres) {
console.log(sres.text);
});
验证码识别
以上问题解决后,PHPSESSID 这个 cookie 非常容易获取,模拟请求一次取 cookie 即可。手动在代码中输入浏览器中显示的验证码,模拟登录可以成功,然后取登录后的 cookie,确实也可以愉快玩耍,不过问题来了,验证码图片无法识别了!
昨天晚上试了下直接在登录页面右键复制下来验证码,然后用 tesseract.js 是可以解析的,但是用 Node down 下来的却不能解析,alsotang 大大说可能是 "buffer 在 ondata 的相加问题"。刚才试了下昨天手动保存验证码图片,成功解析的 case 也报错了,感觉是 tesseract.js 这个库还不是很成熟,过段时间再看看。
下载图片用的是这个函数:
// to download picture
function download(url, savePath) {
var req = http.get(url, function(res){
var binImage = '';
res.setEncoding('binary');
res.on('data', function(chunk){
binImage += chunk;
});
res.on('end', function(){
if (!binImage) {
console.log('image data is null');
return null;
}
fs.writeFile(savePath, binImage, 'binary', function(err){
if (err) throw err;
console.log('It\'s saved!'); // 文件被保存
});
});
});
}
对 Node 不熟,这个问题留坑以后待解决了。
问题解决过程中,除了上面的帖子,还在 v 站发了个帖子,如何模拟登录有验证码的网站?,FYI