前言:
python googletrans库使用代理出现 'str' object has no attribute 'request' 问题解决方案直接看 4-4 尝试解决问题
想直接用我包装好的文件google_translator.py,直接看 五、问题解决
其余部分,是我解决问题的探索过程。有兴趣可以了解下。
一、googletrans安装
googletrans库是google翻译官方库。一般用3.0.0版本最为稳定,默认也是这个版本。
通过命令安装。
pip install googletrans
但官方其实已发布到4.0.0rc1版本(其实也没感觉差别,至少我的bug一样还出现)
如果需要4.0版本,可以通过指令安装
pip install googletrans==4.0.0rc1
本文基于最新版本编写
googletrans==4.0.0rc1
httpx==0.13.3 (固定)
requests==2.28.2 (最新版本)
二、使用
使用挺简单的
【测试在香港服务器可用】
# -*- encoding: utf-8 -*-
from googletrans import Translator
T = Translator()
res = T.translate('这是一个神奇的网站')
print('原语言:', res.src)
print('目标语言:', res.dest)
print('原文:', res.origin)
print('译文:', res.text)
结果
原语言: zh-CN
目标语言: en
原文: 这是一个神奇的网站
译文: This is a magical website
三、使用代理
【但 国内几乎是不通的,毕竟是google,google,还是google嘛】
所以我开发中用到代理。但出现了异常
【此代码可能会有异常】
# -*- encoding: utf-8 -*-
from googletrans import Translator
proxy = '账号:密码'
proxies = {
'http': f'http://{proxy}@服务地址:服务端口',
'https': f'http://{proxy}@服务地址:服务端口'
}
T = Translator(proxies=proxies)
res = T.translate('这是一个神奇的网站')
print('原语言:', res.src)
print('目标语言:', res.dest)
print('原文:', res.origin)
print('译文:', res.text)
四、遇到问题与排查
4-1、 问题
但我用代理出现异常
'str' object has no attribute 'request'
尝试了网上说的【googletrans库换成py-googletrans库】和【from httpcore import SyncHTTPProxy 用这个初始化代理后使用】都未能成功。
(大家可以试试,也许能成功的)
4-2 问题排查
排查发现,无论哪个版本,googletrans库 都锁定了httpx==0.13.3,而这个库的这版本对代理非常不友好。
【注意!绝对不能升级httpx版本!否者googletrans直接报错!】
httpx最大版本都0.23.3了.....这都不升级吗?去官方gitlab一看,googletrans好几年没升级了,难怪。(本文编写于2023年2月)
而googletrans库的 client.py里似乎也说明了 代理方法未实现 (# pragma: nocover 好像是"未覆盖实现"的意思)
4-3、异常调试
通过异常断点追踪发现,报错的异常在于 self.client.post 函数调用。而 self.client 是 httpx.Client 类型,这就难搞了。
不过细看一下返回值,r.status_coder r.text 这两个老伙计是不是很熟悉?
没错,就是 requests.post 返回结果的成员名一致吗?这就是我的 突破口。
通过 函数注入 的方式重写 self.client.post 函数。用老伙计 requests.post 来代替。
但有3个问题需要注意下!
① 官方的 self.client.post 调用是没有传代理的,Translator也没有直接存储下来(类型转译为SyncHTTPTransport类型)。所以需要我们重新包装下。
② 官方是要返回结果 r.text 和 r。替换self.client.post返回类型后 r 值的利用可能有bug,这个只能测试了。
③ httpx的timeout是 Timeout类型,而requests的超时是int类型。需要转译或重新包装下。
4-4 尝试解决问题
用自定义post覆盖原库post方法。改版后的代码
【在大陆服务器测试可用,代理就不公开了】
# -*- encoding: utf-8 -*-
import requests
from googletrans import Translator
proxy = '账号:密码'
proxies = {
'http': f'http://{proxy}@服务地址:服务端口',
'https': f'http://{proxy}@服务地址:服务端口'
}
def my_post(url, data=None, json=None, **kwargs):
kwargs['proxies'] = proxies
if 1:
# 此处仅验证代理配置是否生效, 正式中无需这段
res = requests.get('https://mybrowserinfo.com/', **kwargs)
print(res.text)
return requests.post(url, data, json, **kwargs)
T = Translator(proxies=proxies)
res = T.translate('这是一个神奇的网站')
# 注入式覆盖方法
T.client.post = my_post
print('原语言:', res.src)
print('目标语言:', res.dest)
print('原文:', res.origin)
print('译文:', res.text)
输出
...[省略]
<title>Your IP Address: 88.214.1.245 - MyBrowserInfo.com (My Browser Info)</title>
...[省略]
原语言: zh-CN
目标语言: en
原文: 这是一个神奇的网站
译文: This is a magical website
ip地址成功更换,并且翻译也没有报错! 成功了。
五、问题解决
既然问题解决了,就可以自己重新包装下函数了。
文件名 google_translator.py
【大陆本机测试可用】
# -*- coding: utf-8 -*-
# google_translator.py
# google 翻译
# 依赖pip googletrans==4.0.0rc1
# 依赖pip httpx==0.13.3
import warnings
import requests
from googletrans import Translator
from googletrans.constants import LANGUAGES, DEFAULT_USER_AGENT
DEBUG = globals().get('DEBUG', False)
GOOGLE_SERVICE_LS = ['translate.google.com'] # , 'translate.google.co.kr']
GOOGLE_SERVICE_LS_CN = ['translate.google.cn']
__version__ = '1.0.0.0'
class GoogleTranslator():
def __init__(self, service_urls: list = GOOGLE_SERVICE_LS, ua: str = DEFAULT_USER_AGENT,
proxies: dict = None, timeout: int = None, debug=DEBUG):
"""
:service_urls list google翻译服务地址
:ua str 请求UA
:proxies dict 代理
:timeout int 超时
:*debug bool 调试模式
"""
self._translator = Translator(service_urls=service_urls, user_agent=ua,
proxies=None)
self.proxies = proxies
self.timeout = timeout
self._debug = debug
# 伪造post现场
self._translator.client.post = self._my_post
def _my_post(self, url: str, data=None, json=None, **kwargs):
"""
*自定义post逻辑,请勿外部调用
"""
if self.proxies:
kwargs['proxies'] = self.proxies
if self.timeout:
kwargs['timeout'] = self.timeout
return requests.post(url, data, json, **kwargs)
def translator(self, text: str, src='auto', dest='en'):
"""
google翻译
传入:
text 翻译文本
src 原文语言(auto自定识别)
dest 翻译语言
返回:
{
'src': res.src, #原语言
'dest': res.dest, #目标语言
'origin': res.origin, #原文
'text': res.text, #译文
}
"""
if not 'auto' == src and not src in set(LANGUAGES):
src = 'auto'
warnings.warn(f"src must 'auto' or one of {set(LANGUAGES)}")
if not dest in set(LANGUAGES):
dest = 'en'
warnings.warn(f"dest must one of {set(LANGUAGES)}")
for i in range(3):
try:
res = self._translator.translate(text, dest, src)
return {
'src': res.src, # 原语言
'dest': res.dest, # 目标语言
'origin': res.origin, # 原文
'text': res.text, # 译文
# 'pronunciation': res.pronunciation #译文发音 默认None
}
except Exception as e:
if self._debug:
raise e
else:
continue
return {}
def translators(self, texts: list, src='auto', dest='en'):
"""
google翻译
传入:
text 翻译文本
src 原文语言(auto自定识别)
dest 翻译语言
返回:
[{
'src': res.src, #原语言
'dest': res.dest, #目标语言
'origin': res.origin, #原文
'text': res.text, #译文
}]
"""
res = []
for text in texts:
res.append(self.translator(text, src, dest))
return res
if __name__ == '__main__':
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
proxy = '账号:密码'
proxies = {
'http': f'http://{proxy}@代理地址:代理端口',
'https': f'http://{proxy}@代理地址:代理端口'
}
A = GoogleTranslator(service_urls=GOOGLE_SERVICE_LS, ua=ua, proxies=proxies, debug=True, timeout=120)
# A._translator.client.post = my_post #requests.post
print(A.translator('this is a good day', dest='zh-cn'))
print(A.translator('这是一个神奇的网站', dest='en'))
测试结果
{'src': 'en', 'dest': 'zh-cn', 'origin': 'this is a good day', 'text': '这是美好的一天'}
{'src': 'zh-CN', 'dest': 'en', 'origin': '这是一个神奇的网站', 'text': 'This is a magical website'}
后记:
这种注入式该函数的方式一般不推荐使用。因为有风险!而且这次能成功,刚好是httpx返回值和requests返回值结构相似。否者要注入重写的是 _build_rpc_request 这函数,是比较麻烦的。
******************************************************************
其实,也可以定位到对应 googletrans\client.py 文件,直接修改 120行的post方法。
(注意原类里 self.client.proxies 是 SyncHTTPTransport类型,用requests要重新转译为str类型)
(注意原类里 self.client.timeout 是 Timeout类型,如果用requests且需要超时,需要转译为int类型)
*****************************************************************
转载请注明出处: https://my.oschina.net/jacky326/blog/7554697