我决定为我的脚本添加一个GUI。这个脚本是一个简单的网络爬虫。我决定使用一个工作线程,因为下载和解析数据可能需要一段时间。我决定使用PySide,但我对Qt的了解很有限。
由于脚本应该在遇到验证码时等待用户输入,所以我决定它应该等待直到
似乎等待信号并不像我想象中那么简单,在寻找了一段时间之后,我找到了几个类似于这个的解决方案。跨线程发射信号和在工作线程中使用本地事件循环使我的解决方案变得有些复杂。
经过几个小时的调试,它仍然无法正常工作。
应该发生什么:
- 下载数据直到遇到验证码并进入循环 - 下载验证码并将其显示给用户,通过调用
实际发生了什么:
- …见上文… - 退出
代码有些复杂,因此我添加了一些伪代码-Python混合体,省略了不重要的内容:
由于脚本应该在遇到验证码时等待用户输入,所以我决定它应该等待直到
QLineEdit
触发returnPressed
,然后将其内容发送到工作线程,以便它可以将其发送进行验证。这应该比忙等待按下回车键要好。似乎等待信号并不像我想象中那么简单,在寻找了一段时间之后,我找到了几个类似于这个的解决方案。跨线程发射信号和在工作线程中使用本地事件循环使我的解决方案变得有些复杂。
经过几个小时的调试,它仍然无法正常工作。
应该发生什么:
- 下载数据直到遇到验证码并进入循环 - 下载验证码并将其显示给用户,通过调用
self.loop.exec_()
启动QEventLoop
- 通过连接self.line_edit.returnPressed.connect(self.worker.stop_waiting)
在main_window
类中调用一个工作线程的槽来通过调用loop.quit()
退出QEventLoop
- 验证验证码并循环,如果验证失败,则重试最后一个可以下载的URL,然后继续下一个URL实际发生了什么:
- …见上文… - 退出
QEventLoop
不起作用。self.loop.isRunning()
在调用其exit()
之后返回False
。self.isRunning
返回True
,因此该线程似乎没有在奇怪的情况下死掉。但是,该线程停留在self.loop.exec_()
行。因此,该线程被困在执行事件循环,即使事件循环告诉我它已经不再运行。
- GUI和工作线程类的槽都有响应。我可以看到文本被发送到工作线程,事件循环和线程本身的状态,但是在上述行之后,没有任何东西被执行。代码有些复杂,因此我添加了一些伪代码-Python混合体,省略了不重要的内容:
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_()