网页翻译聚合工具开发记录

May 6,2018   16304 words   59 min

Tags: Web

之前利用JS和Cookie开发了搜索聚合的网页,见这篇博客。 受它的启发,又想着可以考虑开发一个“翻译聚合”工具,即实现“一次输入、多个翻译”的结果。 在多个结果中可以进行对比筛选,选择一个最好的翻译结果。

1.思路与框架

总体思路与框架如下图所示。 对于每一个翻译都定义出对应的翻译函数用于调用API接口,并且编写一个总的翻译接口用于统一处理输入数据。 因为API的不同,百度、有道采用直接拼接URL,利用JS发送GET请求的方式获得结果。 而谷歌、必应因为安全认证方面更加严格,不允许直接通过网页中的JS发起GET请求来获取数据。 因此采用WebSocket作为媒介,网页先发送数据到服务器,服务器收到数据以后,在服务器上对数据进行处理,调用翻译API,完成后再返回给网页端。

2.主要技术

主要技术分为两方面,一是客户端,一是服务器端。用到了Python、HTML、Javascript语言。

(1)客户端

对于客户端而言,主要涉及到利用JS进行WebSocket的数据收发、JS发送GET请求以及对数据的解析等。 除此之外还有一些界面布局的内容,如HTML表格的使用等。 JS的WebSocket使用前面博客已经说过了。这里简单介绍一下利用JS(JQuery)发送GET请求。

a.JQuery不带参GET请求

首先介绍最简单不带参数的GET请求。

<!doctype html>
<head>
    <meta charset="utf-8"/>
</head>
<body>
<div>可打开浏览器控制台查看结果</div>
<script src="https://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
$.ajax({
    url: 'https://zhaoxuhui.top',
    success: function (data) {
        console.log(data);
    } 
});
</script>
</body>

实现效果如下。获取到了对应网页的源代码并输出在了控制台中。

b.JQuery带参GET请求

请求参数列表见这篇教程,里面写的很详细。这里直接演示代码。 这里的代码是百度翻译API的示例。点击这里下载。

<!doctype html>
<head>
    <meta charset="utf-8"/>
</head>
<body>
<div>可打开浏览器控制台查看结果</div>
<script src="https://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="./md5.js"></script>
<script type="text/javascript">
var appid = '00000000000000000000';
var key = 'xxxxxxxxxxxxxxxxxx';
var salt = (new Date).getTime();
var query = 'apple';
var from = 'en';
var to = 'zh';
var str1 = appid + query + salt +key;
var sign = MD5(str1);
$.ajax({
    //必选,GET请求的基本地址
	url: 'https://api.fanyi.baidu.com/api/trans/vip/translate',
    //可选,表示请求类型,默认为GET
	type: 'get',
    //可选,GET返回的data数据类型,不写的话JQuery会自动判断
	dataType: 'jsonp',
    //可选,发送给服务器的数据内容,格式为“键值对”
	data: {
        q: query,
        appid: appid,
        salt: salt,
        from: from,
        to: to,
        sign: sign
    },
	//可选,GET成功后的动作,可以扫描都不做,也可以传入函数接收data
    success: function (data) {
        console.log(data);
    } 
});
</script>
</body>

这样get生成的部分请求URL截图如下。

(2)服务器端

服务器端主要利用Python实现,一些基本的字符串操作(split、substring等)。 关于如何使用WebSocket在网页和服务器间进行数据交互,在这篇博客中已经介绍了。

3.百度翻译

(1)账户注册与服务开通

百度翻译开放平台网址是这个:https://api.fanyi.baidu.com/api/trans/product/index

要使用百度翻译API首先得注册一个百度开发者账户,我在之前用百度地图SDK时就注册过了,这里就不用再注册了。 注册的话选择“个人开发者”,并且要进行实名认证。注册完成后就可以使用了。下面是我的控制台面板。

在产品与服务里找到通用翻译API,点击“立即使用”即可开通服务。 百度翻译的收费规则是每月200万字免费,多于两百万字每100万49元。 因为我们是个人开发者,使用量也不大,所以基本也就等于免费使用了。

(2)官方API文档

百度翻译API通过HTTP接口调用,所以并不限制语言,C、Python、JS、Java都可以。 关于具体需要哪些请求参数见说明文档。 它的返回值是一个固定格式的JSON字符串,利用原生JS解析即可。

4.有道翻译

(1)账户注册

官网地址是这里:https://ai.youdao.com/

总体而言算是比较简单的,按照提示一步步操作就可以了。 有道的收费规则是每100万字符48元,和百度相同。 他没有每个月的免费200万字符,但是在注册成功后会赠送100元优惠券到账户。所以也算是免费了。

(2)翻译API文档

仔细研究他的文档会发现,他的调用方式和百度翻译的调用方式可以说是完全一样的。除了返回的JSON结果格式有点区别,其它几乎没有区别。 官方文档在这里

5.谷歌翻译

谷歌翻译API算是这四个里面最难搞定的一个了,但配置好之后使用倒也很简单,但配置过程挺痛苦的。 官方网址是这个:https://cloud.google.com/translate/?hl=zh-cn(可能需要科学上网)

(1)注册账户

首先需要注册Google Cloud Platform的账户,用自己已有的Google账户授权一下就可以了。 然后关键是需要提交支付信息,需要一张可以正常国际信用卡,否则账户无法激活。 不知道为什么我交行的VISA卡试了几次都不能用,工行的MasterCard一下子就验证成功了。 添加付款方式后,首选会从你的卡里扣去10元钱,然后过一会又会重新退给你。

注册成功后,会送你300美元的体验券,可以让你体验Google Cloud Platform上的各种服务,有效期是1年。

同样谷歌翻译的API也不是免费的,收费规则是每百万字符20美元。这就有点小贵了,换算成人民币将近150。 虽然有点小贵,但有时不得不承认,谷歌翻译的质量还是蛮高的。 还好在这一年的时间里可以用300美元的奖金,也算相当于免费了。

(2)官方文档与配置

官方文档点击查看,全英文的。 不过还好,基本都还能看懂,而且有时候觉得看英文的instruction比看国内很多质量不高的博客效率高得多。 尤其是这种官方文档,基本按着步骤来不会出什么问题。

a.获取密钥

要想使用谷歌翻译API,根据官方文档首先是创建一个GCP项目,然后在项目中添加翻译的API、添加付款方式等等。这些总体还算比较好操作。 然后在项目界面中,找到“启用API并获取密钥等凭据”,如下图。 完成可以下载一个JSON格式的密钥,这个文件很重要,后续认证都是通过读取它实现的,请妥善保管。下面是我建好的项目。

b.安装Google Cloud SDK

然后还需要在机器上安装Google Cloud SDK,在这个网页上找到你对应的系统,按照提示安装即可。 然后在控制台中运行如下命令

gcloud auth activate-service-account --key-file=[PATH]

其中[PATH]就是刚刚下载的密钥的路径。到这一步环境就配置好了。 在系统中就可以直接使用curl命令来使用API了,使用方法参见这个网页。 例如如下命令

curl -s -X POST -H "Content-Type: application/json" \
    -H "Authorization: Bearer "$(gcloud auth print-access-token) \
    --data "{
  'q': 'The Great Pyramid of Giza (also known as the Pyramid of Khufu or the
        Pyramid of Cheops) is the oldest and largest of the three pyramids in
        the Giza pyramid complex.',
  'source': 'en',
  'target': 'es',
  'format': 'text'
}" "https://translation.googleapis.com/language/translate/v2"

这里也看到了,在curl命令里也用到了gcloud命令来进行授权检测等操作,所以不要想着使用的翻译API与GCloud SDK没有关系就偷懒不安装SDK了。 如果一切顺利的话,会得到如下返回值。

{
  "data": {
    "translations": [
      {
        "translatedText": "La Gran Pirámide de Giza (también conocida como la
         Pirámide de Khufu o la Pirámide de Keops) es la más antigua y más
         grande de las tres pirámides en el complejo de la pirámide de Giza."
      }
    ]
  }
}

这就说明你系统使用翻译API的必要环境就配好了。

c.安装Cloud Translation API Client Libraries

不要忘了我们一开始的目的是什么了,是使用翻译API。而且我们是在服务器端利用Python调用API。 因此可以安装谷歌翻译的Python包,方便调用。如何安装和使用库在这个网页中有详细说明。 利用PIP安装的命令是

pip install --upgrade google-cloud-translate
d.创建GOOGLE_APPLICATION_CREDENTIALS环境变量

在刚刚那个网页中,也详细说了如何在不同系统中创建这个变量。这里就以我的服务器CentOS为例,创建命令如下。

export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

其中[PATH]依旧是密钥文件的路径。

(3)翻译API的使用

上面配置、安装了这么久就是为了可以愉快地使用下面这几行代码,这里以我使用的Python为例。

# Imports the Google Cloud client library
from google.cloud import translate

# Instantiates a client
translate_client = translate.Client()

# The text to translate
text = u'Hello, world!'
# The target language
target = 'zh'

# Translates some text into Russian
translation = translate_client.translate(
    text,
    target_language=target)

print(u'Text: {}'.format(text))
print(u'Translation: {}'.format(translation['translatedText']))

至此Google翻译API的配置、安装与使用就介绍完了。

(4)配置中遇到的问题
a.Python版本问题

由于CentOS系统自带的Python版本不符合Google的要求,因此必须要升级系统自带的Python。 这又是一个挺费时的任务了,中途也是遇到各种错误。可以参考Google的Python环境配置指导。 这里简单记录一下一种新的安装PIP的方法。之前在这篇博客中说的使用yum命令进行安装。 但也可以输入如下命令安装:

wget https://bootstrap.pypa.io/get-pip.py
python get-pip.py

这样便安装完成了。这样做的好处是可以指定给哪个python安装pip。 按照yum的方式安装,是把pip装到了系统自带的python中,但如果想装到另一个版本的python,这个命令就不起作用了。 但是利用get-pip.py就可以实现,可参考这个网页。只需要写出python的全路径就可以了,如下。

wget https://bootstrap.pypa.io/get-pip.py
/usr/local/bin/python get-pip.py

这就把pip安装到了/usr/local/bin/python这个python中。

b.环境变量重启后消失

由于之前在创建环境变量的时候直接在控制台输入的命令,这种变量生命是非常短的,因此需要创建永久的环境变量。 具体做法也很简单,修改/etc/profile,在文件末尾加上如下代码:

export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"

然后保存文件,并执行source /etc/profile命令使其生效。 更多有关创建环境变量的内容可以参考这篇博客

6.必应翻译

官方网址是这里:https://azure.microsoft.com/zh-cn/services/cognitive-services/translator-text-api/

它是作为Azure的一部分来提供,这点和Google很像。可能是因为公司大了以后东西太多,都喜欢整合到一个平台上。

使用它之前需要先关联你的Microsoft账户,成功以后也会赠送200美元的体验金用于购买服务等。不过好像只有20天的期限,过期作废。 注册过程也需要验证支付方式,绑定国际信用卡,并且会扣除1美元。 至于他的收费标准,和百度翻译一样,每月免费200万字节。

至于必应翻译的使用方法,可以参考最新的API文档。 使用方法比谷歌翻译要简单,因为不需要配置一大堆东西。 使用curl方法即可调用。例如下面是一个简单调用示例。

curl -X POST "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from=en&to=zh-Hans" -H "Ocp-Apim-Subscription-Key: <client-secret>" -H "Content-Type: application/json" -d "[{'Text':'Hello, what is your name?'}]"

返回值为

[
    {
        "translations":[
            {"text":"你好, 你叫什么名字?","to":"zh-Hans"}
        ]
    }
]

由于参数比较简单,所以其实也可以用GET进行一些包装修饰,实现调用。 在这里我直接利用Python进行字符串拼接,拼接出一个curl命令,并利用Python执行这个命令并接收返回值,实现翻译。

3.代码实现

(1)网页客户端

网页客户端代码如下。

<head>
    <title>一劳永"译" - Secret Land(秘境)</title>
    <meta charset="utf-8"/>
    <meta http-equiv="content-language" content="zh-cn">
    <meta name="description" content=\"一劳永\"译-Secret Land(秘境)/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <link rel="stylesheet" href="assets/css/main.css"/>
    <link rel="icon" type="image/x-icon" href="assets/images/icon.ico"/>
    <noscript>
        <link rel="stylesheet" href="assets/css/noscript.css"/>
    </noscript>
    <meta name="theme-color" content="#324573">
    <script src="https://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js"></script>
    <script src="assets/js/md5.js"></script>

    <script type="text/javascript">
        function trans_google(flag) {
            if (flag == "jp") {
                flag = "ja";
            } else if (flag == "th") {
                flag = "tha";
            } else if (flag == "spa") {
                flag = "es";
            }

            var search = document.getElementById("input_content");
            var out_content_google = document.getElementById("out_content_google")
            search_content = search.value;
            var type = "**Google**";
            var to = flag;
            var str = to + "zxh" + type + search_content;
            var ws = new WebSocket('ws://206.189.92.110:4200/');
            ws.onopen = function () {
                ws.send(str);
            };

            ws.onclose = function (evt) {
            };

            ws.onmessage = function (event) {
                out_content_google.value = event.data;
            };
        }

        function trans_bing(flag) {
            if (flag == "jp") {
                flag = "ja";
            } else if (flag == "spa") {
                flag = "es";
            }

            var search_content = document.getElementById("input_content");
            var output_content_bing = document.getElementById("out_content_bing")
            search_content = search_content.value;
            var type = "**Bing**";
            var to = flag;
            var str = to + "zxh" + type + search_content;
            var ws = new WebSocket('ws://206.189.92.110:4200/');
            ws.onopen = function () {
                ws.send(str);
            };

            ws.onclose = function (evt) {
            };

            ws.onmessage = function (event) {
                output_content_bing.value = event.data;
            };
        }

        function trans_youdao(flag) {
            if (flag == "zh") {
                flag = "zh-CHS";
            } else if (flag == "en") {
                flag = "EN";
            } else if (flag == "jp") {
                flag = "ja";
            } else if (flag == "spa") {
                flag = "es";
            } else if (flag == 'th') {
                out_content_youdao.value = '不支持该语言翻译';
                return;
            }

            var appid = '0000000000';
            var key = 'xxxxxxxxxx';
            var salt = (new Date).getTime();
            var search = document.getElementById("input_content");
            var out_content_youdao = document.getElementById("out_content_youdao");
            search_content = search.value;
            var query = search_content;
            var from = 'auto';
            var to = flag;
            var str1 = appid + query + salt + key;
            var sign = MD5(str1);
            $.ajax({
                url: 'https://openapi.youdao.com/api?',
                type: 'get',
                dataType: 'jsonp',
                data: {
                    q: query,
                    from: from,
                    to: to,
                    appKey: appid,
                    salt: salt,
                    sign: sign,
                },
                success: function (data) {
                    dst = data.translation;
                    out_content_youdao.value = dst;
                }
            });
        }

        function trans_baidu(flag) {
            if (flag == 'ko') {
                flag = 'kor';
            } else if (flag == 'fr') {
                flag = 'fra';
            }

            var appid = '000000000000000000';
            var key = 'xxxxxxxxxxxxxxxxxxxx';
            var salt = (new Date).getTime();
            var search = document.getElementById("input_content");
            var out_content_baidu = document.getElementById("out_content_baidu");
            search_content = search.value;
            var query = search_content;
            var from = 'auto';
            var to = flag;
            var str1 = appid + query + salt + key;
            var sign = MD5(str1);
            $.ajax({
                url: 'https://api.fanyi.baidu.com/api/trans/vip/translate',
                type: 'get',
                dataType: 'jsonp',
                data: {
                    q: query,
                    appid: appid,
                    salt: salt,
                    from: from,
                    to: to,
                    sign: sign
                },
                success: function (data) {
                    len = data.trans_result.length;
                    dst = "";
                    for (var i = 0; i < len; i++) {
                        dst = dst + data.trans_result[i].dst;
                    }
                    out_content_baidu.value = dst;
                }
            });
        }

        function translation() {
            var chinese = document.getElementById("chinese");
            var english = document.getElementById("english");
            var japanese = document.getElementById("japanese");
            var korean = document.getElementById("korean");
            var french = document.getElementById("french");
            var russian = document.getElementById("russian");
            var spain = document.getElementById("spain");

            var flag = "";
            if (chinese.checked) {
                flag = 'zh';
            } else if (english.checked) {
                flag = 'en';
            } else if (japanese.checked) {
                flag = 'jp';
            } else if (korean.checked) {
                flag = 'ko';
            } else if (french.checked) {
                flag = 'fr';
            } else if (russian.checked) {
                flag = 'ru';
            } else if (spain.checked) {
                flag = 'spa';
            } else {
                alert("请至少选择一种语言");
                return;
            }

            trans_baidu(flag);
            trans_youdao(flag);
            trans_google(flag);
            trans_bing(flag);
        }
    </script>

    <style type="text/css">
        body {
            height: 100%;
            background-image: url("assets/images/bk01.jpg");
            background-repeat: no-repeat;
            background-size: cover;
            background-position: bottom center;
            background-attachment: fixed;
        }
    </style>
</head>

<body class="is-loading">
<!-- Wrapper -->
<div id="wrapper">
    <!-- Main -->
    <section id="main">
        <header>
            <h1>一劳永"译"</h1>
            <table style="width: 90%">
                <tr>
                    <td>
                        <textarea type="text" id="out_content_baidu"
                                  placeholder="百度翻译"
                                  readonly="true"
                                  style="height:180px;width:400px;line-height:1.2;color:#000000;padding:0.5em 0.5em;background-color: #e6efff;"></textarea>
                    </td>
                    <td>
                        <textarea type="text" id="input_content"
                                  placeholder="在此输入待翻译文字,自动检测语言..."
                                  onkeydown=" if(event.keyCode==13) {translation();}"
                                  style="height:180px;width:400px;color:#000000;line-height:1.2;padding:0.5em 0.5em"></textarea>
                    </td>
                    <td>
                        <textarea type="text" id="free_content"
                                  placeholder="自由剪贴板"
                                  style="height:180px;width:400px;line-height:1.2;color:#000000;padding:0.5em 0.5em;background-color: #f3fff3;"></textarea>
                    </td>
                </tr>
                <tr>
                    <td>
                        <textarea type="text" id="out_content_youdao"
                                  placeholder="有道翻译"
                                  readonly="true"
                                  style="height:180px;width:400px;line-height:1.2;color:#000000;padding:0.5em 0.5em;background-color: #f3f3f3"></textarea>
                    </td>
                    <td>
                        <textarea type="text" id="out_content_google"
                                  placeholder="谷歌翻译"
                                  readonly="true"
                                  style="height:180px;width:400px;line-height:1.2;color:#000000;padding:0.5em 0.5em;background-color: #fff5f4"></textarea>
                    </td>
                    <td>
                        <textarea type="text" id="out_content_bing"
                                  placeholder="必应翻译"
                                  readonly="true"
                                  style="height:180px;width:400px;line-height:1.2;color:#000000;padding:0.5em 0.5em;background-color: #fff7e3"></textarea>
                    </td>
                </tr>
            </table>
            <table>
                <tr>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="chinese"
                                                          style="display: none;"/><label for="chinese">中文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="english"
                                                          style="display: none;"/><label for="english">英文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="japanese"
                                                          style="display: none;"/><label for="japanese">日文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="korean"
                                                          style="display: none;"/><label for="korean">韩文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="french"
                                                          style="display: none;"/><label for="french">法文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="russian"
                                                          style="display: none;"/><label for="russian">俄文</label></td>
                    <td style="text-align:center;"><input type="radio" radiogroup="group1" name="lang" id="spain"
                                                          style="display: none;"/><label for="spain">西班牙文</label></td>
                </tr>
            </table>
        </header>
        <footer>
            <ul class="actions">
                <li><input id="btn_search" type="button" value="翻 译" onclick="translation()"
                           style="width:100%;"></li>
            </ul>
        </footer>
    </section>
</div>
<script>
    if ('addEventListener' in window) {
        window.addEventListener('load', function () {
            document.body.className = document.body.className.replace(/\bis-loading\b/, '');
        });
        document.body.className += (navigator.userAgent.match(/(MSIE|rv:11\.0)/) ? ' is-ie' : '');
    }
</script>
</body>
(2)服务器端

服务器端采用Python编写,利用PythonWebsocketServer库实现。

# coding=utf-8
import logging
from websocket_server import WebsocketServer
from google.cloud import translate
import json
import commands
import sys


def bind_bing(text, target):
    part1 = "curl -X POST \"https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&"
    part2 = "to=" + target + "\""
    part3 = " -H \"Ocp-Apim-Subscription-Key: your-auth-key\" -H \"Content-Type: application/json\" "
    part4 = "-d \"[{\'Text\':\'"
    part5 = "\'}]\""
    request = part1 + part2 + part3 + part4 + text + part5
    return request


def decode_bing(text):
    jsonData = text[text.find('['):text.rfind(']') + 1]
    text = json.loads(jsonData)
    return text[0]['translations'][0]['text']


def trans_bing(text, target):
    com = bind_bing(text, target)
    result = commands.getoutput(com)
    result = decode_bing(result)
    return result


def translation(string):
    reload(sys)
    sys.setdefaultencoding('utf-8')

    res = string.split("zxh")
    flag = res[0]
    trans_str = res[1]
    result = ""

    if trans_str.__contains__("**Google**"):
        trans_str = trans_str[10:]
        result = trans_google(trans_str, flag)
    elif trans_str.__contains__("**Bing**"):
        trans_str = trans_str[8:]
        result = trans_bing(trans_str, flag)
    else:
        result = ""
    return result


def trans_google(text, target):
    translate_client = translate.Client()
    trans = translate_client.translate(text, target_language=target)

    res = trans['translatedText']
    return res


def new_client(client, server):
    print("Client(%d) has joined." % client['id'])


# Called for every client disconnecting
def client_left(client, server):
    print("Client(%d) disconnected" % client['id'])


# Called when a client sends a message
def message_received(client, server, message):
    if len(message) > 200:
        message = message[:200] + '..'
    print("Client(%d) said: %s" % (client['id'], message))


def message_trans(client, server, message):
    if message.__contains__("zxh"):
        print("A translation request.")
        result = translation(message)
        server.send_message(client, result)


server = WebsocketServer(4200, host='', loglevel=logging.INFO)
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_trans)
server.run_forever()

4.功能测试

直接访问网址https://zhaoxuhui.top/translate 即可测试,测试结果如下(可单独放大查看)。

本文作者原创,未经许可不得转载,谢谢配合

返回顶部