我决定在我的一个脚本中添加一个 GUI。该脚本是一个简单的网络爬虫。我决定使用工作线程,因为下载和解析数据可能需要一段时间。我决定使用 PySide,但我对 Qt 的一般知识非常有限。
由于脚本应该在遇到验证码时等待用户输入,我决定它应该等到QLineEdit
触发returnPressed
,然后将其内容发送到工作线程,以便它可以发送它进行验证。这应该比忙着等待按下返回键要好。
似乎等待信号并不像我想象的那么简单,在搜索了一段时间后,我遇到了几个类似的解决方案。不过,跨线程发信号和工作线程中的本地事件循环使我的解决方案更加复杂。
经过几个小时的修补,它仍然无法正常工作。
应该发生什么:
- 下载数据直到引用验证码并进入循环
- 下载验证码并将其显示给用户,首先
QEventLoop
调用self.loop.exec_()
QEventLoop
通过调用通过类中loop.quit()
连接的工作线程槽退出self.line_edit.returnPressed.connect(self.worker.stop_waiting)
main_window
- 如果验证失败,则验证验证码并循环,否则重试现在应该可以下载的最后一个 url,然后继续下一个 url
怎么了:
...看上面...
退出
QEventLoop
不起作用。调用它之后self.loop.isRunning()
返回。return ,因此线程似乎在奇怪的情况下没有死。线程仍然停在该行。因此,即使事件循环告诉我它不再运行,线程仍被卡在执行事件循环中。False
exit()
self.isRunning
True
self.loop.exec_()
GUI 的响应与工作线程类的槽一样。我可以看到发送到工作线程的文本、事件循环的状态和线程本身,但是在执行上述行之后什么都没有。
代码有点复杂,因此我添加了一些伪代码-python-mix,省略了不重要的:
class MainWindow(...):
# couldn't find a way to send the text with the returnPressed signal, so I
# added a helper signal, seems to work though. Doesn't work in the
# constructor, might be a PySide bug?
helper_signal = PySide.QtCore.Signal(str)
def __init__(self):
# ...setup...
self.worker = WorkerThread()
self.line_edit.returnPressed.connect(self.helper_slot)
self.helper_signal.connect(self.worker.stop_waiting)
@PySide.QtCore.Slot()
def helper_slot(self):
self.helper_signal.emit(self.line_edit.text())
class WorkerThread(PySide.QtCore.QThread):
wait_for_input = PySide.QtCore.QEventLoop()
def run(self):
# ...download stuff...
for url in list_of_stuff:
self.results.append(get(url))
@PySide.QtCore.Slot(str)
def stop_waiting(self, text):
self.solution = text
# this definitely gets executed upon pressing return
self.wait_for_input.exit()
# a wrapper for requests.get to handle captcha
def get(self, *args, **kwargs):
result = requests.get(*args, **kwargs)
while result.history: # redirect means captcha
# ...parse and extract captcha...
# ...display captcha to user via not shown signals to main thread...
# wait until stop_waiting stops this event loop and as such the user
# has entered something as a solution
self.wait_for_input.exec_()
# ...this part never get's executed, unless I remove the event
# loop...
post = { # ...whatever data necessary plus solution... }
# send the solution
result = requests.post('http://foo.foo/captcha_url'), data=post)
# no captcha was there, return result
return result
frame = MainWindow()
frame.show()
frame.worker.start()
app.exec_()