スクリプトの1つにGUIを追加することにしました。スクリプトは単純なWebスクレイパーです。データのダウンロードと解析には時間がかかる可能性があるため、ワーカースレッドを使用することにしました。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()
戻ります。そのようなスレッドは奇妙な状況下で死ぬようには見えなかったので、を返します。それでもスレッドはその行で停止します。そのため、イベントループがもう実行されていないことを通知しても、スレッドはイベントループの実行でスタックします。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_()