Update XianyuAutoAsync.py

This commit is contained in:
zhinianboke 2025-08-14 22:58:22 +08:00
parent f7b2043f8a
commit 1374373da1

View File

@ -119,6 +119,10 @@ class XianyuLive:
# 记录订单详情锁的使用时间
_order_detail_lock_times = {}
# 商品详情缓存24小时有效
_item_detail_cache = {} # {item_id: {'detail': str, 'timestamp': float}}
_item_detail_cache_lock = asyncio.Lock()
def _safe_str(self, e):
"""安全地将异常转换为字符串"""
try:
@ -862,7 +866,7 @@ class XianyuLive:
return False
async def fetch_item_detail_from_api(self, item_id: str) -> str:
"""从外部API获取商品详情
"""获取商品详情优先使用浏览器备用外部API支持24小时缓存
Args:
item_id: 商品ID
@ -879,6 +883,181 @@ class XianyuLive:
logger.debug(f"自动获取商品详情功能已禁用: {item_id}")
return ""
# 1. 首先检查缓存24小时有效
async with self._item_detail_cache_lock:
if item_id in self._item_detail_cache:
cache_data = self._item_detail_cache[item_id]
cache_time = cache_data['timestamp']
current_time = time.time()
# 检查缓存是否在24小时内
if current_time - cache_time < 24 * 60 * 60: # 24小时
logger.info(f"从缓存获取商品详情: {item_id}")
return cache_data['detail']
else:
# 缓存过期,删除
del self._item_detail_cache[item_id]
logger.debug(f"缓存已过期,删除: {item_id}")
# 2. 尝试使用浏览器获取商品详情
detail_from_browser = await self._fetch_item_detail_from_browser(item_id)
if detail_from_browser:
# 保存到缓存
async with self._item_detail_cache_lock:
self._item_detail_cache[item_id] = {
'detail': detail_from_browser,
'timestamp': time.time()
}
logger.info(f"成功通过浏览器获取商品详情: {item_id}, 长度: {len(detail_from_browser)}")
return detail_from_browser
# 3. 浏览器获取失败使用外部API作为备用
logger.warning(f"浏览器获取商品详情失败尝试外部API: {item_id}")
detail_from_api = await self._fetch_item_detail_from_external_api(item_id)
if detail_from_api:
# 保存到缓存
async with self._item_detail_cache_lock:
self._item_detail_cache[item_id] = {
'detail': detail_from_api,
'timestamp': time.time()
}
logger.info(f"成功通过外部API获取商品详情: {item_id}, 长度: {len(detail_from_api)}")
return detail_from_api
logger.warning(f"所有方式都无法获取商品详情: {item_id}")
return ""
except Exception as e:
logger.error(f"获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
return ""
async def _fetch_item_detail_from_browser(self, item_id: str) -> str:
"""使用浏览器获取商品详情"""
try:
from playwright.async_api import async_playwright
logger.info(f"开始使用浏览器获取商品详情: {item_id}")
playwright = await async_playwright().start()
# 启动浏览器参照order_detail_fetcher的配置
browser_args = [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--disable-gpu',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-renderer-backgrounding',
'--disable-features=TranslateUI',
'--disable-ipc-flooding-protection',
'--disable-extensions',
'--disable-default-apps',
'--disable-sync',
'--disable-translate',
'--hide-scrollbars',
'--mute-audio',
'--no-default-browser-check',
'--no-pings'
]
# 在Docker环境中添加额外参数
if os.getenv('DOCKER_ENV'):
browser_args.extend([
'--single-process',
'--disable-background-networking',
'--disable-client-side-phishing-detection',
'--disable-hang-monitor',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-web-resources',
'--metrics-recording-only',
'--safebrowsing-disable-auto-update',
'--enable-automation',
'--password-store=basic',
'--use-mock-keychain'
])
browser = await playwright.chromium.launch(
headless=True,
args=browser_args
)
# 创建浏览器上下文
context = await browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
)
# 设置Cookie
cookies = []
for cookie_pair in self.cookies_str.split('; '):
if '=' in cookie_pair:
name, value = cookie_pair.split('=', 1)
cookies.append({
'name': name.strip(),
'value': value.strip(),
'domain': '.goofish.com',
'path': '/'
})
await context.add_cookies(cookies)
logger.debug(f"已设置 {len(cookies)} 个Cookie")
# 创建页面
page = await context.new_page()
# 构造商品详情页面URL
item_url = f"https://www.goofish.com/item?id={item_id}"
logger.info(f"访问商品页面: {item_url}")
# 访问页面
await page.goto(item_url, wait_until='networkidle', timeout=30000)
# 等待页面完全加载
await asyncio.sleep(3)
# 获取商品详情内容
try:
# 等待目标元素出现
await page.wait_for_selector('.desc--GaIUKUQY', timeout=10000)
# 获取商品详情文本
detail_element = await page.query_selector('.desc--GaIUKUQY')
if detail_element:
detail_text = await detail_element.inner_text()
logger.info(f"成功获取商品详情: {item_id}, 长度: {len(detail_text)}")
# 清理资源
await browser.close()
await playwright.stop()
return detail_text.strip()
else:
logger.warning(f"未找到商品详情元素: {item_id}")
except Exception as e:
logger.warning(f"获取商品详情元素失败: {item_id}, 错误: {self._safe_str(e)}")
# 清理资源
await browser.close()
await playwright.stop()
return ""
except Exception as e:
logger.error(f"浏览器获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
return ""
async def _fetch_item_detail_from_external_api(self, item_id: str) -> str:
"""从外部API获取商品详情备用方案"""
try:
from config import config
auto_fetch_config = config.get('ITEM_DETAIL', {}).get('auto_fetch', {})
# 从配置获取API地址和超时时间
api_base_url = auto_fetch_config.get('api_url', 'https://selfapi.zhinianboke.com/api/getItemDetail')
timeout_seconds = auto_fetch_config.get('timeout', 10)
@ -889,7 +1068,6 @@ class XianyuLive:
# 使用aiohttp发送异步请求
import aiohttp
import asyncio
timeout = aiohttp.ClientTimeout(total=timeout_seconds)
@ -901,21 +1079,20 @@ class XianyuLive:
# 检查返回状态
if result.get('status') == '200' and result.get('data'):
item_detail = result['data']
logger.info(f"成功获取商品详情: {item_id}, 长度: {len(item_detail)}")
logger.debug(f"商品详情内容: {item_detail[:200]}...")
logger.info(f"外部API成功获取商品详情: {item_id}, 长度: {len(item_detail)}")
return item_detail
else:
logger.warning(f"API返回状态异常: {result.get('status')}, message: {result.get('message')}")
logger.warning(f"外部API返回状态异常: {result.get('status')}, message: {result.get('message')}")
return ""
else:
logger.warning(f"API请求失败: HTTP {response.status}")
logger.warning(f"外部API请求失败: HTTP {response.status}")
return ""
except asyncio.TimeoutError:
logger.warning(f"获取商品详情超时: {item_id}")
logger.warning(f"外部API获取商品详情超时: {item_id}")
return ""
except Exception as e:
logger.error(f"获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
logger.error(f"外部API获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
return ""
async def save_items_list_to_db(self, items_list):