Я решил добавить графический интерфейс к одному из моих скриптов.Скрипт представляет собой простой веб-скребок.Я решил использовать рабочий поток, так как загрузка и анализ данных могут занять некоторое время.Я решил использовать PySide, но мои знания Qt в целом весьма ограничены.
Поскольку сценарий должен ожидать ввода данных от пользователя при обнаружении капчи, я решил, что он должен подождать, пока QLineEdit
не сработает returnPressed
и затем отправить его содержимое в рабочий поток, чтобы он мог отправить его на проверку.Это должно быть лучше, чем занято - ожидание нажатия клавиши возврата.
Кажется, что ожидание сигнала не так просто, как я думал, и после поиска какое-то время я наткнулсянесколько решений, похожих на this .Сигнализация между потоками и локальный цикл обработки событий в рабочем потоке усложняют моё решение.
После нескольких часов работы оно все равно не будет работать.
Что предполагаетсячтобы это произошло:
- Загрузите данные до обращения к капче и введите цикл
- Скачайте капчу и отобразите ее пользователю, запустите
QEventLoop
, вызвав self.loop.exec_()
- Выйдите из
QEventLoop
, вызвав loop.quit()
в слоте рабочих потоков, который подключен через self.line_edit.returnPressed.connect(self.worker.stop_waiting)
в main_window
классе - Проверка капчи и цикла в случае сбоя проверки, в противном случае повторите последний URL, который долженбыть загружаемым сейчас, а затем перейти к следующему URL
Что происходит:
... см. выше ...
Выход QEventLoop
не работает.self.loop.isRunning()
возвращает 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_()