第一次个人编程作业

作业链接: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)

通过性能分析可以发现,程序主要耗时集中在:

  1. 文件读取模块 read_file()
  2. 文本编码解析过程
  3. 文本预处理模块 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

测试结果:
覆盖率截图如下:

屏幕截图 2026-09-15 193223

从测试结果可以看出:

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
posted @ 2026-09-13 21:43  DENG^n  阅读(28)  评论(0)    收藏  举报