前言:
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