From 70f293db96ff69bd2c7223f5a5648819d1520e01 Mon Sep 17 00:00:00 2001 From: houhuan Date: Sun, 17 May 2026 18:50:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=87=AA=E5=8A=A8=E7=BB=95=E8=BF=87=20S?= =?UTF-8?q?SL=20=E8=AF=81=E4=B9=A6=E8=BF=87=E6=9C=9F=E7=9A=84=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 导出下载链接的 SSL 证书过期,Chrome 会弹出"您的连接不是私密连接"页面 阻止下载。新增 _bypass_ssl_interstitial() 方法,自动点击「高级」→「继续前往」 绕过 SSL 拦截,并在下载事件超时后做二次 SSL 绕过尝试。 Co-Authored-By: Claude Opus 4.7 --- automation/secsion.py | 88 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/automation/secsion.py b/automation/secsion.py index e996b58..bb806e8 100644 --- a/automation/secsion.py +++ b/automation/secsion.py @@ -196,13 +196,16 @@ class SecsionDownloader: # 点击导出报表并捕获下载 logger.info("点击导出报表...") - download_timeout = 120000 # 2 分钟 + download_timeout = 120000 # 2 分钟,给 SSL 绕过留足时间 try: async with page.expect_download(timeout=download_timeout) as download_info: await export_btn.click() logger.info("等待文件下载中...") + # 点击导出后,可能弹出 SSL 证书过期拦截页面 + await self._bypass_ssl_interstitial(page) + download = await download_info.value filename = download.suggested_filename save_path = os.path.join(self.download_dir, filename) @@ -211,8 +214,25 @@ class SecsionDownloader: return save_path except Exception as download_err: - # Playwright download 事件未触发,尝试文件系统兜底检测 + # Playwright download 事件未触发,尝试 SSL 绕过后再等 logger.warning(f"Playwright 下载事件捕获失败: {download_err}") + + # 二次尝试:可能 SSL 页面刚出现,再尝试绕过 + bypassed = await self._bypass_ssl_interstitial(page) + if bypassed: + logger.info("SSL 拦截已绕过,等待下载...") + try: + async with page.expect_download(timeout=30000) as dl_info: + pass + download = await dl_info.value + filename = download.suggested_filename + save_path = os.path.join(self.download_dir, filename) + await download.save_as(save_path) + logger.info(f"SSL 绕过后下载成功: {save_path}") + return save_path + except Exception: + logger.warning("SSL 绕过后仍未触发下载事件") + logger.info("尝试文件系统兜底检测...") # 等待一小段时间让可能的下载完成 @@ -290,6 +310,70 @@ class SecsionDownloader: # 过滤掉临时文件和调试截图 return [f for f in new_files if not f.endswith(('.crdownload', '.tmp')) and not f.startswith('debug_')] + async def _bypass_ssl_interstitial(self, page): + """ + 绕过 Chrome SSL 证书错误拦截页面 + + secsion.com 的导出下载链接 SSL 证书过期,Chrome 会弹 + "您的连接不是私密连接" 警告页。点 "高级" → "继续前往"。 + + Returns: + bool: 是否成功绕过(或无需绕过) + """ + try: + await page.wait_for_timeout(2000) + current_url = page.url + logger.debug(f"SSL 绕过检查: 当前 URL={current_url}") + + # 检查是否在 SSL 错误页面 + is_ssl_error_page = ( + 'chrome-error' in current_url or + 'security' in current_url.lower() or + await page.evaluate( + """() => { + return document.querySelector('#details-button') !== null || + document.querySelector('#proceed-link') !== null || + document.body?.innerText?.includes('您的连接不是私密连接') || + document.body?.innerText?.includes('NET::ERR_CERT'); + }""" + ) + ) + + if not is_ssl_error_page: + return False + + logger.info("检测到 SSL 证书错误拦截页面,尝试绕过...") + + # 点击 "高级" 按钮展开详情 + details_btn = page.locator('#details-button') + if await details_btn.count() > 0: + await details_btn.click() + await page.wait_for_timeout(500) + logger.info("已点击「高级」") + + # 点击 "继续前往 xxx(不安全)" + proceed_link = page.locator('#proceed-link') + if await proceed_link.count() > 0: + await proceed_link.click() + await page.wait_for_timeout(2000) + logger.info("已点击「继续前往(不安全)」,SSL 绕过成功") + return True + + # 备选:中文按钮文字 + unsafe_link = page.get_by_text('继续前往') + if await unsafe_link.count() > 0: + await unsafe_link.click() + await page.wait_for_timeout(2000) + logger.info("已点击「继续前往」,SSL 绕过成功") + return True + + logger.warning("SSL 拦截页面检测到但未找到绕过按钮") + return False + + except Exception as e: + logger.warning(f"SSL 绕过检查异常: {e}") + return False + async def _set_date(self, page, input_box, date_str): """ 设置 TDesign 日期选择器的值