前言

  昨天总结了一下爬虫的抓取方法,并提到一定要通过PyQt写一个桌面软件。链接
  今天通过不断修改,已经完美地将所有功能实现了。

使用说明

  打开压缩包会看到四个文件。
爬虫界面
  使用说明已经简单交代了软件的用法。
  templete.xls 和 data.txt 文件可以先忽视,后续会交代这两个文件的作用。
  先打开 pyYouthExcel-ver2.0.exe

  初次打开软件会看到两个窗口。
爬虫界面
  左边黑乎乎的窗口是控制台
  当程序出错或者无响应的时候,可以通过控制台看到反馈的信息。
  右边的窗口就是程序的主界面。

软件界面
  这个界面就是软件主界面
  最上面是数据获取设置,清除掉输入框中的文本可以看到输入提示。
软件界面
  没错,这里获取的就是文件夹中的两个文件信息。
  当然也可以自己创建相关的文件,点击选择可以选到相关的路径中
软件界面
  获取完之后,界面会出现完整的路径
软件界面
  这个路径和之前的 data.txt 路径有什么不同效果吗?
  其实两个路径都实现了相同的功能。
  路径很长的称之为绝对路径,从盘符开始索引。
  路径很对的称之为相对路径,从当前程序所在的目录开始索引。

  下面就是 输入网页URL 的区域了
  这里可以将青年之声的提问链接输入进去
软件界面
软件界面
  当然也可以输入多条链接,链接之间用回车分行即可
软件界面
  再下面就是选择文件保存路径
  基本操作相信也不用说明了,直接输入文件名称则以相对路径保存到当前程序启动目录中。
  确认输入无误之后,点击一键统计。
软件界面
  执行完毕之后可以在目录中看到多了两个文件
软件界面
  其实只是多出了生成文件, qt.conf 文件在打开程序之后自动生成,不影响使用。
  下面打开 test.xls 文件
软件界面
  所有的统计操作都通过程序完成了。

  程序操作讲解完毕,重点来看看配置文件都是什么吧
   data.txt 存储的是同学的相关信息
软件界面
  另外有一个快捷的操作,务必使用此方法。
  在Excel上复制同学们的信息
  可以直接粘贴到txt文档中
软件界面
  代码背后会识别这个间隔,输入正确才能让程序正确运行。
  手动输入这个间隔 不是按空格 而是按键盘 Tab 键

  !!! 这里输入的姓名是指 青年之声用户名 !!!
  如果用户名与真实姓名不一致 请输入 青年之声用户名 !!!
  程序会根据 输入的用户名进行匹配 !!!
  输入不匹配就不会输出 合格 !!!
  请务必检查清楚 !!!

  最后就是 templete.xls 了
  其实这个文件只是获取它的基础板式
软件界面
  如果不想要这个基础板式,可以去掉配置文件的索引。
软件界面
  这样子生成的就是配置文件部分的 Excel 文档
软件界面

代码分析

  这个部分与软件使用无关,只是个人总结一下代码编写的过程。
  下面贴出软件的源码。

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
import re
import xlwt
import xlrd
import xlutils.copy
import requests
import time
import os
import traceback

class Downloader(QDialog):

def __init__(self):
QDialog.__init__(self)

# 创建窗口
self.resize(750,300)
layout = QGridLayout()
self.infoTitle = QLabel("<center><b>数据获取设置</b></center>")
self.title = QLabel("<center><b>输入网页URL</b></center>")
self.url = QPlainTextEdit()
self.save_location = QLineEdit()
self.DB_location = QLineEdit()
self.templete_location = QLineEdit()
self.progress = QProgressBar()
download = QPushButton("一键统计")
browse = QPushButton("选择")
DB_browse = QPushButton("选择")
templete_browse = QPushButton("选择")

self.save_location.setPlaceholderText("文件保存路径")
self.DB_location.setPlaceholderText("青年之声用户名信息存储文本")
self.templete_location.setPlaceholderText("Excel模板文件")

self.progress.setValue(0)
self.progress.setAlignment(Qt.AlignHCenter)

num = 1

layout.addWidget(self.infoTitle,num-1,0,1,4)
layout.addWidget(self.DB_location,num,0,1,3)
layout.addWidget(DB_browse,num,3,1,1)
layout.addWidget(self.templete_location,num+1,0,1,3)
layout.addWidget(templete_browse,num+1,3,1,1)
layout.addWidget(self.title,num+2,0,1,4)
layout.addWidget(self.url,num+3,0,1,4)
layout.addWidget(self.save_location,num+4,0,1,3)
layout.addWidget(browse,num+4,3,1,1)
layout.addWidget(self.progress,num+5,0,1,4)
layout.addWidget(download,num+6,0,1,4)

self.setLayout(layout)

self.setWindowTitle("青年之声统计神器 - 制作者:16级数字媒体技术2班梁伟添")
self.DB_location.setText("data.txt")
self.templete_location.setText("templete.xls")
self.setFocus()

download.clicked.connect(self.download)
browse.clicked.connect(self.browse_file)
DB_browse.clicked.connect(self.browse_DB)
templete_browse.clicked.connect(self.browse_templete)

def browse_file(self):
save_file = QFileDialog.getSaveFileName(self, caption="保存文件到", directory=".",filter="Excel (*.xls)")
self.save_location.setText(QDir.toNativeSeparators(save_file))

def browse_DB(self):
DB_file = QFileDialog.getOpenFileNames(self, caption="获取青年之声用户名信息", directory=".",filter="txt (*.txt)")
# 空数组处理
if not DB_file:
return
self.DB_location.setText(QDir.toNativeSeparators(DB_file[0]))

def browse_templete(self):
templete_file = QFileDialog.getOpenFileNames(self, caption="获取模板Excel文件", directory=".",filter="Excel (*.xls)")
# 空数组处理
if not templete_file:
return
self.templete_location.setText(QDir.toNativeSeparators(templete_file[0]))

def download(self):
url = self.url.toPlainText()
save_location = self.save_location.text()
DB_location = self.DB_location.text()
templete_location = self.templete_location.text()

print("开始写入")

if DB_location == "":
QMessageBox.warning(self, "Warning", "用户数据不能为空")
return

try:
with open(DB_location,'r') as f:
info = f.read()
except Exception:
QMessageBox.warning(self, "Warning", "青年之声用户名信息获取失败\n检查文件路径是否正确")
return

reg = r'(.*)'
studentReg = re.compile(reg)
self.studentName = re.findall(studentReg,info)
# 清除空字符串
self.studentName = [x for x in self.studentName if x != '']

# 遍历获取 url 输入框中的所有链接
reg = r'(http.*)'
urldReg = re.compile(reg)
urlList = re.findall(urldReg,url)

# 创建 excel 对象 和 excel 表
if templete_location != "":
try:
rb = xlrd.open_workbook(templete_location,formatting_info=True)
book = xlutils.copy.copy(rb)
sheet = book.get_sheet(0)
except Exception:
QMessageBox.warning(self, "Warning", "Excel模板获取失败\n检查文件路径是否正确")
return

else:
book = xlwt.Workbook(encoding='utf-8', style_compression=0)
sheet = book.add_sheet('青年之声统计', cell_overwrite_ok=True)


# 设置字体样式
font0 = xlwt.Font()
font0.name = '微软雅黑'

alignment = xlwt.Alignment()
alignment.horz = xlwt.Alignment.HORZ_CENTER
alignment.vert = xlwt.Alignment.VERT_CENTER

style0 = xlwt.XFStyle()
style0.font = font0
style0.alignment = alignment

try:
az = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
column = 4
r = 0

for name in self.studentName:
userClass = name.split('\t')[0]
userNum = name.split('\t')[1]
userName = name.split('\t')[2]
sheet.write(r+2, 0, r+1 ,style0)
sheet.write(r+2, 1, userClass ,style0)
sheet.write(r+2, 2, userNum ,style0)
sheet.write(r+2, 3, userName ,style0)
r += 1

# 分离处理 url 链接
for originUrl in urlList:

# 获取url
reg = r'quId=(.*?)&'
quIdReg = re.compile(reg)
quId = re.findall(quIdReg,originUrl)[0]
url = "https://api.12355.net/pc/service/getReplysByQuestionId?quId=%s&page=1&rows=500" % quId
askUrl = "https://api.12355.net/pc/service/getQuesDetail?quId=%s" % quId

try:
# 获取html页面
response = requests.get(url)
html = response.text
response = requests.get(askUrl)
askHtml = response.text
except Exception:
traceback.print_exc()
QMessageBox.warning(self, "Warning", "网络连接失败")
return

if html == "":
break

# 获取提问时间
reg = r'"askTime":"(.*?)"'
askReg = re.compile(reg)
askList = re.findall(askReg,askHtml)

# 获取用户名
reg = r'"creatorName":"(.*?)"'
nameReg = re.compile(reg)
nameList = re.findall(nameReg,html)

# 获取回复信息
reg = r'"replyContent":"(.*?)"'
contentReg = re.compile(reg)
contentList = re.findall(contentReg,html)

# 获取回复时间
reg = r'"replyTime":"(.*?)"'
timeReg = re.compile(reg)
timeList = re.findall(timeReg,html)

row = 0
# 批量生成超链接
sheet.write(1, column, xlwt.Formula('HYPERLINK("%s";"问题%s")' % (originUrl,column-3)),style0)
for name in self.studentName:
index = 0

# 检测回复是否符合条件
for creatorName in nameList:
# 检测回复是否匹配 用户名
if name.split('\t')[2] == creatorName:
# 检测是否是问题当天的时间进行回复
if askList[0].split(' ')[0] == timeList[index].split(' ')[0]:
# 去除标点 检测回复是否超过5个字
reg = r"(?u)\w"
textReg = re.compile(reg)
textList = re.findall(textReg,contentList[index])
if len(textList) >= 5:
sheet.write(row+2, column , "合格" ,style0)
break

index += 1

row += 1

# 进度条加载
percent = (column-3) * 100 / len(urlList)
self.progress.setValue(int(percent))
print(str(int(percent))+"%")

# 纵向求和 判断数组是否越界的情况
if column < 26:
sheet.write(row+2, column , xlwt.Formula("COUNTIF(%s3:%s%s,\"合格\")" % (az[column],az[column],row+2)) ,style0)
else:
letter = "%s%s" % (az[int(column/26-1)],az[column%26])
sheet.write(row+2, column , xlwt.Formula("COUNTIF(%s3:%s%s,\"合格\")" % (letter,letter,row+2)) ,style0)

column += 1

# 横向求和
r = 0
column -= 1
sheet.write(r+1, column+1 , "合计" ,style0)
# 判断数组是否越界的情况
if column < 26:
for name in self.studentName:
sheet.write(r+2, column+1 , xlwt.Formula("COUNTIF(%s%s:%s%s,\"合格\")" % (az[4],r+3,az[column],r+3)) ,style0)
r += 1
sheet.write(r+2, column+1 , xlwt.Formula("SUM(%s%s:%s%s)" % (az[4],r+3,az[column],r+3)) ,style0)
else:
letter = "%s%s" % (az[int(column/26-1)],az[column%26])
for name in self.studentName:
sheet.write(r+2, column+1 , xlwt.Formula("COUNTIF(%s%s:%s%s,\"合格\")" % (az[4],r+3,letter,r+3)) ,style0)
r += 1
sheet.write(r+2, column+1 , xlwt.Formula("SUM(%s%s:%s%s)" % (az[4],r+3,letter,r+3)) ,style0)

book.save(save_location)

except Exception:
traceback.print_exc()
QMessageBox.warning(self, "Warning", "数据写入失败")
return

print("完成写入")
QMessageBox.information(self, "Information", "数据写入完成")
self.progress.setValue(0)
# # 清空输入
# self.url.setText("")
# self.save_location.setText("")

app = QApplication(sys.argv)
dl = Downloader()
dl.show()
app.exec_()

  这次观看了 Pluralsight - Python Desktop Application Development 教程
  参照了文件下载器的开发源码进行开发的。
  因为教程是采用 Python3 的 我上次写的怕从是基于 Python2 写的。
  所以这次重新用 Python3 的方法重写获取网页的方法,其实也不复杂。
  需要安装 requests 模块

  这次开发中遇到最大的坑就是处理 Excel 表格数据
  Python操作 Excel 需要安装很多额外的库 可以参照网上的帖子和博文
  操作Excel的时候,for循环的很多输出需要不断运行程序来检验正确,这些都还好,花些时间还是可以制作出来的。
  最大的问题在于求和表达式的输出上
  表达式需要根据Excel表格的列序号进行输出,然而列序号是英文字母ABCD,如何通过数字来对应ABCD就让人很头疼。
  特别是超过了26个字母的时候会编程 AA AB AC 的格式,输出正确的求和公式就更加复杂。
  不过最后还是通过求模的方式解决了这个问题。

  最后的问题就是如何将 py 脚本输出成 exe 可执行文件了
  这个可以安装 pyinstaller 模块
  Python可以通过这个模块将 py 脚本自动封装
  方便而且逼格满满!

总结

  Python开发比起C++简单很多,想起C++的MFC框架就很难受。
  之前我也用过基于C++的SFML框架,同样也没有PyQt来得便利,毕竟PyQt就是专门针对界面开发的,SFML只是基于OpenGL开发游戏的。
  PyQt的学习还不够,而且还有Qt Designer等着我。
  近期继续攻克PyQt的教程,另外 Pluralsight - Python Desktop Application Development 也可以写一波笔记了。