node构建自己的API服务器系列四:快递单识别服务

原创 Dean 程序相关,系列文章 二维码阅读
2018-10-08 22:17

在node构建自己的API服务器系列的前三节,我们搭建了一个简单的框架,并在此基础上实现了三个比较简单的服务(日志服务、IP解析服务,代理服务),本节我们将实现一个稍微复杂一点的服务,快递单识别服务。

在日常生活中,我们会有各种各样的快递包裹,我们经常需要去查询包裹的状态,每次都手输快递单号是很烦的,作为程序猿的我们就会想能不能做一个快递单识别呢?拍张快递单的照片上传一下就自动帮我们识别出快递单号并查询包裹的物流状态,关键是怎么识别呢?

一般这种比较复杂的图片识别基本上都是基于机器学习并通过大量的数据训练出来的,但是对于个人来说要构建一个机器学习模型并训练好复杂度可想而知,我们肯定是想用最简单的方式去实现,换个思路想,快递单上面都有二维码或者条形码,这个二维码或者条形码肯定是和单号有关联的,那我们识别出这个二维码或者条形码是不是就识别出单号了呢?答案是肯定的,而识别二维码或者条形码这种类似的服务网上已经有了,接着我找到了草料二维码的二维码识别服务。

在写代码之前我们需要先分析一下草料二维码识别过程中发出的请求,以便于我们后面写代码模拟。如果对于请求分析还不熟悉的可以先看看我之前两篇关于模拟登录中关于请求分析的文章,后面我也准备写一系列关于分析请求写爬虫的文章。

1、SmartQQ最新登录协议分析

2、如何使用node模拟登陆Tapd(腾讯敏捷产品研发平台)

首先我们先访问草料二维码网站https://cli.im/tools找到有二维码识别功能的地方

打开开发者工具,点击上传二维码,会弹出图片选择框,我们选择一张快递单图片(没有的网上自行下载),可以发现发出了三个异步请求

先看第一个请求,是一个Options请求,是和服务端确认是否支持请求方法的。

再看第二个请求,上传了我们选择的图片,并返回了一个图片地址

第三个请求,将第二个请求得到的图片地址传了过去,并返回了识别的结果。

其实整个过程就做了两件事情,先上传图片然后识别图片返回结果,清楚了这个过程,写代码就很简单了,但是有一点需要注意的是这两个请求的response中都有setCookie的操作,一般这种都是有连续性的,所以我们需要维护cookie的状态。

接下来我们就可以开始写代码了,首先实现用于获取识别结果的util方法,代码:

const fs = require('fs');
const path = require('path');
const request = require('request');

// 维持cookie状态的
const r = request.defaults({ jar: true });

// API
const UPLOAD_API = 'https://upload.api.cli.im/upload.php?kid=cliim';
const REC_API = 'https://cli.im/apis/up/deqrimg';
const REC_PAGE = 'https://cli.im/deqr/';

// 上传图片
function uploadQR(imgPath) {
  return new Promise((resolve, reject) => {
    const formData = {
      Filedata: fs.createReadStream(imgPath)
    };
    r({
      url: UPLOAD_API,
      method: 'POST',
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Referer': 'https://cli.im/deqr/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
      },
      formData
    }, (err, response, body) => {
      if (err) {
        reject()
      } else {
        const matches = decodeURIComponent(body).match(/#([^>]*)">/);
        if (matches && matches.length) {
          try {
            const ret = JSON.parse(decodeURIComponent(matches[1]));
            if (ret && ret.data && ret.data.path) {
              resolve(ret.data.path);
            } else {
              reject();
            }
          } catch (err) {
            reject(err);
          }
        } else {
          reject();
        }
      }
    });
  });
}

// 识别图片
function recQR(imgUrl) {
  return new Promise((resolve, reject) => {
    r({
      url: REC_API,
      method: 'POST',
      headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'Referer': 'https://cli.im/deqr/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
      },
      formData: {
        img: imgUrl
      }
    }, function(err, response, body) {
      if (err) {
        reject();
      } else {
        try {
          const ret = JSON.parse(body);
          if (ret && ret.info && ret.info.data) {
            resolve(ret.info.data);
          } else {
            reject();
          }
        } catch (err) {
          reject(err);
        }
      }
    });
  });
}

module.exports = (imgPath) => {
  return uploadQR(imgPath)
      .then(recQR);
};

请自行将其添加到对应位置。

接下来,我们规划一下路由,这个路由比较简单:

/qrRec 接受上传的文件并返回识别结果

然后还是老规矩在routes目录下新建路由处理文件qrRec.js, 编写代码:

const express = require('express');
const recQR = require('../utils/qrRec');
const { setHeader, setJSONResponse } = require('../utils/res');
const router = express.Router();

router.post('/', function(req, res, next) {
  const { file } = req.files;
  const tmp_path = file.path;
  setHeader(res, 'content-type', 'application/json;charset=utf-8');
  setHeader(res, 'Access-Control-Allow-Origin', '*');
  recQR(tmp_path)
    .then(data => {
      setJSONResponse(res, {
        ret: 0,
        data: data
      });
    })
    .catch(err => {
      setJSONResponse(res, {
        ret: -1
      });
    });
});

module.exports = router;

由于要上传图片,所以我们需要post请求方式,而在express中我们要处理post文件上传,需要导入一个中间件connect-multiparty,可以在命令行通过下面的命令安装:

npm i connect-multiparty --save-dev

然后需要在入口文件中使用这个中间件:

...
const multipart = require('connect-multiparty');
...
app.use(multipart());
...

最后在入口文件中建立路由映射关系:

app.use('/qr-rec', qrRecHandler);

在命令行运行命令

node app.js

通过向http://127.0.0.1:1314/qr-rec上传快递单就可以识别单号了,当然这个也是可以识别二维码的。

文章的最后,附上源码,素材图和一个以前写的识别案例,你可以猛戳这里查看案例。

识别素材图:

源码下载请点击这里

系列文章导航:

第一节:node构建自己的API服务器系列一:日志服务

第二节:node构建自己的API服务器系列二:IP解析服务

第三节:node构建自己的API服务器系列三:代理服务

第五节:node构建自己的API服务器系列五:邮件发送服务

本文地址:https://www.deanhan.cn/node-api-server-series-4.html
版权声明:本文为原创文章,版权归 Dean 所有,欢迎分享本文,转载请保留出处!
  • 支付宝二维码 支付宝
  • 微信二维码 微信