fix: 自动绕过 SSL 证书过期的拦截页面
导出下载链接的 SSL 证书过期,Chrome 会弹出"您的连接不是私密连接"页面 阻止下载。新增 _bypass_ssl_interstitial() 方法,自动点击「高级」→「继续前往」 绕过 SSL 拦截,并在下载事件超时后做二次 SSL 绕过尝试。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+86
-2
@@ -196,13 +196,16 @@ class SecsionDownloader:
|
|||||||
|
|
||||||
# 点击导出报表并捕获下载
|
# 点击导出报表并捕获下载
|
||||||
logger.info("点击导出报表...")
|
logger.info("点击导出报表...")
|
||||||
download_timeout = 120000 # 2 分钟
|
download_timeout = 120000 # 2 分钟,给 SSL 绕过留足时间
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with page.expect_download(timeout=download_timeout) as download_info:
|
async with page.expect_download(timeout=download_timeout) as download_info:
|
||||||
await export_btn.click()
|
await export_btn.click()
|
||||||
logger.info("等待文件下载中...")
|
logger.info("等待文件下载中...")
|
||||||
|
|
||||||
|
# 点击导出后,可能弹出 SSL 证书过期拦截页面
|
||||||
|
await self._bypass_ssl_interstitial(page)
|
||||||
|
|
||||||
download = await download_info.value
|
download = await download_info.value
|
||||||
filename = download.suggested_filename
|
filename = download.suggested_filename
|
||||||
save_path = os.path.join(self.download_dir, filename)
|
save_path = os.path.join(self.download_dir, filename)
|
||||||
@@ -211,8 +214,25 @@ class SecsionDownloader:
|
|||||||
return save_path
|
return save_path
|
||||||
|
|
||||||
except Exception as download_err:
|
except Exception as download_err:
|
||||||
# Playwright download 事件未触发,尝试文件系统兜底检测
|
# Playwright download 事件未触发,尝试 SSL 绕过后再等
|
||||||
logger.warning(f"Playwright 下载事件捕获失败: {download_err}")
|
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("尝试文件系统兜底检测...")
|
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_')]
|
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):
|
async def _set_date(self, page, input_box, date_str):
|
||||||
"""
|
"""
|
||||||
设置 TDesign 日期选择器的值
|
设置 TDesign 日期选择器的值
|
||||||
|
|||||||
Reference in New Issue
Block a user