This commit is contained in:
2025-11-20 11:42:18 +08:00
parent 1bd91df9a1
commit e2d2b0b75b
29 changed files with 2100 additions and 48 deletions

143
back/utils/browser_api.py Normal file
View File

@@ -0,0 +1,143 @@
import datetime
import asyncio
import httpx
from loguru import logger
from utils.decorators import handle_exceptions_unified
class BrowserApi:
"""
浏览器接口
"""
def __init__(self):
self.local_url = 'http://127.0.0.1:54345'
self.headers = {'Content-Type': 'application/json'}
# 使用异步 HTTP 客户端,启用连接池和超时设置
self.client = httpx.AsyncClient(
base_url=self.local_url,
headers=self.headers,
timeout=httpx.Timeout(30.0, connect=10.0), # 总超时30秒连接超时10秒
limits=httpx.Limits(max_keepalive_connections=50, max_connections=100), # 连接池配置
)
async def __aenter__(self):
"""异步上下文管理器入口"""
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器出口,关闭客户端"""
await self.aclose()
async def aclose(self):
"""关闭 HTTP 客户端"""
if self.client:
await self.client.aclose()
# 打开指纹浏览器
@handle_exceptions_unified()
async def open_browser(self, id: str, jc: int = 0):
"""
打开指纹浏览器(异步优化版本)
:param jc: 计次
:param id: 浏览器id
:return:http, pid
"""
if jc > 3:
return None, None
url = '/browser/open'
data = {
'id': id
}
try:
res = await self.client.post(url, json=data)
res.raise_for_status() # 检查 HTTP 状态码
res_data = res.json()
logger.info(f'打开指纹浏览器: {res_data}')
if not res_data.get('success'):
logger.error(f'打开指纹浏览器失败: {res_data}')
return await self.open_browser(id, jc + 1)
data = res_data.get('data')
http = data.get('http')
pid = data.get('pid')
logger.info(f'打开指纹浏览器成功: {http}, {pid}')
return http, pid
except httpx.TimeoutException as e:
logger.error(f'打开指纹浏览器超时: {e}')
if jc < 3:
return await self.open_browser(id, jc + 1)
return None, None
except httpx.RequestError as e:
logger.error(f'打开指纹浏览器请求错误: {e}')
if jc < 3:
return await self.open_browser(id, jc + 1)
return None, None
except Exception as e:
logger.error(f'打开指纹浏览器异常: {e}')
if jc < 3:
return await self.open_browser(id, jc + 1)
return None, None
# 关闭指纹浏览器
@handle_exceptions_unified()
async def close_browser(self, id: str, jc: int = 0):
"""
关闭指纹浏览器(异步优化版本)
:param jc: 计次
:param id: 浏览器id
:return:
"""
if jc > 3:
return None
url = '/browser/close'
data = {
'id': id
}
try:
res = await self.client.post(url, json=data)
res.raise_for_status() # 检查 HTTP 状态码
res_data = res.json()
logger.info(f'关闭指纹浏览器: {res_data}')
if not res_data.get('success'):
msg = res_data.get('msg', '')
# 如果浏览器正在打开中,等待后重试(不是真正的错误)
if '正在打开中' in msg or 'opening' in msg.lower():
if jc < 3:
# 等待 1-3 秒后重试(根据重试次数递增等待时间)
wait_time = (jc + 1) * 1.0 # 第1次重试等1秒第2次等2秒第3次等3秒
logger.info(f'浏览器正在打开中,等待 {wait_time} 秒后重试关闭: browser_id={id}')
await asyncio.sleep(wait_time)
return await self.close_browser(id, jc + 1)
else:
# 超过重试次数,记录警告但不作为错误
logger.warning(f'关闭指纹浏览器失败浏览器正在打开中已重试3次: browser_id={id}')
return None
else:
# 其他错误,记录为错误并重试
logger.error(f'关闭指纹浏览器失败: {res_data}')
if jc < 3:
await asyncio.sleep(0.5) # 短暂等待后重试
return await self.close_browser(id, jc + 1)
return None
logger.info(f'关闭指纹浏览器成功: browser_id={id}')
return True
except httpx.TimeoutException as e:
logger.error(f'关闭指纹浏览器超时: {e}')
if jc < 3:
await asyncio.sleep(1.0)
return await self.close_browser(id, jc + 1)
return None
except httpx.RequestError as e:
logger.error(f'关闭指纹浏览器请求错误: {e}')
if jc < 3:
await asyncio.sleep(1.0)
return await self.close_browser(id, jc + 1)
return None
except Exception as e:
logger.error(f'关闭指纹浏览器异常: {e}')
if jc < 3:
await asyncio.sleep(1.0)
return await self.close_browser(id, jc + 1)
return None
browser_api = BrowserApi()