
简介本资源是一份面向计算机专业本科生的课程设计与毕业设计实践项目聚焦智能招聘场景中的简历结构化处理与人岗匹配核心问题。系统基于Python构建融合大模型解析与语义匹配双技术路径利用Grok-beta大模型完成非结构化简历文本到JSON的精准解析结合google-bert/bert-base-chinese模型计算岗位描述与简历字段的语义相似度并辅以结构化数据规则加权提升匹配合理性。压缩包共9个文件6个Python主模块、1个YAML配置、1个MD说明文档、1个TXT停用词表总大小仅19KB轻量易部署目录结构清晰涵盖AI调用、提示工程、匹配算法、配置管理等完整链路。目前已有71人学习下载提供可直接运行的端到端源码、标准化prompt设计、中文BERT微调适配方案及详细使用说明适合课程设计快速复现、毕设功能扩展或NLP工程实践参考。 先说说我为什么会去做这个东西。起因挺实在的——之前帮HR那边处理校招简历几百份PDF堆在邮箱里光是统一格式就得花小半天更别说逐个看技能栈、匹配岗位。当时我就在想这活儿能不能让程序先筛一遍于是就有了这个基于Python的智能简历解析与人岗匹配系统也算是我课程设计里最有实用价值的一个项目。整个项目的核心就是两件事把非结构化的简历文本变成结构化数据然后根据岗位要求给候选人排序打分。如果你也正在做类似的NLP项目或者想给简历筛选这档子事找个自动化方案这篇文章应该能帮你省不少弯路。1. 项目整体设计与技术选型1.1 需求拆解简历解析到底在解什么先别急着写代码得想清楚简历解析本质上是个什么问题。一份常规简历内容包含姓名、电话、邮箱、教育经历、工作经历、项目经历、技能列表、自我评价等等板块。但问题在于简历是纯文本或PDF格式千奇百怪——有人用表格排版有人用两栏布局有人把技能写成一堆逗号分隔的词有人把项目经历写得像流水账。程序要做的就是从这些无结构或半结构文本里把关键信息准确抽取出来然后按照预先定义好的字段填入数据库或JSON结构。我把需求拆成了几个层次文件解析层读PDF、Word、HTML简历提取纯文本内容。信息抽取层识别姓名、联系方式、教育经历、工作经历、技能、项目经历等实体。结构化输出层将抽取结果整理成统一的JSON格式喂给下游的人岗匹配模块。人岗匹配层基于JD职位描述和简历的结构化数据计算匹配度并排名。这里最关键也最容易被忽视的是第二层。很多新手一上来就搞深度学习想用BERT做命名实体识别但忽略了信息抽取的前置条件是文本干净、段落分得清。如果PDF解析出来一堆乱码或者Word里表格结构丢失再牛的模型也白搭。1.2 为什么选Python生态与效率的权衡选择Python几乎是必然的。一是简历解析要处理PDF、Word、HTMLPython有非常成熟的库PyPDF2、pdfplumber、python-docx、BeautifulSoup各管一摊二是信息抽取阶段可以利用现成的中文NLP工具比如jieba分词、LAC词法分析或者直接上HanLP三是如果后续要升级成深度学习方案PyTorch、Transformers这些都在Python生态里迁移动线很短。但Python也有它的毛病。最明显的是打包分发不方便——你写好了想给HR那边部署总不能让人家装Python环境、装依赖库。所以我最后用Flask做了个简单的Web界面把解析和匹配逻辑封装成API用Docker打包这样在任何机器上docker run就能跑起来。这也算是一个进阶经验做课程设计、做项目别只停留在notebook里能跑最好能给别人用。1.3 技术方案选型规则引擎NLP评分权重这里必须提一个重要的方案判断。刚开始我打算直接上BERT做序列标注觉得简历命名实体识别就是个标准的NER问题。但后来发现几个问题一是没有标注数据自己标几百份简历工作量太大二是简历领域实体其实很有规律比如手机号正则就搞定了邮箱正则也搞定了教育经历找大学学院本科硕士这些关键词就能定位没必要杀鸡用牛刀三是纯规则方案可解释性强HR问你为什么这个人排第一你能明确说是技能匹配度90%、学历匹配度80%加权出来的结果。所以最终的技术路线是规则引擎做主NLP做辅。具体来说联系方式、邮箱、薪资期望正则表达式直接抽取。教育经历、工作经历、项目经历先按关键词切块再在每个块内做实体抽取。技能提取维护一个技能词库结合词频统计和词向量相似度做匹配。人岗匹配把JD和简历分别向量化用TF-IDF和Word2Vec两种方式计算相似度再加权重调制。这条路线的好处是可控性强每步都能看到中间结果方便调试也方便写报告。缺点也有后文会提到。2. 简历解析核心模块实现2.1 PDF与Word文本提取踩坑与选型简历最常见的格式是PDF和Word还有少量HTML。我逐个说下我的经验。PDF提取首选pdfplumber而不是PyPDF2。PyPDF2提取中文文本时经常出现错位和乱码尤其是扫描版PDF文字根本提不出来是图片不是文字。pdfplumber能处理大部分文字版PDF还能保留一些版面信息比如通过page.extract_text()拿到文本配合extract_tables()能处理表格型简历。import pdfplumber def extract_text_from_pdf(pdf_path): text with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: page_text page.extract_text() if page_text: text page_text \n return text但这里有个坑pdfplumber对两栏排版的简历会从左到右读导致语义错乱——把左栏的技能和右栏的工作经历混在一起。解决思路是先用page.rects和page.lines检测页面是否有竖直分隔线如果检测到分栏就按栏裁剪后分别提取。不过这属于进阶优化我的初版系统没做直接靠后处理硬扛。Word提取分两种格式.docx直接用python-docx.doc是老格式python-docx不支持需要先转换。转换方案是用LibreOffice的命令行模式soffice --headless --convert-to docx input.doc --outdir output_dir然后在Python里用subprocess调用即可。这个方法在Windows和Linux上都可行只要装了LibreOffice。import subprocess def convert_doc_to_docx(doc_path, output_dir): subprocess.run([ soffice, --headless, --convert-to, docx, doc_path, --outdir, output_dir ], checkTrue) return os.path.join(output_dir, os.path.basename(doc_path).replace(.doc, .docx))python-docx提取文本时有一个细节document.paragraphs只能拿正文段落拿不到表格里的内容。很多简历会用表格排版所以必须额外解析表格from docx import Document def extract_text_from_docx(docx_path): doc Document(docx_path) text \n.join([p.text for p in doc.paragraphs if p.text.strip()]) # 提取表格内容 for table in doc.tables: for row in table.rows: row_text | .join([cell.text.strip() for cell in row.cells]) if row_text.strip(): text \n row_text return text2.2 简历信息结构化分块与抽取拿到纯文本之后下一步就是分成结构化字段。我把这个过程分成了两步段落分块和字段抽取。段落分块思路很简单就是根据关键词把文本切出教育经历工作经历项目经历技能自我评价等区域。每个区域对应一组关键词触发词。SECTION_KEYWORDS { education: [教育经历, 教育背景, 学历, 学校, 大学, 专业], work_experience: [工作经历, 工作经验, 实习经历, 任职], project_experience: [项目经历, 项目经验, 参与项目, 项目名称], skills: [技能, 专业技能, 技术栈, 熟悉, 掌握], self_evaluation: [自我评价, 个人总结, 自我描述], basic_info: [个人信息, 基本信息, 联系方式], } def split_sections(text): lines text.split(\n) current_section unknown sections {} for line in lines: line line.strip() if not line: continue matched False for section, keywords in SECTION_KEYWORDS.items(): if any(kw in line for kw in keywords): current_section section sections.setdefault(section, []) matched True break if not matched: sections.setdefault(current_section, []).append(line) return {k: \n.join(v) for k, v in sections.items()}这段代码的问题是触发词大学出现在教育经历区域里没问题但如果正文某处提到大学期间获得XX奖项就可能误判。我加大了一个约束只有当一行文字较短比如少于20个字符并且包含触发词时才认为是分节标记。这样准确率提升了不少。字段抽取每个字段其实都有自己的套路姓名一般在简历最开始且不在分节标记之后。可以用分词后的人名识别也可以粗暴一点取第一行前两个或三个汉字配合常用姓名用字校验。手机号正则1[3-9]\d{9}。邮箱正则[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}。教育经历判断学历关键词本科、硕士、博士、大专、高中再找学校名XX大学、XX学院再找时间段20XX-20XX。工作经历找公司名用后缀规则有限公司科技集团等、职位工程师经理总监等、时间段。技能命中技能词库的词。这里有一个我反复调优的细节技能抽取。技能词库从哪来我是从拉勾和BOSS直聘的常见岗位JD里人工整理了一份初版大约500个技能词包括Python、Java、C、MySQL、Redis、Docker、Kubernetes、TensorFlow、PyTorch等等。但候选人简历里可能会写精通Python而不是Python所以需要先分词再匹配。import jieba def extract_skills(text, skill_dict): found_skills set() words jieba.lcut(text) for word in words: if word in skill_dict: found_skills.add(word) # 处理熟悉Django框架这类词库里可能是Django for skill in skill_dict: if skill in text: found_skills.add(skill) return list(found_skills)这个双重匹配方案在初版中是够用的但后来我发现一个效率问题——每份简历都要过一遍包含几百个词的技能词库字符串匹配是O(n*m)的复杂度。简历数量到了几千份速度就明显下来了。优化方案是先对简历文本建一个倒排索引或者用AC自动机做多模式匹配。我实际用了pyahocorasick库速度提升了一个数量级。2.3 中文命名实体识别用LAC做精细抽取规则方案能解决80%的问题但总有一些简历是非典型的。比如有人把教育经历写在自我评价里有人完全没有分段标题整个简历就是一段话。这时候规则就抓瞎了需要上真正的NLP工具。我试过三种方案最终选了百度开源的LACLexical Analysis of Chinesejieba词性标注速度最快但模型粗糙人名、机构名识别率低。HanLP识别效果好但模型较大部署稍麻烦。LAC兼顾速度和精度而且可以自定义词典适合简历领域。LAC的用法很简单from LAC import LAC lac LAC(modeseg) lac.load_customization(custom_dict.txt, sepNone) text 张三毕业于北京大学计算机科学与技术专业曾在字节跳动担任后端工程师。 seg_result lac.run(text) # 输出: [张三, , 毕业于, 北京大学, 计算机科学与技术, 专业, , 曾在, 字节跳动, 担任, 后端工程师, 。] # 词性: [PER, w, v, ORG, nt, n, w, d, ORG, v, n, w]注意LAC的词性标注里PER是人名ORG是机构名。这正好能帮我抽取姓名和学校、公司。但你千万别只依赖LAC——它的机构识别虽然不错但会把北京大学标成ORG这个没问题可有些简历里的北大的小伙伴这种口语化表达它就没办法了。所以我的做法是LAC识别结果作为候选用规则做二次校验比如学校名必须包含大学学院公司名必须包含有限公司科技等词。自定义词典很重要我把自己整理的高校名单和常见公司名加进了custom_dict.txt识别准确率立刻提升了一截。这个文件长这样北京大学 5000 清华大学 5000 字节跳动 5000 腾讯科技 5000 ...后面的数字是词频权重LAC会优先把这些词作为整体切分。效果很明显不会再把北京大学切成北京/大学。3. 人岗匹配的实现思路3.1 匹配目标与难点人岗匹配要解决的是给定一个岗位JD和一批简历怎么排序的问题。这个问题的难点不是纯文本相似度而是要理解岗位需要什么和候选人有什么之间的语义关系。举个例子JD里写熟悉Spring Boot简历里写用过Java Web开发。两者用词完全不同但人眼一看就知道是匹配的。如果只用字符匹配或TF-IDF余弦相似度算出来的分数会很低。这就是人岗匹配的核心难点——语义鸿沟。所以我把匹配拆成了几个维度每个维度单独算分最后加权求和技能匹配度JD技能列表 vs 简历技能列表计算Jaccard相似度或词向量相似度。学历匹配度JD要求本科及以上简历是硕士满分本科也满分因为门槛过了低于门槛则低分。工作年限匹配度JD要求3年以上简历5年满分简历2年按比例给分。项目相似度JD里的项目类型关键词如电商系统推荐系统与简历项目描述里的关键词比较。语言风格相似度这是一个补充信号如果你实在没招了可以用但别作为主要指标。3.2 技能匹配从Jaccard到词向量初版我用的是Jaccard相似度公式很简单J(A, B) |A ∩ B| / |A ∪ B|A是JD要求的技能集合B是简历技能集合交集大小除以并集大小。这个指标在技能匹配上有一定区分度但问题也明显JD写了Python简历写了Python3字面上不匹配实际完全匹配。所以我引入了归一化处理把Python3python都归一到python。词向量方案是进一步优化。我用预训练的Word2Vec中文词向量比如腾讯开源的词向量把JD和简历的技能描述分别转化为向量然后计算余弦相似度。方案是这样的import numpy as np from gensim.models import KeyedVectors model KeyedVectors.load_word2vec_format(tencent-embedding.bin, binaryTrue) def get_sentence_vector(tokens, model, embedding_size200): vectors [model[token] for token in tokens if token in model] if not vectors: return np.zeros(embedding_size) return np.mean(vectors, axis0) def cosine_similarity(vec_a, vec_b): dot np.dot(vec_a, vec_b) norm_a np.linalg.norm(vec_a) norm_b np.linalg.norm(vec_b) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b)不过这里要用一个预训练好的中文词向量文件体积大概在2GB左右加载速度慢内存占用高。如果你是在课程设计中做演示建议先用gensim自带的如果你能下载到或者用HuggingFace上的较小模型代替。还有个小技巧可以先在后台加载模型第一次请求时比较慢之后命中缓存就快了。3.3 匹配分数的权重设计匹配分数不是简单把所有维度加在一起而是要有权重。这个权重怎么定我一开始是拍脑袋技能0.4、学历0.2、年限0.2、项目0.2后来发现不同岗位的侧重点根本不同——招算法工程师技能的权重要更高招实习生学历权重要降下来学习态度和基础更重要。所以我做了个简单版的岗位类型自适应权重岗位类型技能权重学历权重年限权重项目权重算法/研究0.300.250.150.30后端/工程0.400.150.200.25测试/运维0.350.150.250.25产品/运营0.200.200.300.30这个表格是我根据常识初步设定的后续如果要更科学可以用简单的逻辑回归让人工标注一批匹配结果学习出每个岗位类型的最优权重。不过课程设计阶段人工设定加上合理解释已经足够了。最后输出的匹配分是0到100之间的标准化分数。为了让HR看起来直观我还加了一个等级划分90分以上强烈推荐75-89分建议面试60-74分可备用60分以下暂不推荐这一步虽然简单但对系统的可用性提升非常大。4. 实操过程与核心代码实现4.1 项目目录结构先看下完整的项目结构方便你对照着搭resume_parser/ ├── app.py # Flask Web入口 ├── parser/ │ ├── __init__.py │ ├── file_extractor.py # PDF/Word/HTML文本提取 │ ├── section_splitter.py # 简历分块 │ ├── entity_extractor.py # 信息抽取正则LAC │ └── skill_extractor.py # 技能抽取 ├── matcher/ │ ├── __init__.py │ ├── jd_parser.py # JD解析 │ ├── similarity.py # 相似度计算 │ └── scoring.py # 匹配评分 ├── models/ │ └── skill_dict.txt # 技能词库 ├── templates/ │ └── index.html # 上传页面 ├── requirements.txt └── README.md4.2 简历解析主流程代码这是整个系统的核心我把解析主流程拆成三步提取文本、分块、抽实体。import json from parser.file_extractor import extract_text_from_pdf, extract_text_from_docx, extract_text_from_html from parser.section_splitter import split_sections from parser.entity_extractor import extract_entities from parser.skill_extractor import extract_skills def parse_resume(file_path): # Step 1: 文本提取 if file_path.endswith(.pdf): raw_text extract_text_from_pdf(file_path) elif file_path.endswith(.docx): raw_text extract_text_from_docx(file_path) elif file_path.endswith(.doc): docx_path convert_doc_to_docx(file_path, temp_dir) raw_text extract_text_from_docx(docx_path) elif file_path.endswith(.html) or file_path.endswith(.htm): raw_text extract_text_from_html(file_path) else: raise ValueError(f不支持的文件格式: {file_path}) if not raw_text.strip(): raise ValueError(文本提取为空可能上传了扫描版PDF请先OCR) # Step 2: 分块 sections split_sections(raw_text) # Step 3: 实体抽取 entities extract_entities(raw_text, sections) # Step 4: 技能抽取单独处理因为技能可能出现在多个section all_text \n.join(sections.values()) if isinstance(sections, dict) else raw_text entities[skills] extract_skills(all_text, skill_dict) return entities这里有一个判断PDF提取为空时要提示用户可能是扫描版。这属于用户体验细节但实际用起来很关键——不然用户传了份扫描件系统返回一堆空字段他以为是你系统坏了。4.3 人岗匹配核心代码匹配模块接收两个输入解析好的简历JSON和一段JD文本。JD同样要经过解析但JD的结构化字段比简历少通常只需要技能、学历要求、年限要求。from matcher.jd_parser import parse_jd from matcher.similarity import compute_skill_similarity, compute_text_similarity from matcher.scoring import score_candidate def match_resume_to_jd(resume_json, jd_text): jd_info parse_jd(jd_text) # 技能匹配 skill_score compute_skill_similarity(resume_json.get(skills, []), jd_info[skills]) # 学历匹配 edu_score 1.0 if eval_education(resume_json[education], jd_info.get(min_education, 大专)) else 0.3 # 年限匹配 exp_years extract_experience_years(resume_json.get(work_experience, [])) required_years jd_info.get(required_years, 3) exp_score min(1.0, exp_years / required_years) if required_years 0 else 1.0 # 项目/描述相似度 desc_score compute_text_similarity( resume_json.get(project_experience, ), jd_info.get(description, ) ) # 加权总分 final_score score_candidate( skill_scoreskill_score, edu_scoreedu_score, exp_scoreexp_score, desc_scoredesc_score, job_typejd_info.get(job_type, engineer) ) return final_scorescore_candidate内部就是用前面那张权重表做的加权WEIGHT_TABLE { algorithm: {skill: 0.30, edu: 0.25, exp: 0.15, desc: 0.30}, engineer: {skill: 0.40, edu: 0.15, exp: 0.20, desc: 0.25}, test: {skill: 0.35, edu: 0.15, exp: 0.25, desc: 0.25}, product: {skill: 0.20, edu: 0.20, exp: 0.30, desc: 0.30}, } def score_candidate(skill_score, edu_score, exp_score, desc_score, job_type): weights WEIGHT_TABLE.get(job_type, WEIGHT_TABLE[engineer]) raw (weights[skill] * skill_score weights[edu] * edu_score weights[exp] * exp_score weights[desc] * desc_score) return round(raw * 100, 2)4.4 Flask接口与前端页面为了让系统能给人用我写了一个极简的Web上传页面。后端提供两个接口POST /parse上传简历文件返回解析后的JSON。POST /match上传简历文件输入JD文本返回匹配分数和详细得分。from flask import Flask, request, jsonify, render_template import os app Flask(__name__) UPLOAD_FOLDER uploads os.makedirs(UPLOAD_FOLDER, exist_okTrue) app.route(/) def index(): return render_template(index.html) app.route(/parse, methods[POST]) def parse(): file request.files.get(resume) if not file: return jsonify({error: 未上传文件}), 400 file_path os.path.join(UPLOAD_FOLDER, file.filename) file.save(file_path) result parse_resume(file_path) return jsonify(result) app.route(/match, methods[POST]) def match(): file request.files.get(resume) jd_text request.form.get(jd, ) if not file or not jd_text.strip(): return jsonify({error: 请同时上传简历和JD}), 400 file_path os.path.join(UPLOAD_FOLDER, file.filename) file.save(file_path) resume_json parse_resume(file_path) match_score match_resume_to_jd(resume_json, jd_text) return jsonify({score: match_score, resume: resume_json})前端页面也比较简单一个文件选择框一个JD粘贴框一个开始匹配按钮结果区域展示分数和解析出来的简历结构化数据。5. 常见问题与排坑实录5.1 PDF中文乱码与扫描版问题这应该是简历解析里最劝退新人的一个坑。PyPDF2提取中文时经常出现乱码有一部分原因是PDF里中文字体的编码映射不是标准的UnicodePyPDF2的解析器处理不好。升级到pdfplumber后大部分问题解决了但仍有少量PDF因为字体嵌入不全而乱码。我的经验是对于这种PDF改用pdfminer.six的extract_text它处理中文字体的鲁棒性更好。from pdfminer.high_level import extract_text as pdfminer_extract_text def extract_text_from_pdf_fallback(pdf_path): return pdfminer_extract_text(pdf_path)把pdfplumber作为第一方案pdfminer作为fallback准确率能提升不少。扫描版PDF就无解了必须上OCR。我试过Tesseract中文识别效果一般需要下载chi_sim语言包。后来用PaddleOCR效果好了很多但对硬件有要求。在课程设计阶段我建议直接提示用户暂不支持扫描版不要自己硬扛OCR不然会陷入调优的无底洞。5.2 Word表格内容丢失前面提到过python-docx的document.paragraphs拿不到表格内容。但还有一个更隐蔽的坑很多人用WPS或Word制作简历时表格单元格里可能还嵌入了文本框python-docx默认也拿不到文本框里的内容。解决办法是额外遍历w:txbxContent标签from docx.oxml.ns import qn def extract_textbox_content(doc): texts [] for txbx in doc.element.body.iter(qn(w:txbxContent)): for p in txbx.iter(qn(w:p)): text .join(node.text or for node in p.iter(qn(w:t))) if text.strip(): texts.append(text.strip()) return \n.join(texts)这个技巧在文档里很少被提到但实际中遇到表格型简历的概率很高建议提前处理好。5.3 分块不准导致实体抽取错误分块逻辑最大的问题是误判。我遇到过一个真实案例简历里教育经历那一栏写着2009-2013 本科 北京大学但候选人把专业技能写在右栏恰好技能这个词出现在教育经历下方第一行结果直接把整个技能板块归到了教育经历下面导致后面的学历信息抽取失败。解决办法是在分块时加入安全边界机制每个section一旦开始只有当遇到明确的下一节触发词时才会切换如果遇到不确定的触发词先记录下来等下一行再确认。5.4 技能词库覆盖不全初版技能词库只有500词后来在用真实简历测试时发现很多人的技能写得很具体比如PyTorch LightningSpring Cloud Alibaba这些在通用词库里没有。优化方法是做动态扩展把简历中词频高且出现在熟悉掌握精通等动词后面的名词短语作为候选技能人工审核后加入词库。def extract_candidate_skills(text): candidates [] for sentence in re.split(r[。\n], text): match re.search(r(熟悉|掌握|精通|了解)\s*([\w\-\.\/]), sentence) if match: candidates.append(match.group(2)) return candidates这算是半自动维护词库的土办法但确实有效。5.5 匹配评分不够聪明初版匹配时我的技能相似度用的是Jaccard结果出现了很多尴尬场景JD要求精通Python简历写熟悉Python两者都包含Python但精通和熟悉的熟练度差异完全没体现。后来我在技能库中为每个技能增加了熟练度等级词匹配如果简历里是精通技能权重1.0熟悉技能权重0.7了解技能权重0.4。效果立刻好了很多。另外还有一个细节学历匹配要处理硕士以上还是本科以上的差异。JD写本科及以上学历硕士、博士都算过门槛本科也算过门槛只是硕士和博士在学历维度上并没有额外加分。但如果是硕士及以上那本科就不合格直接淘汰。这个逻辑要分清楚不能简单按学历级别线性给分。6. 扩展方向与个人心得做完这个项目我自己最深的体会是简历解析这种任务看起来是个NLP问题实际上是个工程问题。真正花时间的地方不在模型的选型而在处理各种现实中的脏数据、格式差异和边界情况。你把十份真实简历喂进去跑一遍就会发现完美方案是根本不存在的只能做一个覆盖率尽可能高、同时有兜底策略的系统。如果后续想做深我建议朝这几个方向走引入基于BERT的文档级信息抽取训练一个小型模型专门做简历实体识别解决规则方案处理不了的长尾情况。用PySpark做分布式解析把处理速度从每秒几份提升到每秒几十份应对大批量校招场景。把匹配模块升级成学习排序Learning to Rank人工标注一批历史录用数据让模型自动学习岗位和简历的匹配模式。加一个可视化看板展示简历解析的置信度、各字段抽取情况帮助HR判断哪些结果需要人工复核。最后再说一个实际部署的小技巧如果要把这个系统给非技术人员用别让他们在本地跑Python。用Docker封装好环境Dockerfile里把LibreOffice也装好用来处理.doc文件一键启动省心很多。我一开始就是没管这件事结果HR那边部署环境花了整整半天后来把依赖全部打进镜像就再没出过问题。本文还有配套的精品资源点击获取