Revert "支持复用色花堂常驻浏览器会话"

This reverts commit a8070a7214.
This commit is contained in:
liuwei
2026-04-27 15:44:55 +08:00
parent a8070a7214
commit 9a84b6b313
3 changed files with 43 additions and 151 deletions

View File

@@ -5,7 +5,6 @@ import subprocess
import requests
from io import BytesIO
import undetected_chromedriver as uc
from selenium import webdriver
# 注意不要禁用析构函数否则会导致Chrome进程泄漏
# if os.name == 'nt':
@@ -31,57 +30,6 @@ from PyPDF2 import PdfReader, PdfWriter
from loguru import logger
def _build_chrome_options(debugger_address=None):
"""构建 Chrome 启动参数;当提供调试地址时,表示附着到外部常驻浏览器。"""
# 这里统一使用 Selenium 的 ChromeOptions
# 这样既能给 undetected_chromedriver 复用,也能给 Selenium 直连现有浏览器复用。
options = webdriver.ChromeOptions()
if debugger_address:
# 当需要复用已经启动的浏览器时,只需要告诉 ChromeDriver 去连接哪个调试地址。
# 这种模式下不会新拉起浏览器进程,因此也不应该再塞 headless 等启动参数。
options.add_experimental_option("debuggerAddress", debugger_address)
return options
# 下面这组参数用于“自己启动浏览器”的场景。
# Linux 服务器上继续使用 headless避免任务依赖桌面环境。
if os.name != 'nt':
options.headless = True
options.add_argument('--headless=new')
else:
options.headless = False
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-extensions')
options.add_argument('--disable-background-networking')
options.add_argument('--disable-crash-reporter')
options.add_argument('--disable-in-process-stack-traces')
options.add_argument('--disable-logging')
options.add_argument('--disable-dev-shm-usage')
return options
def _normalize_browser_config(browser_config=None):
"""整理浏览器配置,保证后续逻辑总能拿到结构稳定的字典。"""
browser_config = browser_config or {}
return {
"reuse_existing_browser": bool(browser_config.get("reuse_existing_browser", False)),
"debugger_host": str(browser_config.get("debugger_host", "127.0.0.1") or "127.0.0.1").strip(),
"debugger_port": int(browser_config.get("debugger_port", 9222) or 9222),
"allow_launch_fallback": bool(browser_config.get("allow_launch_fallback", True)),
}
def _probe_existing_browser(debugger_host, debugger_port):
"""探测常驻浏览器调试端口是否可用,并返回浏览器端的元信息。"""
version_url = f"http://{debugger_host}:{debugger_port}/json/version"
response = requests.get(version_url, timeout=5)
response.raise_for_status()
return response.json()
def _detect_local_chrome_major_version():
"""检测本机 Chrome/Chromium 主版本号,尽量让 ChromeDriver 跟浏览器版本保持一致。"""
# 这里按不同平台准备一组常见的 Chrome/Chromium 可执行文件位置。
@@ -163,42 +111,6 @@ def _create_chrome_driver(options):
raise
def _attach_to_existing_browser(browser_config):
"""附着到已经启动的 Chrome 调试会话,避免重复创建和管理浏览器进程。"""
debugger_host = browser_config["debugger_host"]
debugger_port = browser_config["debugger_port"]
debugger_address = f"{debugger_host}:{debugger_port}"
# 先探测调试端口,能提前把“端口没开”与“连接成功”的原因写清楚,排查会更轻松。
browser_meta = _probe_existing_browser(debugger_host, debugger_port)
browser_version = browser_meta.get("Browser", "未知版本")
logger.info(f"准备复用常驻浏览器: {debugger_address},浏览器信息: {browser_version}")
options = _build_chrome_options(debugger_address=debugger_address)
driver = webdriver.Chrome(options=options)
return driver
def _create_browser_session(browser_config=None):
"""根据配置决定是复用常驻浏览器,还是回退到自管理浏览器。"""
normalized_config = _normalize_browser_config(browser_config)
if normalized_config["reuse_existing_browser"]:
try:
driver = _attach_to_existing_browser(normalized_config)
# attached_to_existing_browser=True 表示后续清理时不能去关闭用户自己的常驻浏览器。
return driver, True
except Exception as exc:
if not normalized_config["allow_launch_fallback"]:
logger.error(f"复用常驻浏览器失败,且已禁止启动备用浏览器: {exc}")
raise
logger.warning(f"复用常驻浏览器失败,准备回退到自管理浏览器: {exc}")
options = _build_chrome_options()
driver = _create_chrome_driver(options)
return driver, False
def download_image(url, session):
"""使用同步的 session 下载图片,确保 Cookie 一致"""
try:
@@ -226,13 +138,34 @@ def add_pdf_encryption(pdf_file, password="4000"):
logger.error(f"PDF加密失败: {e}")
def fetch_and_create_pdf(url, browser_config=None):
def fetch_and_create_pdf(url):
driver = None
attached_to_existing_browser = False
service = None
try:
# 优先复用外部常驻浏览器,避免插件自己创建和管理浏览器进程;
# 如果未启用复用,或者复用失败但允许回退,则继续使用原来的自管理浏览器方案。
driver, attached_to_existing_browser = _create_browser_session(browser_config)
options = uc.ChromeOptions()
# 规避检测的关键配置
# 在Linux服务器上使用headless模式
if os.name != 'nt':
options.headless = True
options.add_argument('--headless=new') # 使用新版headless模式
else:
options.headless = False
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-extensions')
options.add_argument('--disable-background-networking')
# 确保进程能被正确清理
options.add_argument('--disable-crash-reporter')
options.add_argument('--disable-in-process-stack-traces')
options.add_argument('--disable-logging')
options.add_argument('--disable-dev-shm-usage')
# 创建 driver 实例。
# 这里不再把版本硬编码成 144而是优先跟随本机 Chrome 版本;
# 如果首次启动时仍然遇到版本不匹配,再从报错里解析真实版本自动重试。
driver = _create_chrome_driver(options)
logger.info(f"正在访问: {url}")
driver.get(url)
@@ -331,30 +264,24 @@ def fetch_and_create_pdf(url, browser_config=None):
logger.exception(f"抓取异常: {e}")
return ""
finally:
# --- 确保Chrome进程被完全关闭 ---
if driver:
if attached_to_existing_browser:
try:
logger.debug("正在安全关闭浏览器...")
# 先关闭所有标签页和窗口
try:
# 这里是“借用”用户已经在运行的浏览器,只释放当前 WebDriver 会话即可。
# 明确不执行 close(),避免把用户正在用的标签页关掉。
logger.debug("当前使用的是外部常驻浏览器,仅释放 WebDriver 会话,不关闭浏览器本体")
driver.quit()
driver.close()
except Exception as e:
logger.error(f"释放外部浏览器会话时出错: {e}")
else:
try:
logger.debug("正在安全关闭自管理浏览器...")
try:
driver.close()
except Exception as e:
logger.warning(f"关闭浏览器窗口时出错: {e}")
logger.warning(f"关闭浏览器窗口时出错: {e}")
driver.quit()
logger.debug("浏览器已完全关闭")
except Exception as e:
logger.error(f"关闭浏览器时出错: {e}")
# 强制退出所有Chrome进程
driver.quit()
logger.debug("浏览器已完全关闭")
except Exception as e:
logger.error(f"关闭浏览器时出错: {e}")
# 只有当本次浏览器由当前任务自己拉起时,才需要额外清理潜在残留进程。
if os.name != 'nt' and not attached_to_existing_browser:
# 额外保险强制清理残留的Chrome进程仅Linux
if os.name != 'nt':
try:
import psutil
current_user = os.getlogin()
@@ -375,11 +302,10 @@ def fetch_and_create_pdf(url, browser_config=None):
logger.warning(f"强制清理Chrome进程时出错: {e}")
def pdf_file_path_undetected(browser_config=None):
def pdf_file_path_undetected():
try:
url = 'https://www.sehuatang.net/forum.php?mod=forumdisplay&fid=103&filter=typeid&typeid=481'
# 将插件配置透传给抓取函数,便于优先复用外部常驻浏览器会话。
pdf_path = fetch_and_create_pdf(url, browser_config=browser_config)
pdf_path = fetch_and_create_pdf(url)
if pdf_path:
logger.info(f"返回的PDF文件路径{pdf_path}")
return True, pdf_path