python 自动批改 PDF 作业

背景:

去年担任了一门课的助教,课程比较麻烦,每个学生需要提交四分实验报告,模板都是一样的。本以为助教会轻轻松松,结果我们需要批改每个学生的PDF报告,当时着急在学校系统上成绩,所以都是大致浏览一下报告,给出各个学生各个项目的分数,整理出来了详细成绩EXCEL。每个实验报告都有一个评分表,就是下图这样:

本来以为上完成绩就完事大吉了,结果到了课程结束后的三个月后,老师又找到了我,说学校需要把每个学生的电子版报告存档,所以每个电子版PDF作业都要有批改痕迹。。。每一项小分都要打上。。。打的小分还要和之前的EXCEL成绩表中的小分对应上。。。小分没有给满还要注明在哪里减的分,减了多少分。。。还要有教师评语。。。教师评语还要有区分。。。

我算了一下,每个报告有6项小分(就是上图中的ABCDE和综合评分),再加上教师评语、老师签字、时间、和五个减分,也就是说一个报告我要批14项。一个学生四个报告,一个班132个学生,一共三个班。。。也就是说我要批1584个报告,那总共批改的地方要有 14 * 1584 = 22176,假如我鼠标操作特别快,两秒批一项,不吃不喝要批改 22176*2秒=739.2分钟=12.32小时。。。就这还没有算我需要对照EXCEL打分来回切换和打开PDF的时间。。。

对这种无脑操作,只能寄希望万能的 Python

 

实现思路

我最先了解到,python中的Py2PDF2库能够实现两个pdf融合,类似于加水印。我尝试了一下,确实可以实现这种功能。但是后来用Py2PDF2库出现了中文PDF文件融合乱码的情况,改用了pdfrw库才解决。

我的自动批改作业功能实现流程就是:

1. 生成评分表的PDF文件 ScoreSheetPdf

效果如下:

2. 将学生作业的PDF文件 OriginalPdf 与 ScoreSheetPdf 融合

效果如下:

3. 生成减分PDF 文件 MinusPointsPdf

4. 将学生作业的PDF文件 OriginalPdf 与 MinusPointsPdf 融合

效果如下:

python 代码

再具体的细节这里就不多介绍了,也许这里的代码只适合我遇到的情况。

学python不久,代码肯定还存在一些不足,请各位大佬批评指正。

附上代码下载链接,里面不止有py文件,还有代码中使用到excel成绩表和PDF作业(已隐去学生信息)。

 

# 该代码是为了 根据EXCEL分数记录表格 自动批改 pdf 作业
# 前提是已经 EXCEL分数记录表格 即各个学生的各个作业的各个项的分数已知
# 然后利用该代码实现 将各项分数填到作业中,省去了人工填写的麻烦
# 实现的功能有:
#   1. 根据设定格式生成 scoreSheet.pdf
#   2. 将scoreSheet.pdf 与 作业.pdf 融合在一起,相当于加入水印
#   3. 将各个减分项 随机打在 作业.pdf 中
#   4. 循环遍历
# 作者:HuaQinglong
# 时间:2019-10-16

from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont('SimKai', 'SimKai.ttf'))

from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
from reportlab.lib import colors

from pdfrw import PdfReader, PdfWriter, PageMerge

import numpy as np
import pandas as pd

import os

# 加入空行
def addSpace(elements, Style, num):
    for i in range(num):
        t = Paragraph(u'<br/>%s<br/>' % ' ', Style['Normal'])
        elements.append(t)
    return elements

# 根据各项分数生成评语
def creatComment(ScoreList):
    Score1 = ScoreList[0]
    Score2 = ScoreList[1]
    Score3 = ScoreList[2]
    Score4 = ScoreList[3]
    Score5 = ScoreList[4]

    comment = '该生'
    myarray = np.random.randint(0, 2, 4)
    if Score1 >= 95:
        if myarray[0] == 0:
            comment = comment + '预习认真'
        else:
            comment = comment + '了解实验目的'
    elif Score1 >= 90:
        if myarray[1] == 0:
            comment = comment + '预习较认真'
        else:
            comment = comment + '较了解实验目的'
    elif Score1 >= 80:
        if myarray[2] == 0:
            comment = comment + '预习相对认真'
        else:
            comment = comment + '清楚实验目的'
    else:
        if myarray[3] == 0:
            comment = comment + '预习深度不够'
        else:
            comment = comment + '不够清楚实验目的'
    comment = comment + ','

    if Score2 >= 95:
        if myarray[0] == 0:
            comment = comment + '实验过程完整'
        else:
            comment = comment + '操作规范'
    elif Score2 >= 90:
        if myarray[1] == 0:
            comment = comment + '实验过程较完整'
        else:
            comment = comment + '操作正确'
    elif Score2 >= 80:
        if myarray[2] == 0:
            comment = comment + '实验过程相对完整'
        else:
            comment = comment + '操作较正确'
    else:
        if myarray[3] == 0:
            comment = comment + '实验过程不够完整'
        else:
            comment = comment + '操作不够规范'
    comment = comment + ','

    if Score3 >= 95:
        if myarray[0] == 0:
            comment = comment + '实验结果正确'
        else:
            comment = comment + '问题回答正确'
    elif Score3 >= 90:
        if myarray[1] == 0:
            comment = comment + '实验结果较正确'
        else:
            comment = comment + '问题回答较正确'
    elif Score3 >= 80:
        if myarray[2] == 0:
            comment = comment + '实验结果相对正确'
        else:
            comment = comment + '问题回答相对正确'
    else:
        if myarray[3] == 0:
            comment = comment + '实验结果不够正确'
        else:
            comment = comment + '问题回答不够正确'
    comment = comment + ','

    if Score4 >= 95:
        if myarray[0] == 0:
            comment = comment + '程序完整'
        else:
            comment = comment + '代码规范'
    elif Score4 >= 90:
        if myarray[1] == 0:
            comment = comment + '程序较完整'
        else:
            comment = comment + '代码较规范'
    elif Score4 >= 80:
        if myarray[2] == 0:
            comment = comment + '程序相对完整'
        else:
            comment = comment + '代码相对规范'
    else:
        if myarray[3] == 0:
            comment = comment + '程序不够完整'
        else:
            comment = comment + '代码存在错误'
    comment = comment + ','

    if Score5 >= 95:
        if myarray[0] == 0:
            comment = comment + '报告内容充实'
        else:
            comment = comment + '报告分析深刻'
    elif Score5 >= 90:
        if myarray[1] == 0:
            comment = comment + '报告内容较充实'
        else:
            comment = comment + '报告分析较深刻'
    elif Score5 >= 80:
        if myarray[2] == 0:
            comment = comment + '报告内容相对充实'
        else:
            comment = comment + '报告分析相对深刻'
    else:
        if myarray[3] == 0:
            comment = comment + '报告内容不够充实'
        else:
            comment = comment + '报告分析不够深刻'
    comment = comment + '。'
    return comment

# 根据单项分数 生成单页减分PDF
def deScoreSheet(deScore, pdfName):
    Style = getSampleStyleSheet()

    bt = Style['Normal']  # 字体的样式
    bt.fontName = 'SimKai'  # 使用的字体
    bt.fontSize = 14  # 字号
    bt.wordWrap = 'CJK'  # 该属性支持自动换行,'CJK'是中文模式换行,用于英文中会截断单词造成阅读困难,可改为'Normal'
    bt.firstLineIndent = 32  # 该属性支持第一行开头空格
    bt.leading = 12  # 该属性是设置行距
    bt.alignment = 2  # 0:居左  1:居中  2:居右
    bt.textColor = colors.red  # 颜色设置为红色

    elements = []

    if len(deScore) == 1:
        numSpace = np.random.randint(4, 24, 1)
        elements = addSpace(elements, Style, numSpace[0])
        t = Paragraph('-' + str(deScore[0]), bt)
        elements.append(t)
    elif len(deScore) == 2:
        numSpace = np.random.randint(4, 12, 2)
        for i in range(len(deScore)):
            elements = addSpace(elements, Style, numSpace[i])
            t = Paragraph('-' + str(deScore[i]), bt)
            elements.append(t)
    elif len(deScore) == 3:
        numSpace = np.random.randint(4, 8, 3)
        for i in range(len(deScore)):
            elements = addSpace(elements, Style, numSpace[i])
            t = Paragraph('-' + str(deScore[i]), bt)
            elements.append(t)
    elif len(deScore) == 4:
        numSpace = np.random.randint(4, 6, 4)
        for i in range(len(deScore)):
            elements = addSpace(elements, Style, numSpace[i])
            t = Paragraph('-' + str(deScore[i]), bt)
            elements.append(t)
    elif len(deScore) == 5:
        numSpace = np.random.randint(2, 4, 5)
        for i in range(len(deScore)):
            elements = addSpace(elements, Style, numSpace[i])
            t = Paragraph('-' + str(deScore[i]), bt)
            elements.append(t)
    pdf = SimpleDocTemplate(pdfName)
    pdf.multiBuild(elements)

# 根据各项分数 在作业PDF中加入减分文本
def randDeScoreSheet(ScoreList, OriginalPdf, MinusPointsPdf):
    deScoreList = []
    Score1 = ScoreList[0]
    Score2 = ScoreList[1]
    Score3 = ScoreList[2]
    Score4 = ScoreList[3]
    Score5 = ScoreList[4]

    deScoreList.append(100 - Score1)
    deScoreList.append(100 - Score2)
    deScoreList.append(100 - Score3)
    deScoreList.append(100 - Score4)
    deScoreList.append(100 - Score5)

    if numPages >= 8:
        for i in range(5):
            deSsore = [deScoreList[i]]
            deScoreSheet(deSsore, MinusPointsPdf)
            markPdfs(OriginalPdf, OriginalPdf, MinusPointsPdf, i + 3)
    elif numPages >= 6:
        deSsore = [deScoreList[0:2]]
        deScoreSheet(deSsore, MinusPointsPdf)
        markPdfs(OriginalPdf, OriginalPdf, MinusPointsPdf, 3)
        deSsore = [deScoreList[2:4]]
        deScoreSheet(deSsore, MinusPointsPdf)
        markPdfs(OriginalPdf, OriginalPdf, MinusPointsPdf, 4)
        deSsore = [deScoreList[4]]
        deScoreSheet(deSsore, MinusPointsPdf)
        markPdfs(OriginalPdf, OriginalPdf, MinusPointsPdf, 5)
    elif numPages >= 4:
        deScoreSheet(deScoreList, MinusPointsPdf)
        markPdfs(OriginalPdf, OriginalPdf, MinusPointsPdf, 3)
    else:
        print('PDF文件页数过短' + OriginalPdf)

# 根据各项分数 生成评分PDF
def markScoreSheet(ScoreList, comment, teacher, time, pdfName):
    Score1 = ScoreList[0]
    Score2 = ScoreList[1]
    Score3 = ScoreList[2]
    Score4 = ScoreList[3]
    Score5 = ScoreList[4]
    Score6 = np.rint(np.mean(ScoreList))
    Score6 = Score6.astype(np.int)
    Style = getSampleStyleSheet()

    bt = Style['Normal']     #字体的样式
    bt.fontName = 'SimKai'    #使用的字体
    bt.fontSize = 14            #字号
    bt.wordWrap = 'CJK'    #该属性支持自动换行,'CJK'是中文模式换行,用于英文中会截断单词造成阅读困难,可改为'Normal'
    bt.firstLineIndent = 32  #该属性支持第一行开头空格
    bt.leading = 12             #该属性是设置行距
    bt.alignment = 2             #0:居左  1:居中  2:居右
    bt.textColor = colors.red    # 颜色设置为红色

    elements = []
    elements = addSpace(elements, Style, 4)

    t = Paragraph(str(Score1), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 5)

    t = Paragraph(str(Score2), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 5)

    t = Paragraph(str(Score3), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 3)

    t = Paragraph(str(Score4), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 2)

    t = Paragraph(str(Score5), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 2)

    t = Paragraph(str(Score6), bt)
    elements.append(t)

    elements = addSpace(elements, Style, 1)

    t = Paragraph(comment, bt)
    elements.append(t)
    elements = addSpace(elements, Style, 1)
    t = Paragraph(teacher, bt)
    elements.append(t)
    t = Paragraph(time, bt)
    elements.append(t)

    pdf = SimpleDocTemplate(pdfName)

    pdf.multiBuild(elements)

# 将PDF加水印并保存
def markPdfs(input_file, output_file, watermark_file, page_name):
    # define the reader and writer objects
    reader_input = PdfReader(input_file)
    writer_output = PdfWriter()
    watermark_input = PdfReader(watermark_file)
    watermark = watermark_input.pages[0]

    # go through the pages one after the next

    merger = PageMerge(reader_input.pages[page_name])
    merger.add(watermark).render()

    # write the modified content to disk
    writer_output.write(output_file, reader_input)

# 根据学生姓名查找学生作业PDF路径
def findPdfAcStuName(studentName, pdfFileList):  # 根据学生姓名找到其pdf报告

    OriginalPdf = []
    for i in range(len(pdfFileList)):
        iPdfFile = pdfFileList[i]
        if studentName in iPdfFile:
            OriginalPdf = iPdfFile
            break

    return OriginalPdf

if __name__ == '__main__':

    teacher = '刘慈欣'
    time = '2019.10.15'
    ScoreSheetPdf = 'ScoreSheet.pdf'          # 评分pdf
    MinusPointsPdf = 'MinusPoints.pdf'        # 减分pdf

    # 生成 PDF 路径列表
    dirpath = '.\综合实验'   # 根目录
    pdfFileList = []
    for root, dirs, files in os.walk(dirpath):
        for file in files:
            pdfFileList.append(os.path.join(root, file))

    # 循环评分,根据成绩表中的姓名查找文件路径
    df = pd.read_excel('详细成绩.xls')

    studentNameArr = df.ix[:, '姓名\t'].values
    studentNum = len(studentNameArr)

    for i in range(studentNum):
        istudent = studentNameArr[i]
        ScoreList = []
        ScoreList.append(df.ix[i, 5])
        ScoreList.append(df.ix[i, 6])
        ScoreList.append(df.ix[i, 7])
        ScoreList.append(df.ix[i, 8])
        ScoreList.append(df.ix[i, 9])

        OriginalPdf = findPdfAcStuName(istudent, pdfFileList)
        if len(OriginalPdf):
            try:
                assignment = PdfReader(OriginalPdf)
                numPages = len(assignment.pages)
            except:
                numPages = 0
                print(istudent + ' 批改失败 ***************')

            if numPages > 0:
                # print('页数' + str(numPages))
                # 生成评语
                comment = creatComment(ScoreList)
                # 生成评分表PDF
                markScoreSheet(ScoreList, comment, teacher, time, ScoreSheetPdf)
                # 原始PDF与评分表PDF 合并
                markPdfs(OriginalPdf, OriginalPdf, ScoreSheetPdf, 1)
                # 原始PDF 随机位置减分
                randDeScoreSheet(ScoreList, OriginalPdf, MinusPointsPdf)
                # print(istudent + ' 完成')
            else:
                print(istudent + ' 批改失败 ***************')
        else:
            print(istudent + ' 未查找到 PDF 文件 ***************')

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值