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

基于FastAPI复用Chrome实例实现HTML转PDF:存在哪些潜在问题?

问题

我正在开发一个按需将HTML渲染为PDF文件的REST服务。调研多种方案后,发现Chrome本身的PDF渲染效果最佳,因此打算借助pyhtml2pdf库,将Selenium和Chromedriver封装到FastAPI中。但使用该库时,每次API调用都会启动新的Chrome实例,导致接口调用速度慢且内存占用高。于是我参考该库的部分代码,改为在服务启动时创建单个Chrome实例,通过全局对象供接口调用,这使速度提升了10倍,内存占用也大幅降低。请问这种将Selenium/Chrome封装到FastAPI接口中的方案存在哪些弊端?我忽略了什么?

实现代码

import sys
import json
import base64
import os
import re
import requests

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import staleness_of
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By

from fastapi import FastAPI, Query
from fastapi.responses import HTMLResponse
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
from fastapi.middleware.cors import CORSMiddleware

webdriver_options = Options()
webdriver_prefs = {}
driver = None

webdriver_options.add_argument("--headless")
webdriver_options.add_argument("--disable-gpu")
webdriver_options.add_argument("--no-sandbox")
webdriver_options.add_argument("--disable-dev-shm-usage")
webdriver_options.experimental_options["prefs"] = webdriver_prefs

webdriver_prefs["profile.default_content_settings"] = {"images": 2}

service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=webdriver_options)

@asynccontextmanager
async def lifespan(app: FastAPI):
    global driver
    print("Staring PDF service")
    yield
    driver.quit()

app = FastAPI(lifespan=lifespan)

@app.get("/get_pdf/")
def get_pdf(serial: str):
    global driver

    url = 'https://example.com/templates/serial.php?serial=' + serial

    html = requests.get(url)
    local_html_file = serial + "_htmldata.html"
    f = open(local_html_file, "w")
    f.write(html.text)
    f.close()

    local_pdf_file = serial + ".pdf"

    driver.get(f'file:///home/ubuntu/pdf2/'+local_html_file)

    calculated_print_options = {
        "landscape": False,
        "displayHeaderFooter": False,
        "printBackground": True,
        "preferCSSPageSize": True,
        "marginLeft": 0.4,
        "marginRight": 0.5,
        "scale": 0.75,
    }

    result = __send_devtools(
        driver, "Page.printToPDF", calculated_print_options)

    with open(local_pdf_file, "wb") as file:
        file.write(base64.b64decode(result["data"]))

    os.remove(local_html_file)

    return "done"

def __send_devtools(driver, cmd, params={}):
    resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
    url = driver.command_executor._url + resource
    body = json.dumps({"cmd": cmd, "params": params})
    response = driver.command_executor._request("POST", url, body)

    if not response:
        raise Exception(response.get("value"))

    return response.get("value")


# Run the FastAPI application using uvicorn server
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("pdf_rest:app", host="0.0.0.0", port=8000, reload=True)
潜在弊端与忽略的问题
  • 并发请求冲突:FastAPI默认多线程运行,单个Chrome实例全局共享,多请求同时调用driver.get()或执行DevTools命令时会出现上下文混乱——比如A请求刚打开页面,B请求就覆盖当前页面,导致两个请求的PDF渲染结果全部出错。Selenium的WebDriver实例本身不具备线程安全性,多线程并发访问必然引发竞态条件。
  • 请求间状态污染:Chrome实例的状态会在请求间残留,比如前一个请求的Cookie、LocalStorage、页面缓存甚至JS执行环境变量,都可能干扰下一个请求的渲染结果。即便你禁用了图片加载,其他页面遗留的状态仍可能影响后续渲染。
  • 长期运行的稳定性问题:持续运行的Chrome实例会逐渐积累内存碎片、残留进程,比如页面渲染后的后台进程未彻底释放,时间一长会导致内存占用缓慢上升,甚至出现Chrome崩溃。一旦Chrome崩溃,整个服务会直接失效,必须重启才能恢复。
  • 错误隔离缺失:单个Chrome实例出错后,所有后续请求都会受影响。比如某请求渲染的HTML包含恶意JS或死循环,导致Chrome进程卡死,整个服务将无法处理新请求,直到重启Chrome。而多实例方案至少能隔离单个请求的错误影响范围。
  • 扩展性受限:当请求量增大时,单个Chrome实例的处理能力有限,无法通过增加线程提升吞吐量(因为线程安全问题)。你只能通过部署多个服务实例扩容,无法在单个服务内利用多线程或进程提升处理能力。
  • 文件操作并发风险:代码中用serial作为文件名,若业务上允许serial重复,会出现文件写入/读取冲突,导致文件内容被覆盖或读取错误。且文件操作是同步的,多线程下会加剧阻塞。
  • 生命周期管理漏洞:你在lifespan中退出driver,但如果服务异常终止(比如强制kill),Chrome进程可能无法正常退出,变成僵尸进程占用系统资源。另外,当前代码在服务启动时就创建了driver,而非lifespan的启动阶段,若开启uvicorn的reload=True,可能会残留多个Chrome进程。
  • 缺乏超时与容错机制:代码未对页面加载、PDF渲染设置超时,若某个页面加载缓慢或卡住,会阻塞整个线程,影响其他请求处理。同时没有重试逻辑,一旦渲染失败直接返回错误,无容错能力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 21:24:52