简介:《Runningman游戏大全》是一份面向团建组织者、聚会策划人及综艺节目编导的游戏方案合集,旨在解决活动创意枯竭、规则设计零散的问题。整份资料共1个PDF文档,约41KB,体量轻便,便于手机或电脑随时查阅。目前已有81人学习下载,说明其在实际活动组织中有一定参考价值。文档系统收录了Running Man节目中泥潭抢裤子、撕名牌、指压板接力、一杯茶时间、水池飞椅、无穷花开及情景设置等经典项目,并补充了大量日常易实施的互动游戏,例如脸部饼干挑战、衣服夹子拔河、看眼色、记歌词、乒乓球识字、倒水游戏、抓小偷与抢椅子等。每个游戏均给出基本规则、胜负判定与可选变体,覆盖体力对抗、智力推理、快速反应和团队协作;读者可直接按规则落地组织,也能结合场地与人数灵活改编。
1. Runningman游戏大全.pdf 到手先别急着转 Word
同事甩过来一个 300 多页的《Runningman游戏大全.pdf》,需求通常是"帮我整理成能搜的文档"。绝大多数人第一反应是丢进 pdf 编辑器里导出 Word,结果分栏的游戏规则错位、图片和表格标题脱节、页码混进正文,改回来的时间比重新录入还长。这份 PDF 真正决定后续工作量的,不是它有多少页,而是它里面有没有一层可提取的文本层,以及这层文本用的是哪种编码。有文本层,整条链路是"解析—清洗—索引";没有文本层,就得切换到"渲染—图像预处理—OCR",两套方案的工具和坑完全不同。这篇按一线处理顺序讲:先给 PDF 做结构体检,再做批量解析与切分,然后落到 Markdown 和全文索引,最后补浏览器预览与印前校对的技巧。
2. 拆开 Runningman游戏大全.pdf 的物理结构:对象流、字体与文本层
拿到一个 pdf 文档,先别写业务代码。PDF 不是"带格式的文本",而是一堆间接对象加一张交叉引用表:页面内容在内容流里,内容流又可能被 FlateDecode 压缩,文字用什么字形、字形对应哪个 Unicode,全靠字体资源里的编码表决定。跳过这一步直接 extract_text,得到的往往是空白或者一堆问号,然后误判成"这是扫描件"。先把结构摸清,后面每一步都能省下返工。
2.1 用 qpdf 和 pdffonts 给 Runningman游戏大全.pdf 做体检
三条命令就能定位问题所在,不用装重型 pdf 编辑器。
# 1. 页数、版本、是否加密 qpdf --show-npages "Runningman游戏大全.pdf" qpdf --show-encryption "Runningman游戏大全.pdf" # 2. 结构自检,能查出 xref 损坏、流长度不对 qpdf --check "Runningman游戏大全.pdf" # 3. 列出字体:重点看 type 和 emb 两列 pdffonts -f 1 -l 5 "Runningman游戏大全.pdf"--show-npages给总页数,用来估算后续切分粒度;--show-encryption用来判断有没有打开密码或权限位限制,有权限位限制的 PDF 在部分库里会直接抛异常;--check报出的 warning 不一定致命,但出现 "damaged" 时最好先qpdf --qdf修复一份副本再处理。pdffonts的输出里,emb=yes表示字体已嵌入,emb=no表示依赖系统字库,type是 TrueType、Type0、Type1 还是 CID 字体。如果整份文档的字体列全是空的,那基本可以确定是纯图片扫描件,直接跳到第 3 章的 OCR 路线。
| 字体类型 | 常见来源 | 文本提取表现 | 应对方式 |
|---|---|---|---|
| TrueType 子集 | Word、WPS 导出 | 一般正常,偶发缺字 | 直接用 pdfplumber |
| Type0 / CID | 中文排版软件、LaTeX | 有 ToUnicode 才正常 | 检查 CMap,必要时走 OCR |
| Type1 | 早期排版、pdf 转曲 前 | 多数字符无法映射 | 渲染后 OCR |
| 无字体记录 | 扫描仪、截图打印 | 提取为空 | 渲染 + 图像预处理 + OCR |
2.2 对象流与 Filter:为什么有的页提取出来是空白
内容流被压缩是常态,FlateDecode只是 zlib 压缩,解压后是 PDF 的操作符序列,像BT /F1 12 Tf (游戏名) Tj ET这种。截断、乱码、整页空白,通常来自三种情况:内容流被ObjStm打包进对象流,老的解析器找不到;用了LZWDecode或ASCII85Decode的组合过滤;或者是页面用了 Form XObject 嵌套,文字实际画在另一个流里。qpdf 解压成 QDF 格式后可以直接看纯文本内容流,这是排查的最快路径。
# 解压全部流,输出可读的 QDF 版本,方便肉眼确认文字是否真实存在 qpdf --qdf --object-streams=disable "Runningman游戏大全.pdf" /tmp/debug.qdf # 在 QDF 里搜索操作符,看页面到底画了什么 grep -n "Tj\|TJ" /tmp/debug.qdf | head -20--object-streams=disable把对象从ObjStm里拆出来,这一步对排查"文本提取为空"极其有效。grep出来的Tj/TJ操作符前后就是实际文字,如果这里能看到中文字符,说明文本层存在,问题出在提取库的编码映射;如果只有Do和图像 XObject,那才是真扫描件。
2.3 字体子集与 ToUnicode CMap:中文游戏名提取成乱码的根因
最烦人的情况是:文本能提取出来,但全是\u0001\u0002或者一串英文字母。原因是这类 PDF 用了字体子集化,字体里只嵌入了用到的几十个字形,字符编码是自定义的 CID,必须靠ToUnicodeCMap 才能反查到真正的 Unicode。pdf 解析 遇到这种文档,pypdf 和早期版本的 pdfminer 会直接给出乱码,PyMuPDF 和 pdfplumber 因为内部做了 CMap 回退处理,命中率高得多。判断方法很简单:
import fitz doc = fitz.open("Runningman游戏大全.pdf") page = doc[10] # 拿原始字典,看 CID 到 Unicode 的映射是否缺失 raw = page.get_text("rawdict") for block in raw["blocks"][:1]: for line in block.get("lines", []): for span in line["spans"]: print(span["font"], repr(span["text"])) # 对照:如果 rawdict 正常而下面这行为空或乱码,就是 CMap 问题 print(repr(page.get_text("text")[:120]))get_text("rawdict")返回的是带字体名、字号、坐标的完整结构,span["font"]能告诉你这段文字来自哪个字体。如果字体名带+前缀(如ABCDEF+SimSun),说明是子集字体。这时优先换 PyMuPDF 的textpage提取,仍失败就退到渲染加 OCR,不要在编码问题上死磕——一个 300 页的合集,OCR 全量跑一遍的成本远低于逐页调 CMap。
2.4 pdfplumber 与 PyMuPDF 双引擎对照提取
两个库定位不同:pdfplumber 强在字符级坐标和表格线识别,适合抓规则表;PyMuPDF 强在速度和结构还原,适合全量文本。稳妥做法是先小样本对照,选命中率高的那个跑全量。
import pdfplumber, fitz, re SAMPLE = "Runningman游戏大全.pdf" def probe(pdf_path, pages=(0, 1, 2)): with pdfplumber.open(pdf_path) as pdf: for i in pages: t = pdf.pages[i].extract_text() or "" # 中文字符占比,低于 0.3 说明提取质量差 ratio = len(re.findall(r"[\u4e00-\u9fff]", t)) / max(len(t), 1) print(f"[plumber] p{i} chars={len(t)} cn_ratio={ratio:.2f}") doc = fitz.open(pdf_path) for i in pages: t = doc[i].get_text("text") ratio = len(re.findall(r"[\u4e00-\u9fff]", t)) / max(len(t), 1) print(f"[mupdf] p{i} chars={len(t)} cn_ratio={ratio:.2f}") probe(SAMPLE)cn_ratio是这个脚本的核心指标:中文字符占提取文本的比例。低于 0.3 基本说明提取出来的是乱码或页眉页脚噪声,高于 0.6 说明文本层健康。先跑三页取平均,再决定全量用哪个引擎。这一步花两分钟,能避免后面几小时的清洗。
3. 批量解析 Runningman游戏大全.pdf:分页切分、书签重建与表格落地
结构摸清之后进入批量处理。一份游戏合集通常有几层天然边界:书签(大纲)、章标题文字、页眉里的栏目名。用书签切分最省事也最准,没有书签就退到按标题正则匹配。这一步的产物是若干小 PDF 加一份结构化清单,后面无论是做索引还是转格式,都基于这份清单。
3.1 用 pypdf 按章节切分成独立小文件
先把书签树读出来,再按书签页码切分。注意书签页码是 1-based,pypdf 的切片是 0-based,差一页会导致整章内容错位,这是最常见的低级错误。
from pypdf import PdfReader, PdfWriter import re, os SRC = "Runningman游戏大全.pdf" OUT = "chapters" os.makedirs(OUT, exist_ok=True) reader = PdfReader(SRC) total = len(reader.pages) # 收集书签:(标题, 起始页 0-based) marks = [] def walk(outlines, depth=0): for item in outlines: if isinstance(item, list): walk(item, depth + 1) else: marks.append((item.title, reader.get_destination_page_number(item))) walk(reader.outline) # 每个书签的结束页 = 下一个书签的起始页 marks.sort(key=lambda x: x[1]) for idx, (title, start) in enumerate(marks): end = marks[idx + 1][1] if idx + 1 < len(marks) else total writer = PdfWriter() for p in range(start, end): writer.add_page(reader.pages[p]) safe = re.sub(r"[\\/:*?\"<>|]", "_", title)[:60] path = f"{OUT}/{idx:03d}_{safe}.pdf" with open(path, "wb") as f: writer.write(f) print(path, f"pages={end - start}")reader.get_destination_page_number(item)把书签目标解析成 0-based 页码,比直接读/Dest数组更稳,能处理命名目标。add_page是一页一页搬,保留原页面内容但会丢掉原文档的书签,如果新文件也要保留大纲,需要在 write 前用writer.add_outline_item重建。末尾的安全文件名替换是必要的,游戏标题里出现?、/的概率不低,尤其是在 Windows 上会直接报错。
3.2 没有书签时:用页眉文字重建 Runningman游戏大全.pdf 的目录
很多合集是打印合成出来的,大纲是空的。这时用页眉/页脚里的固定文字(比如"第三关"或栏目名)做锚点,用 PyMuPDF 的search_for定位页码,效果比全文正则稳定。
import fitz, re doc = fitz.open("Runningman游戏大全.pdf") pattern = re.compile(r"^\s*游戏\s*\d+|^\s*第\s*[一二三四五六七八九十]+\s*关") toc = [] for i, page in enumerate(doc): # 只取页面顶部 12% 区域,避开正文里的同名关键词 clip = fitz.Rect(0, 0, page.rect.width, page.rect.height * 0.12) head = page.get_text("text", clip=clip).strip() m = pattern.search(head) if m: toc.append((i, head.split("\n")[0][:40])) for i, t in toc[:15]: print(i + 1, t)clip参数限定提取区域,这是关键:正文里也可能出现"第三关"字样,只取顶部区域能把误报压到很低。page.rect.height * 0.12是经验值,双栏排版或带大 logo 的版式可以调到 0.15。拿到 (页码, 标题) 列表后,逻辑与 3.1 完全一致,只是把书签换成这个列表。
3.3 扫描页处理:渲染、歪斜校正纠偏与漂白加深
纯图片页走 OCR。但直接把渲染图丢给 OCR 引擎,中文识别率通常只有七八成,因为扫描件有倾斜、底色发灰、噪点。预处理三步:灰度化、倾斜角检测与旋转、自适应二值化。
import fitz, cv2, numpy as np, pytesseract doc = fitz.open("Runningman游戏大全.pdf") page = doc[42] # 300dpi 渲染,低于 200dpi 中文识别率会明显掉 pix = page.get_pixmap(dpi=300) img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width, pix.n) gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) # 倾斜角估计:用最小外接矩形拿角度,超过 0.3 度才旋转 coords = np.column_stack(np.where(gray < 200)) angle = cv2.minAreaRect(coords.astype(np.float32))[-1] if angle < -45: angle += 90 if abs(angle) > 0.3: h, w = gray.shape M = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) gray = cv2.warpAffine(gray, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE) # 漂白加深:先用大核做背景估计,再相除,最后自适应二值化 bg = cv2.medianBlur(gray, 51) norm = cv2.divide(gray, bg, scale=255) binary = cv2.adaptiveThreshold(norm, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 12) text = pytesseract.image_to_string(binary, lang="chi_sim+eng", config="--psm 6") print(text[:300])dpi=300是中文 OCR 的性价比拐点,再高收益有限但耗时翻倍。倾斜角检测用minAreaRect比霍夫直线轻量得多,abs(angle) > 0.3的阈值是为了避免把正方形文本块误判成倾斜。背景估计的核大小 51 要大于笔画宽度但小于文字行高,太小会把字吃掉,太大会保留底色不均。--psm 6表示假定为统一文本块,适合规则排版的游戏说明;如果原页是双栏,改成--psm 4或先做栏切分。
3.4 把规则表提取成 CSV 落到本地
合集里常有关卡参数表,这类页用 pdfplumber 的表格线识别最省事,前提是表格有可见边框。无边框表就退到按字符坐标聚类,但代码量翻好几倍,通常不值得。
import pdfplumber, csv rows_all = [] with pdfplumber.open("Runningman游戏大全.pdf") as pdf: for pno in range(20, 60): page = pdf.pages[pno] for tb in page.extract_tables({ "vertical_strategy": "lines", "horizontal_strategy": "lines", "snap_tolerance": 3, }): for row in tb: # 去掉全空行,单元格内换行压成空格 cells = [(c or "").replace("\n", " ").strip() for c in row] if any(cells): rows_all.append([pno + 1] + cells) with open("rules.csv", "w", newline="", encoding="utf-8-sig") as f: w = csv.writer(f) w.writerow(["page"] + [f"col{i}" for i in range(len(rows_all[0]) - 1)]) w.writerows(rows_all) print("rows:", len(rows_all))vertical_strategy=lines强制按实际线段切分,比默认的lines混合模式更稳地处理跨页表头。snap_tolerance=3允许 3 像素内的线段误差,扫描件可放宽到 5。encoding="utf-8-sig"是为了让 Excel 双击打开不乱码,这个细节比代码本身更容易被忽略。写入前追加页码列,日后回溯原页非常方便。
4. 从 Runningman游戏大全.pdf 到可检索格式:清洗、Markdown 与全文索引
切分和提取只是中间产物。真正的交付物是"能搜、能引用、能对照原页"的一份数据。这一步的两个绊脚石是页眉页脚噪声和断行,前者会让索引里塞满重复词,后者会让关键词跨行搜不到。
4.1 文本清洗:剥离页眉页脚与合并断行
先按行频统计找出重复出现的行,再用坐标辅助判断是不是在页边距区域。纯文本方案对固定模板 PDF 足够用。
import fitz, re from collections import Counter doc = fitz.open("Runningman游戏大全.pdf") pages = [doc[i].get_text("text") for i in range(doc.page_count)] # 统计每页首行和末行,出现频次超过 60% 页数的判定为页眉页脚 edges = [] for t in pages: lines = [l.strip() for l in t.splitlines() if l.strip()] if lines: edges.append(lines[0]); edges.append(lines[-1]) noise = {k for k, v in Counter(edges).items() if v > len(pages) * 0.6} print("噪声行样本:", list(noise)[:5]) def clean(text): out = [] for line in text.splitlines(): s = line.strip() if not s or s in noise: continue if re.fullmatch(r"[-—\s]*\d{1,4}[-—\s]*", s): # 纯页码 continue out.append(s) # 中文断行合并:上一行不以句末标点结尾且下一行不以序号开头 merged = [] for s in out: if merged and not re.search(r"[。!?;:”】)]$", merged[-1]) \ and not re.match(r"^[((]?\d+[、.))]", s): merged[-1] += s else: merged.append(s) return "\n".join(merged) cleaned = [clean(t) for t in pages] print(cleaned[30][:200])频次阈值 0.6 是可调的:页眉页脚在正文页几乎都出现,偶尔缺页不影响判定;调太低会把某些反复出现的标题行误删,调太高则漏删。断行合并的判据是"上一行没结束标点 + 下一行不是新条目",这个规则对中文说明书类文档命中率高,对代码片段或表格会误合,所以清洗应该在已经排除表格页之后再做。
4.2 生成 Markdown 并建 SQLite FTS5 全文索引
Markdown 是给人看的,FTS5 是给检索用的。两个都产出,互相补位。如果团队习惯用 VSCode 写文档,用 markdown 转 pdf 输出成品也很顺,但核心数据一定要留在可解析的文本侧。
import sqlite3, re con = sqlite3.connect("runningman.db") con.execute("DROP TABLE IF EXISTS rules") con.execute("""CREATE VIRTUAL TABLE rules USING fts5( chapter, page UNINDEXED, body, tokenize='unicode61')""") # unicode61 对中文按字符切分,够用;要词级检索可换 jieba 预处理后写入 def split_chapters(pages_text, toc): result = [] for i, (pno, title) in enumerate(toc): end = toc[i + 1][0] if i + 1 < len(toc) else len(pages_text) body = "\n".join(pages_text[pno:end]) result.append((title, pno + 1, body)) return result for title, pno, body in split_chapters(cleaned, [(20, "游戏一"), (45, "游戏二")]): con.execute("INSERT INTO rules(chapter, page, body) VALUES (?,?,?)", (title, pno, body)) con.commit() # 检索:命中片段加高亮标记,方便回溯原页 for row in con.execute(""" SELECT chapter, page, snippet(rules, 2, '【', '】', '…', 18) FROM rules WHERE rules MATCH ? ORDER BY rank LIMIT 5 """, ("撕名牌",)): print(row)tokenize='unicode61'会把中文按单字切开,用两个字以上的查询词做短语匹配时需要用引号包成"撕 名牌",否则会被当成两个独立词。snippet的第 4、5 个参数是左右高亮符号和省略号,输出长度由第 6 个参数控制,18 个 token 在中文里大约对应 30 到 40 个字,够看清上下文。ORDER BY rank用 FTS5 内置的相关度排序,比按页码排序更符合检索直觉。
4.3 pdf 转 word 和 pdf 转 cad 的边界:什么时候该放弃
这两件事经常被一起提,但难度差着量级。pdf 转 word 对文本层健康、版式简单的文档效果尚可,转出来的问题主要是分栏和文本框错位;对扫描件必须先 OCR,且转换后必然要人工校对表格。pdf 转 cad 则是另一回事:矢量 PDF 可以导出 DXF,但导出的是线段和填充的描线集合,没有图层语义、没有尺寸标注对象、没有块引用,拿来当图纸的参考底图可以,当可编辑图纸不现实。判断规则列在下面。
| 转换目标 | 前提条件 | 现实产出 | 建议 |
|---|---|---|---|
| pdf 转 word | 有文本层,单栏为主 | 可编辑文本 + 图片,表格需校对 | 只转需要改的章节 |
| pdf 转 markdown | 有文本层 | 结构清晰,可入版本库 | 批量交付首选 |
| pdf 转 cad | 矢量 PDF 且无扫描图层 | 描线 DXF,无图层语义 | 仅作底图参考 |
| 扫描件转文本 | 300dpi 以上 | OCR 文本,需人工抽检 | 先做纠偏和漂白加深 |
如果只是想给非技术同事一份能改的稿子,转 word 加人工校对是成本最低的路径;如果目标是做资料库,直接走 Markdown 加 FTS5,跳过 word 这一环,反而少一次格式损耗。
5. 让 Runningman游戏大全.pdf 在浏览器里直接翻:预览、打印与印前校对
最后落到交付侧的三个高频需求。前端内嵌预览不要直接<embed>整个 300 页文件,首屏会卡住,正确做法是后端切分成单章文件后按需加载,前端用 pdf.js 只渲染当前页;需要打印归档走 pdf 虚拟打印链路,但要注意"打印成 PDF"会重新栅格化字体,文本层可能丢失,能直接合并原件就不要重打;印前外发前必须看尺寸和字库,缺字库的文档要么嵌入要么做 pdf 转曲。
前端渲染一页的最小实现如下,配合弹窗组件使用时,把 canvas 挂到弹层里,页面销毁时记得释放文档对象,否则翻几十页后内存会持续上涨。
import * as pdfjsLib from "pdfjs-dist"; pdfjsLib.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.js"; async function renderPage(url, pageNo, canvas) { const doc = await pdfjsLib.getDocument({ url, cMapUrl: "/cmaps/", cMapPacked: true }).promise; const page = await doc.getPage(pageNo); // scale 按容器宽度反算,避免固定 1.5 倍在高分屏上发虚 const viewport = page.getViewport({ scale: 1 }); const scale = canvas.parentElement.clientWidth / viewport.width; const vp = page.getViewport({ scale }); canvas.width = vp.width; canvas.height = vp.height; await page.render({ canvasContext: canvas.getContext("2d"), viewport: vp }).promise; return doc; // 调用方负责 doc.destroy() }cMapUrl指向 CMaps 目录,中文 PDF 缺了它会出现方块或丢字,这是前端预览中文文档最常见的故障。scale由容器宽度反算而不是写死,能在不同屏幕下保持一致观感。返回的doc一定要在组件卸载时调用destroy(),否则 worker 和缓存都不会释放。
印前校对用 pdfinfo 和 pdffonts 各跑一遍:pdfinfo看页面尺寸是否统一,混排的 A4 和 Letter 会在装订时出问题;pdffonts确认所有emb列为yes,有no的字体说明对外发送后对方打开可能变形。真需要转曲,优先用 Ghostscript 的-dNoOutputFonts走一遍,代价是文件变大、文本层彻底消失,所以只在外发副本上做,工作副本保持原样。
# 尺寸统计:输出各页宽高,检查是否有异常页 pdfinfo -box "Runningman游戏大全.pdf" | grep -i "page size" # 转曲副本,仅用于外发印刷,原件务必保留 gs -dNoOutputFonts -sDEVICE=pdfwrite -o out_curve.pdf "Runningman游戏大全.pdf"真正常被忽略的是页面尺寸不统一这件事——它不会报错,只会在装订和打印时暴露,而这时返工成本最高。
本文还有配套的精品资源,点击获取