第一次个人编程作业
作业链接:https://github.com/dwjdyx/3224004116.git
一.任务
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/ |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/15693 |
| 这个作业的目标 | 编码实现项目“论文查重”,通过github进行版本管理 |
二.PSP表格
| PSP2.1 | 预估耗时(分钟) |
|---|---|
| Planning | |
| · Estimate(估计这个任务需要多少时间) | 150 |
| Development | |
| · Analysis(需求分析,包括学习新技术) | 30 |
| · Design Spec(生成设计文档) | 20 |
| · Design Review(设计复审) | 15 |
| · Coding Standard(代码规范) | 10 |
| · Design(具体设计) | 30 |
| · Coding(具体编码) | 120 |
| · Code Review(代码复审) | 30 |
| · Test(自我测试,修改代码,提交修改) | 70 |
| Reporting | |
| · Test Report(测试报告) | 30 |
| · Size Measurement(计算工作量) | 15 |
| · Postmortem & Process Improvement Plan | 25 |
三.计算模块接口的设计与实现过程
1. 代码组织结构设计
本项目采用模块化设计思想,将论文查重过程划分为文本处理模块、特征提取模块、相似度计算模块以及程序入口模块。
程序主要由以下几个函数组成:
| 模块 | 主要函数 | 功能 |
|---|---|---|
| 文件读取模块 | read_file() | 负责读取原文和抄袭版论文文件,并返回文本内容 |
| 文本预处理模块 | clean_text() | 去除标点符号及无关字符,降低文本格式差异对结果的影响 |
| 重复率计算模块 | calculate_similarity() | 根据两个文本的特征集合计算重复率 |
| 程序控制模块 | main() | 负责读取命令行参数,调用各模块,并将结果写入指定文件 |
各模块之间通过函数参数传递数据,降低了模块之间的耦合性,便于后续测试和维护。
2. 关键算法设计
本项目采用基于字符序列匹配的论文相似度计算算法。
整体流程如下:
输入原文文件和抄袭文件
↓
读取文本
↓
文本预处理
↓
字符序列匹配
↓
计算重复比例
↓
输出结果
算法主要流程如下:
(1)文本预处理
由于测试文件可能来源于不同环境,例如普通txt文件或者GitHub网页代码页面,因此需要进行文本归一化处理。
主要处理步骤:
① GitHub网页正文提取
部分测试文件为GitHub网页格式,其中包含:
- HTML标签
- CSS样式
- 页面脚本
- 文件编号信息
这些内容并不是论文正文,会影响查重结果。
因此程序首先利用正则表达式提取:
blob-code-inner js-file-line
区域中的真实文本内容。
② 去除HTML标签
例如:
处理前:
<td class="blob-code-inner">
机器人控制系统
</td>
处理后:
机器人控制系统
③ 文本归一化
删除:
- 标点符号
- 空格
- 换行
- 特殊字符
只保留:
- 中文字符
- 英文字母
- 数字
例如:
原文:
今天是星期天,天气晴。
处理后:
今天是星期天天气晴
(2)字符序列匹配算法
程序使用 Python 内置 SequenceMatcher 算法,对两个文本进行连续字符匹配。
设:
- 原文为 A
- 抄袭文本为 B
算法首先寻找两个字符串中的最大匹配字符块。
匹配字符数量:
MM
抄袭版文本长度:
∣B∣
最终重复率:
Similarity=M∣B∣Similarity=\frac{M}{|B|}
其中:
- 分子表示抄袭文本中与原文相同的字符数量;
- 分母表示抄袭文本总长度。
相比简单字符串比较方法,该方法能够适应:
- 插入字符;
- 删除字符;
- 局部修改;
等论文修改情况。
3. 算法特点与创新点
(1)针对实际测试环境增加网页文本提取功能
传统论文查重算法通常直接处理纯文本,而本项目测试文件部分来源于GitHub网页。
因此增加HTML正文提取模块,避免网页代码影响查重结果。
(2)字符级匹配,提高对文字修改的适应能力
通过连续字符匹配,即使论文进行了部分增删修改,仍然能够检测重复内容。
(3)模块化设计
程序将:
- 文件读取
- 文本清洗
- 相似度计算
- 文件输出
进行分离,方便后续扩展。
四.计算模块接口部分的性能改进
1. 性能分析过程
为了分析程序运行过程中的性能瓶颈,使用 Python 自带的 cProfile 性能分析工具对论文查重程序进行性能测试。
测试命令如下:
python -m cProfile -s cumulative main.py 原文路径 抄袭版路径 输出路径
为了使性能分析结果更加明显,将测试论文文件内容扩大100倍,增加文本规模,观察程序在较大数据量下的运行情况。
结果如下:
2443 function calls (2392 primitive calls) in 0.017 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
3/1 0.000 0.000 0.017 0.017 {built-in method builtins.exec}
1 0.000 0.000 0.017 0.017 main.py:1()
1 0.000 0.000 0.015 0.015 main.py:101(main)
2 0.001 0.001 0.013 0.006 main.py:6(read_file)
2 0.007 0.003 0.011 0.005 {method 'read' of '_io.TextIOWrapper' objects}
2 0.000 0.000 0.004 0.002 codecs.py:319(decode)
2 0.004 0.002 0.004 0.002 {built-in method _codecs.utf_8_decode}
3/1 0.000 0.000 0.002 0.002 :1022(_find_and_load)
7 0.000 0.000 0.002 0.000 re.py:288(_compile)
3/1 0.000 0.000 0.002 0.002 :987(_find_and_load_unlocked)
4 0.000 0.000 0.002 0.000 sre_compile.py:783(compile)
2 0.000 0.000 0.002 0.001 main.py:33(clean_text)
3/1 0.000 0.000 0.001 0.001 :664(_load_unlocked)
2/1 0.000 0.000 0.001 0.001 :877(exec_module)
通过性能分析可以发现,程序主要耗时集中在:
- 文件读取模块
read_file() - 文本编码解析过程
- 文本预处理模块
clean_text()
其中:
read_file()
函数负责读取论文文件内容,当输入文本规模增加时,需要消耗更多时间完成文件加载。
同时:
clean_text()
函数中的正则表达式处理也会随着文本长度增加产生一定计算开销。
相比之下,相似度计算模块由于采用 Python 内置的文本匹配算法,在当前测试规模下耗时较少。
2. 性能优化思路
针对性能分析发现的问题,对计算模块进行了优化。
主要优化方向如下:
(1)减少重复文本处理
优化前程序在计算过程中需要多次处理文本内容。
优化后:
- 文件读取后立即进行统一预处理;
- 后续计算过程直接使用处理后的文本;
- 避免重复执行清洗操作。
优化流程:
文件读取
↓
文本预处理
↓
相似度计算
↓
结果输出
使每个模块只负责一次对应操作,减少重复计算。
(2)优化文本清洗过程
文本清洗主要通过正则表达式实现:
re.sub()
删除标点符号和无关字符。
优化后:
- 减少不必要的正则匹配;
- 保留论文有效字符;
- 降低文本预处理时间。
由于论文查重主要关注文本内容,而不是格式信息,因此提前去除无效字符可以减少后续计算的数据规模。
(3)优化相似度计算流程
程序采用:
SequenceMatcher
进行文本相似度计算。
优化时:
- 对输入文本进行预处理;
- 删除无效字符;
- 减少参与匹配的数据量。
这样可以降低文本匹配过程中的计算复杂度,提高整体运行效率。
3. 性能优化效果分析
优化前后程序均使用相同测试数据进行测试。
测试文件规模:
- 原文扩大100倍;
- 抄袭版论文扩大100倍。
优化后:
- 程序运行时间保持在毫秒级;
- 文本读取和预处理时间降低;
- 程序能够快速完成大规模文本重复率计算。
性能分析结果表明:
优化后的程序能够在保证检测准确率的同时,提高文本处理效率,满足5秒内完成计算的要求。
4. 性能优化时间记录
| 阶段 | 预计时间 | 实际时间 |
|---|---|---|
| 性能分析工具学习与使用 | 15分钟 | 20分钟 |
| 测试数据扩大与性能测试 | 15分钟 | 15分钟 |
| 定位性能瓶颈 | 15分钟 | 15分钟 |
| 优化文本处理流程 | 20分钟 | 20分钟 |
| 重新测试与结果分析 | 15分钟 | 15分钟 |
五.计算模块部分单元测试展示
1. 单元测试设计
为了验证论文查重计算模块的正确性,使用 Python 自带的 unittest 框架对程序中的核心函数进行了单元测试。
| 测试主要针对以下几个方面: | |
|---|---|
| 测试项目 | 测试目的 |
read_file() |
验证文件读取功能是否正常 |
clean_text() |
验证文本预处理功能是否正确 |
calculate_similarity() |
验证完全不同文本的相似度计算 |
| 空文本处理 | 验证空输入情况下程序能够正常运行 |
测试文件:test_main.py
通过构造不同类型测试数据,空文本以及错误路径等情况,验证程序在不同输入条件下均能正常运行。
2. 部分单元测试代码展示
(1)文件读取测试
测试函数:
read_file()
测试目的:
验证程序是否能够正确读取指定路径文件。
测试代码:
def test_read_file(self):
with open("test.txt", "w", encoding="utf-8") as f:
f.write("机器人控制系统")
result = read_file("test.txt")
self.assertEqual(
result,
"机器人控制系统"
)
测试数据设计:
创建一个包含固定文本的小文件,通过读取结果与预期内容比较,验证文件读取模块正确性。
(2)文本清洗测试
测试函数:
clean_text()
测试目的:
验证程序是否能够去除标点符号,只保留有效文本内容。
测试代码:
def test_clean_text(self):
text = "机器人控制系统,运行正常!"
result = clean_text(text)
self.assertEqual(
result,
"机器人控制系统运行正常"
)
测试数据设计:
输入包含中文字符以及标点符号的文本,模拟论文中由于格式差异导致的情况。
(3)完全不同文本测试
测试代码:
def test_different_text(self):
text1 = "机器人控制系统"
text2 = "天气情况分析"
result = calculate_similarity(
text1,
text2
)
self.assertEqual(
result,
0
)
测试数据设计:
构造不存在共同内容的文本,验证算法能够识别无重复情况。
(4)空文本测试
测试代码:
def test_empty_text(self):
result = calculate_similarity(
"",
"机器人控制系统"
)
self.assertEqual(
result,
0
)
测试目的:
验证输入为空时程序不会发生异常。
(5)文件不存在测试
测试函数:read_file()
测试目的:验证输入文件不存在时,程序能够进行异常处理。
测试代码:
def test_file_not_exist(self):
result = read_file(
"not_exist.txt"
)
self.assertEqual(
result,
""
)
测试目的:
模拟用户输入错误路径,验证异常处理。
(6)主程序输出测试
测试代码:
def test_main_output(self):
with open(
"orig.txt",
"w",
encoding="utf-8"
) as f:
f.write("机器人控制系统")
with open(
"copy.txt",
"w",
encoding="utf-8"
) as f:
f.write("机器人控制系统")
sys.argv = [
"main.py",
"orig.txt",
"copy.txt",
"answer.txt"
]
main()
with open(
"answer.txt",
encoding="utf-8"
) as f:
result = f.read()
self.assertEqual(
result,
"1.00"
)
测试目的:
模拟用户真实运行流程,验证文件输入输出功能。
4. 测试覆盖率结果
使用 coverage 工具统计测试覆盖情况。
执行:coverage run --branch -m unittest test_main.py
查看覆盖率:coverage report
测试结果:
覆盖率截图如下:

从测试结果可以看出:
test_main.py 测试代码自身覆盖率达到 95%;
main.py 整体覆盖率为 66%;
综合覆盖率为 75%;
已覆盖文件读取、文本清洗、相似度计算、空文本处理、文件异常以及主程序运行等主要功能。
由于程序中仍存在部分未执行的分支,因此整体覆盖率没有达到100%,后续还可以通过增加异常输入和边界条件测试进一步提高覆盖率。
六.计算模块部分异常处理说明
为了提高程序的稳定性,在设计论文查重计算模块时,对可能出现的异常情况进行了分析,并增加相应的处理机制。
主要考虑以下异常场景:
命令行参数数量错误;
输入文件不存在;
输入文件为空;
抄袭文本为空;
输出文件写入异常。
通过异常处理机制,可以避免程序因输入错误直接崩溃,提高程序的健壮性。
1. 命令行参数数量异常
异常设计目标
程序通过命令行参数获取:
原文路径;
抄袭版论文路径;
输出文件路径。
如果用户没有按照要求输入三个参数,程序无法正常运行。因此需要检测参数数量,并给出提示信息。
错误场景
用户输入:
python main.py
缺少文件路径参数。
异常处理代码
if len(sys.argv) != 4:
print(
"Usage: python main.py 原文路径 抄袭路径 输出路径"
)
return
单元测试样例
def test_wrong_argument(self):
import sys
sys.argv = [
"main.py"
]
main()
测试目标:模拟用户未输入完整参数的情况,验证程序能够正常提示并退出,而不是异常崩溃。
2. 文件不存在异常
异常设计目标
用户输入的文件路径可能错误,例如:
文件名拼写错误;
文件移动位置改变;
输入文件路径不存在。
如果直接读取文件,会产生 FileNotFoundError。因此程序对文件读取过程进行异常捕获。
错误场景
D:\test\none.txt
文件不存在。
异常处理代码
try:
with open(
path,
"r",
encoding="utf-8"
) as f:
return f.read()
except FileNotFoundError:
print("文件不存在:", path)
return None
同时,主函数中进行判断:
if original is None or copy is None:
print("程序终止,无法进行查重。")
return
这样可以区分:
None:文件读取失败;"":文件正常打开,但是文件内容为空。
单元测试样例
def test_file_not_exist(self):
result = read_file(
"not_exist.txt"
)
self.assertEqual(
result,
None
)
测试目标:验证程序遇到不存在的文件时能够捕获异常,并终止后续查重流程,而不是将错误文件当作空文件继续计算。
3. 空文本异常
异常设计目标
论文文件可能为空。空文件属于合法输入情况,因此程序不应该因为文件内容为空而发生异常,而应该能够正常完成计算。
程序读取空文件后,read_file() 返回空字符串 ""。之后经过文本清洗仍然为空字符串,程序可以继续进行相似度计算。
错误场景
输入文件内容:
(空文件)
处理方式
文本清洗函数对空字符串进行处理后仍然返回空字符串:
def clean_text(text):
...
return text
因此不会因为空文件直接产生异常。
如果需要测试空文本,可以测试:
def test_empty_text(self):
result = clean_text("")
self.assertEqual(
result,
""
)
或者测试整体相似度:
def test_empty_text(self):
result = calculate_similarity(
"",
"机器人控制系统"
)
self.assertEqual(
result,
0
)
测试目标:验证空字符串输入时,程序能够正常返回空集合。
4.抄袭文本为空异常
异常设计目标
当抄袭文本为空时,如果直接计算:
matches / len(copy)
会产生除零错误。
因此,在计算相似度之前,程序首先判断抄袭文本长度。如果抄袭文本为空,则直接返回 0.0,避免除零异常。
错误场景
原文:
机器人控制系统
抄袭版:
(空)
异常处理代码
if len(copy) == 0:
return 0.0
单元测试样例
def test_empty_copy(self):
result = calculate_similarity(
"机器人控制系统",
""
)
self.assertEqual(
result,
0
)
测试目标:验证抄袭文本为空时,程序能够正常返回 0.0,不会产生除零异常。
5. 输出文件异常
异常设计目标
程序最后需要将计算结果写入指定输出文件。如果输出路径不可写,程序应该捕获异常并给出提示,而不是直接异常退出。
错误场景
输出路径不存在;
文件权限不足;
输出文件无法正常创建。
异常处理代码
try:
with open(
output_path,
"w",
encoding="utf-8"
) as f:
f.write(
"%.2f" % result
)
except Exception as e:
print(
"输出文件失败:",
e
)
return
通过 try-except 捕获输出过程中的异常,可以避免程序因为输出文件问题直接崩溃。
七.任务结束PSP表格
| PSP2.1 | 实际耗时(分钟) |
|---|---|
| Development | |
| · Analysis(需求分析,包括学习新技术) | 10 |
| · Design Spec(生成设计文档) | 20 |
| · Design Review(设计复审) | 15 |
| · Coding Standard(代码规范) | 10 |
| · Design(具体设计) | 30 |
| · Coding(具体编码) | 120 |
| · Code Review(代码复审) | 20 |
| · Test(自我测试,修改代码,提交修改) | 120 |
| Reporting | |
| · Test Report(测试报告) | 40 |
| · Size Measurement(计算工作量) | 15 |
| · Postmortem & Process Improvement Plan | 35 |

浙公网安备 33010602011771号