Cainiao Wrap Express query interface call

Disclaimer: All the content in this article is for educational use only.

Cainiao Wrap is a product of Alibaba's Cainiao Network, which can be used to query Taobao's express tracking number. In addition to express delivery status and progress, it also includes Taobao's delivery process. Recently, due to a product demand, it is necessary to synchronize Taobao's logistics information, so I will study how to call the interface to automatically obtain it.

Research ideas

  1. Cainiao Wrapping official website, enter the courier number to query, example: 491772628733, open the browser debugging window before querying
  2. Observe the NetWork window, filter XHR , find that two requests are sent, observe the response, the first ret is: "FAIL_SYS_ILLEGAL_ACCESS::Illegal request" , the second call is successful
  3. Comparing the request parameters of the two requests, there is only one less c value. If you are careful, you will find that the returned result of the first response also contains the c value.
  4. The query interface contains 12 parameters. In order to call, you need to figure out the meaning of each parameter and how to generate it.

code follow up

dom elements and events

The button triggering of the web page means that the button click event is triggered, and the dom of the query button is

  <span id="J_SearchBtn" class="search-btn" data-spm-anchor-id="a2d5h.11386884.0.i1"></span>

The id is J_SearchBtn, look at the Event Listeners of the element, and see the registered source code location according to the click event.

$("#J_SearchBtn").on("click", function() {
    if (!$(".search-container").hasClass("loading")) {
        var o = $.trim($("#J_SearchInput").val());
        if ("" === o)
            return;
        e._handleSearch(o)
    }
})

call link

Continue to follow up the code call, you can find that the call link is

  1. _handleSearch
  2. _requestPackage
  3. lib.mtop.request
  4. n.prototype.request
  5. c.__processRequestMethod, c.__processToken, c.__processRequestUrl, c.__processRequest
  6. __getTokenFromCookie, __requestJSON

The code is mainly in the file https://g.alicdn.com/mtb/lib-mtop/2.4.2/mtop.js

// 函数_requestPackage,从这可以得到api、AntiCreep、v、data、timeout、dataType这6个参数
lib.mtop.request({
    api: i.queryLogisticPackageByMailNo,
    AntiCreep: !0,
    v: "1.0",
    data: {
      mailNo: e
    },
    timeout: 5e3,
    type: "GET",
    dataType: "json",
    isSec: 0,
    ecode: 0
}, o, r)

// 函数__processRequestUrl,从这可以得到jsv、appKey、t、sign这4个参数
var f = "//" + (d.prefix ? d.prefix + "." : "") + (d.subDomain ? d.subDomain + "." : "") + d.mainDomain + "/h5/" + c.api.toLowerCase() + "/" + c.v.toLowerCase() + "/"
  , g = c.appKey || ("waptest" === d.subDomain ? "4272" : "12574478")
  , i = (new Date).getTime()
  , j = h(d.token + "&" + i + "&" + g + "&" + c.data)
  , k = {
    jsv: x,
    appKey: g,
    t: i,
    sign: j
}

// 函数__processRequestMethod,从这可以得到参数type
var b = this.params
  , c = this.options;
"get" === b.type && "jsonp" === b.dataType ? c.getJSONP = !0 : "get" === b.type && "originaljsonp" === b.dataType ? c.getOriginalJSONP = !0 : "get" === b.type && "json" === b.dataType ? c.getJSON = !0 : "post" === b.type && (c.postJSON = !0),
a()

// 函数n.prototype.__requestJSON,可以得到参数c,实际是cookie中读取_m_h5_c值
h.CDR && j(y) && (h.querystring.c = decodeURIComponent(j(y))),
// 函数__getTokenFromCookie,其中y为_m_h5_c,逻辑就是从cookie中读取_m_h5_c值,字符串分别使用';'和'_'拆分两次,得到第一段
var a = this.options;
return a.CDR && j(y) ? a.token = j(y).split(";")[0] : a.token = a.token || j(z),
a.token && (a.token = a.token.split("_")[0]),
o.resolve()

How to set the value of _m_h5_c in the cookie? There are two ways to set the cookie: 1. The response of the http request is set by Set-Cookie; 2. It is set by the document.cookie of JS. By observing all http requests, there is no such thing as the The cookie value is set, so it can be determined that it is set through JS

key parameter

The sign value in the parameter is obtained through the encryption algorithm, because it has been compressed and it is difficult to see what algorithm it is. You can only guess and try, the length is 32 bits, and the md5 algorithm is guessed.

Analysis of encryption results of commonly used encryption algorithms:

|Algorithm|Encryption Result| | ------ | ------ | |MD5|32bit| |SHA1|40bit| |SHA224|56bit| |SHA256|64bit| |AES,DES| ends with =|

  // 加密前
  6e16c205e17e519707aae3e0938bde68&1534671229873&12574478&{"mailNo":"491772628733"}

  // 加密后
  7b3a2b38d314ae5d619ceaf0ab55eeab

browser emulation

At this point, the source and rules of the 12 parameter values ​​required for the interface call are known, and the interface call is spliced ​​according to the rules, but it is found that an error is still returned.

  [ 'FAIL_SYS_TOKEN_EMPTY::令牌为空' ]

Since it is a simulated browser behavior, the server side may check the Referer, Origin and other values, and check the browser's request, and it does include it, so modify the Header of the http request and set these two values. After verification, you only need to add Origin.

other conjectures

  1. The third segment in the cookie value is 2 hours and 30 minutes away from the current time, which should be the expiration time of the cookie on the server
  2. For the four parameters of cookie, sign, t, and appKey, the server will perform encryption verification again, and determine whether it has expired by time.

Complete demo code - nodejs version

const axios = require('axios')
const crypto = require('crypto')

function getSign(token, now, appKey, data) {
  const md5 = crypto.createHash('md5')
  const signStr = `${token}&${now}&${appKey}&${data}`
  md5.update(signStr)
  return md5.digest('hex')
}

// cookie默认值,随便填一个即可
async function queryLogistic(mailNo, cookie = '078c9a3abffad4fc5c0dba2509eb19a0_1534611715157;5ffe99dead01566b71a5107d653cfccb') {
  const now = Date.now()
  const appKey = '12574478'
  const mailData = JSON.stringify({ mailNo })
  const token = cookie.split('_')[0]
  const sign = getSign(token, now, appKey, mailData)

  const { data } = await axios({
    url: 'https://h5api.m.taobao.com/h5/mtop.cnwireless.cnlogisticdetailservice.wapquerylogisticpackagebymailno/1.0/',
    headers: {
      Origin: 'https://www.guoguo-app.com'
    },
    params: {
      jsv: '2.4.2',
      appKey,
      t: now,
      sign,
      api: 'mtop.cnwireless.CNLogisticDetailService.wapqueryLogisticPackageByMailNo',
      AntiCreep: true,
      v: 1.0,
      timeout: 5000,
      type: 'originaljson',
      dataType: 'json',
      c: cookie,
      data: mailData,
    }
  })
  return data
}

(async() => {
  const {c, data, ret} = await queryLogistic('491772628733')
  // 如果返回c值,则需要重新使用返回的cookie值发送请求
  if (c) {
    const resp = await queryLogistic('491772628733', c)
    console.info(resp)
  } else {
    console.info(data)
  }
})()

Reprinted in: https://my.oschina.net/yunuo/blog/1930356

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324112990&siteId=291194637