記事は長いことが判明しました。内容を確認して、興味のある項目をクリックしてください。
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の場合:
Linuxの場合:
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
マークアップのパラメーターでは、ボタンの線幅とサイズ変更を示します。そうしないと、ボタンが巨大になります。
bot.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
bot.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にボットをデプロイします。
最初にHerokuとGithubに登録する必要があります。
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を削除してください。 結局のところ、誰もそれを使用する必要はありません。
おめでとうございます!
作業は終了し、ボットはリモートで動作します。
参照資料
github上のボットの最終コード
ボット管理API
デプロイについて
pipenvについて
素晴らしいガイド、誰かが役に立つかもしれません
おわりに
誰かが興味を持っていれば、記事を書くという目標は達成されます。 誰かが、より複雑なボット(webhook、ユーザー設定を含む接続されたデータベースなど)に関する記事を見たい場合は、書いてください。
更新
UPD1
- コンテンツにアンカーを追加しました。
- コードをgithubとherokaにアップロードするためのアルゴリズムを変更しました。
- PyTelegramBotAPIのバージョンは削除されました。 Herokuは、新しいバージョンでも正常に動作するようになりました。