Ho deciso di aggiungere una GUI a uno dei miei script. Lo script è un semplice raschietto web. Ho deciso di utilizzare un thread di lavoro in quanto il download e l'analisi dei dati possono richiedere del tempo. Ho deciso di usare PySide, ma la mia conoscenza di Qt in generale è piuttosto limitata.PySide attende il segnale dal thread principale in un thread di lavoro
Poiché lo script deve attendere l'input dell'utente dopo aver trovato un captcha, ho deciso che doveva attendere fino a quando uno QLineEdit
genera returnPressed
e quindi invia il suo contenuto al thread di lavoro in modo che possa inviarlo per la convalida. Dovrebbe essere migliore di quello occupato, in attesa che venga premuto il tasto Invio.
Sembra che attendere un segnale non sia così semplice come pensavo sarebbe stato e dopo aver cercato un po 'mi sono imbattuto in diverse soluzioni simili a this. Segnalare attraverso i thread e un ciclo di eventi locale nel thread di lavoro rendono la mia soluzione un po 'più complicata.
Dopo averlo armeggiato per diverse ore, non funzionerà ancora.
quello che dovrebbe accadere:
- Scarico dati fino a quando riferito a captcha e inserire un ciclo
- scaricare CAPTCHA e visualizzarlo per l'utente, avviare
QEventLoop
chiamandoself.loop.exec_()
- Exit
QEventLoop
chiamandoloop.quit()
in uno slot di thread di lavoro che è collegato tramiteself.line_edit.returnPressed.connect(self.worker.stop_waiting)
nella classemain_window
- Convalidare captcha e loop se validati il fallisce, altrimenti ripetere l'ultimo URL che dovrebbe essere scaricabile ora, poi andare avanti con l'URL successivo
Cosa succede:
... vedi sopra ...
Chiusura
QEventLoop
non funziona.self.loop.isRunning()
restituisceFalse
dopo aver chiamato il numeroexit()
.self.isRunning
restituisceTrue
, in quanto tale thread non sembra morire in circostanze strane. Il thread si arresta ancora sulla rigaself.loop.exec_()
. Come tale, il thread è bloccato nell'esecuzione del ciclo degli eventi anche se il ciclo degli eventi mi dice che non è più in esecuzione.La GUI risponde come gli slot della classe del thread di lavoro. Riesco a vedere il testo inviato al thread di lavoro, lo stato del ciclo degli eventi e il thread stesso, ma nulla dopo l'esecuzione della riga sopra menzionata.
Il codice è un po 'contorto, in quanto tale, aggiungo un po' di pseudo-codice-python-mix tralasciando il poco importante:
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_()
Infatti, che ha risolto il problema. Grazie. –