-
Notifications
You must be signed in to change notification settings - Fork 4
/
gui.py
346 lines (289 loc) · 10.4 KB
/
gui.py
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
@version : v1.0
@author : fangzheng
@contact : fangzheng@rp-pet.cn
@software : PyCharm
@filename : gui.py
@create time: 2024/2/21 11:33 AM
@modify time: 2024/2/21 11:33 AM
@describe :
-------------------------------------------------
"""
import os
import sys
import time
import traceback
from PySide2.QtCore import QFile, Signal, QObject, QThreadPool, QCoreApplication
from PySide2.QtCore import QRunnable, Slot
from PySide2.QtGui import QIcon
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QTextBrowser, QSystemTrayIcon, QMenu, QAction
from lianjia import LianjiaSpider, create_table
from utils.util_area import all_province, get_city_by_province
from utils.util_print import logqueue
basedir = os.path.dirname(__file__)
class WorkerSignals(QObject):
"""
自定义信号处理
"""
finished = Signal() # worker执行信号
error = Signal(tuple) # worker执行错误信号
result = Signal(object) # worker执行结果信号
progress = Signal(int) # worker执行进度信号
class Worker(QRunnable):
"""
实际工作线程(通用)
从 QRunnable 继承到处理程序工作线程设置、信号和结果返回。
:param callback: 要在此工作线程上运行的函数回调。提供的参数和kwargs 将传递给运行器。
:type callback: function
:param args: 要传递给回调函数的参数
:param kwargs: 要传递给回调函数的关键字
"""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
self.kwargs['progress_callback'] = self.signals.progress
@Slot()
def run(self):
"""
通过初始化的args,kwargs来初始化实际执行函数
"""
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
if result:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
class MySignals(QObject):
"""
打印日志信息的信号处理
"""
# Signal类有2个参数
# 参数QTextBrowser:代表你要在哪个组件上进行信号操作
# 参数str:表示传递的参数类型,这里是str类型
log_print = Signal(QTextBrowser, str)
export_statu_print = Signal(str)
# 进度条处理信号
progress_print = Signal(int)
# 信号实例化
global_ms = MySignals()
class LianJiaGui:
def __init__(self):
# 从文件中加载UI定义
# 从 UI 定义中动态 创建一个相应的窗口对象
# 注意:里面的控件对象也成为窗口对象的属性了
# 比如 self.ui.button , self.ui.textEdit
ui_path = os.path.join(basedir, "static", "ui", "lianjia_spider.ui")
self.ui = QUiLoader().load(ui_path)
self.log_run_flag = True
# 创建一个QFile对象,加载QSS文件
qss_path = os.path.join(basedir, "static", "ui", "lianjia_spider.qss")
style_file = QFile(qss_path)
style_file.open(QFile.ReadOnly)
# 设置窗口图标
icon_path = os.path.join(basedir, "static", "image", "logo.png")
icon = QIcon(icon_path)
self.ui.setWindowIcon(icon)
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
# 为整个应用程序设置样式表
app.setStyleSheet(style_file.readAll().data().decode("utf-8"))
# 初始化所有省份下拉框数据
self.ui.province_comboBox.addItems(all_province())
# 省份下拉框发生变化
self.ui.province_comboBox.currentIndexChanged.connect(self.handleProvinceChange)
# 开始按钮点击事件
self.ui.start_bt.clicked.connect(self.handleStartSpider)
# 导出Excel按钮点击事件
self.ui.export_bt.clicked.connect(self.handleExportBtnSpider)
# 停止按钮点击事件
self.ui.stop_bt.clicked.connect(self.handleStopBtnSpider)
# 自定义信号的处理函数
global_ms.log_print.connect(self.logPrint)
global_ms.export_statu_print.connect(self.exportStatuPrint)
self.threadpool = QThreadPool()
self.lj = LianjiaSpider(province_name=None)
self.lj.signals = global_ms
# 使用说明
self.readInstructions()
self.startLog()
def startLog(self):
# 开启一个写日志线程
self.log_run_flag = True
log_worker = Worker(self.logThreadFunc)
self.threadpool.start(log_worker)
def readInstructions(self):
try:
txt_file_path = os.path.join(basedir, "static", "使用说明.txt")
with open(txt_file_path, "r", encoding="utf-8") as file:
for line in file:
self.ui.textBrowser.append(line.strip())
except FileNotFoundError:
self.ui.textBrowser.append("找不到使用说明文件!")
except Exception as e:
self.ui.textBrowser.append(f"读取文件时出错:{str(e)}")
def logPrint(self, fb, text):
"""
打印内容到页面组件上
"""
fb.append(str(text))
fb.ensureCursorVisible()
def exportStatuPrint(self, text):
"""
打印导出状态到标签上
"""
self.ui.export_text.setText(str(text))
def handleStartSpider(self):
"""
开始采集按钮点击事件
"""
self.start_flag()
province = self.getProvinceText()
city = self.getCityText()
self.disbleButton()
self.ui.textBrowser.clear()
# 开启采集数据线程
self.lj.province_name = province
self.lj.city_name = city
worker = Worker(self.spiderThreadFunc)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
self.threadpool.start(worker)
self.startLog()
def progress_fn(self, n):
print("%d%% done" % n)
def logThreadFunc(self, progress_callback):
while True:
if not self.log_run_flag:
return
logtext = logqueue.get()
if logtext is None:
break
else:
global_ms.log_print.emit(self.ui.textBrowser, str(logtext))
time.sleep(0.5)
def spiderThreadFunc(self, progress_callback):
self.lj.spider_by_condition()
# 回调函数:用于打印进度条
# for n in range(0, 5):
# time.sleep(1)
# progress_callback.emit(n)
def dbinitThreadFunc(self, progress_callback):
self.lj.province_name = self.getProvinceText()
self.lj.city_name = self.getCityText()
self.lj.run_flag = True
self.lj.db_init()
def print_output(self, s):
print(s)
def thread_complete(self):
print("THREAD COMPLETE!")
def handleResetBtnSpider(self):
"""
初始化区域按钮点击事件
"""
self.start_flag()
province = self.getProvinceText()
city = self.getCityText()
self.disbleButton()
self.ui.textBrowser.clear()
# 开启采集数据线程
self.lj.province_name = province
self.lj.city_name = city
worker = Worker(self.dbinitThreadFunc)
worker.signals.result.connect(self.print_output)
worker.signals.finished.connect(self.thread_complete)
worker.signals.progress.connect(self.progress_fn)
self.threadpool.start(worker)
self.startLog()
def handleExportBtnSpider(self):
"""
导出Excel
"""
self.lj.province_name = self.getProvinceText()
self.lj.city_name = self.getCityText()
self.lj.to_excel()
def handleStopBtnSpider(self):
"""
停止按钮点击事件
"""
self.stop_flag()
self.threadpool.waitForDone()
self.ui.statusbar.clearMessage()
self.enableButton()
self.ui.textBrowser.clear()
def disbleButton(self):
"""
禁用相关按钮
"""
self.ui.province_comboBox.setEnabled(False)
self.ui.city_comboBox.setEnabled(False)
self.ui.start_bt.setEnabled(False)
self.ui.export_bt.setEnabled(False)
def enableButton(self):
"""
启用相关按钮
"""
self.ui.province_comboBox.setEnabled(True)
self.ui.city_comboBox.setEnabled(True)
self.ui.start_bt.setEnabled(True)
self.ui.export_bt.setEnabled(True)
def handleProvinceChange(self):
"""
省份下拉框发生变化事件
"""
province = self.getProvinceText()
citys = get_city_by_province(province)
self.ui.city_comboBox.clear()
self.ui.city_comboBox.addItems(citys)
def getProvinceText(self):
"""
获取省份文本
"""
return self.ui.province_comboBox.currentText()
def getCityText(self):
"""
获取城市文本
"""
return self.ui.city_comboBox.currentText()
def closeEvent(self, event):
QApplication.processEvents()
self.stop_flag()
QCoreApplication.quit() # 退出应用程序的事件循环
self.stop_thread()
self.threadpool.waitForDone()
def start_flag(self):
self.lj.run_flag = True
self.log_run_flag = True
def stop_flag(self):
self.lj.run_flag = False
self.log_run_flag = False
if __name__ == '__main__':
create_table()
app = QApplication([])
lianjia = LianJiaGui()
lianjia.ui.show()
# app.setQuitOnLastWindowClosed(False) # 禁用窗体本身关闭按钮
icon_path = os.path.join(basedir, "static", "image", "logo.png")
icon = QIcon(icon_path)
# Create the tray
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)
menu = QMenu()
quit_menu = QAction("退出")
quit_menu.triggered.connect(app.quit)
menu.addAction(quit_menu)
tray.setContextMenu(menu)
sys.exit(app.exec_())