
简介清华大学计算机科学与技术专业课程表.pdf整理自该校信息学院本科指导性教学计划是一份针对计算机专业学生和考研备考者的课程规划参考。文件中仅1个PDF却完整列出大一至大四春秋两季的必修课与选修课包含思想道德修养、微积分、离散数学、程序设计基础、数据结构、操作系统、编译原理、计算机网络、计算机系统结构等核心课程以及人工智能导论、数据库系统原理、模式识别、数据挖掘、嵌入式系统等方向课并清楚标注每门课的学分、周学时、考试/考查方式和先修要求。资源整体仅96KB下载后可方便打印或反复查阅也便于离线随时对照。目前已有1146人浏览学习适合用于制定大学学习计划、对比高校课程体系也可为自学者梳理计算机核心知识提供一份清晰的路线图。1. 一份课程表 PDF为什么值得当成技术文档拆“清华大学计算机科学与技术专业课程表.pdf”在搜索里出现的频率远超一份普通课表的预期。多数人点进去是想看清华计算机本科到底上什么课——数据结构放在第几学期、有没有编译原理、AI 是不是必修。但对一线工程师来说这份 PDF 的价值并不在“看个新鲜”而在两件事一是它是一份公开的、有权威性的培养方案能反推出一个计算机专业学生从零到能干活需要经过哪些核心训练二是它本身就是一个 PDF 表格解析的好样本——跨页、合并单元格、双栏排版、中文专业名词几乎把 pdfplumber 和 camelot 会遇到的坑全踩一遍。这篇文章就用“课程表 PDF”当载体先拆它的课程体系结构再给你一套能直接跑的 PDF 表格抽取方案把课程表转成 CSV 和结构化 JSON。中间会讲清楚为什么某些参数必须这样设、某些输出为什么是乱的要换一条路最后落到一个实用操作把课程表变成你自己的学习路线图。过程里出现的清华课程名只作为数据处理对象来用能搜到、能下载、能复现。2. 清华计算机课程表的培养方案结构从通识到专业方向的四层递进2.1 为什么先看课程结构而不是先看课名拿到一份课程表 PDF第一反应往往是找“AI”“大数据”这类标题党关键词。但真正常规的计算机专业培养方案是按“通识基础 → 数理基础 → 专业核心 → 专业方向/选修”四个层次铺开的。清华计算机的课表也遵循这个逻辑不是把热门课堆在一起。你只有先理解这四层才知道 PDF 里的表格该怎么按行解析——因为表格的行顺序就是这个培养层次顺序。课程表 PDF 里的数据特征也是按这个层次展开的第 1 层是公共必修政治、英语、体育学分多、课号固定行内容高度重复第 2 层是数学和物理高等数学、线性代数、概率论、大学物理课号以数字开头学分权重高第 3 层是专业核心数据结构、计算机系统、操作系统、计算机网络、编译原理、数据库这部分课程名带明显专业词第 4 层是专业选修和方向课AI 入门、计算机图形学、体系结构、软件工程课程名各异学分浮动大。解析 PDF 时这四类数据的行格式几乎不一样。前两层的行里有大量公共课字段中间是纯核心课后面选修课经常出现“任选 X 门”“限选”这类备注。用一条规则从头拆到底一定出错。2.2 公开课表中常见的学分和学期标记规则常规的清华课程表行里会出现以下字段课程代码、课程名称、学分、学时、开课学期、先修要求、备注。课程代码一般 5 位比如 30240392 这类格式中间不出现字母学分通常保留一位小数或整数学期标记常见写法是“春秋”“秋”“春”或者直接给数字 1、3、5、7 代表第几学期。这些字段规则在抽取 CSV 时是有用的映射依据。课程代码可以转成字符串保留前导零学分必须转 float学期要拆成可枚举的 int。代码实现时我会先按这些规则设计一个行解析函数再套到 PDF 表格上。网上不少解析教程喜欢一上来就用 pdftotext 扔文本正则但课程表这种结构化数据直接用表格抽取工具会更稳。2.3 这张表对应的人才培养逻辑从指令到系统拆开清华计算机课表你会发现专业核心课的排序是有讲究的。通常顺序是程序设计基础 → 数据结构 → 计算机系统或计算机系统导论→ 操作系统 → 计算机网络 → 编译原理 → 数据库。这个顺序本身就是计算机专业的学习依赖图先懂指令和数据再懂运行时和系统调用再懂跨机器通信和语言翻译。这份顺序表完全可以作为 PDF 解析结果正确性的校验依据。你抽出来的课程顺序如果出现了“操作系统在数据结构前面”基本可以判定是 PDF 文本流排序出了问题——双栏排版把左右两列读串了。这种校验方式比肉眼核对每一行快得多也是把课程表当数据用的第一步。3. 用 Python 解析课程表 PDF从文本抽取到结构化表格3.1 环境准备两个库和一个可复现的基线处理课程表 PDF我用的是 pdfplumber 做初步探测、camelot 做表格抽取。原因很直接pdfplumber 能拿到每个字符的坐标适合处理双栏错位camelot 的 lattice 模式能识别表格线对带框线的课程表效果稳定。安装命令如下pip install pdfplumber camelot-py[base] pandas openpyxl注意 camelot-py 的包名要带连字符import 时用camelot。装完先跑一段探测代码确认 PDF 页面尺寸和文本量再决定用什么模式抽。import pdfplumber with pdfplumber.open(清华计算机课程表.pdf) as pdf: for i, page in enumerate(pdf.pages): text page.extract_text() or tables page.find_tables() print(f第 {i1} 页: 文本 {len(text)} 字符, 表格 {len(tables)} 个)这段输出的用途是两个一是确认表格在当前页能被识别到二是看文本量和表格数量是否合理。如果某页有数据但是extract_text()返回空串这个 PDF 很可能是扫描图片需要走 OCR 而不是文本抽取。3.2 用 camelot 的 lattice 模式抽带框线表格清华课程表的公开版本多数是教务系统导出的 PDF表格线比较规整适合用 lattice 模式。核心参数是pages和line_scale。line_scale 控制表格线检测的敏感度默认是 15遇到打印后扫描的 PDF 经常需要调低到 8 或者调高到 30。import camelot tables camelot.read_pdf( 清华计算机课程表.pdf, pages1-6, flavorlattice, line_scale15, ) print(f共解析出 {tables.n} 张表格) # 把每张表转成 pandas DataFrame for idx, table in enumerate(tables): df table.df print(f表格 {idx1}: 形状 {df.shape}) print(df.head(3).to_string())这里的df的每一行对应 PDF 里的表格行每一列对应单元格。问题在于表头可能不在第一行而是一页一个重复表头。实际打印df之后你会看到多页表格合在同一个对象里行号是连续的但表头会反复出现。后面清洗时要按表头行去重。3.3 处理跨页表格和合并单元格用 stream 模式兜底lattice 模式碰到跨页表格合并单元格时常常把“课程名称”列拆成两半。这时候我一般切到 stream 模式用columns参数显式指定列的 x 坐标边界。先跑一次table.cell_text或者table.cols拿到列坐标再回填。tables camelot.read_pdf( 清华计算机课程表.pdf, pages2, flavorstream, columns[61, 152, 240, 320, 400], ) df tables[0].dfstream 模式不依赖表格线靠文本坐标分行分列。columns里的每一组数字代表相邻两列的分界 x 坐标单位是 PDF 的点point不是像素。这个值怎么拿用 pdfplumber 找课程名那一行的x0和x1import pdfplumber with pdfplumber.open(清华计算机课程表.pdf) as pdf: page pdf.pages[1] words page.extract_words() # 取前 20 个词的 x0 坐标按范围观察列位置 xs sorted(set(round(w[x0], 1) for w in words)) print(xs[:20])这段代码会把当前页所有单词的左侧坐标列出来你会看到明显的坐标簇比如 61、152、240、320、400。每个簇就是一列的起点把这些值填进 camelot 的columns参数即可。这是 stream 模式参数调整最常规的操作路径。3.4 清洗逻辑把 DataFrame 变成可用的课程表 CSV抽取出来的 DataFrame 是原始单元格要变成可用的课程表 CSV至少要做三步清洗第一行是表头时要识别并设置为 column names空行和全 NaN 行删掉课程代码占位类似“30240392”的转成字符串。import pandas as pd def clean_table(df: pd.DataFrame) - pd.DataFrame: # 第 0 行是否表头用是否包含课程关键词判断 if df.iloc[0].astype(str).str.contains(课程|学期|学分).any(): df.columns df.iloc[0] df df.drop(index0) df df.dropna(howall) df df.replace(r^\s*$, pd.NA, regexTrue).dropna(howall) # 课程代码列保持字符串去掉 .0 if 课程代码 in df.columns: df[课程代码] df[课程代码].astype(str).str.replace(r\.0$, , regexTrue) return df.reset_index(dropTrue) # 应用到所有解析出的表 dfs [clean_table(t.df) for t in tables] result pd.concat(dfs, ignore_indexTrue) result.to_csv(清华计算机课程表.csv, indexFalse, encodingutf-8-sig)清洗逻辑里有两处容易踩坑。如果表头行不在第 0 行而是在第 1 行或第 2 行上面的判断会失败。稳妥做法是遍历前 5 行找到第一行同时包含“课程代码”和“学分”的行当表头。另一个坑是编码直接存to_csv默认 utf-8在 Windows 上 Excel 打开会乱码必须用utf-8-sig。3.5 从 CSV 转结构化 JSON按学期聚合课程CSV 是给人看的给程序用还是得 JSON。把清洗后的 DataFrame 按开课学期聚合成嵌套结构这样前端展示或后续做学习路线图都方便。import json from collections import OrderedDict def df_to_json(df: pd.DataFrame) - str: by_semester OrderedDict() for _, row in df.iterrows(): sem str(row.get(开课学期, 未知)).strip() by_semester.setdefault(sem, []).append({ code: row.get(课程代码, ), name: row.get(课程名称, ), credit: row.get(学分, ), hours: row.get(学时, ), }) return json.dumps(by_semester, ensure_asciiFalse, indent2) with open(清华计算机课程表.json, w, encodingutf-8) as f: f.write(df_to_json(result))这个步骤里ensure_asciiFalse是关键不写的话中文全变成\uXXXX。输出的 JSON 结构是一个字典key 是学期名value 是课程对象列表。后续你用前端渲染课表、做课程依赖分析、甚至算每个学期的学分总和都是基于这个结构。4. PDF 表格解析的 4 个高发坑双栏、公式、坐标和 OCR 兜底4.1 双栏排版导致文本流串行x 坐标必须参与排序课程表 PDF 最常见的排版是首页横向双栏左侧是公共课右侧是专业课。pdfplumber 的extract_text()返回文本时会先按 y 坐标排序、再按 x 坐标排序但碰到双栏且 y 坐标交错的表格输出的文本流会左右穿插。比如“高等数学”和“数据结构”在同一行解析结果会变成“高等数据结构数学”。处理方式是绕开extract_text()用extract_words()按坐标手动组装行with pdfplumber.open(清华计算机课程表.pdf) as pdf: page pdf.pages[0] words page.extract_words() # 按 y 坐标聚类成行同行内按 x0 排序 rows {} for w in words: key round(w[top] / 5) # 5 个单位内的词视为同一行 rows.setdefault(key, []).append(w) for key in sorted(rows): line [w[text] for w in sorted(rows[key], keylambda w: w[x0])] print( | .join(line))这里把top坐标除以 5 再取整起到聚类效果。课程表的行高一般在 10 到 15 个单位除以 5 能保证同一行里的词分到同一组又不会让相邻行并组。这个阈值要看实际 PDF课程表紧凑的用 3松散的用 8。输出检查没问题后再用同样的坐标逻辑生成新的 DataFrame。4.2 表格线不完整line_scale 和 threshold 怎么调带框线 PDF 转扫描件后表格线会变淡甚至断裂。lattice 模式识别不到完整闭合线表格解析出来是一坨文本。调参顺序是先调line_scale从 15 往下调到 10、8观察t[0].df的 shape 是否变规整。for scale in [15, 12, 10, 8]: t camelot.read_pdf( 清华计算机课程表.pdf, pages3, flavorlattice, line_scalescale, ) if t.n 0: print(fline_scale{scale}: {t[0].df.shape})如果 line_scale 调到 8 还是拆不开就改用 join 模式用threshold参数控制文本行合并的容差。camelot 内部把文本按纵坐标聚类threshold默认是 1调大可以让零散文本更容易被归为一行。常见做法是按档位递进每次只改一个参数别同时调两三个否则出了问题无法定位是哪一步造成的。4.3 中文符号和全角空格清洗从 PDF 抽出来的文本经常混入全角空格、破折号、课程名称里的“-”和“—”。这些字符不会让解析崩溃但会让 CSV 里的课程名称对不上、JSON 里出现肉眼不可见的脏数据。清洗规则我放在一个函数里统一处理import re def normalize_text(s) - str: if not isinstance(s, str): return str(s) s s.replace(\u3000, ) # 全角空格 s s.replace(—, -) # 破折号统一 s re.sub(r\s, , s) # 多个空格压一个 s s.replace(课程名称\n, ) # 单元格内换行去掉 return s.strip()全角空格的 Unicode 是\u3000肉眼看不出来用print(repr(text))才能发现。处理这个需要把清洗函数在 DataFrame 上 apply 到所有列而不是只处理“课程名称”列。4.4 扫描版 PDF 无文本层OCR 是最后一道兜底如果 pdfplumber 对某个页面返回空文本说明这一页没有文本层是纯扫描图。这时文本解析路径全线失效需要先 OCR 再解析。常用方案是先用 OCR 工具把 PDF 变成带文本层的 PDF再重新走 camelot。我自己一般会先取一页转成 300 DPI 的 PNG看下清晰度再决定要不要整本做 OCR。更便宜的做法是只用 OCR 提取文本不做表格恢复。课程表的格子对 OCR 来说太细文字叠线、跨格字符都是常态恢复表格的代价比手工录一遍还高。所以扫描版课程表我通常走两条路要么只抽文本关键词用正则找课程名和学分要么标记为“低质量数据源”不纳入后续的数据分析。5. 把课程表变成学习路线图用解析结果做选课和技能映射解析完成的课程表 JSON 已经有了全部课程数据把它变成可用的学习路线图是最后一公里的工作。常规做法是给课程加两个维度的标签先修依赖和技能映射。先修依赖决定学习顺序技能映射决定这门课对应工程里的什么能力。比如“数据结构”对应的工程技能是算法设计和代码抽象“编译原理”对应的是文本解析和状态机设计而“操作系统”对应的是并发、内存管理和系统调用理解。具体实现我推荐用同一个 JSON 文件加一个skills字段然后按学期统计你每个技能域覆盖多少门课courses json.load(open(清华计算机课程表.json, encodingutf-8)) skill_map { 数据结构: [算法, 抽象, 复杂度], 编译原理: [解析, 状态机, 中间表示], 操作系统: [并发, 内存, 系统调用], } def add_skills(courses, skill_map): for sem, items in courses.items(): for c in items: c[skills] skill_map.get(c[name], []) return courses courses add_skills(courses, skill_map) # 统计每个学期知识点数量 for sem, items in courses.items(): skill_count sum(len(c.get(skills, [])) for c in items) print(f{sem}: 课程 {len(items)} 门, 技能点 {skill_count})这个思路的核心是把你从“看 PDF”变成“用 PDF”。同样的数据你可以继续往 JSON 里追加“推荐教材”“prerequisite 课程代码”这些字段甚至可以把它接进本地知识库用课程名和文本块做向量检索。不过要注意清华课程表里少数课程是见实习、毕业设计没有明确学分对应聚合统计时建议单独过滤。到这一步最初那份课程表 PDF 已经成了结构化数据可以支撑选课规划、技能自测、甚至简历项目选型。整个流程从pdfplumber探测到camelot抽取再到清洗和 JSON 落地所有步骤和参数都能直接在任意课程表 PDF 上复现。你拿到的不是一份课程表而是一条把任何 PDF 表格变成数据的流水线。本文还有配套的精品资源点击获取