前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >nodejs实现简单的自动约车

nodejs实现简单的自动约车

作者头像
星回
发布2018-08-02 15:25:02
1K0
发布2018-08-02 15:25:02
举报
文章被收录于专栏:星回的实验室星回的实验室

最近约车真是越来越难了,网上约车经常车位刚放出来便已空空如也。突然回想起之前学车时教练反复提到的约车软件,去淘宝上一查:我去,卖出去一千多份了!还能约到车那就是有鬼了……此刻我深深怀疑这个软件是他们自家开发的,贵圈水真深。然而作为一名程序猿的尊严是不允许我去买这软件的……于是花了一天捣鼓出来一个极其简陋的约车系统,虽然因为官方网站对这方面的限制很多,效果并不是很好,不过试用了一下淘宝的爆款约车软件基本确定原理相同,那么就满足了吧……(挽尊可矣)

软件使用nodejs实现,理由一个字,简单,方便。在此记录下一些思路。

验证码识别

首先,要实现自动约车,验证码是第一个需要突破的关卡。这里我用了google著名的图像识别库tesseract-ocr,基本可以对一些简单的纯字母组成的验证码完成识别。对一些有不同颜色的验证码,再辅以graphicsmagick库做一些图像灰度化处理之后再识别,能提高一些准确率。可用npm下载nodejs的tesseract和graphicsmagick库,前提是计算机内得预先安装这两款软件。安装过程请参照:

tesseract: https://www.npmjs.com/package/node-tesseract

graphicsmagick: https://www.npmjs.com/package/gm

第一步,我们需要访问主页面得到验证码。一般网站验证码会存于session之中,因此我们需要通过response中的set-cookie字段来获取该次请求的session id,并存入之后每次请求request头携带的Cookie中,这样服务器才能将脚本发出的多个请求归入同一次会话,保存登录状态。代码如下:

代码语言:javascript
复制
// Refresh the validcode and then download it.
function downloadValidCode() {
    var deferred = Q.defer();
    if (!VALIDIMG_SOURCE) {
        deferred.reject('null resource');
    }
    var file = fs.createWriteStream(DOWNLOAD_DIR + FILE_NAME); 
    var options = {
        hostname: HOST,
        port: 80,
        path: '/' + VALIDIMG_SOURCE,
        headers: {
            'Cookie': COOKIE
        }
    };

    http.get(options, function(res) {  
        res.on('data', function(data) { 
             file.write(data);  
        })
        .on('end', function() {
            file.end();  

            if (res.headers['set-cookie']) {
                COOKIE = res.headers['set-cookie'] + ';';
            }
            deferred.resolve(); 
        })
        .on('error', function(e) {
            deferred.reject(e);
        });  

    });  

    return deferred.promise;
}    

fs获取文件流并将验证码存入下载目录供之后图像处理函数调用。这里还使用了nodejs中的q实现了一个promise的API,方便之后程序主循环实现调用链,将在下文提到。

代码语言:javascript
复制
function recognizeValidCode() {
    var deferred = Q.defer();
    log('recognizing');
    gm(DOWNLOAD_DIR + FILE_NAME).colorspace('GRAY').write(PROCESS_DIR + FILE_NAME, function(err) {
        if (err) {
            deferred.reject(err);
        }
        // Recognize text of any language in any format
        tesseract.process(PROCESS_DIR + FILE_NAME,function(err, text) {
            if (err) {
                deferred.reject(err);
            } else {
                log('validcode is: ' + text);
                code = text.trim();
                deferred.resolve();
            }
        });
    });

    return deferred.promise;
}

以上代码是对验证码的图像处理,看起来就很明了了——首先利用gm对图像作灰度化处理,然后调用tesseract识别出文字,最后去掉前后可能会产生的空白符。这里存在一些问题:约车官网的验证码中有一些噪点,时常会干扰识别的准确性。解决这个问题的方法并不是没有,需要动用一些图像去噪的算法,考虑到无视之的情况下正确率也有约25%,因此出于成本和效率的综合考量,决定直接采用暴力破解(其实是懒)。

得到验证码之后,将用户名、密码、验证码和Cookie以表单形式发送到login接口,大功告成。当然,以上方式仅限于简单验证码,对于12306之类上升到图灵测试层次的验证码目前还并没有思考解决方案……

预约

登录搞定之后,预约就是体力活了。通过抓包和对它的js代码分析出预约操作的各个参数,然后模拟格式无限发请求。需要注意的是,官网对发请求的频率有严格限制,因此一般设个几分钟的间隔,不然就成DDoS了……

调用链

nodejs的“回调地狱”应该是它的一个比较著名的现象了,这是由于它事件驱动以及异步编程的特性所致。大致说来,就是下图这种效果:

代码语言:javascript
复制
step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

解决该问题的方案是使用promise。实现了promise的函数会将回调结果传入链条中下一个方法中处理。我在约车软件的主循环中需要这样一个逻辑:刷新验证码 --> 下载验证码并识别 --> 登录 --> 预约循环 --> 若session过期,重新刷新验证码登录过程。

在这条调用链中存在很多条件判断和异常处理,要是每个方法都做一次显然会令代码显得十分臃肿且不便调试。nodejs中,使用q来实现promise。实现过程如下:

在一个函数中,首先声明defer:

代码语言:javascript
复制
var deferred = Q.defer();

然后,若结果成功,则调用resolve方法,链条中下一个节点的第一个回调函数便会处理成功回调;反之,若失败则调用reject方法,下一节点的第二个回调函数将会处理失败回调。

代码语言:javascript
复制
if (content.indexOf('true') !== -1) {
    deferred.resolve();
} else if (content.indexOf('验证码不符合!') != -1) {
    deferred.reject('wrong validcode');
} else {
    deferred.reject('unknown error');
}

最后,函数返回一个promise的API,以便能够实现链式调用。

代码语言:javascript
复制
return deferred.promise;

一个登录、预约循环便如下所示:

代码语言:javascript
复制
function login(startTime, endTime, date) {
    downloadValidCode()
        .then(recognizeValidCode)
        .then(requestLogin)
        .then(function() {
            log('Login successfully!');
            reserve(startTime, endTime, date);
        }, function(e) {
            log(e);
            // If login failed, then login again.
            setTimeout(function() {
                login(startTime, endTime, date);
            }, parseInt(INTERVAL) * 1000);
        });
}
存在问题

上述的一些方案能解决基本的需求,但还存在很多问题。比如官方网站对访问频率有很严格的限制,若在尝试登录时脸黑六次以上都没有识别出正确的验证码,那么极有可能ip会被屏蔽两小时。我还不太清楚nodejs的http客户端如何像C#的http client一样自由设置代理ip,因此目前暂时以手动切换ip来解决这个问题……如果以后有了新的思路再来解决这个问题好了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 验证码识别
  • 预约
  • 调用链
  • 存在问题
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档