如何在 Linux 上将 CHM 转换为单个 PDF:无垃圾和失效链接
Source: Dev.to
如果你有 bsm_api.chm 并且需要得到 一个 正常的 PDF,而不是「900 个 HTML + 泪水」的「文件夹」,下面是可行的流水线:
CHM → HTML → (сборка) → PDF
最终将会得到:
- 一个
manual.pdf; - 没有损坏的内部链接(在原文件允许的范围内);
- 正常的导航/目录(如果 CHM 本身是合格的)。
问题是什么
CHM — 不是“单个文本文件”,而是一个容器:HTML + 图片 + 目录 + 内部链接。
常见痛点:
| 问题 | 为什么会发生 |
|---|---|
| 转换器生成 1000 页而不是一个 PDF | 对每个 HTML 文件单独处理 |
| 链接和字符编码被破坏 | 未考虑相对路径和不同的 charset |
| PDF 看起来像 2007 年的截图 | 将光栅图像而不是文本进行转换 |
工作方案
1️⃣ 安装工具
Ubuntu/Debian
sudo apt update
sudo apt install -y \
extractchm \
wkhtmltopdf \
python3 \
python3-bs4 \
python3-lxml
如果没有 extractchm(很少见,但有时会),可以从 chmlib 安装:
sudo apt install -y chmlib-tools # 会提供 extract_chmLib 工具
检查安装
extractchm -h || true
wkhtmltopdf --version
2️⃣ 解压 CHM 为 HTML
# 创建工作文件夹
mkdir -p ~/work/chm2pdf/{src,html,out}
cp bsm_api.chm ~/work/chm2pdf/src/
cd ~/work/chm2pdf
方案 A(extractchm)
extractchm -o html src/bsm_api.chm
方案 B(extract_chmLib)
extract_chmLib src/bsm_api.chm html
检查是否解压成功
ls -la html | head
find html -maxdepth 2 -type f | head
3️⃣ 找到“入口点”(主页)
通常是 index.html、default.html、start.html 或目录中的其他文件。
ls html | grep -iE 'index|default|start|main' || true
如果不明显——寻找最大的 HTML 文件:
find html -type f -iname '*.html' -printf '%s\t%p\n' | sort -n | tail -20
4️⃣ 合并为单一 HTML(重要步骤)
wkhtmltopdf 在接收 单个 HTML 页面时效果更好,而不是上千个。
下面是一个最小的 Python 脚本,实现以下功能:
- 读取目录(如果找到
.hhc/toc文件则使用目录); - 若没有目录,则按字母顺序拼接 HTML(效果稍差,但可用);
- 删除
<script>、<iframe>等噪声标签; - 将相对链接转换为本地路径。
tools/merge.py
#!/usr/bin/env python3
import os
import re
from pathlib import Path
from bs4 import BeautifulSoup
ROOT = Path("html").resolve()
OUT = Path("out")
OUT.mkdir(parents=True, exist_ok=True)
def list_html_files():
"""
Универсальный fallback — склеим HTML по имени.
При желании можно добавить парсер .hhc/.hhk.
"""
files = sorted(ROOT.rglob("*.html"))
# отбрасываем мелкие служебные файлы
files = [p for p in files if p.is_file() and p.stat().st_size > 200]
return files
def clean_body(html_path: Path) -> str:
data = html_path.read_bytes()
# пробуем несколько кодировок
for enc in ("utf-8", "cp1251", "windows-1251", "latin-1"):
try:
text = data.decode(enc)
break
except UnicodeDecodeError:
continue
else:
text = data.decode("utf-8", "ignore")
soup = BeautifulSoup(text, "lxml")
# выкидываем потенциальный мусор
for tag in soup(["script", "iframe", "noscript"]):
tag.decompose()
body = soup.body or soup
# нормализуем якоря и локальные ресурсы
for a in body.find_all("a", href=True):
href = a["href"].strip()
# убираем внешние ссылки и mailto
if href.startswith(("http://", "https://", "mailto:")):
continue
a["href"] = href.replace("\\", "/") # windows‑style слеши
# добавляем заголовок‑разделитель
title = soup.title.get_text(strip=True) if soup.title else html_path.name
header = f"\n## {title}\n\n"
return header + str(body)
def main():
parts = []
for p in list_html_files():
try:
parts.append(clean_body(p))
except Exception as e:
print(f"skip {p}: {e}")
merged = """
CHM merged
body { font-family: Arial, sans-serif; line-height: 1.45; }
pre, code { white-space: pre-wrap; word-wrap: break-word; }
h1 { page-break-before: always; }
""" + "".join(parts) + """
"""
(OUT / "merged.html").write_text(merged, encoding="utf-8")
print("OK -> out/merged.html")
if __name__ == "__main__":
main()
运行脚本
mkdir -p tools out
python3 tools/merge.py
ls -la out/merged.html
5️⃣ 将 merged.html 转换为 PDF
wkhtmltopdf \
--enable-local-file-access \
--encoding utf-8 \
--page-size A4 \
--margin-top 12 --margin-bottom 12 \
--margin-lef
检查结果
ls -lah out/manual.pdf
file out/manual.pdf
快速 sanity‑check: 从 PDF 中提取几行文本。
python3 - <<'PY'
import subprocess
p = subprocess.run(
["pdftotext", "out/manual.pdf", "-"],
capture_output=True, text=True
)
print(p.stdout[:800])
PY
如果缺少 pdftotext:
sudo apt install -y poppler-utils
常见错误及其解决方案
| 错误 | 原因 | 解决方案 |
|---|---|---|
❌ Blocked access to file / 图片不可见 | wkhtmltopdf 默认会阻止本地资源 | 添加 --enable-local-file-access(见上文) |
| ❌ 乱码代替俄文 | HTML 使用 CP1251 编码,但被合并为 UTF‑8 | merge.py 已经尝试使用 cp1251/windows-1251。如果仍然有问题——检查实际编码:`file -i html/*.html |
| ❌ 目录缺失,章节顺序混乱 | CHM 将结构存储在 .hhc/.hhk 中,而我们是按“字母顺序”合并的 | 为特定的 CHM 编写目录解析器(通常 20‑40 行)。如果需要帮助——发送 html/ 中的文件列表,我会逐一指点。 |
适用场景
- 本地 – 快速获取 PDF,交给 LLM/同事/客户。
- CI/CD – 自动生成 PDF 文档(如果 CHM 作为制品)。
- VPS – 可以在没有 GUI 的情况下运行,仅使用控制台。
另请参阅
- 在 Linux 中搜索文件:适用于 Ubuntu/CentOS 的命令 — 诊断与
find。 find:查找大文件 — 当 PDF 突然“变大”。
如果需要针对特定 CHM 进行脚本改进,请告诉我,我可以帮助适配目录解析器。
rsync:安全复制并显示进度 — 转移已解压的 CHM 或已完成的 PDF