作为其Google Cloud的一部分,Google为Google Translation API提供了基于使用情况的费用结构。 还有一个未记录的API ,可以不使用key来使用,但仅在几次请求后就拒绝工作。 使用Google Chrome浏览器的网站翻译功能时,值得注意的是,页面的翻译质量非常好,没有任何明显的限制。
显然,这里已经使用了高级nmt模型。 但是,谷歌浏览器内部使用哪种API来翻译内容,并且即使在服务器端,也可以直接使用该API吗? 要分析网络流量,建议使用Wireshark或Telerik Fiddler之类的工具,它们也可以分析加密的流量。 但是Chrome甚至可以免费发送它发送的页面翻译请求:可以使用Chrome DevTools轻松查看它们:

如果进行翻译,则通过“复制>复制为cURL(bash)”将关键的POST请求捕获到https://translate.googleapis.com并在Postman之类的工具中执行它,例如,您可以再次发送请求而不会出现任何问题:

URL参数的含义也很明显:
键 | 示例值 | 含义 |
安诺 | 3 | 注释模式(影响返回格式) |
客户 | te_lib | 客户信息(可变,值通过Google Translate网络界面为“ webapp”;会影响返回格式和速率限制) |
格式 | html | 字符串格式(对于翻译HTML标签很重要) |
v | 1.0 | Google翻译版本号 |
键 | AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw | API密钥(请参见下文) |
对数 | vTE_20200210_00 | 协议版本 |
sl | 德 | 源语言 |
tl | 恩 | 目标语言 |
SP | 纳米 | ML模型 |
tc | 1 | 未知 |
sr | 1 | 未知 |
k | 709408.812158 | 令牌(见下文) |
时尚 | 1 | 未知 |
还设置了一些请求标头-但这些标头通常可以忽略。 手动取消选择所有标头(包括来自用户代理的标头)后,在输入特殊字符时(此处是翻译“ Hello World ”时),发现编码问题:

如果您重新激活用户代理(通常不会造成任何伤害),则该API会传递UTF-8编码的字符:

我们已经在这里了吗,我们是否拥有在Chrome浏览器之外使用此API的所有信息? 如果将要转换的字符串(POST请求的数据字段q )从“ Hello world”更改为“ Hello world ! “,我们收到一条错误消息:

现在,我们使用网站翻译功能在Google Chrome浏览器中再次翻译了修改后的内容,发现除了参数q之外,参数tk也已更改(所有其他参数均保持不变):

显然,这是一个依赖于字符串的令牌,其结构不容易看到。 当您开始网站翻译时,将加载以下文件:
- 1个CSS文件: translateelement.css
- 4个图形: translate_24dp.png (2x), gen204 (2x)
- 2个JS文件: main_de.js , element_main.js
这两个JavaScript文件被混淆并缩小。 JS Nice和de4js之类的工具现在正在帮助我们使这些文件更具可读性。 为了实时调试它们,我们建议使用Chrome扩展程序,它可以动态地在本地传输远程文件:

现在我们可以调试代码了(必须先在本地服务器上激活CORS )。 生成令牌的相关代码部分似乎已隐藏在本部分的element_main.js文件中:
b7739bf50b2edcf636c43a8f8910def9
在这里,通过一些移位将文本散列。 但不幸的是,我们仍然缺少一个难题:除了参数a (即要翻译的文本)之外,另一个参数b传递给函数Bp() -这种种子似乎不时发生变化,并且还包括流入哈希。 但是他来自哪里? 如果跳转到Bp()的函数调用,则会发现以下代码段:
b7739bf50b2edcf636c43a8f8910def9
函数Hq预先声明如下:
b7739bf50b2edcf636c43a8f8910def9
Deobfuscater在这里留下了一些垃圾。 在用相应的字符串替换String.fromCharCode('...')之后,删除过时的a()并将函数调用[c(),c()]拼凑在一起,结果是:
b7739bf50b2edcf636c43a8f8910def9
甚至更容易:
b7739bf50b2edcf636c43a8f8910def9
函数yq先前定义为:
b7739bf50b2edcf636c43a8f8910def9
种子似乎在全局对象google.translate._const._ctkk中,该对象在运行时可用。 但是它在哪里设置? 在另一个先前加载的JS文件main_de.js中,至少在开始时也可用。 我们在开始时添加以下内容:
b7739bf50b2edcf636c43a8f8910def9
在控制台中,我们实际上获得了当前种子:

剩下的就是谷歌浏览器本身了,后者显然提供了种子。 幸运的是,其源代码(Chromium,包括Translate组件)是开源的,因此可以公开获得。 我们将存储库拉到本地,然后在components / translate / core / browser文件夹中的translate_script.cc文件中找到对TranslateScript :: GetTranslateScriptURL函数的调用:
b7739bf50b2edcf636c43a8f8910def9
带有URL的变量在同一文件中硬定义:
b7739bf50b2edcf636c43a8f8910def9
如果现在我们更仔细地检查一下element.js文件(再次对其进行模糊处理之后),我们会发现硬设置条目c._ctkk - google.translate对象也进行了相应设置,并且触发了所有相关资产(我们之前已经发现过)的加载:
b7739bf50b2edcf636c43a8f8910def9
现在,参数键仍在考虑中(值为AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw)。 这似乎是通用的浏览器API密钥(也可以在某些Google结果中找到)。 它在Chromium中的components / translate / core / rows文件夹中的文件translate_url_util.cc中设置。:
b7739bf50b2edcf636c43a8f8910def9
该密钥是从哑数值google_apis / google_api_keys.cc中生成的:
b7739bf50b2edcf636c43a8f8910def9
但是,测试表明,没有此关键参数,API调用的工作原理相同。 如果您尝试使用该API,则如果成功,您将获得状态码200 。 如果随后遇到限制,则会返回状态代码411 ,并显示消息“ POST请求需要内容长度的标头”。 因此,建议包括此标头(在Postman中自动设置为临时标头)。
当一个请求中包含多个句子时,翻译后的字符串的返回格式不常见。 各个句子用i- / b-HTML标记括起来:

另外,谷歌浏览器不会将完整的HTML发送给API,而是在请求中保存href等属性值(并设置索引,以便以后可以在客户端分配标签):

如果您从te_lib (Google Chrome)更改POST密钥客户端的值 在webapp ( Google翻译网站)上,您将获得最终的翻译字符串:

问题是,与通过te_lib相比,您更有可能遇到速率限制(为进行比较:对于webapp,这是在40,000个字符之后达到的,而te_lib没有速率限制)。 因此,我们需要仔细研究Chrome如何解析结果。 我们将在element_main.js中找到它:
b7739bf50b2edcf636c43a8f8910def9
如果您将整个 HTML 代码发送到 API,它会在翻译后的响应中保留属性。 因此,我们不必模仿整个解析行为,而只需从响应中提取最终的翻译字符串。 为此,我们构建了一个小的 HTML 标签解析器,它丢弃最外面的 <i> 标签,包括其内容,并删除最外面的 <b> 标签。 有了这些知识,我们现在可以(在使用composer require fzaninotto / faker vielhuber / stringhelper安装依赖项之后)构建翻译 API 的服务器端版本:
b7739bf50b2edcf636c43a8f8910def9
以下是在具有不同带宽和IP地址的五个不同系统上进行的初始测试的结果:
字符 | 每个请求的字符 | 持续时间 | 错误率 | 通过官方API的费用 |
13.064.662 | ~250 | 03:36:17小时 | 0% | 237,78€ |
24.530.510 | ~250 | 11:09:13小时 | 0% | 446,46€ |
49.060.211 | ~250 | 20:39:10小时 | 0% | 892,90€ |
99.074.487 | ~1000 | 61:24:37小时 | 0% | 1803,16€ |
99.072.896 | ~1000 | 62:22:20小时 | 0% | 1803,13€ |
Σ284.802.766 | 〜Ø550 | Σ159:11:37小时 | 0% | Σ€5183.41 |
注意:此博客文章(包括所有脚本)仅用于测试目的。 不要使用用于生产的脚本,而不是与官方合作谷歌翻译API 。