Каждый день мы просматриваем habr. Каждый день заходим на главную ленту и крутим её. Что, если автоматизировать этот просмотр?
В статье я расскажу, как я писал telegram-бота на python3, который вытаскивает заголовки статей с habr и пишет их в telegram.
Как это реализовать?
У python3 есть библиотека – beautiful soap 4. С помощью этой библиотеки можно парсить сайты.
Парсинг (parsing) — это сбор информации из сторонних источников и сайтов для использования полученных данных в различных целях.
Простыми словами, я пишу скрипт, который вытаскивает из сайта какую-то информацию (в нашем случае заголовки статей). Подробнее о парсинге сайтов можно найти здесь.
Как можно видеть по “этому” скрину,
Каждая статья в хабре состоит из тэгов “ article” со своим id.
Можно просто уменьшать переменную, пока она не совпадет с id. Этот способ очень долгий. Надо ждать, пока python3 выполнит ~700 000 итераций!
Есть другой вариант. Можно найти все тэги “ article”, отвечающие за статьи, и поочереди перебирать эти тэги. Цикл останется, но будет делать уже 20(столько статей на глав. экране) итераций.
from itertools import count
from re import I
import requests
from bs4 import BeautifulSoup
url2 = "https://habr.com/ru/all/"
response2 = requests.get(url2)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
print(out + " url: " + "https://habr.com" + urlOut)
Теперь приклеевыем этот кусок кода к pyTelegramBotAPI:
import requests
from bs4 import BeautifulSoup
import telebot
bot = telebot.TeleBot("TOKEN")
url2 = "https://habr.com/ru/all/"
response2 = requests.get(url2)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
@bot.message_handler(commands=["start"])
def start(m, res=False):
bot.send_message(m.chat.id, "Bot is started.")
@bot.message_handler(commands=["habr"])
def habr(message):
global url2
response2 = requests.get(url2)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
bot.send_message(message.chat.id, out)
bot.polling(none_stop=True, interval=0)
Очень важно прикрепить эти строчки в фунцию habr, т.к. если этого не сделать, наш бот не будет обновлятся.
Эти строчки:
response2 = requests.get(url2)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
Это пока не совсем удобно! Ссылки-то нету!
Проблему со ссылкой легко исправить. Надо просто добавить эту строчку в функцию “habr”:
urlOut = "https://habr.com" + tag[i].find("h2").find("a").get("href")
И исправить вывод с "out" на "out + " url: " + urlOut".
Вот, что получается в telegram:
Вывод слишком резкий получается. Надо сделать задержку между каждым выводом.
Импортируем sleep из time:
from time import sleep
И добавить задержку в цикл:
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag[i].find("h2").find("a").get("href")
bot.send_message(message.chat.id, out + "url: " + urlOut)
sleep(5)
В telegram всё тоже-самое, но между каждой статьей – задержка.
И добавим новости:
@bot.message_handler(commands=["news"])
def habr(message):
global url
response2 = requests.get(url)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag[i].find("h2").find("a").get("href")
bot.send_message(message.chat.id, out + "url: " + urlOut)
sleep(5)
Знаете, что я вам скажу? Это опять неудобно! А что, если я не захочу читать дальше? Нужна кнопка, отвечающая за стоп. Поизучав, как это работает, я нашел и url-кнопку, которая отвечает за переход на сайт. Отдельная кнопка красивее, чем тупо ссылка! Короче, делаем две Inline кнопки.
Для начало нужно добавить в “habr” и “news” эти строки:
markup = types.InlineKeyboardMarkup(row_width=2)
btn_url = types.InlineKeyboardButton(text="Go to habr.", url=urlOut)
btn_stop = types.InlineKeyboardButton(text="STOP IT!", callback_data="stop")
markup.add(btn_url, btn_stop)
и для “habrNews” :
markup = types.InlineKeyboardMarkup(row_width=2)
btn_url = types.InlineKeyboardButton(text="Go to habr.", url=urlOut)
btn_stop = types.InlineKeyboardButton(text="STOP IT!", callback_data="stop_news")
markup.add(btn_url, btn_stop)
и добавить аргумент в bot.send_message. Теперь он выглядит так:
bot.send_message(message.chat.id, out + "url: " + urlOut, reply_markup=markup)
Теперь добавим обработчик события callback кнопки:
@bot.callback_query_handler(func=lambda call:True)
def call_repley(call):
if call.message and call.data == "stop":
None
И вот тут я прям встал. Я не знал, что делать! Мне надо было передать сообщение из “call_repley” в “habr”. В habrЕ умные дядьки пишут, что лучшим вариантом будет передать это через callback_data, но я 12-ти летний пацан! Мне показалось слишком сложным эта схема. Дело в том, что умные дядьки из habrА перед тем, как пишут через callback_data, пишут через глобальные переменные. Я не понимал, как работают глабальные переменные до того, как встретил эту статью. Спасибо, DanyByLuckyCraft!
И так, пишем через глобальные переменные:
isCall = False
@bot.callback_query_handler(func=lambda call:True)
def call_repley(call):
global isCall
if call.message and call.data == "stop":
isCall = True
@bot.message_handler(commands=["habr"])
def habr(message):
global isCall
И обрабатываем событие в “habr”:
if isCall:
break
Как это выглядит:
Двигаем дальше.
Теперь надо сделать ещё одну кнопку, чтобы листать вперед. Смотря на ту-же статью, я осознал, что можно перелистывать страницы, вместо того, чтобы высылать их поочередно. Если мы будем перелистывать страницы, можно кнопку “STOP IT!” убрать. И если мы будем перелистывать страницы, не будет возможности посмотреть предыдущие статьи. Надо добавить кнопку “назад”.
В этот раз функция “habr” полностью переделывается:
@bot.message_handler(commands=["habr"])
def habr(message):
global page
global pages
page = 0
pages = []
response = requests.get(url2)
response.raise_for_status()
soup = BeautifulSoup(response.text, "lxml")
tag2 = soup.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag2[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag2[i].find("h2").find("a").get("href")
pages.append([out, urlOut])
markup = markUP(pages=pages, page=page)
bot.send_message(message.chat.id, pages[page][0], reply_markup=markup)
Теперь объясню. Я создал переменные “pages” и “page”, которые отывечают за перелистывание страниц. В (уже список) “pages” каждую итерацию я добавляю еще список из переменных out и urlOut. И переменная “page” отвечает за индекс конкретного элемента [out, urlOut] в “pages”.
В коде можно заметить строчку
markup = markUP(pages=pages, page=page)
Она отвечает за присвоения переменной “markup” некой функции “markUP”. Я сделал отдельную функцию “markUP”:
def markUP(pages, page):
markup = types.InlineKeyboardMarkup(row_width=2)
btn_url = types.InlineKeyboardButton(text="Go to habr.", url=pages[page][1])
btn_next = types.InlineKeyboardButton(text="Next page.", callback_data="habr_next")
btn_back = types.InlineKeyboardButton(text="Back page.", callback_data="habr_back")
markup.add(btn_back, btn_next, btn_url)
return markup
Я просто взял эти сточки
markup = types.InlineKeyboardMarkup(row_width=2)
btn_url = types.InlineKeyboardButton(text="Go to habr.", url=urlOut)
btn_stop = types.InlineKeyboardButton(text="STOP IT!", callback_data="stop_news")
markup.add(btn_url, btn_stop)
и перенес их в функцию, чтобы не повторять их 4 раза.
call_reply тоже пришлось переделать:
@bot.callback_query_handler(func=lambda call:True)
def call_repley(call):
global page
global pages
if call.message and call.data == "habr_back":
page -= 1
try:
markup = markUP(pages=pages, page=page)
bot.edit_message_text(pages[page][0], reply_markup = markup, chat_id=call.message.chat.id, message_id=call.message.message_id)
except:
bot.answer_callback_query(call.id, show_alert=True, text="Такой статьи нету.")
page += 1
if call.message and call.data == "habr_next":
page += 1
try:
markup = markUP(pages=pages, page=page)
bot.edit_message_text(pages[page][0], reply_markup = markup, chat_id=call.message.chat.id, message_id=call.message.message_id)
except:
bot.answer_callback_query(call.id, show_alert=True, text="Такой статьи нету.")
page -= 1
После нажатия кнопки, первое сообщение редактируется и заменяется другим, с другим значением “page” или другими [out, urlOut]. Конструкция try-except нужна, чтобы значение page не вышло за рамки “pages”(короче, чтобы ошибка “IndexError: list index out of range” не появилась).
Теперь не забываем про “habrNews”:
@bot.message_handler(commands=["news"])
def habrNews(message):
global page
global pages
page = 0
pages = []
response2 = requests.get(url)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag[i].find("h2").find("a").get("href")
pages.append([out, urlOut])
markup = markUP(pages=pages, page=page)
bot.send_message(message.chat.id, pages[page][0], reply_markup=markup)
Ну вот и всё!
Полный код:
from re import T
import requests
from bs4 import BeautifulSoup
import telebot
from telebot import types
bot = telebot.TeleBot("TOKEN")
url = "https://habr.com/ru/news/"
url2 = "https://habr.com/ru/all/"
response = requests.get(url)
response.raise_for_status()
response2 = requests.get(url2)
response2.raise_for_status()
soup = BeautifulSoup(response.text, "lxml")
tag2 = soup.find_all("article", class_="tm-articles-list__item")
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
@bot.message_handler(commands=["start"])
def start(m, res=False):
bot.send_message(m.chat.id, "Bot is started.")
page = 0
pages = []
def markUP(pages, page):
markup = types.InlineKeyboardMarkup(row_width=2)
btn_url = types.InlineKeyboardButton(text="Go to habr.", url=pages[page][1])
btn_next = types.InlineKeyboardButton(text="Next page.", callback_data="habr_next")
btn_back = types.InlineKeyboardButton(text="Back page.", callback_data="habr_back")
markup.add(btn_back, btn_next, btn_url)
return markup
@bot.callback_query_handler(func=lambda call:True)
def call_repley(call):
global page
global pages
if call.message and call.data == "habr_back":
page -= 1
try:
markup = markUP(pages=pages, page=page)
bot.edit_message_text(pages[page][0], reply_markup = markup, chat_id=call.message.chat.id, message_id=call.message.message_id)
except:
bot.answer_callback_query(call.id, show_alert=True, text="Такой статьи нету.")
page += 1
if call.message and call.data == "habr_next":
page += 1
try:
markup = markUP(pages=pages, page=page)
bot.edit_message_text(pages[page][0], reply_markup = markup, chat_id=call.message.chat.id, message_id=call.message.message_id)
except:
bot.answer_callback_query(call.id, show_alert=True, text="Такой статьи нету.")
page -= 1
@bot.message_handler(commands=["habr"])
def habr(message):
global page
global pages
page = 0
pages = []
response = requests.get(url2)
response.raise_for_status()
soup = BeautifulSoup(response.text, "lxml")
tag2 = soup.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag2[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag2[i].find("h2").find("a").get("href")
pages.append([out, urlOut])
markup = markUP(pages=pages, page=page)
bot.send_message(message.chat.id, pages[page][0], reply_markup=markup)
@bot.message_handler(commands=["news"])
def habrNews(message):
global page
global pages
page = 0
pages = []
response2 = requests.get(url)
response2.raise_for_status()
soup2 = BeautifulSoup(response2.text, "lxml")
tag = soup2.find_all("article", class_="tm-articles-list__item")
for i in range(0, 10):
out = tag[i].find("h2").find("span").text
urlOut = "https://habr.com" + tag[i].find("h2").find("a").get("href")
pages.append([out, urlOut])
markup = markUP(pages=pages, page=page)
bot.send_message(message.chat.id, pages[page][0], reply_markup=markup)
bot.polling(none_stop=True, interval=0)
Весь код также можно найти в githubЕ.
Я забыл сказать, как этого бота сделать через fatherBot, но это и так все знают.
Как я уже упомянул, мне 12 лет. Это значит, что программировать и создавать ботов могут все!
Спасибо за внимание!