В этом материале вы узнаете, как создать Telegram-канал, который будет сам обновляться, получая данные из открытых источников:
-
Код напишем на Python.
-
Запускаться он будет в AWS Lambda.
-
Для парсинга (т. е. получения) данных будет использоваться библиотека BeautifulSoup.
-
Чтобы добавить нашему софту persistance layer будем использовать базу данных AWS DynamoDB.
-
P. S. Все полностью бесплатно, Amazon ничего платить не нужно.
Цель статьи — объяснить работу с DynamoDB, BeautifulSoup и AWS Lambda. Если точнее — как связать все это в единый рабочий процесс для решения практической задачи.
Далее я опишу абсолютно синтетическую задачу, которая тем не менее отлично подходит в качестве простейшего примера для изучения вышеуказанных технологий.
Допустим вы фанат музыки жанра техно и хотите регулярно получать обновления о последнем поступившем музыкальном альбоме с вашего любимого сайта hardwax.com. Заходить на сайт вручную неудобно и вам бы хотелось иметь Telegram-канал, в котором каждые 15 минут публикуется последний музыкальный альбом. Задача осложняется тем, что вы не хотите видеть ни одну ссылку второй раз. Вы слушаете альбом единожды и полностью забываете про него. Таким образом, алгоритм должен запоминать, какие альбомы он уже публиковал в канале и не допускать повторной публикации.

-
Создание телеграм-канала и бота.
Итак, начнем. Для начала создадим Telegram-канал. Назовем его Hardwax techno:

Кто-то должен обновлять Telegram-канал, ведь сам по себе канал обновляться не может. Для этих целей создадим бота, который будет заниматься обновлением. В Telegram боты создаются через отца всех ботов — @BotFather:
-
Здороваемся (
/start) -
Создаем бота (
/newbot) -
Даем имя боту (
hardwax_techno_poster) -
Даем ему username (
hardwax_techno_poster_bot, должно заканчиваться на_bot) -
В конце получаем токен для управления ботом, его нужно сохранить.

Добавим созданного бота в администраторы, для этого переходим в настройки канала — > Administrators:

Далее Add Admin, выбираем нашего бота и проставляем все галочки:

-
Регистрация на AWS и создание функции.
Сетап в Telegram готов, пора переходить к AWS. Очевидно, первое, что необходимо сделать это зарегистрировать аккаунт. Здесь подробно описано, как это сделать и ничего при этом не заплатить.
Сделали аккаунт AWS, переходим к созданию функции. Набираем в строке поиска ‘lambda’ и переходим в соответствующий раздел:

Нажимаем ‘Create function’, выбираем имя функции. Runtime = ‘Python 3.9’ или более поздняя версия.

-
Импортируем зависимости.
Чтобы парсить веб и управлять Telegram-каналом нам понадобятся зависимости — библиотеки. Чтобы использовать их в AWS Lambda предусмотрены слои — своеобразные пакеты зависимостей, подключаемые к функции. Создадим такой слой.
Для этого нужно подготовить все необходимые библиотеки в .zip-архиве. Всего нам понадобятся 3 библиотеки:
-
requests— вездесущая библиотека Python для запросов. -
bs4или BeautifulSoup — набор инструментов для парсинга веб-страниц. -
python-telegram-bot— библиотека, упрощающая работу с ботами в Telegram.
Чтобы подготовить архив, на машине Mac или Linux с установленным Python3 и менеджером пакетов pip выполним в командной строке следующие команды:
-
Создаем директорию для архива, куда положим наши зависимости.
mkdir -p ~/python
-
Переходим внутрь созданной директории.
cd ~/python
-
Устанавливаем наши зависимости в текущую директорию, указывая подходящие версии.
pip install --target=. bs4==0.0.1 python-telegram-bot==6.1.0 requests==2.24.0
-
Возвращаемся обратно.
cd ~
-
Создаем архив с зависимостями.
zip -r hardwax_poster_layer.zip python
Загрузим получившийся .zip-архив с зависимостями в AWS.
Для этого наберем в поиске Layers -> Create layer. Задаем имя слоя hardwax_poster_layer, выбираем Upload a .zip file, указываем путь до архива. Далее выбираем архитектуру x86_64 и Runtime = Python 3.9.

Мы успешно создали слой. Копируем и сохраняем его ARN (ID слоя или Amazon Resource Name), он пригодится нам дальше:

Добавим слой к нашей функции. Для этого переходим на страницу управления нашей функцией, скроллим вглубь страницы и выбираем Add layer:

Кликаем Specify an ARN, вводим в поле ранее полученный ARN слоя. Нажимаем Verify, чтобы убедиться, что все корректно. Наконец жмем Add, чтобы прикрепить слой к функции.

Протестируем, что в окружении AWS теперь действительно есть наши зависимости. Для этого прямо в браузере модифицируем код, чтобы функция в ответ печатала версию библиотеки для работы с Telegram из наших зависимостей. Нажимаем Deploy, чтобы изменения вступили в силу.

import json import telegram def lambda_handler(event, context): # TODO implement return { 'statusCode': 200, 'body': json.dumps(f'Telegram version is {telegram.__version__}') }
Прежде чем AWS даст протестировать наш код, нужно создать тестовое событие. Для этого нажимаем на большую оранжевую кнопку Test. В нашем случае нам не нужно делать ничего, кроме как дать событию имя вроде my_test_event. В других случаях в функцию можно передавать различные JSON — объекты и удобно смотреть, как она их обрабатывает.

Снова нажимаем большую оранжевую кнопку Test и AWS запускает наш код. Переходим во вкладку Execution result и видим, что функция вернула корректную строку Telegram library version is 6.1.0. Это значит, что наши зависимости в окружении и можно идти дальше.

-
Задаем переменные среды.
Мы собираемся обновлять Telegram-канал с помощью нашей функции. Для этого в код необходимо передать токен для бота, который будет публиковать новые посты в канале в качестве администратора. Лучший способ сделать это — создать Environment variable или переменную среды для токена. Просто вписать токен в тело кода небезопасно. Чтобы создать переменную среды переходим в Configuration -> Environment variables -> Edit.

Добавляем переменную с названием telegram_token и значением токена, который мы ранее получили от бота:

-
Выставляем таймаут.
Также поменяем таймаут функции или максимальное время ее работы. По умолчанию у AWS Lambda функций он выставлен на 3 секунды, что слишком мало для нашего случая. Для этого перейдем General configuration -> Edit.

Для нас больше подходит таймаут в 15 минут:

-
Настраиваем роли.
По умолчанию AWS не разрешает использовать DynamoDB из AWS Lambda. Это сделано из соображений безопасности, поэтому сперва нам нужно настроить права. Для этого наберем в строке поиска Roles. Здесь мы видим список всех ролей, которые есть в нашем аккаунте AWS. Отдельная роль автоматически создается под каждую Lambda-функцию. Мы можем найти роль, которая была создана для нашей ф-и с помощью поиска, как показано на скриншоте:

Прикрепим к роли ф-и права для управления DynamoDB. Для этого нажимаем Attach policies:

Находим с помощью поиска нужные нам права AmazonDynamoDBFullAccess, выбираем Attach policies:

Перед запуском скрипта посмотрим, что в DynamoDB нет таблиц. Для этого наберем в поиске DynamoDB:

-
Код.
Права настроены, переменные среды инициализированы, зависимости подтянуты. Теперь можно перейти к коду. Используем следующий код:
import requests import boto3 from boto3.dynamodb.conditions import Key from bs4 import BeautifulSoup from botocore.exceptions import ClientError import telegram import os def lambda_handler(event, context): # Create table if necessary dynamodb = boto3.resource('dynamodb', region_name='us-east-2') table_name = 'HardwaxLinks' try: params = { 'TableName': table_name, 'KeySchema': [ {'AttributeName': 'Link', 'KeyType': 'HASH'}, ], 'AttributeDefinitions': [ {'AttributeName': 'Link', 'AttributeType': 'S'} ], 'ProvisionedThroughput': { 'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10 } } table = dynamodb.create_table(**params) table.wait_until_exists() except ClientError: table = dynamodb.Table(table_name) page_number = 1 keep_going = True while keep_going: page_url = f'https://hardwax.com/techno/?page={page_number}' print(f'Getting albums from {page_url}') # Get all album links from page response = requests.get(page_url).content soup = BeautifulSoup(response, "html.parser") page_album_links = ['https://hardwax.com' + x.get('href')\ for x in soup.findAll('a', {'class': 'an'})] n_albums = len(page_album_links) if n_albums > 0: page_number += 1 print(f'Got {n_albums} albums...') for link in page_album_links: already_processed =\ len(table.query(KeyConditionExpression=Key('Link').eq(link))\ ['Items']) > 0 # check if a link is in the table if not already_processed: bot = telegram.Bot(token=os.environ['telegram_token']) bot.send_message(chat_id="@hardwax_techno",\ text=f'{link}', parse_mode=telegram.ParseMode.HTML) table.put_item(Item={'Link': link}) # update table print(f'Posted {link}') return else: print(f'Already processed {link}') else: keep_going = False
Что делает код?
-
Создаем таблицу на DynamoDB с одним полем Link. В таблице будет храниться все опубликованные в канале ссылки. Если таблица уже создана, то пропускаем этот шаг. Для этого добавляем try/except блок.
-
Переходим по ссылке https://hardwax.com/techno и листаем страницы. На каждой новой странице делаем следующее:
-
Получаем список ссылок на альбомы с этой страницы, используя библиотеку BeautifulSoup. Перестаем листать, если количество таких альбомов 0, т. е. мы дошли до конца.
-
Для каждой ссылки проверяем, есть ли она в таблице DynamoDB. Это можно делать за константное время, в этом прелесть этой базы данных. Таблица представляет из себя нечто вроде огромного словаря и по хэшу получает значения для ключа (нашей ссылки). В данном случае значением является простое наличие ссылки в таблице.
-
Если ссылка есть в таблице, то пропускаем ее. Это значит, что мы уже публиковали ее в канале.
-
Если ссылки нет в таблице, то публикуем ее в канал и добавляем в таблицу новую запись. В следующий раз она будет присутствовать в таблице и мы сможем пропустить ее. Как только ссылка опубликована и запись в таблицу добавлена, завершаем выполнение функции. В противном случае продолжаем, пока не найдем ссылку для публикации.
-
Обновляем файл lambda_function.py из браузера. Не забываем нажать на Deploy, чтобы изменения вступили в силу. Код обновлен, давайте протестируем, нажав Test:

Выполнение завершилось, посмотрим результаты. Из логов видно, что ф-я получила 45 альбомов на первой странице и запостила первый же из них.

В канале видим опубликованную ссылку на альбом:

Переходим на web-страницу и видим, что это первый альбом в категории.

Проверим, как изменилась таблица в DynamoDB. Для этого переходим DynamoDB -> Tables -> HardwaxLinks -> Explore table items. Видим, что в таблице появилась наша ссылка.

Проверим, что функция не постит одну и ту же ссылку повторно. Для этого снова запустим ее и посмотрим на логи. Видим, что ф-я пропустила предыдущую ссылку, потому что она уже была опубликована и находится в таблице.

-
Расписание.
Чтобы завершить автоматизацию Telegram-канала настроим расписание. Для этого наберем в поиске Amazon EventBridge -> Schedules:

Выбираем Recurring schedule -> Rate-based schedule. Rate expression = 15 minutes.

Target = AWS Lambda:

Выбираем нашу функцию:

Готово! Посты в канале публикуются с интервалом в 15 минут, без повторений.

-
Что можно улучшить?
В этом туториале мы многое делали руками. Например, вручную обновляли код в браузере, формировали архив с зависимости и загружали его в AWS. Гораздо удобнее использовать CI/CD пайплайн для кода и зависимостей. Идея такая: вы обновляете репозиторий на GitHub, а функция и слой обновляются автоматически. Подробная инструкция описана в моем предыдущем материале — CI/CD для AWS Lambda через GitHub Actions.
ссылка на оригинал статьи https://habr.com/ru/post/706020/
Добавить комментарий