最近在折腾一个小功能。

我想给 QQ 机器人加一个通知:当玩家上线的时候,自动打开 BlueMap 网页,截一张图,然后发到群里面。

本质上就是后台启动浏览器,打开网页,截图,再把图片发出去。

技术上我用的是 Playwright。
它是一个无头浏览器方案,可以在没有图形界面的情况下直接启动 Chromium,然后加载网页、执行脚本、截图,非常适合服务器环境。

本地开发阶段一切正常。

问题基本全部出现在部署到服务器之后。


第一个坑:Playwright 浏览器下载不了

服务器的网络环境非常差。

pip install playwright 装完之后,还需要额外执行:

python -m playwright install chromium

这个命令会下载一整套无头 Chromium,体积非常大。

在国内服务器上:

  • 下载慢

  • 容易失败

  • Docker build 阶段基本不可用

也就是说,正常在线安装浏览器这条路根本走不通。


我的做法:直接离线拷贝浏览器

我最后用了一个比较投机取巧的方法。

在我的 Linux 笔记本上挂 VPN 先把 Chromium 下载好,然后把整个浏览器目录直接复制出来,放进 Docker 的工作目录里。

文件夹在:/home/[Username]/.cache/ms-playwright/ ,Username 是你的用户名。root 就在 root 的.cache 下

再通过设置 docker 环境变量:

PLAYWRIGHT_BROWSERS_PATH=/workspace/ms-playwright

强制让 Playwright 使用这个离线浏览器。

等于完全跳过在线下载这一步。


第二个坑:浏览器在,但启动不了

结果浏览器文件虽然在,但还是启动失败。

报错基本都是:

error while loading shared libraries: libxxx.so: cannot open shared object file

后来才意识到:

浏览器本体可以离线拷贝,
但 Chromium 依赖的系统动态库还是必须安装。

否则根本跑不起来。

所以即使不执行 playwright install,Docker 里也必须把 Chrome 的运行时依赖补齐。


另外一个问题:⬜字体和中文字体

默认环境没有字体,中文等字符会显示⬜,此外 Linux 下的 Chrome / Playwright 经常会调用到日文字形,截图出来很奇怪。

所以我顺便装了 Noto CJK 字体,并且手动调整字体优先级,让:

SC > TC > JP

这样中文显示就正常了。


最终 Dockerfile

下面就是我最后能稳定运行的完整 Dockerfile。

# =========================================================
# Base: Python slim
# =========================================================
FROM python:3.12-slim

# =========================================================
# 时区
# =========================================================
ENV TZ=Asia/Shanghai
RUN apt update && apt -y install tzdata \
    && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
    && echo $TZ > /etc/timezone

# =========================================================
# 系统依赖 + 浏览器运行库 + 字体
# =========================================================
RUN apt update && apt -y install \
        # ===== chromium / chrome 运行时依赖 =====
        libnss3 \
        libnspr4 \
        libxss1 \
        libatk-bridge2.0-0 \
        libgtk-3-0 \
        libasound2 \
        libcups2 \
        libdrm2 \
        libgbm1 \
        libx11-xcb1 \
        libxcomposite1 \
        libxdamage1 \
        libxrandr2 \
        libpangocairo-1.0-0 \
        libpango-1.0-0 \
        libxshmfence1 \
        libglu1-mesa \
        ca-certificates \
        curl wget gnupg unzip \
        \
        # ===== 字体(关键) =====
        fontconfig \
        fonts-noto \
        fonts-noto-cjk \
        fonts-noto-color-emoji \
        fonts-dejavu-core \
        fonts-dejavu-extra \
        fonts-liberation \
    && apt clean \
    && rm -rf /var/lib/apt/lists/*

# =========================================================
# 中文字体优先级:SC > TC > JP
# 解决 Chrome / Playwright 使用日文字形问题
# =========================================================
RUN mkdir -p /etc/fonts/conf.d && \
    printf '%s\n' \
'<?xml version="1.0"?>' \
'<!DOCTYPE fontconfig SYSTEM "fonts.dtd">' \
'<fontconfig>' \
'  <alias>' \
'    <family>sans-serif</family>' \
'    <prefer>' \
'      <family>Noto Sans CJK SC</family>' \
'      <family>Noto Sans CJK TC</family>' \
'      <family>Noto Sans CJK JP</family>' \
'    </prefer>' \
'  </alias>' \
'  <alias>' \
'    <family>serif</family>' \
'    <prefer>' \
'      <family>Noto Serif CJK SC</family>' \
'      <family>Noto Serif CJK TC</family>' \
'      <family>Noto Serif CJK JP</family>' \
'    </prefer>' \
'  </alias>' \
'</fontconfig>' \
> /etc/fonts/conf.d/99-cjk-prefer-sc.conf && \
    fc-cache -f -v

# =========================================================
# 工作目录
# =========================================================
WORKDIR /workspace

# =========================================================
# pip 国内镜像
# =========================================================
ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
ENV PIP_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn

# =========================================================
# 虚拟环境
# =========================================================
RUN python -m venv /workspace/.venv \
    && /workspace/.venv/bin/pip install --upgrade pip setuptools wheel

ENV PATH="/workspace/.venv/bin:$PATH"

启动命令

最后用下面这条命令作为 dokcer 启动命令启动:

bash -c "PLAYWRIGHT_BROWSERS_PATH=/workspace/ms-playwright ./venv/bin/python app.py"

核心就是指定浏览器路径,让 Playwright 使用离线拷贝的 Chromium。

其实用 docker 环境变量指定也是一样的,我 bash 更熟悉一点,就投机取巧了。


总结

这次踩坑主要就三件事:

  • 国内服务器下载 Chromium 很痛苦

  • 离线浏览器还需要系统动态库

  • Linux 字体优先级会导致中文异常

最后的做法也很简单:

浏览器离线拷贝 + 手动安装依赖库 + 调整字体。

记录一下,省得以后再踩一次。