From df6097a1848efb24607df6d87a194fcb844e73e0 Mon Sep 17 00:00:00 2001 From: John Smith Date: Fri, 23 Apr 2021 21:35:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=99=BE=E5=BA=A6=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E5=AE=98=E6=96=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 5 +++ data/config.example.ini | 27 +++++++++++++++ models/translate.py | 73 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index 8fb2aa2..2653552 100644 --- a/config.py +++ b/config.py @@ -109,6 +109,11 @@ class AppConfig: translator_config['secret_id'] = section['secret_id'] translator_config['secret_key'] = section['secret_key'] translator_config['region'] = section['region'] + elif type_ == 'BaiduTranslate': + translator_config['source_language'] = section['source_language'] + translator_config['target_language'] = section['target_language'] + translator_config['app_id'] = section['app_id'] + translator_config['secret'] = section['secret'] else: raise ValueError(f'Invalid translator type: {type_}') diff --git a/data/config.example.ini b/data/config.example.ini index 28384ae..3d2a392 100644 --- a/data/config.example.ini +++ b/data/config.example.ini @@ -113,3 +113,30 @@ secret_key = # 北京:ap-beijing;上海:ap-shanghai;香港:ap-hongkong;首尔:ap-seoul # 完整地域列表见文档:https://cloud.tencent.com/document/api/551/15615#.E5.9C.B0.E5.9F.9F.E5.88.97.E8.A1.A8 region = ap-shanghai + + +[baidu_translate] +# 文档:https://fanyi-api.baidu.com/ +# 定价:https://fanyi-api.baidu.com/product/112 +# * 标准版完全免费,不限使用字符量(QPS=1) +# * 高级版每月前200万字符免费,超出后仅收取超出部分费用(QPS=10),49元/百万字符 +# * 尊享版每月前200万字符免费,超出后仅收取超出部分费用(QPS=100),49元/百万字符 + +# 类型:百度翻译 +type = BaiduTranslate + +# 请求间隔时间(秒),等于 1 / QPS +query_interval = 1.5 +# 最大队列长度,注意最长等待时间等于 最大队列长度 * 请求间隔时间 +max_queue_size = 9 + +# 自动:auto;中文:zh;日语:jp;英语:en;韩语:kor +# 完整语言列表见文档:https://fanyi-api.baidu.com/doc/21 +# 源语言 +source_language = zh +# 目标语言 +target_language = jp + +# 百度翻译开放平台应用ID和密钥 +app_id = +secret = diff --git a/models/translate.py b/models/translate.py index 928d083..6dad978 100644 --- a/models/translate.py +++ b/models/translate.py @@ -7,6 +7,7 @@ import hashlib import hmac import json import logging +import random import re from typing import * @@ -22,7 +23,7 @@ NO_TRANSLATE_TEXTS = { } _main_event_loop = asyncio.get_event_loop() -_http_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) +_http_session = None _translate_providers: List['TranslateProvider'] = [] # text -> res _translate_cache: Dict[str, str] = {} @@ -35,6 +36,9 @@ def init(): async def _do_init(): + global _http_session + _http_session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) + cfg = config.get_config() if not cfg.enable_translate: return @@ -63,6 +67,11 @@ def create_translate_provider(cfg): cfg['target_language'], cfg['secret_id'], cfg['secret_key'], cfg['region'] ) + elif type_ == 'BaiduTranslate': + return BaiduTranslate( + cfg['query_interval'], cfg['max_queue_size'], cfg['source_language'], + cfg['target_language'], cfg['app_id'], cfg['secret'] + ) return None @@ -464,3 +473,65 @@ class TencentTranslate(FlowControlTranslateProvider): def _on_cool_down_timeout(self): self._cool_down_timer_handle = None + + +class BaiduTranslate(FlowControlTranslateProvider): + def __init__(self, query_interval, max_queue_size, source_language, target_language, + app_id, secret): + super().__init__(query_interval, max_queue_size) + self._source_language = source_language + self._target_language = target_language + self._app_id = app_id + self._secret = secret + + self._cool_down_timer_handle = None + + @property + def is_available(self): + return self._cool_down_timer_handle is None and super().is_available + + async def _do_translate(self, text): + try: + async with _http_session.post( + 'https://fanyi-api.baidu.com/api/trans/vip/translate', + data=self._add_sign({ + 'q': text, + 'from': self._source_language, + 'to': self._target_language, + 'appid': self._app_id, + 'salt': random.randint(1, 999999999) + }) + ) as r: + if r.status != 200: + logger.warning('BaiduTranslate request failed: status=%d %s', r.status, r.reason) + return None + data = await r.json() + except (aiohttp.ClientConnectionError, asyncio.TimeoutError): + return None + error_code = data.get('error_code', None) + if error_code is not None: + logger.warning('BaiduTranslate failed: %s %s', error_code, data['error_msg']) + self._on_fail(error_code) + return None + return ''.join(result['dst'] for result in data['trans_result']) + + def _add_sign(self, data): + str_to_sign = f"{self._app_id}{data['q']}{data['salt']}{self._secret}" + sign = hashlib.md5(str_to_sign.encode('utf-8')).hexdigest() + return {**data, 'sign': sign} + + def _on_fail(self, code): + if self._cool_down_timer_handle is not None: + return + + sleep_time = 0 + if code == '54004': + # 账户余额不足,需要手动处理,等5分钟 + sleep_time = 5 * 60 + if sleep_time != 0: + self._cool_down_timer_handle = asyncio.get_event_loop().call_later( + sleep_time, self._on_cool_down_timeout + ) + + def _on_cool_down_timeout(self): + self._cool_down_timer_handle = None