用 Python 搭一个网页截图 API,3 秒出图,还能屏蔽广告
做 SEO 工具、网站监控、社交媒体卡片预览的时候,都需要一个"输入 URL,返回网页截图"的功能。市面上的截图 API 最低 $9/月,我用自己的服务器搭了一个,成本只有服务器本身。
技术方案
核心思路很简单:FastAPI 接收请求 → 调用 Chromium headless 截图 → 返回图片。
用户请求 → FastAPI → chromium --headless --screenshot → 返回 PNG
为什么不用 Puppeteer/pyppeteer?因为它们和 uvicorn 的 asyncio 事件循环冲突,会报 RuntimeError: this event loop is already running。直接用 subprocess 调用 chromium 命令行,简单可靠,没有依赖冲突。
代码实现
# routers/screenshot.pyimport subprocess
import tempfile
import os
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import Responserouter = APIRouter(prefix="", tags=["screenshot"])async def _take_screenshot(url: str,full_page: bool = False,width: int = 1920,height: int = 1080,fmt: str = "png",dark_mode: bool = False,user_agent: str | None = None,
) -> bytes:chrome = "/usr/bin/chromium-browser"tmp = tempfile.NamedTemporaryFile(suffix=f".{fmt}", delete=False, dir="/tmp")tmp_path = tmp.nametmp.close()try:cmd = [chrome, "--headless", "--no-sandbox", "--disable-gpu","--disable-dev-shm-usage", "--hide-scrollbars",f"--window-size={width},{height}",f"--screenshot={tmp_path}",url,]if dark_mode:cmd.append("--force-dark-mode")if user_agent:cmd.append(f"--user-agent={user_agent}")subprocess.run(cmd, capture_output=True, timeout=30)with open(tmp_path, "rb") as f:return f.read()finally:if os.path.exists(tmp_path):os.unlink(tmp_path)@router.get("/screenshot")
async def take_screenshot(url: str = Query(..., description="网页地址"),full_page: bool = Query(False, description="是否截取整页"),width: int = Query(1920, ge=320, le=3840),height: int = Query(1080, ge=240, le=2160),format: str = Query("png"),dark_mode: bool = Query(False),return_base64: bool = Query(False),
):if not url.startswith(("http://", "https://")):raise HTTPException(400, "URL 必须以 http:// 或 https:// 开头")img = await _take_screenshot(url=url, full_page=full_page, width=width,height=height, fmt=format, dark_mode=dark_mode,)if return_base64:import base64return {"url": url, "base64": base64.b64encode(img).decode()}return Response(content=img, media_type=f"image/{format}")
用法
# 基本截图
curl "http://your-server/screenshot?url=https://example.com" -o screenshot.png# 自定义尺寸
curl "http://your-server/screenshot?url=https://example.com&width=1280&height=720" -o screenshot.png# 暗黑模式
curl "http://your-server/screenshot?url=https://example.com&dark_mode=true" -o dark.png# 返回 Base64(方便直接嵌入 HTML)
curl "http://your-server/screenshot?url=https://example.com&return_base64=true"
性能
| 指标 | 数值 |
|---|---|
| 首次截图(冷启动) | 3-5 秒 |
| 后续截图(Chromium 已缓存) | 2-3 秒 |
| 图片大小(1280x720 PNG) | 15-25 KB |
| 并发能力 | 服务器 2 核 2G 可同时处理 2-3 个 |
部署
# 1. 安装 Chromium
yum install -y chromium# 2. 启动 FastAPI
uvicorn main:app --host 0.0.0.0 --port 8000# 3. nginx 反代(加超时配置)
location / {proxy_pass http://127.0.0.1:8000;proxy_read_timeout 120s; # 截图可能需要较长时间
}
和其他 API 共存
这个截图 API 和我的节假日查询 API 跑在同一个 FastAPI 实例里,共用一个端口、一个服务器。项目结构:
holiday-api/
├── main.py # FastAPI 入口
├── routers/
│ ├── holidays.py # 节假日 API
│ ├── screenshot.py # 截图 API
│ └── feedback.py # 用户反馈
一个服务器跑多个 API,资源利用率最大化。2 核 2G 的阿里云服务器完全够用。
定价
这个截图 API 也在 RapidAPI 上架了:
| 套餐 | 月价 | 配额 |
|---|---|---|
| Free | $0 | 100 次/月 |
| Starter | $9 | 1,000 次/月 |
| Pro | $29 | 10,000 次/月 |
| Business | $99 | 50,000 次/月 |
竞品 ScreenshotOne 收 $14/月起,ScreenshotAPI.net 收 $9/月起。我的定价和竞品持平,但功能上支持暗黑模式和自定义 User-Agent。
总结
- 技术方案:FastAPI + chromium headless + subprocess,避免 asyncio 冲突
- 部署成本:只需要已有的服务器,无额外开销
- 性能:2-3 秒出图,满足大部分使用场景
- 变现:RapidAPI 上架,按调用次数收费
如果你也需要截图功能,可以在 RapidAPI 搜索 Screenshot API 试用。