電報ボットの作成とホスティング。 AからZ

こんにちは、habrchane! python3で電報ボットを作成するというハッキングされたテーマが何であれ、コードの最初の行からボットの展開までのパスを示す指示は見つかりませんでした(少なくとも私が見たすべての方法は少し時代遅れです)。 この記事では、BotFatherの作成からHerokuへのボットのデプロイまで、ボットを作成するプロセスを示します。



記事は長いことが判明しました。内容を確認して、興味のある項目をクリックしてください。



PSより複雑なボットの作成に関する記事、つまり webhook、ユーザー設定などのデータベースを使用します。





まず、ボットが何をするかを決定する価値があります。 解析してHabrのヘッダーを提供する、単純なボットを作成することにしました。

それでは、始めましょう。



ボットファーザー



まず、ボットをTelegramに登録する必要があります。 これを行うには:



@BotFatherの検索ドライブで、ボットの父との対話に入ります。



/ newbotを作成します。 ボットの名前(ダイアログに表示されるもの)を指定します。 彼を見つけることができる彼のユーザー名を示します。



PSそれはボット/ボットで終了する必要があります



こっち APIキーとボットへのリンクが提供されました。 APIキーを保存してボットと対話することをお勧めします。そうすることで、後でBotFatherとの通信を掘り下げる必要がなくなります



次に、いくつかのコマンドを追加します。/setcommandsと1つのメッセージを記述します 。 / setcommandsはコマンドを追加しませんが、最初から設定します。コマンドを送信します。



all - " "

top - ""








BotFatherの作業はこれで終わりです。次の部分に進みましょう。



pipenvをインストールして構成します。 最初の打ち上げ。



まず、メインのbot.pyコードが含まれるファイルを作成します。 ボットが大きい場合、関数、クラスなどを配置するファイルをすぐに作成します。そうしないと、コードの可読性がゼロになる傾向があります。 parser.pyを追加します



もちろんない場合はpipenvをインストールしてください



Windowsの場合:



 pip install pipenv
      
      





Linuxの場合:



 sudo pip3 install pipenv
      
      





プロジェクトフォルダーにpipenvをインストールします。



 pipenv install
      
      





興味のあるライブラリをインストールします。 PyTelegramBotAPIを使用します。 解析のためにも、BeautifulSoup4を追加します。



 pipenv install PyTelegramBotAPI pipenv install beautifulsoup4
      
      





コードを書き始めています!



bot.pyを開き、ライブラリをインポートしてメイン変数を作成します。



 import telebot import parser #main variables TOKEN = "555555555:AAAAaaaAaaA1a1aA1AAAAAAaaAAaa4AA" bot = telebot.TeleBot(TOKEN)
      
      





ボットを実行します。 エラーを探します。



開始方法
Windowsの場合:



 python bot.py
      
      





Linuxの場合:



 python3 bot.py
      
      







エラーが表示されない場合は、続行します。



ハンドラー。 コマンドとメッセージに応答します



ボットに応答するように指示する時間です。 彼の答えを役に立つものにすることさえ可能です。



相互作用の基本。 コマンドへの応答



ユーザーインタラクション、つまり ハンドラーは、彼のコマンドとメッセージに応答するために使用されます。



最も単純なものから始めましょう:コマンド/ startおよび/ goに答えます



 @bot.message_handler(commands=['start', 'go']) def start_handler(message): bot.send_message(message.chat.id, ',   ,      ') bot.polling()
      
      





次に、それが何であり、どのように機能するかを理解しましょう。 文字列を含む配列に等しいmessage_handlerパラメーターコマンドを渡します。コマンドは、以下で説明するように応答します。 (彼はこれらすべてのコマンドに同じように答えます)。 次に、send_messageを使用し、そこにチャットIDを書き込みます(message.chat.idから取得できます)。そこにメッセージと実際にはメッセージ自体を送信します。 コードの最後にbot.polling()を書くことを忘れないでください。そうしないと、ボットはすぐにシャットダウンします。 なぜそうなるのかは後でわかります。



これで、ボットを開始し、彼に書き込み/開始または/行けば、彼は答えます。



PSメッセージは文字列だけでなく、原則として何でもかまいません。



PSSメッセージとは?
これは、送信者、チャット、およびメッセージ自体に関する情報を格納するjsonオブジェクトです。



 { 'content_type': 'text', 'message_id': 5, 'from_user': { 'id': 333960329, 'first_name': 'Nybkox', 'username': 'nybkox', 'last_name': None }, 'date': 1520186598, 'chat': { 'type': 'private', 'last_name': None, 'first_name': 'Nybkox', 'username': 'nybkox', 'id': 333960329, 'title': None, 'all_members_are_administrators': None }, 'forward_from_chat': None, 'forward_from': None, 'forward_date': None, 'reply_to_message': None, 'edit_date': None, 'text': '/start', 'entities': [<telebot.types.MessageEntity object at 0x7f3061f42710>], 'audio': None, 'document': None, 'photo': None, 'sticker': None, 'video': None, 'voice': None, 'caption': None, 'contact': None, 'location': None, 'venue': None, 'new_chat_member': None, 'left_chat_member': None, 'new_chat_title': None, 'new_chat_photo': None, 'delete_chat_photo': None, 'group_chat_created': None, 'supergroup_chat_created': None, 'channel_chat_created': None, 'migrate_to_chat_id': None, 'migrate_from_chat_id': None, 'pinned_message': None }
      
      







相互作用の基本。 テキストメッセージに返信します。



次に、ボットのテキストメッセージを処理します。 知っておく必要がある最も重要なことは、メッセージのテキストがmessage.textに保存されていることと、message_handlerでテキストを処理するためにcontent_types = ['text']を渡す必要があることです。



次のコードを追加します。



 @bot.message_handler(content_types=['text']) def text_handler(message): text = message.text.lower() chat_id = message.chat.id if text == "": bot.send_message(chat_id, ',   -  .') elif text == " ?": bot.send_message(chat_id, ',   ?') else: bot.send_message(chat_id, ',     :(')
      
      





ここで、いくつかの変数を追加しました:変数テキスト内のメッセージのテキストを移動して(小文字で、キャップ、フェンスなどを書いた人に不必要な問題が生じないように)、毎回、message.chat.idを個別の変数に入れましたメッセージを参照してください。 また、特定のメッセージに応答するための小さな分岐と、理解できないボットメッセージの場合の応答を構築しました。



最終コード
 import bs4 import parser #main variables TOKEN = "555555555:AAAAaaaAaaA1a1aA1AAAAAAaaAAaa4AA" bot = telebot.TeleBot(TOKEN) #handlers @bot.message_handler(commands=['start', 'go']) def start_handler(message): bot.send_message(message.chat.id, ',   ,      ') @bot.message_handler(content_types=['text']) def text_handler(message): text = message.text.lower() chat_id = message.chat.id if text == "": bot.send_message(chat_id, ',   -  .') elif text == " ?": bot.send_message(chat_id, ',   ?') else: bot.send_message(chat_id, ',     :(') bot.polling()
      
      







相互作用の基本。 写真、文書、音声などへの答え。



写真、ステッカー、文書、音声などに応答するため content_types = ['text']を変更するだけです。



このコードを追加して、写真付きの例を検討してください。



 @bot.message_handler(content_types=['photo']) def text_handler(message): chat_id = message.chat.id bot.send_message(chat_id, '.')
      
      





すべてのタイプのコンテンツ:



text, audio, document, photo, sticker, video, video_note, voice, location, contact, new_chat_members, left_chat_member, new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, supergroup_chat_created, channel_chat_created, migrate_to_chat_id, migrate_from_chat_id, pinned_message







私たちは一連の答えを構築しています。



基本的なアクションを完了して、真剣に何かを始める時です。 答えの連鎖を構築してみましょう。 これにはregister_next_step_handler()が必要です。 register_next_step_handler()の仕組みを理解するための簡単な例を作成しましょう。



 @bot.message_handler(commands=['start', 'go']) def start_handler(message): chat_id = message.chat.id text = message.text msg = bot.send_message(chat_id, '  ?') bot.register_next_step_handler(msg, askAge) def askAge(message): chat_id = message.chat.id text = message.text if not text.isdigit(): msg = bot.send_message(chat_id, '   ,   .') bot.register_next_step_handler(msg, askAge) #askSource return msg = bot.send_message(chat_id, ',     ' + text + ' .')
      
      





そのため、最初の関数bot.register_next_step_handler(msg、askAge)が追加され、そこに送信したいメッセージと、ユーザーの応答の後の次のステップを送信します。



2番目の関数では、すべてがより興味深いものです。ここでは、ユーザーが数字を入力したかどうかをチェックし、そうでない場合は、関数は「年齢は数字でなければなりません。もう一度入力してください」というメッセージで再帰的に呼び出します。 ユーザーがすべてを正しく入力した場合、ユーザーは応答を受け取ります。



しかし、ここには問題があります。 コマンド/ goまたは/ startを再呼び出しすると、混乱が始まります。



画像

これは簡単に修正できます。スクリプトのステータスを確認する変数を追加します。



 @bot.message_handler(commands=['start', 'go']) def start_handler(message): global isRunning if not isRunning: chat_id = message.chat.id text = message.text msg = bot.send_message(chat_id, '  ?') bot.register_next_step_handler(msg, askAge) #askSource isRunning = True def askAge(message): chat_id = message.chat.id text = message.text if not text.isdigit(): msg = bot.send_message(chat_id, '   ,   .') bot.register_next_step_handler(msg, askAge) #askSource return msg = bot.send_message(chat_id, ',     ' + text + ' .') isRunning = False
      
      





単純なチェーンの構築により、先に進みましょう。



パーサーをチェーンに追加します。



まず、パーサー自体が必要です。 [ベスト]タブと[行のすべて]タブには、追加のフィルターがあります:それぞれ日、週、月、≥10、≥25、≥50、≥100のフィルターがあることに注意してください。

もちろん、1つの関数でパーサーを作成できますが、2つに分割して、コードを読みやすくします。



パーサー。
 import urllib.request from bs4 import BeautifulSoup def getTitlesFromAll(amount, rating='all'): output = '' for i in range(1, amount+1): try: if rating == 'all': html = urllib.request.urlopen('https://habrahabr.ru/all/page'+ str(i) +'/').read() else: html = urllib.request.urlopen('https://habrahabr.ru/all/'+ rating +'/page'+ str(i) +'/').read() except urllib.error.HTTPError: print('Error 404 Not Found') break soup = BeautifulSoup(html, 'html.parser') title = soup.find_all('a', class_ = 'post__title_link') for i in title: i = i.get_text() output += ('- "'+i+'",\n') return output def getTitlesFromTop(amount, age='daily'): output = '' for i in range(1, amount+1): try: html = urllib.request.urlopen('https://habrahabr.ru/top/'+ age +'/page'+ str(i) +'/').read() except urllib.error.HTTPError: print('Error 404 Not Found') break soup = BeautifulSoup(html, 'html.parser') title = soup.find_all('a', class_ = 'post__title_link') for i in title: i = i.get_text() output += ('- "'+i+'",\n') return output
      
      







その結果、パーサーは、リクエストに基づいて記事のタイトルを含む行を返します。

取得した知識を使用して、パーサーに関連付けられたボットを作成しようとしています。 別のクラスを作成し(これはおそらく間違ったメソッドですが、記事のメイントピックではなくpythonに既に適用されています)、このクラスのオブジェクトに可変データを保存することにしました。



最終コード:



bot.py
 import telebot import bs4 from Task import Task import parser #main variables TOKEN = '509706011:AAF7ghlYpqS5n7uF8kN0VGDCaaHnxfZxofg' bot = telebot.TeleBot(TOKEN) task = Task() #handlers @bot.message_handler(commands=['start', 'go']) def start_handler(message): if not task.isRunning: chat_id = message.chat.id msg = bot.send_message(chat_id, ' ?') bot.register_next_step_handler(msg, askSource) task.isRunning = True def askSource(message): chat_id = message.chat.id text = message.text.lower() if text in task.names[0]: task.mySource = 'top' msg = bot.send_message(chat_id, '   ?') bot.register_next_step_handler(msg, askAge) elif text in task.names[1]: task.mySource = 'all' msg = bot.send_message(chat_id, '   ?') bot.register_next_step_handler(msg, askRating) else: msg = bot.send_message(chat_id, '  .   .') bot.register_next_step_handler(msg, askSource) return def askAge(message): chat_id = message.chat.id text = message.text.lower() filters = task.filters[0] if text not in filters: msg = bot.send_message(chat_id, '   .   .') bot.register_next_step_handler(msg, askAge) return task.myFilter = task.filters_code_names[0][filters.index(text)] msg = bot.send_message(chat_id, '  ?') bot.register_next_step_handler(msg, askAmount) def askRating(message): chat_id = message.chat.id text = message.text.lower() filters = task.filters[1] if text not in filters: msg = bot.send_message(chat_id, '  .   .') bot.register_next_step_handler(msg, askRating) return task.myFilter = task.filters_code_names[1][filters.index(text)] msg = bot.send_message(chat_id, '  ?') bot.register_next_step_handler(msg, askAmount) def askAmount(message): chat_id = message.chat.id text = message.text.lower() if not text.isdigit(): msg = bot.send_message(chat_id, '    .  .') bot.register_next_step_handler(msg, askAmount) return if int(text) < 1 or int(text) > 11: msg = bot.send_message(chat_id, '    >0  <11.  .') bot.register_next_step_handler(msg, askAmount) return task.isRunning = False output = '' if task.mySource == 'top': output = parser.getTitlesFromTop(int(text), task.myFilter) else: output = parser.getTitlesFromAll(int(text), task.myFilter) msg = bot.send_message(chat_id, output) bot.polling(none_stop=True)
      
      





ここではnone_stop=True)



bot.polling



に追加されbot.polling



。これは、すべてのエラーでボットがbot.polling



しないためです。

Task.py
 class Task(): isRunning = False names = [ ['', '', ''], ['', ' ', 'all'] ] filters = [ ['', '', ''], [' ', '10', '25', '50', '100'] ] filters_code_names = [ ['daily', 'weekly', 'monthly'], ['all', 'top10', 'top25', 'top50', 'top100'] ] mySource = '' myFilter = '' def __init__(self): return
      
      





parser.py
 import urllib.request from bs4 import BeautifulSoup def getTitlesFromAll(amount, rating='all'): output = '' for i in range(1, amount+1): try: if rating == 'all': html = urllib.request.urlopen('https://habrahabr.ru/all/page'+ str(i) +'/').read() else: html = urllib.request.urlopen('https://habrahabr.ru/all/'+ rating +'/page'+ str(i) +'/').read() except urllib.error.HTTPError: print('Error 404 Not Found') break soup = BeautifulSoup(html, 'html.parser') title = soup.find_all('a', class_ = 'post__title_link') for i in title: i = i.get_text() output += ('- "'+i+'",\n') return output def getTitlesFromTop(amount, age='daily'): output = '' for i in range(1, amount+1): try: html = urllib.request.urlopen('https://habrahabr.ru/top/'+ age +'/page'+ str(i) +'/').read() except urllib.error.HTTPError: print('Error 404 Not Found') break soup = BeautifulSoup(html, 'html.parser') title = soup.find_all('a', class_ = 'post__title_link') for i in title: i = i.get_text() output += ('- "'+i+'",\n') return output
      
      







理論 ボットとの相互作用の方法。



長いポーリングを使用して、ボットからメッセージに関するデータを受信します。



bot.polling(none_stop=True)







完全に異なる方法を使用するオプションがあります-webhook。 したがって、ボット自体は、メッセージの受信などに関するデータを送信します。 しかし、この方法は構成がより難しく、単純な指数関数型ボットの場合、使用しないことにしました。



また、追加資料には、使用されたすべての内容と発言内容へのリンクがあります。



マルカピ。 キーボードを追加してすばやく応答します。



最後に、メインコードが追加されます。 これで、マークアップを休んで記述できます。 何度も見たことがあると思いますが、それでもスクリーンショットを添付します。 [スクリーンショット]



マークアップを別のファイル-markups.pyで出力します。



マークアップの記述に複雑なことはありません。 マークアップを作成し、いくつかのパラメーターを指定し、ボタンのペアを作成してマークアップに追加し、 reply_markup=markup



を指定するだけsend_message







markups.py

 from telebot import types source_markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True) source_markup_btn1 = types.KeyboardButton('') source_markup_btn2 = types.KeyboardButton(' ') source_markup.add(source_markup_btn1, source_markup_btn2)
      
      





マークアップのパラメーターでは、ボタンの線幅とサイズ変更を示します。そうしないと、ボタンが巨大になります。



もちろん、各行を個別に埋めることができます。
 markup = types.ReplyKeyboardMarkup() itembtna = types.KeyboardButton('a') itembtnv = types.KeyboardButton('v') itembtnc = types.KeyboardButton('c') itembtnd = types.KeyboardButton('d') itembtne = types.KeyboardButton('e') markup.row(itembtna, itembtnv) markup.row(itembtnc, itembtnd, itembtne)
      
      







bot.py



 def start_handler(message): if not task.isRunning: chat_id = message.chat.id msg = bot.send_message(chat_id, ' ?', reply_markup=m.source_markup) bot.register_next_step_handler(msg, askSource) task.isRunning = True
      
      







取得した知識をボットに適用します。



最終コード
markups.py



 from telebot import types start_markup = types.ReplyKeyboardMarkup(row_width=1, resize_keyboard=True) start_markup_btn1 = types.KeyboardButton('/start') start_markup.add(start_markup_btn1) source_markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True) source_markup_btn1 = types.KeyboardButton('') source_markup_btn2 = types.KeyboardButton(' ') source_markup.add(source_markup_btn1, source_markup_btn2) age_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True) age_markup_btn1 = types.KeyboardButton('') age_markup_btn2 = types.KeyboardButton('') age_markup_btn3 = types.KeyboardButton('') age_markup.add(age_markup_btn1, age_markup_btn2, age_markup_btn3) rating_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True) rating_markup_btn1 = types.KeyboardButton(' ') rating_markup_btn2 = types.KeyboardButton('10') rating_markup_btn3 = types.KeyboardButton('25') rating_markup_btn4 = types.KeyboardButton('50') rating_markup_btn5 = types.KeyboardButton('100') rating_markup.row(rating_markup_btn1, rating_markup_btn2) rating_markup.row(rating_markup_btn3, rating_markup_btn4, rating_markup_btn5) amount_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True) amount_markup_btn1 = types.KeyboardButton('1') amount_markup_btn2 = types.KeyboardButton('3') amount_markup_btn3 = types.KeyboardButton('5') amount_markup.add(amount_markup_btn1, amount_markup_btn2, amount_markup_btn3)
      
      





bot.py

 import telebot import bs4 from Task import Task import parser import markups as m #main variables TOKEN = '509706011:AAF7aaaaaaaaaaaaaaaaaaaAAAaaAAaAaAAAaa' bot = telebot.TeleBot(TOKEN) task = Task() #handlers @bot.message_handler(commands=['start', 'go']) def start_handler(message): if not task.isRunning: chat_id = message.chat.id msg = bot.send_message(chat_id, ' ?', reply_markup=m.source_markup) bot.register_next_step_handler(msg, askSource) task.isRunning = True def askSource(message): chat_id = message.chat.id text = message.text.lower() if text in task.names[0]: task.mySource = 'top' msg = bot.send_message(chat_id, '   ?', reply_markup=m.age_markup) bot.register_next_step_handler(msg, askAge) elif text in task.names[1]: task.mySource = 'all' msg = bot.send_message(chat_id, '   ?', reply_markup=m.rating_markup) bot.register_next_step_handler(msg, askRating) else: msg = bot.send_message(chat_id, '  .   .') bot.register_next_step_handler(msg, askSource) return def askAge(message): chat_id = message.chat.id text = message.text.lower() filters = task.filters[0] if text not in filters: msg = bot.send_message(chat_id, '   .   .') bot.register_next_step_handler(msg, askAge) return task.myFilter = task.filters_code_names[0][filters.index(text)] msg = bot.send_message(chat_id, '  ?', reply_markup=m.amount_markup) bot.register_next_step_handler(msg, askAmount) def askRating(message): chat_id = message.chat.id text = message.text.lower() filters = task.filters[1] if text not in filters: msg = bot.send_message(chat_id, '  .   .') bot.register_next_step_handler(msg, askRating) return task.myFilter = task.filters_code_names[1][filters.index(text)] msg = bot.send_message(chat_id, '  ?', reply_markup=m.amount_markup) bot.register_next_step_handler(msg, askAmount) def askAmount(message): chat_id = message.chat.id text = message.text.lower() if not text.isdigit(): msg = bot.send_message(chat_id, '    .  .') bot.register_next_step_handler(msg, askAmount) return if int(text) < 1 or int(text) > 5: msg = bot.send_message(chat_id, '    >0  <6.  .') bot.register_next_step_handler(msg, askAmount) return task.isRunning = False print(task.mySource + " | " + task.myFilter + ' | ' + text) # output = '' if task.mySource == 'top': output = parser.getTitlesFromTop(int(text), task.myFilter) else: output = parser.getTitlesFromAll(int(text), task.myFilter) msg = bot.send_message(chat_id, output, reply_markup=m.start_markup) bot.polling(none_stop=True)
      
      









やった! 原則としてコードを扱いました。 現在、最も重要なことは、ボットの展開がHerokuではないことです。



Herokuにボットをデプロイします。



最初にHerokuGithubに登録する必要があります。



githubにリポジトリを作成します。 (アバターの左側にあるプラス記号をクリックします)

ここで、Procfile(Windows用のProcfile.windows)が必要です。 作成してbot: python3 bot.py



作成するbot: python3 bot.py







bot.pyからTOKENを削除します。このファイルはgithubにアップロードするため、ここでは必要ありません。 ボットの起動に使用したのと同じ端末を使用して、githubにファイルをアップロードします。 (__pycache__フォルダーを事前に削除します)。



 echo "# HabrParser_Bot" >> README.md git init git add . git add * git commit -m "Initial Commit" -a git remote add origin origin https://github.com/name/botname.git #   git push -u origin master
      
      





Gitはユーザー名とパスワードの入力を求め、落ち着いて入力し、Herokaにボットを展開します。 すべてを同じ端末で作成しています。



TOKENをbot.pyに戻します。このファイルはHerokaにアップロードするため、ここで必要になります。



 heroku login # email   heroku create --region eu habrparserbot #    #PS         ,   . heroku addons:create heroku-redis:hobby-dev -a habrparserbot #   ! heroku buildpacks:set heroku/python git push heroku master heroku ps:scale bot=1 #   heroku logs --tail # 
      
      





ボットをオフにするには
 heroku ps:stop bot
      
      





そして、githubにアップロードする前に忘れずに、bot.pyからTOKENを削除してください。 結局のところ、誰もそれを使用する必要はありません。 もちろん、.gitignoreを使用して、トークンを別のファイルに転送できます。

おめでとうございます!


作業は終了し、ボットはリモートで動作します。



参照資料



github上のボットの最終コード

ボット管理API

デプロイについて

pipenvについて

素晴らしいガイド、誰かが役に立つかもしれません



おわりに



誰かが興味を持っていれば、記事を書くという目標は達成されます。 誰かが、より複雑なボット(webhook、ユーザー設定を含む接続されたデータベースなど)に関する記事を見たい場合は、書いてください。



更新
UPD1
  • コンテンツにアンカーを追加しました。
  • コードをgithubとherokaにアップロードするためのアルゴリズムを変更しました。
  • PyTelegramBotAPIのバージョンは削除されました。 Herokuは、新しいバージョンでも正常に動作するようになりました。



All Articles