
简介国际学生评估项目PISA数学试题文档聚焦15岁学生运用数学解决实际问题的能力适合中学数学教师、教研员及备考学生作为题型参考与思维训练素材。全卷共25题涵盖地衣生长、苹果园规划、骰子相对面、身高变化、图形面积、三角形几何、木匠用料、阳台铺砖、积木搭建等真实情境并附有详细解析从代数、几何、数据分析等角度展示解题思路与方法。特别地地衣问题通过公式dkt代入求直径苹果树数量n²与针叶树8n的方程比较增长率积木模型则需计算外体积并扣减中空部分这些解析有助于学生建立数学模型意识。资源为1个doc文档大小550KB排版清晰便于打印或电子阅读。目前已有686人学习适合用于课堂练习、自主测试或PISA题型专题研究。1. 一张PISA数学试题.doc背后三个常见的处理陷阱拿到一份PISA数学试题.doc先别急着双击打开。这个文件名的背后是一个很典型的工程场景你拿到的是Word 97-2003二进制格式的试题档里面装着情境题、数学公式、统计表格和评分标准。想在本地打开看一个Word就够但如果你想把它批量切分成题目、提取题目对应的图片、把评分标准变成结构化数据直接双击只会得到三样东西乱码、缺失的公式、错位的表格。下面顺着这个场景从格式识别、转换到Python提取把一份.doc变成可靠数据的完整路径翻一遍。这里谈的不是Word操作技巧而是工程师处理旧格式文档时真正会踩到的那套方法。2. 先搞清.doc的结构再动手解析PISA试题才不返工2.1 .doc是OLE2复合文档文本、表格和公式分开存放.doc格式最容易被低估的一点是它内部根本不是线性文本流。Word 97-2003使用OLE2复合文档结构整个文件是一个微型的文件系统里面分了很多流和存储区正文在WordDocument流里表格、图片、OLE嵌入对象分散在独立的Stream中再用偏移量把正文位置和这些对象关联起来。直接用文本编辑器打开一份.doc看到的是大段乱码就是这个原因。所以在解析之前先要确认手里的文件确实是.doc。很多下载站把.docx甚至HTML改后缀名传上来文件名带着.doc内容却是完全另一套格式。判断方法很直接xxd PISA数学试题.doc | head -1如果第一行十六进制以d0 cf 11 e0 a1 b1 1a e1开头这是OLE2复合文档的标准签名才是真正的Word 97-2003二进制文件。如果看到50 4b那是PK开头的ZIP系文件实际是.docx、.xlsx或压缩包改个后缀名而已。这一步十秒钟能做完但是能省掉后面一整轮为什么解析出来是乱码的排错时间。另一个值得提前做的检查是看看文件里有没有Macros和ObjectPool存储区。PISA试题文档基本不会带宏但网上流传的试题集经常被多次加工、粘贴、重新保存不排除混入嵌入对象。用olefile库可以快速列出流结构import olefile ole olefile.OleFileIO(PISA数学试题.doc) for item in ole.listdir(): print(/.join(item))输出里如果看到Macros、ObjectPool这类存储区说明文档里嵌入了宏或OLE对象。宏先把安全策略放一边至少知道这份文档比其他试题更脏后面转换时多留个心眼。2.2 三条解析路径按运行环境选一条处理.doc的成熟路径有三条适用场景完全不同。一张表看清楚工具运行环境输出质量表格公式antiwordLinux/macOS纯文本丢失行列结构不支持公式位置显示异常LibreOffice全平台docx/html/pdf完整保留转docx后保留OMML或图片pywin32 COMWindows原生态完整保留依赖本机Word质量最高我一般的习惯是快速预览内容用antiword正式加工走LibreOffice。把.doc转成.docx再交给python-docx处理是当前生态里最顺的一条路。python-docx本身不支持读取.doc二进制格式所以转换这一步绕不开。Windows环境下pywin32质量最高但需要机器上装了正版Word而且每开一个文档都要拉起COM进程批量处理几百道题时效率很差做自动化也不够干净。2.3 转换前先隔离文件避免版本差异干扰不同机器上的LibreOffice或Word版本不同对.doc的渲染结果会有细微差别。转出来的docx在字体映射、表格宽度、公式对象转换上可能差出几个像素甚至一整行。我的做法是先把所有原始.doc复制到一个独立目录转换也输出到另一个目录原始文件只读不动。这样反复调整解析脚本时可以随时对比是源文件问题还是转换器问题。这个隔离步骤看似多余实际处理整套PISA试题时非常管用。比如某个文件在某台CentOS上转换正常在macOS上多了一个空表格原因往往出在旧的.doc里有个全角空格或合并单元格标记不同转换器对这些边缘情况的容忍度不一样。把环境和输入都固定下来才能保证后面脚本输出的结果可复现。3. 用Python把PISA数学试题.doc转成可检索的文本和表格3.1 先用antiword做一次快速摸底在正式做格式转换之前先用antiword把纯文本拉出来可以快速判断这份PISA数学试题的主题分布、题量大小和大概难度。antiword对Word二进制文档的文字流还原做得很好安装也很简单sudo apt install antiword antiword -m UTF-8 PISA数学试题.doc questions.txt-m参数指定输入文件的字符映射表UTF-8表示按UTF-8编码读取文档内容最后重定向到questions.txt。注意antiword只提取文字流表格会按单元格的顺序输出成一段文字行列关系完全丢失所以它只适合预览不适合作为最终加工手段。如果输出的中文是乱码先去检查当前系统的locale设置locale | grep LANGantiword对中文支持依赖系统的编码映射LANG不是UTF-8时经常出现锟斤拷式乱码。临时切换到UTF-8环境再跑一次export LANGzh_CN.UTF-8这一版输出能告诉你这份试题包含哪些题目、题干大概多长、有没有明显缺页。快速摸底之后再进入正式转换流程。3.2 用LibreOffice无头模式转docx再交给python-docx标准加工流程分两步。第一步是把.doc转成.docxsoffice --headless --convert-to docx --outdir ./converted ./PISA数学试题.doc--headless表示无界面运行适合服务器环境--convert-to docx指定目标格式--outdir指定输出目录不加这个参数时文件会输出到当前目录。转换完成后docx实际是一个ZIP包正文、表格、图片都变成独立的XML和关系文件python-docx可以直接操作。第二步解析。python-docx按段落读取正文from docx import Document doc Document(./converted/PISA数学试题.docx) for para in doc.paragraphs: text para.text.strip() if text: print(f{para.style.name}: {text})这里把每段文字和段落样式一起打印。PISA数学题里题干通常使用正文缩进样式子问题编号会出现在段落文本开头比如1 (a)或2.。打印样式名的意义在于快速区分题干、标题和正文后续切割题目时可以按样式做第一层过滤。一个容易被忽略的细节是docx里的同一段文字经常被Word拆分成多个run原因是修订标记、拼写检查或格式微调会在段落内部插入额外节点。所以不要用paragraph.runs[0].text去取整段内容直接用paragraph.text拼出来的才是完整段落。3.3 表格是PISA题的关键结构单独抽取PISA数学素养测试大量使用真实情境统计表、票价表、折扣表几乎每题都有。转成docx后表格被独立存储python-docx可以直接遍历。提取所有表格的常规写法from docx import Document doc Document(./converted/PISA数学试题.docx) for t_idx, table in enumerate(doc.tables): print(f Table {t_idx} ) for row in table.rows: cells [c.text.strip().replace(\n, ) for c in row.cells] print(\t.join(cells))这段代码遍历文档中每个表格逐行把单元格内容用制表符拼成一行。需要注意row.cells在存在合并单元格时会返回重复对象同一个合并单元格会在多个列位置出现导致打印出来的行列看起来是歪的。要还原PISA题中复杂的行列合并需要对比单元格底层XML节点的身份from docx.oxml.ns import qn seen set() for cell in row.cells: tc cell._tc if id(tc) not in seen: seen.add(id(tc)) print(cell.text.strip())cell._tc拿到的是单元格对应的CT_Tc XML节点合并单元格共享同一个tc对象所以用id(tc)去重能还原真实行结构。PISA题里经常出现跨行的评分标准对照表这一步去重不做后面结构化出来的数据会多出整整一行重复内容。4. 公式、图片和特殊字符PISA数学试题里最难处理的三个对象4.1 公式在.doc里通常是OLE对象或图片文本里读不出PISA数学题里的公式一般不复杂大多是分式、根号、百分比和简单运算式但这些式子一旦进入.doc往往以MathType公式、Word公式编辑器对象或截图的形式存在。转成docx之后MathType对象多数变成嵌入的OLE二进制包Word公式可能会保留为OMML也可能被转换成图片取决于原始文档的编辑工具。直接用paragraph.text去读这些内容全部显示为空只剩下一段看起来断掉的题干。先检查docx里到底藏着多少嵌入对象和图片import zipfile with zipfile.ZipFile(./converted/PISA数学试题.docx) as z: for name in z.namelist(): if oleObject in name or media in name: print(name)这段代码直接打开docx压缩包遍历所有文件名。word/embeddings/oleObject*.bin是MathType或公式编辑器的二进制对象word/media/image*.png是文档里的图片。看到这些输出就能确认公式不是文字无法用普通文本解析手段恢复。一个值得注意的点是转出来的docx里OLE对象仍然打不开。因为这些.bin文件是MathType的私有格式不是标准图片Python这边没有通用解析库。务实的选择是把它当作图片占位符处理在最终结构化数据里记录它的位置而不是试图还原成LaTex或MathML。4.2 用OMML提取原生公式用pandoc转LaTeXOCR做兜底如果原PDF试题里的公式是Word公式编辑器生成的转成docx后会保留OMML格式也就是Office Math Markup Language。OMML节点的命名空间是http://schemas.openxmlformats.org/officeDocument/2006/math可以直接遍历抽取from docx import Document NS_M http://schemas.openxmlformats.org/officeDocument/2006/math doc Document(./converted/PISA数学试题.docx) for math in doc.element.iter(f{{{NS_M}}}oMath): text .join(node.text or for node in math.iter(f{{{NS_M}}}t)) print(text)oMath是公式根节点m:t是公式中的文本运行节点。把每个m:t里的字符拼起来能得到公式的文字内容例如x25。但这种方式会把分数、根号这类结构拉平成一行丢失层次。更好的做法是把整个docx交给pandoc让pandoc把OMML转成LaTeXpandoc -f docx -t latex ./converted/PISA数学试题.docx -o output.texpandoc对OMML的处理比较成熟分式、上标、下标都能正确变成\frac{}{}和^{}。但MathType对象和截图公式它无能为力这类内容在LaTeX导出里会变成包含图片路径或其他占位符。已经变成图片的公式只能上OCR。Tesseract对数学公式的识别效果有限但PISA题公式简单配合字符白名单能提高到可用的程度tesseract formula.png out -l chi_sim --psm 7 -c tessedit_char_whitelist0123456789-()[]/%--psm 7告诉Tesseract把整张图片当作单行文本处理适合公式在图片里居中且只有一行的情况。-c参数设置白名单只识别数字和常见运算符号能明显降低把识别成-这类错误。多行公式可以换成--psm 6但识别率会下降建议多行公式还是人工复核。4.3 图片导出与题目编号对应是结构化试题的常见痛点PISA题通常按题组组织一个大情境下挂三到五个子题图片往往插在情境末段或子题中间。按顺序导出图片时很容易出现题号和图片错位。我的做法是反向定位先标记当前题目编号再遇到图片段落时把图片归属到最近的那个题目编号。import re from docx import Document NS_W http://schemas.openxmlformats.org/wordprocessingml/2006/main doc Document(./converted/PISA数学试题.docx) pattern_num re.compile(r^\s*(\d{1,2})\s*[.、)\-]) current_id for para in doc.paragraphs: match pattern_num.match(para.text) if match: current_id match.group(1) for run in para.runs: drawings run._element.findall(f{{{NS_W}}}drawing) if drawings: print(f题目 {current_id}段落内检测到 {len(drawings)} 个图片)这里用正则匹配段落开头的题号w:drawing是Word文档中图片的XML标签。遇到带drawing的run就认为当前段落的图片属于最近一次匹配到的题目编号。这个方法在PISA试题上准确率很高因为题图几乎总是紧跟在题干文字后面很少出现一个图片段落跨越两个题组的情况。图片文件本身从docx中导出用关系类型定位for rel in doc.part.rels.values(): if image in rel.reltype: with open(fimages/{rel.target_ref.split(/)[-1]}, wb) as f: f.write(rel.target_part.blob)doc.part.rels保存了文档所有关系rel.reltype含image的就是图片关系。按rel.target_ref里的文件名写盘可以保留Word内部的原始命名。这样导出的图片文件名和4.3中检测到的段落位置可以逐一对应上。5. 批量转完全部PISA试题的三个收尾技巧5.1 用find与soffice组合一条命令批量转换一套完整的PISA数学试题按年份和地区经常拆成几十个.doc文件批量转换用find配合soffice一步到位find ./raw -name *.doc -exec soffice --headless --convert-to docx --outdir ./converted {} \;find把每个.doc路径逐个传给sofficeLibreOffice依次转换。注意LibreOffice有单实例限制同一时间只能存在一个soffice进程不要用xargs -P 4之类的方式并发执行否则会因为配置文件锁报错。几十个文件顺序跑通常也就一两分钟不值得为并发冒这个险。5.2 用document.xml的哈希去重而不是只看文件大小网上流传的PISA试题集经常出现同一份试题改了几个文件名、放进了不同文件夹的情况。转成docx后按整个文件算md5可能会因为ZIP包的元数据不同而产生不同哈希更稳妥的做法是只看正文内容unzip -p converted/某套题.docx word/document.xml | md5sumunzip -p直接把压缩包里的document.xml内容输出到标准输出管道交给md5sum计算哈希。两个docx只要正文相同即使图片顺序、ZIP压缩时间不同document.xml的内容也基本相同哈希值可用来做内容级去重。配合文件名一起看能快速发现同一套题被重复存放的情况。5.3 输出一份结构化JSON给下游系统留好接口文本、表格、图片都提取完之后把结果汇总成一份JSON后续做全文检索、题型分类或单题切片都从这个JSON出发不再回看原始.doc。汇总脚本的最终落点import json from docx import Document doc Document(./converted/PISA数学试题.docx) data { paragraphs: [p.text for p in doc.paragraphs if p.text.strip()], tables: [ [[c.text.strip() for c in row.cells] for row in tbl.rows] for tbl in doc.tables ], } with open(pisa.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse保证中文按原文写入而不是变成\uXXXX转义序列indent2让JSON有缩进、可读性好。写完之后用jq做一次快速验证检查表格数量是否和原题一致jq .tables | length pisa.json输出的数字如果和转换前的表格数对得上说明从.doc到docx再到JSON的这条链路是通的如果对不上优先检查3.3节提到的合并单元格去重逻辑而不是怀疑转换环节丢数据。本文还有配套的精品资源点击获取