mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-30 01:27:35 +08:00
Update XianyuAutoAsync.py
This commit is contained in:
parent
f7b2043f8a
commit
1374373da1
@ -118,6 +118,10 @@ class XianyuLive:
|
|||||||
_order_detail_locks = defaultdict(lambda: asyncio.Lock())
|
_order_detail_locks = defaultdict(lambda: asyncio.Lock())
|
||||||
# 记录订单详情锁的使用时间
|
# 记录订单详情锁的使用时间
|
||||||
_order_detail_lock_times = {}
|
_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):
|
def _safe_str(self, e):
|
||||||
"""安全地将异常转换为字符串"""
|
"""安全地将异常转换为字符串"""
|
||||||
@ -862,7 +866,7 @@ class XianyuLive:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def fetch_item_detail_from_api(self, item_id: str) -> str:
|
async def fetch_item_detail_from_api(self, item_id: str) -> str:
|
||||||
"""从外部API获取商品详情
|
"""获取商品详情(优先使用浏览器,备用外部API,支持24小时缓存)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
item_id: 商品ID
|
item_id: 商品ID
|
||||||
@ -879,6 +883,181 @@ class XianyuLive:
|
|||||||
logger.debug(f"自动获取商品详情功能已禁用: {item_id}")
|
logger.debug(f"自动获取商品详情功能已禁用: {item_id}")
|
||||||
return ""
|
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地址和超时时间
|
||||||
api_base_url = auto_fetch_config.get('api_url', 'https://selfapi.zhinianboke.com/api/getItemDetail')
|
api_base_url = auto_fetch_config.get('api_url', 'https://selfapi.zhinianboke.com/api/getItemDetail')
|
||||||
timeout_seconds = auto_fetch_config.get('timeout', 10)
|
timeout_seconds = auto_fetch_config.get('timeout', 10)
|
||||||
@ -889,7 +1068,6 @@ class XianyuLive:
|
|||||||
|
|
||||||
# 使用aiohttp发送异步请求
|
# 使用aiohttp发送异步请求
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import asyncio
|
|
||||||
|
|
||||||
timeout = aiohttp.ClientTimeout(total=timeout_seconds)
|
timeout = aiohttp.ClientTimeout(total=timeout_seconds)
|
||||||
|
|
||||||
@ -901,21 +1079,20 @@ class XianyuLive:
|
|||||||
# 检查返回状态
|
# 检查返回状态
|
||||||
if result.get('status') == '200' and result.get('data'):
|
if result.get('status') == '200' and result.get('data'):
|
||||||
item_detail = result['data']
|
item_detail = result['data']
|
||||||
logger.info(f"成功获取商品详情: {item_id}, 长度: {len(item_detail)}")
|
logger.info(f"外部API成功获取商品详情: {item_id}, 长度: {len(item_detail)}")
|
||||||
logger.debug(f"商品详情内容: {item_detail[:200]}...")
|
|
||||||
return item_detail
|
return item_detail
|
||||||
else:
|
else:
|
||||||
logger.warning(f"API返回状态异常: {result.get('status')}, message: {result.get('message')}")
|
logger.warning(f"外部API返回状态异常: {result.get('status')}, message: {result.get('message')}")
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
logger.warning(f"API请求失败: HTTP {response.status}")
|
logger.warning(f"外部API请求失败: HTTP {response.status}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.warning(f"获取商品详情超时: {item_id}")
|
logger.warning(f"外部API获取商品详情超时: {item_id}")
|
||||||
return ""
|
return ""
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
|
logger.error(f"外部API获取商品详情异常: {item_id}, 错误: {self._safe_str(e)}")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
async def save_items_list_to_db(self, items_list):
|
async def save_items_list_to_db(self, items_list):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user