谷歌翻譯分析和實現(lua版)

2020-08-13 10:27:04

由於實際情況需要,最近需要用到谷歌翻譯部分,直接使用網頁web的url請求就是最好的選擇,下來先做原理的分析。

一、原理分析

1、選擇任意可以F12開啓開發者模式的瀏覽器;

2、找到谷歌翻譯URL的地址:https://translate.google.cn

 

進入谷歌翻譯的主頁,然後去嘗試翻譯一箇中文

 

進入瀏覽器的F12,分析瀏覽器發生哪些url的請求,我們分析到有不少的url請求,我們去尋找有返回翻譯結果的URL請求

 

那麼這個url就是有效的,有返回原文和譯文的結果,然後檢視下直接的url是怎麼樣的

 

這裏可以的到發送的Url。

Request URL:

https://translate.google.cn/translate_a/single?client=webapp&sl=zh-CN&tl=en&hl=en&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=sos&dt=ss&dt=t&otf=1&ssel=0&tsel=0&xid=45662847&kc=6&tk=428760.20440&q=%E4%BD%A0%E5%A5%BD

分析上面的url,可以得到參數不少

 

除了上面指出來的,其他的參數,暫時對我們用處不大,可以參考如下:

  1. sl=en& //source:源語言
  2. tl=zh-CN& //target:目標語言
  3. hl=zh-CN&
  4. dt=at& //備用翻譯
  5. dt=bd& //字典
  6. dt=ex& //例子
  7. dt=ld&
  8. dt=md& //源文字的定義
  9. dt=qca&
  10. dt=rw& //清單
  11. dt=rm& //音譯
  12. dt=ss& //源文字同義詞
  13. dt=t& //僅回覆 回復翻譯結果
  14. ie=UTF-8&
  15. oe=UTF-8&
  16. source=btn&
  17. ssel=0&
  18. tsel=0&
  19. kc=0&
  20. tk=16915.405690&

獲取上面的URL我們可以直接在瀏覽器存取到json的結果的,那麼結果證明,我們只需要上面URL就可以實現文字的存取就好了。

 

二、實現分析

綜合網上的資料顯示,實現谷歌翻譯有兩種方法,歸納起來就是帶不帶token驗證。

1、一種就是不帶token驗證的就是隻使用下面 下麪的URL

https://translate.google.cn/translate_a/single?client=gtx&sl=zh-CN&tl=en&dt=t&q=%E4%BD%A0%E5%A5%BD

https://translate.google.cn/translate_a/single?client=at&sl=zh-CN&tl=en&dt=t&q=%E4%BD%A0%E5%A5%BD

上面兩個URL都可以存取到翻譯結果,對比之前的可以發現少了很多參數,最大的區別就是client的不同,使用at和gtx都可以不需要token驗證,dt=t表示只要翻譯結果,q是翻譯的文字,被urlencode了,但是使用的時候發現,這種方式只要次數一多,就會被遮蔽掉,返回error的結果,那麼這樣的話不能用。

 

2、另一種就是帶上token的驗證Url結果,就是client=webapp,在加上tk,這個tk是動態計算出來的。

大致的原理就是,在我們開啓谷歌翻譯首頁的時候,會伺服器動態下發tkk,我們姑且稱爲token key吧。經過試驗發現這個tkk不是實時變化的,一段時間內都是有效,但是有效時間多久沒有有效的驗證過(至少我們測試階段是可以寫死一個tkk使用的)。右鍵可以開啓原始碼,搜尋tkk可以找到(也有網友找到了計算的例子想了想,這種和直接下發沒差。

 

 

然後很多熱心網友發計算tk的演算法出來,不過都是JS版本的(我們Lua專案移植起來很難啊),下面 下麪的演算法經過傳入瀏覽器裏面獲取的ttk和翻譯的文字計算出來的tk,和瀏覽器裏面獲取的tk是一致的,說明演算法是Ok的,驗證了演算法可用性(菜鳥教學有線上JS編輯器,就省去了弄JS環境驗證演算法了)

var b = function (a, b) {
    var tmp_c, tmp_c2;
    for (var d = 0; d < b.length - 2; d += 3) {
        var c = b.charAt(d + 2),
            tmp_c2 = c,
            c = "a" <= c ? c.charCodeAt(0) - 87 : Number(c),
            tmp_c = c,
            c = "+" == b.charAt(d + 1) ? a >>> c : a << c;
        a = "+" == b.charAt(d) ? a + c & 4294967295 : a ^ c;
    }
    return a
}
var tk =  function (a,TKK) {
    for (var e = TKK.split("."), h = Number(e[0]) || 0, g = [], d = 0, f = 0; f < a.length; f++) {
        var c = a.charCodeAt(f);
        128 > c ? g[d++] = c : (2048 > c ? g[d++] = c >> 6 | 192 : (55296 == (c & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ? (c = 65536 + ((c & 1023) << 10) + (a.charCodeAt(++f) & 1023), g[d++] = c >> 18 | 240, g[d++] = c >> 12 & 63 | 128) : g[d++] = c >> 12 | 224, g[d++] = c >> 6 & 63 | 128), g[d++] = c & 63 | 128)
    }
    a = h;
    for (d = 0; d < g.length; d++) a += g[d], a = b(a, "+-a^+6");
    a = b(a, "+-3^+b+-f");
    a ^= Number(e[1]) || 0;
    0 > a && (a = (a & 2147483647) + 2147483648);
    a %= 1E6;
    return a.toString() + "." + (a ^ h)
}

三、現狀分析

要移植到lua的話,有幾個問題:

1、lua沒有位運算;

2、lua沒有JS一樣的length,charCodeAt,charAt方法,Lua中文佔了三個位元組不能直接用string.byte,lua不能直接獲取unicode編碼

那麼,現在就要實現lua的位運算,以及計算其他不支援的效果

這個網址介紹了Lua的位運算相關操作https://www.jianshu.com/p/115bc9e5b6a6

位運算的例子在這裏可以有

https://github.com/lilien1010/lua-bit/blob/master/bit.lua

那麼現在就可以翻譯到Lua程式碼了

 

四、lua中的實現

Js的b方法翻譯到下面 下麪lua

function rl(a, b)
    local blen = string.len(b)
    local byte_a = string.byte("a", 1)
    local byte_yb = string.byte("+", 1)
    for i = 1, blen - 2, 3 do
        local d = i
        local c = string.byte(b, d + 2)
        if byte_a <= c then 
            c = c - 87
        else
            c = tonumber(string.sub(b, d + 2, d + 2))
        end
        if byte_yb == string.byte(b, d + 1) then 
            c = bit.rshift(a, c)
        else
            c = bit.lshift(a, c)
        end     
        if byte_yb == string.byte(b, d) then 
            a = bit.band(a + c, 4294967295)
        else
            a = bit._xor(a, c) 
        end
    end
    return a
end

Js的tk方法翻譯到Lua爲下面 下麪的方法

--獲取編碼
function getCharCodeAt( chars )
    -- body
    local m = 0
    if chars then 
        local char = chars.char
        local allByte = bit.charCodeAt(char)
        m = allByte[1]
    end
    return m
end
function TranslateGoogle.token(a, tkk)
    local tkkSplit = splitString(tkk, "%.")
    local k = "" 
    local b = tonumber(tkkSplit[1])
    local b1 = tonumber(tkkSplit[2])
    local jd = "." 
    local sb = "+-a^+6"
    local zb = "+-3^+b+-f" 
    local e = {}
    local f = 1
    local splitChars = splitToTable(a)
    local alen = table.getn(splitChars)
    for i = 1, alen do
        local g = i
        local chars = splitChars[g]
        local m = getCharCodeAt( chars )
        if 128 > m then 
            e[f] = m
            f = f + 1
        else
            if 2048 > m then 
                e[f] = bit._or(bit.rshift(m, 6), 192)
                f = f + 1
            else
                local next_chars = splitChars[g + 1]
                local next_m = getCharCodeAt( next_chars )
                if 55296 == bit.band(m, 64512) and g + 1 < alen and 56320 == (bit.band(next_m, 64512)) then 
                    m = 65536 + (bit.lshift(bit.band(m, 1023), 10)) 
                    i = i + 1
                    m = m + (bit.band(next_m, 1023)) 
                    e[f] = bit._or(bit.rshift(m, 18), 240)
                    f = f + 1
                    e[f] = bit._or(bit.band(bit.rshift(m, 12), 63), 128)
                    f = f + 1
                else
                    e[f] = bit._or(bit.rshift(m , 12), 224)
                    f = f + 1
                    e[f] = bit._or(bit.band(bit.rshift(m, 6), 63), 128)
                    f = f + 1
                end
            end
            e[f] = bit._or(bit.band(m, 63), 128)
            f = f + 1
        end
    end
    a = b 
    for f = 1, #e do
        a = a + e[f]
        a = rl(a, sb)
    end
    a = rl(a, zb)
    a = bit._xor(a, b1 or 0)
    if 0 > a then 
        a = bit.band(a, 2147483647) + 2147483648
    end
    a = a % 1E6
    return string.format("%s%s%s", tostring(a), jd, bit._xor(a, b) )
end

 

這裏方法splitString是裁切ttk的點號前後的數位,和splitToTable是裁切字串字元的,就是替換Js方法的chatAt,爲了讓中文也是正常的被裁切出來,這裏就不一一列舉出來具體的實現方法了。

 

最後提下ttk的獲取方法,可以通過存取https://translate.google.cn的返回程式碼,然後通過匹配出tkk("tkk:\'%d+.%d+\'")這個正則,然後就可以獲取動態的tkk了,這樣是爲了防止tkk失效的情況,至少每次都可以獲取最新的tkk使用,通過實驗證明,一天之內tkk應該是有效的

 

總結:整個過程中lua實現tk演算法是比較麻煩的,Js比較容易實現的操作,在lua裏面沒有現成的介面,上面的演算法經過測試可以投產使用了