You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现URL截图与SSL证书查询的异步展示?async/await尝试未果

解决方案思路

核心是把「获取SSL证书信息」和「生成页面截图」拆分为两个独立任务:

  1. 同步快速返回SSL过期时间与基础页面框架
  2. 后台异步执行Selenium截图任务,完成后自动更新前端显示

一、后端架构调整(PHP + Python 协同)

1. 拆分任务流程

用户提交URL后,PHP先同步完成快速的SSL信息查询,立即返回前端页面;同时启动后台异步进程,调用Python脚本处理耗时的截图任务,彻底脱离主请求流程。

2. PHP端实现后台任务

方式一:用exec启动独立后台进程(快速上手)

// 1. 同步获取SSL证书过期时间(示例实现)
$url = $_POST['url'];
$sslExpiry = getSslExpiryDate($url);

// 2. 启动后台截图进程,避免阻塞主请求
$pythonScript = __DIR__ . '/selenium_function.py';
// 用>/dev/null 2>&1 &让进程脱离终端,不占用主请求资源
exec("python3 {$pythonScript} {$url} > /dev/null 2>&1 &");

// 3. 立即返回结果,标记截图状态为"生成中"
echo json_encode([
    'ssl_expiry' => $sslExpiry,
    'screenshot_status' => 'pending',
    'target_url' => $url
]);

// 辅助函数:获取SSL证书过期时间
function getSslExpiryDate($url) {
    $parsed = parse_url($url);
    $host = $parsed['host'];
    $context = stream_context_create(['ssl' => ['capture_peer_cert' => true]]);
    $socket = stream_socket_client("ssl://{$host}:443", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
    $cert = stream_context_get_params($socket)['options']['ssl']['peer_certificate'];
    $certInfo = openssl_x509_parse($cert);
    return date('Y-m-d H:i:s', $certInfo['validTo_time_t']);
}

方式二:消息队列(高并发场景推荐)

如果需要处理大量请求,用Redis/RabbitMQ做消息队列更可靠:

  1. PHP将URL发送到消息队列
  2. 单独编写Python守护进程,监听队列并执行截图任务
  3. 截图完成后,将结果路径存入Redis,关联请求标识

3. Python截图脚本调整

让脚本支持接收URL参数,完成截图后保存状态到Redis,方便前端查询:

import sys
from selenium import webdriver
import redis
import hashlib

def generate_screenshot(url):
    # 无头模式启动Chrome
    options = webdriver.ChromeOptions()
    options.add_argument('--headless=new')
    options.add_argument('--disable-gpu')
    driver = webdriver.Chrome(options=options)
    
    try:
        driver.get(url)
        driver.implicitly_wait(5)  # 等待元素加载
        # 生成唯一文件名
        url_hash = hashlib.md5(url.encode()).hexdigest()
        save_path = f"screenshots/{url_hash}.png"
        driver.save_screenshot(save_path)
        
        # 保存结果到Redis
        r = redis.Redis(host='localhost', port=6379, db=0)
        r.set(f"screenshot:{url}", save_path)
        r.expire(f"screenshot:{url}", 3600)  # 1小时后自动清理
    finally:
        driver.quit()

if __name__ == "__main__":
    target_url = sys.argv[1]
    generate_screenshot(target_url)

二、前端交互实现

前端拿到初始结果后,通过轮询或WebSocket监听截图状态,完成后自动更新显示:

1. 轮询方案(简单易实现)

<div class="result-section">
    <h3>SSL证书信息</h3>
    <p>过期时间:<?= $sslExpiry ?></p>
</div>
<div id="screenshot-area">
    <p>截图生成中,请稍候...</p>
</div>

<script>
const targetUrl = "<?= $url ?>";
const checkInterval = setInterval(() => {
    fetch(`/check_screenshot.php?url=${encodeURIComponent(targetUrl)}`)
        .then(res => res.json())
        .then(data => {
            if (data.status === 'done') {
                clearInterval(checkInterval);
                document.getElementById('screenshot-area').innerHTML = `
                    <h3>页面截图</h3>
                    <img src="/${data.path}" alt="目标页面截图" style="max-width: 100%;">
                `;
            }
        });
}, 2000); // 每2秒查询一次状态

对应的check_screenshot.php:

<?php
$url = $_GET['url'];
$redis = new Redis();
$redis->connect('localhost', 6379);
$screenshotPath = $redis->get(f"screenshot:{url}");

if ($screenshotPath) {
    echo json_encode(['status' => 'done', 'path' => $screenshotPath]);
} else {
    echo json_encode(['status' => 'pending']);
}
?>

2. WebSocket方案(实时性更佳)

如果追求无延迟更新,可使用PHP的Ratchet扩展搭建WebSocket服务,截图完成后主动向前端推送结果,替代轮询。

三、之前async/await无效的原因

你之前的尝试没达到预期,大概率是因为:

  • PHP的async/await仅适用于协程处理HTTP请求或内部IO,调用外部Python进程时若未彻底脱离主请求,仍会阻塞。
  • 原生Selenium是同步阻塞的,即使Python用async语法,也无法让截图任务真正异步执行,必须将任务完全剥离到独立进程中。

内容的提问来源于stack exchange,提问作者yushi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:39:56