Prechádzať zdrojové kódy

fix: 修复 Playwright 资源泄漏问题

1. base.py: init_browser() 将 playwright 保存为 self.playwright 实例变量
2. base.py: close_browser() 添加 playwright.stop() 调用,确保 Playwright server 进程被关闭
3. xiaohongshu.py: get_comments() 的 finally 块添加 playwright.stop() + context.close() + page.close()
4. xiaohongshu.py: 添加 _xhs_sign_executor 的 atexit 清理函数,服务退出时自动 shutdown 线程池
ethanfly 3 dní pred
rodič
commit
c6610a27a5

+ 26 - 5
server/python/platforms/base.py

@@ -199,6 +199,7 @@ class BasePublisher(ABC):
         self.browser: Optional[Browser] = None
         self.context: Optional[BrowserContext] = None
         self.page: Optional[Page] = None
+        self.playwright = None  # Playwright server instance, must be stopped in close_browser()
         self.on_progress: Optional[Callable[[int, str], None]] = None
         self.user_id: Optional[int] = None
         self.publish_task_id: Optional[int] = None
@@ -319,7 +320,7 @@ class BasePublisher(ABC):
         print(
             f"[{self.platform_name}] init_browser: headless={self.headless}", flush=True
         )
-        playwright = await async_playwright().start()
+        self.playwright = await async_playwright().start()
 
         proxy = proxy_config or self.proxy_config
         has_proxy = proxy and isinstance(proxy, dict) and proxy.get("server")
@@ -347,7 +348,7 @@ class BasePublisher(ABC):
         chrome_launched = False
         for channel in ("chrome", "msedge"):
             try:
-                self.browser = await playwright.chromium.launch(
+                self.browser = await self.playwright.chromium.launch(
                     channel=channel, **launch_args
                 )
                 print(
@@ -367,7 +368,7 @@ class BasePublisher(ABC):
                 f"[{self.platform_name}] 回退到 Playwright Chromium(注意: 更容易被平台检测)",
                 flush=True,
             )
-            self.browser = await playwright.chromium.launch(**launch_args)
+            self.browser = await self.playwright.chromium.launch(**launch_args)
 
         # 生成浏览器上下文参数(带反检测配置)
         if STEALTH_AVAILABLE:
@@ -441,10 +442,30 @@ class BasePublisher(ABC):
 
     async def close_browser(self):
         """关闭浏览器"""
+        if self.page:
+            try:
+                await self.page.close()
+            except Exception:
+                pass
+        self.page = None
         if self.context:
-            await self.context.close()
+            try:
+                await self.context.close()
+            except Exception:
+                pass
+        self.context = None
         if self.browser:
-            await self.browser.close()
+            try:
+                await self.browser.close()
+            except Exception:
+                pass
+        self.browser = None
+        if getattr(self, 'playwright', None):
+            try:
+                await self.playwright.stop()
+            except Exception:
+                pass
+        self.playwright = None
 
     async def human_like_delay(self, min_ms: int = 100, max_ms: int = 500):
         """模拟人类操作延迟"""

+ 34 - 1
server/python/platforms/xiaohongshu.py

@@ -2502,6 +2502,9 @@ class XiaohongshuPublisher(BasePublisher):
         total_comments = 0
         has_more = False
         browser = None
+        context = None
+        page = None
+        playwright = None
         print(222222222222222222222222222222222222)
         print(work_id)
         global stored_cookies
@@ -2707,8 +2710,26 @@ class XiaohongshuPublisher(BasePublisher):
                 success=True, platform=self.platform_name, work_id=work_id, total=0
             )
         finally:
+            if page:
+                try:
+                    await page.close()
+                except Exception:
+                    pass
+            if context:
+                try:
+                    await context.close()
+                except Exception:
+                    pass
             if browser:
-                await browser.close()
+                try:
+                    await browser.close()
+                except Exception:
+                    pass
+            if playwright:
+                try:
+                    await playwright.stop()
+                except Exception:
+                    pass
 
     def _timestamp_to_readable(self, ts_ms: int) -> str:
         """将毫秒时间戳转换为可读格式"""
@@ -3191,3 +3212,15 @@ class XiaohongshuPublisher(BasePublisher):
             "account_base": account_base_result,
             "fans_overall_new": fans_overall_new_result,
         }
+
+
+def shutdown_xhs_sign_executor():
+    """关闭签名线程池(服务退出时调用)"""
+    global _xhs_sign_executor
+    if _xhs_sign_executor is not None:
+        _xhs_sign_executor.shutdown(wait=False)
+        _xhs_sign_executor = None
+
+
+import atexit
+atexit.register(shutdown_xhs_sign_executor)