python googletrans 使用代理'str' object has no attribute 'request'异常解决

前言:

up-a98db97341fb6bbff618146cbcb26cea3f3.png

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章