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

 

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