Pythonの劇場ポスターのパーサーからTelegramボットまで。 パート2





チケット検索用のTelegramボット-HappyTicketsBotの開発の話を続けます。 最初は最初の部分にあります。



後半では、ボット自体について話し、コードを共有し、現実になりそうもないアイデアを共有します。 ボットが作成された時点までのほとんどの機能は既にスクリプト形式で記述されていたため、主なタスクはTelegram-messengerを介してユーザーインタラクションインターフェイスを確立することでした。 それは第1部のように大雑把にではないことが判明したため、多くのコードが注目されます。



ネタバレ:HappyTicketsBotは、外国のサーバーをオンにするために飛び立ちませんでした。ローカルおよびロシア語ですが、いつか(私は信じています)開始します=)



更新:ボットが劇場の人々の間で共有された後、彼らはメディアでそれについて書きました。 ユーザーの洪水が急増しました。 数日間のゲームの後、「すぐに拾って、落ちた」ボットはサーバーに飛んで、多くの改善を経験しました。 満足しています=)



1.最初から始める



Telegramボットの設計にはまったく言葉がなかったので、基本的な記事とチュートリアルから始めなければなりませんでした。これらはネットワーク上で非常に多数あります。 はい、ちなみに、当時のバックエンドは何でしたか、想像もしていませんでした)) このレッスンのセットは 、最も有益で応用されました。 Telegramと対話するモジュールはpyTelegramBotAPI( github )でした。



デコレータのイデオロギーの開発に最も時間がかかったのは、 この記事でそれらについて読んでください 。 2つの部分があり、非常に理解しやすいです。



2.ボットとユーザーの対話用のスクリプト。 基本検索



序文と記事の第1部で既に述べたように、ほとんどすべての解析コードは準備ができていました。 検索パラメーターの設定方法を変更するために残った。 これに基づいて、ボット動作スクリプトが構築されました。 ユーザーが使用できるコマンドは、次のセットに制限されています。





基本的な検索スクリプト/検索に従ってユーザーはあるステータスから別のステータスに移動し、フィルターに必要なデータを順番に入力します。 最後のパラメーター(プレゼンテーションの場所)を入力した後、グローバルに宣言された辞書を使用してポスターを直接解析します。キーはユーザーIDで、値は入力された検索パラメーターです。



ユーザーの状態を記憶するために、それらはデータベースに保存されます。 これを使用するには、Vedisモジュール(キーと値のデータベースコンフィギュレーター、 ドキュメントを参照)およびEnum(列挙の操作、詳細1、2 )が使用されます。



別の構成ファイルMyconfig.pyで、ボットパラメーター(Telegramから受信した一意のトークンを含む)を設定し、ユーザーが取り得るステータスを一覧表示します。 彼らは少し出てきました。



from enum import Enum token = "4225555:AAGmfghjuGOI4sdfsdfs5656sdfsdf_c" # ,   (  ) db_file = "Mydatabase.vdb" class States(Enum): """   Vedis    ,        (str) """ S_START = "0" #    S_ENTER_MONTH = "1" S_ENTER_PRICE = "2" S_ENTER_TYPE = "3" S_ENTER_PLACE = "4" S_ENTER_URL="5" #      
      
      





その結果、ある状態から別の状態への簡単な状態遷移のチェーンを取得します。







ストレージには、Vedisデータベースを使用します。 メッセージを送信したユーザーの初期化は、常にmessage.chat.idを介して行われます。



データベースとの相互作用を記述するdbwoker.pyファイルのコード
 from vedis import Vedis import Myconfig as config #      def get_current_state(user_id): with Vedis(config.db_file) as db: try: return db[user_id] except KeyError: #  /     return config.States.S_START.value #  -  #       def set_state(user_id, value): with Vedis(config.db_file) as db: try: db[user_id] = value return True except: print('  !') #   -   return False
      
      







以下は、/ findコマンドによってアクティブ化されるハンドラーの例です。 ご覧のとおり、この例ではデータエントリはありません。ステータスが「S_ENTER_MONTH」に変更されるだけです。 番号の入力に関するメッセージを見たユーザーは、番号を入力してメッセージを送信します。 ステータスがS_ENTER_MONTHのメッセージを受信すると、次のステップが開始されます。 入力エラーの場合、ステータスは変更されません。



  #   @bot.message_handler(commands=["find"]) def cmd_find(message): state = dbworker.get_current_state(message.chat.id) """    ,         .     .    ,           """ if state == config.States.S_ENTER_MONTH.value: bot.send_message(message.chat.id, "    .   ") elif state == config.States.S_ENTER_PRICE.value: bot.send_message(message.chat.id, "      ,   ") elif state == config.States.S_ENTER_TYPE.value: bot.send_message(message.chat.id, ", -    ,       :( ...") else: #  ""   "0" -   bot.send_message(message.chat.id, "      ") dbworker.set_state(message.chat.id, config.States.S_ENTER_MONTH.value) #  
      
      





ボットがステータスS_ENTER_MONTHのユーザーからメッセージを受信すると、次のハンドラーが起動します。 イデオロギー的には、ベース検索スクリプトの他の段階でも発生します。



 @bot.message_handler(func=lambda message: dbworker.get_current_state(message.chat.id) == config.States.S_ENTER_MONTH.value) def user_entering_month(message): if not message.text.isdigit(): bot.send_message(message.chat.id, ",    ") return # 1 num[message.chat.id]=message.text #  if int(num[message.chat.id])>12 or int(num[message.chat.id])<1: bot.send_message(message.chat.id, "     1  12.   ") # 2 return url_list[message.chat.id]=take_url(num[message.chat.id]) #  URL-   if url_list[message.chat.id]==[]: #    bot.send_message(message.chat.id, " ,     .     ") return bot.send_message(message.chat.id, "!      .") dbworker.set_state(message.chat.id, config.States.S_ENTER_PRICE.value) #    
      
      





標準の検索に加えて、興味深いパフォーマンスを保存することができます。



3.価格変更の追跡



ユーザーは興味のあるリストにURLを追加して、価格が下がったときにアラートを受け取ることができます。 基本検索-S_ENTER_URLにはまだリストにないステータスがあったことを覚えています。 で



 @bot.message_handler(commands=["addURL"]) def cmd_add_url(message): bot.send_message(message.chat.id, " url,   .  https://") dbworker.set_state(message.chat.id, config.States.S_ENTER_URL.value) #  @bot.message_handler(func=lambda message: dbworker.get_current_state(message.chat.id) == config.States.S_ENTER_URL.value) def user_entering_URL(message): perf_url=message.text user_id=message.chat.id try: add_new_URL(user_id,perf_url) bot.send_message(message.chat.id, '    !') dbworker.set_state(message.chat.id, config.States.S_START.value) #    except: bot.send_message(message.chat.id, 'URL !    !') dbworker.set_state(message.chat.id, config.States.S_ENTER_URL.value)
      
      





リストを保存するには、.csvファイルを使用します。 それと対話するには、価格の変更を検証して書き込みと読み取りを行ういくつかの機能が必要です。 変更された場合は、ユーザーに通知してください。



  def add_new_URL(user_id,perf_url): WAITING_FILE = "waiting_list.csv" with open(WAITING_FILE, "a", newline="") as file: curent_url='https://'+perf_url text=get_text(curent_url) #   1   minPrice, name,date,typ,place=find_lowest(text) user = [str(user_id), perf_url,str(minPrice)] writer = csv.writer(file) writer.writerow(user)
      
      





価格更新機能コードはわずかに長いです
 def update_prices(bot): WAITING_FILE = "waiting_list.csv" with open(WAITING_FILE, "r", newline="") as file: reader = csv.reader(file) waitingList=[] for row in reader: waitingList.append(list(row)) L=len(waitingList) lowest={} with open(WAITING_FILE, "w", newline="") as fl: writer = csv.writer(fl) for i in range(L): lowest[waitingList[i][1]]=waitingList[i][2] #   URL  for k in lowest.keys(): text=get_text('https://'+k) minPrice, name,date,typ,place=find_lowest(text) #    1   if minPrice==0: #   minPrice=100000 if int(minPrice)<int(lowest[k]): #   ,    lowest[k]=minPrice #    for i in range(L): if waitingList[i][1]==k: #  -  URL   waitingList[i][2]=str(minPrice) #  bot.send_message(int(gen[i][0]),'   '+k+'    '+str(minPrice)) writer.writerows(waitingList) #     .    ... ...
      
      







その結果、 / checkURLコマンドを使用すると、ユーザーはそのような結果を得ることができます(パフォーマンスの名前も表示されるはずですが、これらは「手が届かない」シリーズのものです)。







わかりました 検索、追跡できます。 数人の友人がボットを使い始めました。私は彼らが誰で、何を探しているのかを知りたいと思いました。 この情報はログに書き込むのに適しています。



4.アクティビティとエラーをログに書き込みます



Loggingモジュールはこれに役立ちます。 情報は、ユーザーのステータスがS_ENTER_PLACEからS_STARTに移行するハンドラーで、基本検索が完了する段階でのみ記録されます。 エラー記録は、発生時に発生します。



モジュールの仕組みについてはあまり言えないので、 外部の情報に目を向けた方が良いでしょう。







ロガーの説明
 def save_logs(str): loggerInfo.info(str) #    logging.basicConfig(format = u'%(levelname)-8s [%(asctime)s] %(message)s', level = logging.ERROR, filename = u'loggerErrors.log') global loggerInfo loggerInfo = logging.getLogger(__name__) loggerInfo.setLevel(logging.INFO) handler = logging.FileHandler('loggerUsers.log') handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) loggerInfo.addHandler(handler) log = logging.getLogger("ex")
      
      







接続が切断されたため、ボットは定期的にクラッシュしたため、インターネット接続エラーが検出され、10秒後にボットが自動的に再起動しました。 ただし、常に保存されるとは限らなかったため、必要に応じて上げるためにTeamViewerを実行し続けました。



5.未実現



スクリプトの機能を置き換えるボットを入手しましたが、メッセンジャー内で便利な形式で情報を受け取ることができます。 彼は私の基本的なニーズを閉じました。



モジュールを使用した解体と細いハンドラーの作成は、週末と場合によっては夜間の作業モードで約1か月続きました。 この期間の終わりに、関心が薄れ始め、機能が開始点で立ち往生しました。 webhook-ahsの動作原理を破ることができなかったため、Telegramはブロックされました。 その前に、動作中のサーバーにバックエンドをプルする計画がありましたが、...このためvpnはインストールされません=)



計画に残っているのは次のとおりです。そのうちのいくつかは、夏/冬の憂鬱な夜に一度実現することができます。





ボリショイ劇場で電話がありました。 「Nureyev」のチケットをキャッチしましたが、2晩でhtmlポスターを取り出すことができなかったため、未実現ポスターのリストから除外されました。



合計



怠azineは進歩のエンジンであることが判明し、彼を止めました。 サードパーティのサーバーにボットをアップロードすることはできませんでしたが、Webの分野でより広い能力と知識が必要です。 このプロジェクトは面白く、少し優れたPythonを学び、(通常の機械学習に加えて)別の側面を見ることができ、また劇場で多くの素晴らしい夜をお買い得価格で紹介することができました。 彼のおかげで、彼は一瞬でタスクを閉じました。



どんなに努力しても、この記事にはまだ多くのコードと小さなテキストがありました。 わかりにくい、またはコメントにほとんど記載されていないことを説明させていただきます=)



All Articles