帮某师傅写的一个辅助查学生作业重复率的脚本(批量处理word)

前言

认识的一个师傅最近去一个学校给大学生讲课了,他想查一下学生之间是否有抄袭行为,因此帮助他完成了该脚本。

需求分析

学生交上来的作业是word,文件后缀都是docx,文件名是学号+姓名;文档中包含学生写的文字和图片。因此这里需要对word进行一波处理。

文档属性

需要提取的文档属性,有作者(即文档创建者)最后一次保存者(即文档修改者)创建内容的时间最后一次保存的日期。”

image

文档内容

文档内容中包含文本字符串和图片。

文本字符的组成有中文英文标点符号换行符空格等。因此可以对其做一个数据统计。

图片的md5值是唯一的,因此需要提取所有文档中的图片,病计算其md5

分析总结

1.文档创建者不会出行重复,重复可认定抄袭。

2.创建文档的时间、最后一次保存的日期,会雷同的几率很小,但其两者的差值,可以作为学生完成作业的时间进行参考。

3.文档内容中,中文、英语、空格、换行符、标点符号出现相同个数的会很少。因此可作为判定是否存在抄袭的条件之一。

4.图片的md5值是唯一的、如果两份文档中出现相同的md5值的图片,说明有抄袭行为。

脚本设计

读取所有的文档。

这里使用glob去读取文件。

导入库

1
import glob

代码

1
2
docx_file_list = glob.glob("*.docx")  
print(docx_file_list)

image

返回了一个文件列表。

获取文档对象、处理文档

这里使用python-docx库去提取文档的属性,以及获取到文档的内容。

导入库

1
2
#pip3 install python-docx #安装docx库
import docx

提取文档属性的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def getWordData(doc):  
author = createTime = Editor = endTime = time = '0' #变量初始化,防止出现null的情况
data =[]
try:
author = doc.core_properties.author #文档创建者
createTime = doc.core_properties.created.strftime('%Y-%m-%d %H:%M:%S') #文档创建时间(datetime数据类型)
Editor = doc.core_properties.last_modified_by #文档修改者
endTime = doc.core_properties.modified.strftime('%Y-%m-%d %H:%M:%S') #文档修改时间(datetime数据类型)
time = (doc.core_properties.modified - doc.core_properties.created) #创建时间和修改时间的差值(datetime数据类型)
except:
print("null")
data.append(author)
data.append(createTime)
data.append(Editor)
data.append(endTime)
data.append(time)
return data

docx_file_list = glob.glob("*.docx")
for docxFile in docx_file_list:
docxName = docx.Document(docxFile)
print(getWordData(docxName))

以列表格式,将数据返回输出,方便后续接收处理

image

提取文档中的所有文本字符

1
2
3
4
5
6
#获取文档的所有文本字符  
def getWordStrs(doc):
text = ""
for j in doc.paragraphs:
text += j.text
return text

三个文本的所有字符串如下

image

文本字符统计

然后对所有字符中的中文、英文、空格等字符进行数据统计。
这里接触string库完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def count_str(strs):  
#中文、英文、空格、数字、其他字符
count_Chinese = count_English = count_Space = count_digit = count_Other =0
data = []
for s in strs:
# 英文
if s in string.ascii_letters:
count_English += 1
# 数字
elif s.isdigit():
count_digit += 1
# 空格
elif s.isspace():
count_Space += 1
# 中文
elif s.isalpha():
count_Chinese += 1
# 其他字符
else:
count_Other += 1
data.append(str(count_English))
data.append(str(count_digit))
data.append(str(count_Space))
data.append(str(count_Chinese))
data.append(str(count_Other))
return data

image

提取文档中的图片的代码

word只是一种特殊格式的压缩包,其图片是放在word\media目录下。

image

百度一搜索,发现有现成的,直接就拿来用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def fetch_image(doc_path, desc_path):

img_list = []
if Path(desc_path).is_dir():
pass
else:
os.mkdir(desc_path)
doc = docx.Document(doc_path)
dict_rel = doc.part._rels #rels其实是个目录
for rel in dict_rel:
rel = dict_rel[rel]
#print("rel",rel.target_ref)
if "image" in rel.target_ref:
# create_dir(desc_path)
img_name = re.findall("/(.*)", rel.target_ref)[0] #windos:/
#print("img_name",img_name)
word_name = os.path.splitext(doc_path)[0]
#print("word_name",word_name)
if os.sep in word_name:
new_name = word_name.split('\\')[-1]
else:
new_name = word_name.split('/')[-1]
img_name = f'{new_name}_{img_name}'
img_list.append(img_name)
with open(f'{desc_path}/{img_name}', "wb") as f:
f.write(rel.target_part.blob)

调用方式.

1
2
3
4
5
6
7
8
9
10
docx_file_list = glob.glob("*.docx")  
imagesDir = "images" #图片的保存目录
if Path(imagesDir).is_dir():
pass
else:
os.mkdir(imagesDir)
for docxFile in docx_file_list:
docxName = docx.Document(docxFile)
imgDir = docxFile.replace(".docx","")#学生目录名称
fetch_image(docxFile,imagesDir+"/"+imgDir)#提取所有的文档中的image资源

图片也就全拿到了,并输出到对应学生的文件夹下

image

计算图片MD5

1
2
3
4
5
6
7
8
9
def get_file_md5(file_name):  
m = hashlib.md5()
with open(file_name,'rb') as fobj:
while True:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest()
1
2
for j in os.listdir(imagesDir+"/"+imgDir):  
print(get_file_md5(imagesDir+"/"+imgDir+"/"+j))

image

统计不同word使用图片数量

1
2
3
4
5
6
7
8
9
#图片计数  
def ImgCount(imgDir):
count = 0
file_list=os.listdir(imgDir)
for i in file_list:
img = imgDir + "\\" + i
if os.path.isfile(img):
count = count + 1
return count
1
print(ImgCount(imagesDir+"/"+imgDir))

image

数据存储

这里将数据全部在excel文件(*.xlsx)中

创建学生文档信息表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#学生文档信息表  
def createStudentInfoResultXlsx(workbook):
worksheet = workbook.add_worksheet('学生文档信息')
bold=workbook.add_format(({'bold':True}))
#添加表头
worksheet.write("A1","文件名",bold)
worksheet.write("B1","作者",bold)
worksheet.write("C1","创建时间",bold)
worksheet.write("D1","修改人",bold)
worksheet.write("E1","最后修改时间",bold)
worksheet.write("F1","图片总数",bold)
worksheet.write("G1","字符总数",bold)
worksheet.write("H1","英文字符总数",bold)
worksheet.write("I1","数字字符总数",bold)
worksheet.write("J1","空格字符总数",bold)
worksheet.write("K1","中文字符总数",bold)
worksheet.write("L1","特殊字符总数",bold)
worksheet.write("M1","耗时",bold)
return worksheet
1
2
3
4
5
#创建xlsx  
workbook = xlsxwriter.Workbook('results.xlsx')
#创建学生信息工作薄
worksheetSt = createStudentInfoResultXlsx(workbook)
workbook.close()

image

数据存入学生文档信息表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
row,col=1,0  
for docxFile in docx_file_list:
docxName = docx.Document(docxFile)
imgDir = docxFile.replace(".docx","")#学生目录名称
doc_info = getWordData(docxName) #文档属性
imgLen = str(len(os.listdir(imagesDir+"/"+imgDir))) #学生目录下的图片个数
#往学生文档信息工作薄追加数据
worksheetSt.write(row,col,docxFile)#填第一行第一列的文件名
worksheetSt.write(row,col+1,doc_info[0])#填第一行第二列的作者
worksheetSt.write(row,col+2,doc_info[1])#填第一行第三列的创建时间
worksheetSt.write(row,col+3,doc_info[2])#填第一行第四列的修改人
worksheetSt.write(row,col+4,doc_info[3])#填第一行第五列的最后修改时间
worksheetSt.write(row,col+5,imgLen)#填第一行第六列的图片总数
worksheetSt.write(row,col+6,str(len(getWordStrs(docxName))))#填第一行第七列的总字符数
worksheetSt.write(row,col+7,count_str(getWordStrs(docxName))[0])#填第一行第八列的英文字符总数
worksheetSt.write(row,col+8,count_str(getWordStrs(docxName))[1])#填第一行第九列的数字字符总数
worksheetSt.write(row,col+9,count_str(getWordStrs(docxName))[2])#填第一行第十列的空格字符总数
worksheetSt.write(row,col+10,count_str(getWordStrs(docxName))[3])#填第一行第八列的中文字符总数
worksheetSt.write(row,col+11,count_str(getWordStrs(docxName))[4])#填第一行第九列的特殊字符总数
worksheetSt.write(row,col+12,doc_info[4])#填第一行第十列的耗时
row +=1

这里用了ctrl+t美化表格

image

创建学生图片信息表

1
2
3
4
5
6
7
def createImgInfoResultXlsx(workbook):
worksheet = workbook.add_worksheet('学生文档图片信息')
bold=workbook.add_format(({'bold':True}))
#添加表头
worksheet.write("A1","文件名",bold)
worksheet.write("B1","图片md5",bold)
return worksheet
1
2
#创建图片信息工作薄
worksheetImg = createImgInfoResultXlsx(workbook)

image

数据存入学生图片信息表

1
2
def getFiles(dir):  
return os.listdir(dir)
1
2
3
4
5
6
row2 =1  
for i in getFiles(imagesDir):
for j in getFiles(imagesDir+"/"+i):
worksheetImg.write(row2,col,i)
worksheetImg.write(row2,col+1,get_file_md5(imagesDir+"/"+i+"/"+j))
row2+=1

image

完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import glob  
import docx
import string
import os
from pathlib import Path
import re
import hashlib
import xlsxwriter

'''
Author :jdr
Date: 2022-11-09
Usage: 将脚本和docx放在同目录,执行python3 demo.py即可。
'''

#获取文档属性
def getWordData(doc):
author = createTime = Editor = endTime = time = '0' #变量初始化,防止出现null的情况
data =[]
try:
author = doc.core_properties.author #文档创建者
createTime = doc.core_properties.created.strftime('%Y-%m-%d %H:%M:%S') #文档创建时间(datetime数据类型)
Editor = doc.core_properties.last_modified_by #文档修改者
endTime = doc.core_properties.modified.strftime('%Y-%m-%d %H:%M:%S') #文档修改时间(datetime数据类型)
time = (doc.core_properties.modified - doc.core_properties.created) #创建时间和修改时间的差值(datetime数据类型)
except:
print("null")
data.append(author)
data.append(createTime)
data.append(Editor)
data.append(endTime)
data.append(time)
return data

#获取文档的所有文本字符
def getWordStrs(doc):
text = ""
for j in doc.paragraphs:
text += j.text
return text

#字符计数
def count_str(strs):
#中文、英文、空格、数字、其他字符
count_Chinese = count_English = count_Space = count_digit = count_Other =0
data = []
for s in strs:
# 英文
if s in string.ascii_letters:
count_English += 1
# 数字
elif s.isdigit():
count_digit += 1
# 空格
elif s.isspace():
count_Space += 1
# 中文
elif s.isalpha():
count_Chinese += 1
# 其他字符
else:
count_Other += 1
data.append(str(count_English))
data.append(str(count_digit))
data.append(str(count_Space))
data.append(str(count_Chinese))
data.append(str(count_Other))
return data

#抓取word文件中的图片
def fetch_image(doc_path, desc_path):

img_list = []
if Path(desc_path).is_dir():
pass
else:
os.mkdir(desc_path)
doc = docx.Document(doc_path)
dict_rel = doc.part._rels #rels其实是个目录
for rel in dict_rel:
rel = dict_rel[rel]
#print("rel",rel.target_ref)
if "image" in rel.target_ref:
# create_dir(desc_path)
img_name = re.findall("/(.*)", rel.target_ref)[0] #windos:/
#print("img_name",img_name) word_name = os.path.splitext(doc_path)[0]
#print("word_name",word_name)
if os.sep in word_name:
new_name = word_name.split('\\')[-1]
else:
new_name = word_name.split('/')[-1]
img_name = f'{new_name}_{img_name}'
img_list.append(img_name)
with open(f'{desc_path}/{img_name}', "wb") as f:
f.write(rel.target_part.blob)

#计算文件的md5
def get_file_md5(file_name):
m = hashlib.md5()
with open(file_name,'rb') as fobj:
while True:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest()

#图片计数
def ImgCount(imgDir):
count = 0
file_list=os.listdir(imgDir)
for i in file_list:
img = imgDir + "\\" + i
if os.path.isfile(img):
count = count + 1
return count

#学生文档信息表
def createStudentInfoResultXlsx(workbook):
worksheet = workbook.add_worksheet('学生文档信息')
bold=workbook.add_format(({'bold':True}))
#添加表头
worksheet.write("A1","文件名",bold)
worksheet.write("B1","作者",bold)
worksheet.write("C1","创建时间",bold)
worksheet.write("D1","修改人",bold)
worksheet.write("E1","最后修改时间",bold)
worksheet.write("F1","图片总数",bold)
worksheet.write("G1","字符总数",bold)
worksheet.write("H1","英文字符总数",bold)
worksheet.write("I1","数字字符总数",bold)
worksheet.write("J1","空格字符总数",bold)
worksheet.write("K1","中文字符总数",bold)
worksheet.write("L1","特殊字符总数",bold)
worksheet.write("M1","耗时",bold)
return worksheet

#学生文档图片信息表
def createImgInfoResultXlsx(workbook):
worksheet = workbook.add_worksheet('学生文档图片信息')
bold=workbook.add_format(({'bold':True}))
#添加表头
worksheet.write("A1","文件名",bold)
worksheet.write("B1","图片md5",bold)
return worksheet

def getFiles(dir):
return os.listdir(dir)

if __name__=='__main__':
docx_file_list = glob.glob("*.docx")
imagesDir = "images" #图片的保存目录
if Path(imagesDir).is_dir():
pass
else:
os.mkdir(imagesDir)
#创建xlsx
workbook = xlsxwriter.Workbook('results.xlsx')
#创建学生信息工作薄
worksheetSt = createStudentInfoResultXlsx(workbook)
#创建图片信息工作薄
worksheetImg = createImgInfoResultXlsx(workbook)
# 数据表格偏移
row,col=1,0
for docxFile in docx_file_list:
docxName = docx.Document(docxFile) #声明docx对象
imgDir = docxFile.replace(".docx","")#学生目录名称
fetch_image(docxFile,imagesDir+"/"+imgDir) #获取文档中的图片
doc_info = getWordData(docxName) #文档属性
imgLen = str(len(os.listdir(imagesDir+"/"+imgDir))) #学生目录下的图片个数
#往学生文档信息工作薄追加数据
worksheetSt.write(row,col,docxFile)#填第一行第一列的文件名
worksheetSt.write(row,col+1,doc_info[0])#填第一行第二列的作者
worksheetSt.write(row,col+2,doc_info[1])#填第一行第三列的创建时间
worksheetSt.write(row,col+3,doc_info[2])#填第一行第四列的修改人
worksheetSt.write(row,col+4,doc_info[3])#填第一行第五列的最后修改时间
worksheetSt.write(row,col+5,imgLen)#填第一行第六列的图片总数
worksheetSt.write(row,col+6,str(len(getWordStrs(docxName))))#填第一行第七列的总字符数
worksheetSt.write(row,col+7,count_str(getWordStrs(docxName))[0])#填第一行第八列的英文字符总数
worksheetSt.write(row,col+8,count_str(getWordStrs(docxName))[1])#填第一行第九列的数字字符总数
worksheetSt.write(row,col+9,count_str(getWordStrs(docxName))[2])#填第一行第十列的空格字符总数
worksheetSt.write(row,col+10,count_str(getWordStrs(docxName))[3])#填第一行第八列的中文字符总数
worksheetSt.write(row,col+11,count_str(getWordStrs(docxName))[4])#填第一行第九列的特殊字符总数
worksheetSt.write(row,col+12,doc_info[4])#填第一行第十列的耗时
row +=1

#往学生文档图片信息工作薄追加数据
row2 =1
for i in getFiles(imagesDir):
for j in getFiles(imagesDir+"/"+i):
worksheetImg.write(row2,col,i) #填第一行第一列的文件名
worksheetImg.write(row2,col+1,get_file_md5(imagesDir+"/"+i+"/"+j))#填第一行第二列的MD5
row2+=1

workbook.close()

总结

希望这个脚本能帮助到师傅吧。也顺带学习了一下word的处理方法。

Author: jdr
Link: https://jdr2021.github.io/2022/11/11/帮某师傅写的一个辅助查学生作业重复率的脚本-批量处理word/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.