十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PDF题库数据化:PyMuPDF解析、化学式归一化与SQLite组卷

PDF题库数据化:PyMuPDF解析、化学式归一化与SQLite组卷 简介这份无机化学复习题集以PDF形式整理了化学基础理论的典型考题适合高校化学、化工、材料等专业学生用于期末复习、考研初筛及课程自测内容覆盖化学反应速率、化学平衡、缓冲溶液、溶解平衡、分子轨道理论、溶度积常数、电极电势、氢键与标准生成焓等核心考点。资源包仅含1个PDF文件约375KB题量精炼、便于打印或移动端浏览围绕选择题形式展开每道题都指向一个明确的知识判断点例如活化能对反应速率的影响、催化剂只改变速率常数、KӨ与反应商J及ΔrGӨm的符号关系、O2分子轨道电子排布、Zn(OH)2溶度积计算、CaSO4沉淀条件与共轭酸碱判断等可帮助读者快速定位薄弱环节。目前已有167人浏览学习适合希望用较短时间梳理无机化学高频概念、检验公式运用熟练度的读者参考使用。1. 从一份 0615 版无机化学复习题集说起期末前两周有人把《暨南大学无机化学复习题集-0615.pdf》甩进群里45 道选择、30 道是非、28 道填空、13 道计算。看着不多真动手整理就崩了。页眉被「创创大帝」这四个字刷屏公式的上下标在复制时全部塌陷KӨ 变成 KΔrHӨm 变成 ΔrHmmol∙dm-3 里的间隔号在不同阅读器里显示成方块选择题选项被折行拆开计算题里 2Fe³⁺ 2I⁻ 的电荷、系数、正负号粘成一串。当 PDF 翻着看没问题当数据用全是坑。所以别急着刷题先把它当一份非结构化文本处理判断有没有文本层、洗掉水印、用正则切题、把化学式归一化成统一写法再用 Python 把答案逐题复算一遍做交叉验证最后落进一张能按知识点检索的表里。化学基础一般但会写脚本的人一个晚上就能把这份题集变成可检索、可组卷、能统计错题的私人题库化学很熟但不懂解析的人也能顺着这套流程拿走一份干净的 Markdown 版。2. PDF 文本层解析与化学式上下标还原这份 PDF 是电子的不是扫描件意味着可以直接拿文本层不必上 OCR。先确认这一点再决定后面所有方案。2.1 先探文本层PyMuPDF 抽取与水印清洗用 PyMuPDF 走一遍看每页能抽出多少字符。如果整页字符数低于 50、且大量内容是图片对象那就是扫描件得换 OCR 路线。import fitz # pip install pymupdf doc fitz.open(暨南大学无机化学复习题集-0615.pdf) pages [] for i, page in enumerate(doc): # sortTrue 让文本块按阅读顺序排列避免选项错位 t page.get_text(text, sortTrue) pages.append(t) print(fpage{i1:3} chars{len(t):5} images{len(page.get_images())})关键参数只有两个sortTrue会把文本块按坐标重排选择题选项断行的问题能缓解一大半page.get_images()用来判断这页是不是纯图片。如果某页chars极低而images大于 0就得单独走 OCR 分支不要整本都 OCR——OCR 会把Δ认成A、把下标数字认成主行数字后患无穷。抽完立刻洗水印。这份文件里「创创大帝」是重复串直接降维打击import re WM re.compile(r(创创大帝)) BLANK re.compile(r[\ue000-\uf8ff]) # Symbol 字体映射到私有区的残留码位 def clean(text: str) - str: text WM.sub(, text) text BLANK.sub(, text) text re.sub(r[ \t\u3000], , text) # 合并空白保留换行 text re.sub(r\n{3,}, \n\n, text) # 压缩连续空行 return text.strip()\ue000-\uf8ff这一段是重点。很多化学 PDF 用 Symbol 字体渲染 Δ、θ、∙抽取后会落到 Unicode 私有使用区肉眼看不见但会污染后续正则匹配表现为「明明有 Δ 却匹配不到」。清掉之后要按上下文补回来题面里出现「rH」「fG」「S」前面空一格的位置基本都是 Δ 被吃掉了后文归一化时统一处理。各工具在这个场景下的差别可以直接对照下表选工具抽取速度公式字符保留表格还原依赖体量PyMuPDF最快整本秒级好但私有区字符需自行清洗弱返回纯文本块单包无外部依赖pdfplumber中等好强可拿单元格坐标依赖 pdfminer.sixpdftotextpoppler快一般上下标常丢无需系统装 popplerOCR扫描件专用最慢差Δ/下标易错弱需模型与图像预处理这份题集我选 PyMuPDF 正则原因很直接题目是线性文本没有复杂表格结构pdfplumber 的坐标能力用不上反而多一层依赖。2.2 用正则把连续文本切成题号、题干和选项洗完之后整本是连续的需要按题号和选项字母切开。四类大题的结构不一样正则也要分开写。ITEM re.compile(r^\s*(\d{1,2})\s*[.、]\s*(?\S), re.M) OPTION re.compile(r([A-D])\s*[.、]\s*) SEC re.compile(r^\s*[一二三四五六七八九十]\s*[、.]\s*(选择题|是非题|填空题|计算题)) def split_items(text: str): items [] marks list(ITEM.finditer(text)) for i, m in enumerate(marks): start m.end() end marks[i 1].start() if i 1 len(marks) else len(text) items.append({no: int(m.group(1)), body: text[start:end].strip()}) return items def split_options(body: str): parts OPTION.split(body) if len(parts) 5: # 不是选择题原样返回 return body, {} stem parts[0].strip() opts {parts[i]: parts[i 1].strip() for i in range(1, len(parts) - 1, 2)} return stem, opts逻辑说明ITEM用(?\S)前瞻保证「1.」后面真的跟着内容避免把「0.63」这种小数点误判成题号——这份题里KӨ 0.63、1.8 × 10-5到处是这个前瞻不加切出来的题号会翻倍。参数上要注意三点。re.M必须开否则^只匹配全文开头\d{1,2}限制两位能挡掉「1000 cm3」这类三位数误伤[.、]覆盖句点、顿号、全角句点三种写法——这份 PDF 里三、填空题用的是「1.」部分计算题用的是「1」不写全就会漏。跑完先做一次体检统计每类题的实际数量和题集开头标注的 45/30/28/13 对不上就说明切错了from collections import Counter items split_items(clean(\n.join(pages))) print(Counter(i[no] for i in items).most_common(5)) # 看有没有重复题号重复题号大量出现八成是页眉页码被当成题号吃进来了把页眉行先按固定宽度或位置裁掉再重跑。2.3 化学式的符号归一化映射表切完的文本还不能用ΔrHӨm、KӨ、Ksp、mol∙dm-3这些在题集里写法不统一同一个物理量在不同题目里能写出三四种样子。统一映射一遍后面写校验脚本和省事很多。原始写法归一化结果说明KӨ / Kθ / K⊖K^theta西里尔 Ө 与希腊 θ 混用统一成 ASCIIΔrHӨm / ΔrHmdH_r下角标 r 与上标 Ө 位置信息已丢统一语义化命名ΔfHӨm / ΔfHdH_f生成焓保留 f 标识与 r 区分ΔrGӨmdG_rKspӨ / KsKsp溶度积mol∙dm-3 / mol·L-1mol/L容量单位统一避免体积换算时单位打架═ / / 反应式等号长短不一c(Zn)c(Zn2)题面省略了电荷按上下文补全NORM [ (r[Kk]\s*[Өθ⊖], K^theta), (rΔ\s*r\s*H\s*Ө?\s*m, dH_r), (rΔ\s*f\s*H\s*Ө?\s*m, dH_f), (rΔ\s*r\s*G\s*Ө?\s*m, dG_r), (rΔ\s*r\s*S\s*Ө?\s*m, dS_r), (rmol\s*[∙·]\s*(dm-3|L-1), mol/L), (r[═]{1,}, ), ] def normalize(s: str) - str: for pat, rep in NORM: s re.sub(pat, rep, s) return sΔrSӨm这条容易漏题集里是非题、填空题都出现过熵变和焓变经常同时出现在一道判断题里只映射 H 不映射 S后面的规则打标会漏掉一半热力学题。归一化完成后再存一份原始文本副本任何一步映射错了都能回溯别把原串覆盖掉。3. 用 Python 复算答案热力学、平衡与溶解平衡校验题集自带答案的只有是非题标了 √/X和填空题选择题基本没给答案计算题只有结果。人工核对 100 多道题的数值几乎不可能不出错写脚本复算是最省事的交叉验证方式。3.1 盖斯定律与焓变的线性组合校验计算题第 1 题是典型的盖斯定律组合已知三个反应热求第四个。这类题的通用做法不是逐项推而是把目标反应写成已知反应的线性组合系数解出来直接加权。# (1) CH3COOH(l) 2O2 - 2CO2 2H2O dH1 -870.3 kJ/mol # (2) C(s) O2 - CO2 dH2 -393.5 # (3) H2(g) 1/2 O2 - H2O(l) dH3 -285.8 # 目标 (4) 2C(s) 2H2(g) O2 - CH3COOH(l) dH1, dH2, dH3 -870.3, -393.5, -285.8 # 目标 2*(2) 2*(3) - 1*(1) coef {1: -1, 2: 2, 3: 2} dH4 sum(coef[i] * dH for i, dH in zip([1, 2, 3], [dH1, dH2, dH3])) print(round(dH4, 1)) # -488.3逻辑说明系数来自元素守恒不是凑出来的。目标式左边有 2 个 C只有反应 (2) 提供 C所以 (2) 的系数是 2目标式有 2 个 H只有 (3) 提供 H系数也是 2反应 (1) 的产物里有 CH3COOH要挪到左边所以系数取 -1。氧气自动配平不用管。参数上唯一要盯的是单位。题面里焓变一律是 kJ·mol⁻¹熵变是 J·mol⁻¹·K⁻¹两者混在同一个吉布斯公式里必须先把 S 除以 1000。这类题最常见的错就是把 125.76 J 直接当成 125.76 kJ 代进 ΔG ΔH - TΔS最后算出来的 K 数量级能差几十个数量级。3.2 平衡常数与计量系数的幂律关系第 9、25、35、40 题是同一类考法反应式写法变了K 怎么变。规律只有一条——反应式乘以系数 nK 变成 K 的 n 次方反应式反向K 取倒数。这条规律用代码验证比背结论可靠。def rescale_K(K: float, factor: float) - float: 反应式整体乘以 factor可为负负数表示反向时的新 K return K ** factor K_9 4e-2 # H2 Cl2 2HCl print(rescale_K(K_9, -0.5)) # 5.0 HCl 1/2H2 1/2Cl2 K_25 2e-1 # 1/2H2 1/2Br2 HBr print(rescale_K(K_25, -2)) # 25.0 2HBr H2 Br2 K1 0.63 # N2 3H2 2NH3 print(rescale_K(K1, 0.5), rescale_K(K1, 1/3)) # 第35题 K2、K3 的关系第 40 题那组更典型(1) 的计量数是 (2) 的两倍所以 ΔrG 相差两倍但 K 是平方关系不是两倍关系。用rescale_K(K, 2)一跑就知道选「K1 K2²」的那个选项靠背很容易记反。import math for T, dH, dS in [(298.15, -196.10, 0.12576), (373.15, -196.10, 0.12576)]: dG dH - T * dS # dS 已转成 kJ/(mol·K) K math.exp(-dG * 1000 / (8.314 * T)) # dG 转回 J 代入 print(fT{T} dG{dG:.2f} kJ/mol K{K:.2e})这段对应计算题第 5 题。运行结果是 298.15 K 时 ΔG ≈ -233.6 kJ·mol⁻¹、K ≈ 8×10⁴⁰373.15 K 时 ΔG ≈ -243.0 kJ·mol⁻¹、K ≈ 1×10³⁴。注意温度升高后 K 反而变小——因为该反应 ΔH 0升温对放热反应不利这跟「升温一定让反应更快」是两个层面的问题题集里是非题第 4、5 题就在挖这个坑。3.3 解离度、pH 与溶度积的数值复算弱酸、缓冲、沉淀这三块占了题集近三成公式简单但边界条件多逐题复算能发现不少题面本身的表述瑕疵。题型复算公式代表题号常见坑一元弱酸解离度α √(Ka/c)选择 34、填空 11c 越小 α 越大但 Ka 不变弱酸 Ka 反推Ka cα² ≈ [H⁺]²/c填空 3、计算 7pH 先转 [H⁺] 再平方别丢了 10 的负号AB 型难溶盐s √Ksp选择 12、13溶解度大不代表 Ksp 大同类型才能比AB₂ 型难溶盐s ∛(Ksp/4)计算 10、13少除 4 是最高频错误分步沉淀[X] Ksp/[沉淀剂]计算 8同离子效应与配位效应要分开讨论import math # 选择 340.50 mol/L HAcKa 1.8e-5 Ka, c 1.8e-5, 0.50 alpha math.sqrt(Ka / c) print(falpha{alpha*100:.2f}%) # 0.60% # 计算 10PbCl2Ksp 1.17e-5AB2 型 Ksp 1.17e-5 s (Ksp / 4) ** (1 / 3) print(fs{s:.3e} [Pb2]{s:.3e} [Cl-]{2*s:.3e}) # 选择 15Zn(OH)2 饱和溶液 s 2e-6 s 2e-6 print(fKsp{4*s**3:.2e}) # 3.2e-17对应选项 A三段的逻辑一致先判断难溶盐类型再套对应公式。PbCl₂ 是 AB₂ 型1 mol 溶解产生 1 mol Pb²⁺ 和 2 mol Cl⁻所以 Ksp 4s³反解必须开立方并除以 4。这份题集里选择第 13 题专门问「A 的溶解度大于 B 时 Ksp 谁大」答案是不一定——AB 型和 AB₂ 型的 s 与 Ksp 换算公式不同跨类型比较没有意义。脚本里把盐型作为一个显式参数传进去就不会混。分步沉淀那题计算 8值得单独跑一遍Ba²⁺ 与 Ag⁺ 各 0.1 mol/L滴 Na₂SO₄判据是比较各自开始沉淀所需的 SO₄²⁻ 浓度。Ksp_BaSO4, Ksp_Ag2SO4 1.8e-10, 1.2e-5 c_Ba, c_Ag 0.1, 0.1 need_Ba Ksp_BaSO4 / c_Ba # 1.8e-9 need_Ag Ksp_Ag2SO4 / c_Ag ** 2 # 1.2e-3 print(fBa 需要 {need_Ba:.1e}, Ag 需要 {need_Ag:.1e}) print(fAg 开始沉淀时剩余 c(Ba) {Ksp_BaSO4 / need_Ag:.1e} mol/L)需要 SO₄²⁻ 浓度低的那种离子先沉淀所以 Ba²⁺ 先析出。等 Ag⁺ 开始沉淀时溶液中残余 c(Ba²⁺) ≈ 1.5×10⁻⁷ mol/L远小于常规判据 1×10⁻⁵ mol/L说明第一种离子已沉淀完全。这类「先沉淀 / 是否沉淀完全」的问法在计算题里反复出现写成函数复用最省事。4. 题库落库与知识点标签SQLite 建模与检索题切好了、数值也核过了最后一步是让它能被查。SQLite 单文件、零部署几百道题的量级完全够用。4.1 表结构题目、选项、标签、作答记录PRAGMA foreign_keys ON; CREATE TABLE question ( qid INTEGER PRIMARY KEY, section TEXT NOT NULL, -- 选择/是非/填空/计算 stem TEXT NOT NULL, answer TEXT, raw_text TEXT, -- 归一化前的原始串用于回溯 page_no INTEGER ); CREATE TABLE option ( qid INTEGER REFERENCES question(qid), label TEXT, -- A/B/C/D content TEXT, PRIMARY KEY (qid, label) ); CREATE TABLE tag ( tid INTEGER PRIMARY KEY, name TEXT UNIQUE, -- 化学动力学 / 沉淀溶解平衡 ... chapter TEXT ); CREATE TABLE qtag ( qid INTEGER REFERENCES question(qid), tid INTEGER REFERENCES tag(tid), PRIMARY KEY (qid, tid) ); CREATE TABLE attempt ( aid INTEGER PRIMARY KEY, qid INTEGER REFERENCES question(qid), ts TEXT DEFAULT (datetime(now, localtime)), chosen TEXT, is_right INTEGER CHECK (is_right IN (0, 1)) );raw_text这列别省。归一化是有损的ΔrSӨm被映射成dS_r之后你再也看不出原题写的是熵变还是别的量一旦打标规则出错靠原始串才能批量重跑。is_right加 CHECK 约束是因为组卷统计里经常要按正确率排序混进非 0/1 的值会让聚合结果整体偏移。4.2 规则打标从题面自动挂知识点题目数量不多用关键词规则打标比上模型快得多也更好调试。import re, sqlite3 RULES [ (r活化能|催化剂|速率常数|基元反应|反应速率, 化学动力学), (r平衡常数|反应商|dG_r|dH_r|dS_r|自发|标准态, 热力学与化学平衡), (rKsp|溶度积|沉淀|溶解度, 沉淀溶解平衡), (rpH|解离|缓冲|共轭|质子|电离度, 酸碱平衡), (r电极电势|原电池|电动势|电池符号|氧化还原, 氧化还原与电化学), (r杂化|分子轨道|极性|氢键|量子数|电子构型|配位, 物质结构), ] def tag_it(stem: str) - list[str]: hits [] for pat, name in RULES: if re.search(pat, stem, re.I): hits.append(name) # 一道题可能同时命中「热力学」和「酸碱」全部保留 return hits规则顺序有讲究。「溶度积」既可能出现在酸碱题里同离子效应也可能出现在热力学题里Ksp 与 ΔG 换算所以沉淀溶解平衡这条要放在热力学后面匹配先命中的标签在前后续人工复核时按顺序看效率更高。命中多个标签的题一律保留全部不要取首个——这份题集里选择第 29 题AgCl 在不同溶液中的溶解度同时涉及同离子效应和配位效应只挂一个标签会漏掉复习入口。跑一遍之后的分布大致是这样用来判断标签体系有没有偏知识点主要来源题号节选大致占比化学动力学选择 1/2/6/27是非 4-8填空 10/23/27约 18%热力学与化学平衡选择 4/5/12/19/23/30/38/40计算 1/2/4/5约 22%酸碱平衡选择 3/11/24/26/28/33/34填空 3/4/12计算 3/6/7约 20%沉淀溶解平衡选择 13/15/22/29/41计算 8/10/11约 15%氧化还原与电化学选择 16/20/39/42/43填空 13/14/19/28计算 9/12/13约 17%物质结构与配位选择 10/14/17/21/44/45填空 7/8/24/25/26约 8%物质结构这块占比明显偏低不是标签写错了而是这份题集确实以计算类内容为主。看到这种分布就该知道拿它当复习材料结构部分得另找题源补。4.3 全文检索与按标签组卷中文短句用 FTS5 的unicode61分词器基本无效——它按空格切词中文一整句会被当成一个 token。改用 trigram 分词器SQLite 3.34.0 及以上支持按三字符滑窗建索引中文检索立刻可用。CREATE VIRTUAL TABLE question_fts USING fts5( stem, content question, content_rowid qid, tokenize trigram ); INSERT INTO question_fts (rowid, stem) SELECT qid, stem FROM question;trigram 的代价是索引体积约为原文的 3 到 4 倍几百道题无所谓如果题库上万就得换 jieba 分词后在应用层拼查询串。组卷用一条 JOIN 就能出结果-- 抽 10 道动力学题优先抽没做过的 SELECT q.qid, q.section, q.stem FROM question q JOIN qtag qt ON qt.qid q.qid JOIN tag t ON t.tid qt.tid LEFT JOIN attempt a ON a.qid q.qid WHERE t.name 化学动力学 GROUP BY q.qid ORDER BY COUNT(a.aid) ASC, RANDOM() LIMIT 10;ORDER BY COUNT(a.aid) ASC让没做过的题排在前面RANDOM()做同权重内的随机打散。这里的LEFT JOIN是必须的换成INNER JOIN会把所有没做过的题直接过滤掉——这是组卷脚本里最容易踩的一个表现为「明明有几十道新题组出来的卷子全是老题」。5. 错题聚类与 Anki 卡片导出单题正确率意义有限真正有用的是看同一个知识点是不是连续错——那说明不是粗心是概念没立住。SELECT t.name, COUNT(*) AS total, SUM(CASE WHEN a.chosen IS NULL THEN 1 ELSE 0 END) AS untouched, ROUND(AVG(a.is_right) * 100, 1) AS acc_pct FROM tag t JOIN qtag qt ON qt.tid t.tid LEFT JOIN attempt a ON a.qid qt.qid GROUP BY t.name HAVING total 5 ORDER BY acc_pct ASC;HAVING total 5是为了过滤样本太小的知识点3 道题错 2 道不代表什么。跑出来的正确率低于 60% 的标签就直接锁定为本周复习重点比刷整套卷子高效。导出 Anki 时有个坑要处理题目里的化学式带^{}、_{}这类标记Anki 默认按 HTML 渲染ΔrHӨm里的^会被当成普通字符。把上下标转成sub/sup再导出import csv, re def to_anki_html(s: str) - str: s re.sub(r\^(\w), rsup\1/sup, s) s re.sub(r_(\w), rsub\1/sub, s) return s.replace(\n, br) rows [] for qid, stem, answer in cur.execute(SELECT qid, stem, answer FROM question WHERE answer IS NOT NULL): rows.append([to_anki_html(stem), to_anki_html(answer or ), fqid{qid}]) with open(inorganic_anki.csv, w, newline, encodingutf-8) as f: csv.writer(f).writerows(rows)导入 Anki 时把分隔符设成逗号、字段数设成 3卡片模板里正面用{{Front}}、背面用{{Back}}加{{Tags}}化学式的上下标就能正常显示。最后留一道自查随机抽 10 道题把脚本复算的结果和题集给的答案逐条比对重点看计算题第 8、10、11 题这类带配位或分步沉淀的。三处以上对不上先怀疑归一化映射表而不是自己的化学尤其是mol∙dm-3到mol/L那一条——单位没转干净时溶度积算出来的数量级会整整齐齐地差三个数量级。本文还有配套的精品资源点击获取
返回列表