Decidí agregar una GUI a uno de mis scripts. El script es un simple raspador web. Decidí usar un hilo de trabajo como descarga y analizar los datos puede llevar un tiempo. Decidí usar PySide, pero mi conocimiento de Qt en general es bastante limitado.PySide espera de la señal del hilo principal en un hilo de trabajo
Como se supone que el script espera la entrada del usuario al encontrar un captcha decidí que debería esperar hasta que QLineEdit
dispare returnPressed
y luego envíe su contenido al hilo del trabajador para que pueda enviarlo para su validación. Eso debería ser mejor que ocupado, esperando a que se presione la tecla de retorno.
Parece que esperar una señal no es tan sencillo como pensé que sería y después de buscar por un tiempo encontré varias soluciones similares a this. Sin embargo, la señalización a través de hilos y un bucle de evento local en el hilo de trabajo hacen que mi solución sea un poco más complicada.
Después de jugar con él durante varias horas, todavía no va a funcionar.
Lo que se supone que debe ocurrir:
- Descarga de datos hasta referido al código de imagen y entrar en un bucle
- Descargar código de imagen y mostrarla al usuario, iniciar
QEventLoop
llamandoself.loop.exec_()
- salida
QEventLoop
llamandoloop.quit()
en una ranura de subprocesos de trabajador que está conectada a través deself.line_edit.returnPressed.connect(self.worker.stop_waiting)
en la clasemain_window
- Validar captcha y bucle si validati en la falla, de lo contrario vuelva a intentar la última dirección URL que debe ser descargable ahora, a continuación, pasar a la siguiente url
Lo que sucede:
... ver más arriba ...
Salir de
QEventLoop
no funciona.self.loop.isRunning()
devuelveFalse
después de llamar a suexit()
.self.isRunning
devuelveTrue
, por lo que el hilo no parece morir bajo circunstancias extrañas. Todavía el hilo se detiene en la líneaself.loop.exec_()
. Como tal, el hilo está atascado ejecutando el bucle de evento a pesar de que el bucle de evento me dice que ya no se está ejecutando.La GUI responde al igual que las ranuras de la clase de subprocesos de trabajo. Puedo ver el texto enviado al hilo del trabajador, el estado del bucle de evento y el hilo mismo, pero nada después de que se ejecute la línea mencionada.
El código es un poco complicado, como tal, añado un poco de pseudo-código-python-mix dejando de lado la importancia:
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_()
hecho, que resolvió mi problema. Gracias. –