Как стать автором
Обновить

Погода бот на python

Время на прочтение10 мин
Количество просмотров36K

На тему телеграмм ботов много материал, но погода бота я не находил и поэтому для себя и своих братьев написал свой собственный бот на Python.

В первую очередь нам нужна среда разработки для меня это Pycharm от нашей питерской компании. Установка проста как три рубля поэтому на ней останавливаться не буду.

Пока устанавливается Pycharm, нужно получить токены:

  1. для бота от телеграмма,

  2. для доступа к api accuweather

  3. для доступа к api yandex.weather

Итак, для получения токена от телеграмма Вам необходимо написать @BotFather и следовать инструкции (инструкций по созданию телеграмм-бота полно в интернете).

 По accuweather необходимо зарегистрироваться как разработчику на сайте https://developer.accuweather.com/ во вкладке My Apps добавить новое приложение, App Name это соответственно имя, далее выбираем Limited Trial, Where will the API be used? Выбираем Other, What will you be creating with this API? Выбираем Internal App и Weather App, What programming language is your APP written in? – Python, Is this for Business to Business or Business to Consumer use? - Business to Consumer, Is this Worldwide or Country specific use? – Worldwide, или выбираем Russia или та страна в которой планируется использовать, и последний вопрос What is the public launch date? Тут все просто дата с которой планируется запустить приложение. И нажимаем Create App. После этого вы снова попадете во вкладку My Apps щелкнув по названию своего приложения вы увидите API Key он то Вам и нужен!

Далее нам нужен токен от яндекса. Если у Вас нет почты от яндекса, то создаем, а потом переходим на сайт https://developer.tech.yandex.ru/services/ нажимаем подключить API. Выбираем API Яндекс.Погоды там будет всплывающее окно с вводом фамилии имени, название компании, номер телефона, город и нужно выбрать бесплатный тариф погода на вашем. Нажимаем перейти к API, там будет KEY #1 вот он то Вам и нужен.

Все токены поместим в переменные среды, об этом в конце статьи.

Запускаем pycharm создаем новый проект.

Переходим в Main.py и в первую очередь устанавливаем дополнительные библиотеки:

В нижнем ряду окна нажимаем кнопку Python Packages,  в поиске вводим requests и нажимаем install, тоже повторяем для pyTelegramBotAPI и geopy.

Затем в main.py импортируем библиотеки и токены:

import json
import telebot
import requests as req
from geopy import geocoders
from os import environ


token = environ['token_bot']
token_accu = environ['token_accu']
token_yandex = environ['token_yandex']

Все вся подготовка сделана, теперь переходим к написанию самого бота

Для того чтобы получить прогноз от accuweather нужен уникальный код города, чтобы его получить нужны его координаты, так же координаты города нам понадобятся при запросе к яндексу, поэтому в первую очередь пишем функцию получения координат через библиотеку geopy:

def geo_pos(city: str):
    geolocator = geocoders.Nominatim(user_agent="telebot")
    latitude = str(geolocator.geocode(city).latitude)
    longitude = str(geolocator.geocode(city).longitude)
    return latitude, longitude

Теперь получаем код города:

def code_location(latitude: str, longitude: str, token_accu: str):
    url_location_key = f'http://dataservice.accuweather.com/locations/v1/cities/geoposition/search?apikey={token_accu}&q={latitude},{longitude}&language=ru'
    resp_loc = req.get(url_location_key, headers={"APIKey": token_accu})
    json_data = json.loads(resp_loc.text)
    code = json_data['Key']
    return code

На вход функции даем координаты и токен, префикс “f” перед строкой дает нам вставлять значения переменных в нужные места вместо текста. Далее мы отправляем запрос по указанному url и с нужным заголовком. Хоть resp_loc.text и выглядит как стандартный словарь python на самом деле это json-объект, его нужно переделать в словарь, по ключу ‘Key’ получаем код города.

После этого можно написать уже код получения непосредственно прогноза:

def weather(cod_loc: str, token_accu: str):
    url_weather = f'http://dataservice.accuweather.com/forecasts/v1/hourly/12hour/{cod_loc}?apikey={token_accu}&language=ru&metric=True'
    response = req.get(url_weather, headers={"APIKey": token_accu})
    json_data = json.loads(response.text)
    dict_weather = dict()
    dict_weather['link'] = json_data[0]['MobileLink']
    dict_weather['сейчас'] = {'temp': json_data[0]['Temperature']['Value'], 'sky': json_data[0]['IconPhrase']}
    for i in range(len(json_data):1:):
        time = 'через' + str(i) + 'ч'
        dict_weather[time] = {'temp': json_data[i]['Temperature']['Value'], 'sky': json_data[i]['IconPhrase']}
return dict_weather

По ключу ‘link’ сохраним ссылку на полный прогноз, сохраним погоду сейчас, далее нам понадобится в цикле перебрать оставшиеся часы и так же сохранить их в словаре.

Далее нам нужно получить погоду от яндекса:

def yandex_weather(latitude, longitude, token_yandex: str):
    url_yandex = f'https://api.weather.yandex.ru/v2/informers/?lat={latitude}&lon={longitude}&[lang=ru_RU]'
    yandex_req = req.get(url_yandex, headers={'X-Yandex-API-Key': token_yandex}, verify=False)
    conditions = {'clear': 'ясно', 'partly-cloudy': 'малооблачно', 'cloudy': 'облачно с прояснениями',
                  'overcast': 'пасмурно', 'drizzle': 'морось', 'light-rain': 'небольшой дождь',
                  'rain': 'дождь', 'moderate-rain': 'умеренно сильный', 'heavy-rain': 'сильный дождь',
                  'continuous-heavy-rain': 'длительный сильный дождь', 'showers': 'ливень',
                  'wet-snow': 'дождь со снегом', 'light-snow': 'небольшой снег', 'snow': 'снег',
                  'snow-showers': 'снегопад', 'hail': 'град', 'thunderstorm': 'гроза',
                  'thunderstorm-with-rain': 'дождь с грозой', 'thunderstorm-with-hail': 'гроза с градом'
                  }
    wind_dir = {'nw': 'северо-западное', 'n': 'северное', 'ne': 'северо-восточное', 'e': 'восточное',
                'se': 'юго-восточное', 's': 'южное', 'sw': 'юго-западное', 'w': 'западное', 'с': 'штиль'}

    yandex_json = json.loads(yandex_req.text)
    yandex_json['fact']['condition'] = conditions[yandex_json['fact']['condition']]
    yandex_json['fact']['wind_dir'] = wind_dir[yandex_json['fact']['wind_dir']]
    for parts in yandex_json['forecast']['parts']:
        parts['condition'] = conditions[parts['condition']]
        parts['wind_dir'] = wind_dir[parts['wind_dir']]

    pogoda = dict()
    params = ['condition', 'wind_dir', 'pressure_mm', 'humidity']
    for parts in yandex_json['forecast']['parts']:
        pogoda[parts['part_name']] = dict()
        pogoda[parts['part_name']]['temp'] = parts['temp_avg']
        for param in params:
            pogoda[parts['part_name']][param] = parts[param]

    pogoda['fact'] = dict()
    pogoda['fact']['temp'] = yandex_json['fact']['temp']
    for param in params:
        pogoda['fact'][param] = yandex_json['fact'][param]

    pogoda['link'] = yandex_json['info']['url']
    return pogoda

Начинаем стандартно, но есть нюанс направление ветра и состояние неба в ответе от яндекса пишется на английском языке, делаем словари conditions и wind_dir для перевода. В ответе только фактическая погода и прогноз на следующих два периода (можно сделать и более подробно, но в бесплатном варианте только так).  Сразу в словаре меняем направление ветра и состояние неба. Далее создаем новый словарь и забираем только то что нам нужно из всего ответа. При отладке кода стоит отметить, что в бесплатной версии api яндекса допускается только 50 запросов в сутки.

Далее нам стоит написать функции которые будут отправлять ответом прогнозы. Начнем с аккувезер:

def print_weather(dict_weather, message):
    bot.send_message(message.from_user.id, f'Разрешите доложить, Ваше сиятельство!'
                                           f' Температура сейчас {dict_weather["сейчас"]["temp"]}!'
                                           f' А на небе {dict_weather["сейчас"]["sky"]}.'
                                           f' Температура через три часа {dict_weather["через3ч"]["temp"]}!'
                                           f' А на небе {dict_weather["через3ч"]["sky"]}.'
                                           f' Температура через шесть часов {dict_weather["через6ч"]["temp"]}!'
                                           f' А на небе {dict_weather["через6ч"]["sky"]}.'
                                           f' Температура через девять часов {dict_weather["через9ч"]["temp"]}!'
                                           f' А на небе {dict_weather["через9ч"]["sky"]}.')
    bot.send_message(message.from_user.id, f' А здесь ссылка на подробности '
                                           f'{dict_weather["link"]}')

В эту функцию мы должны погрузить вывод функции weather и сообщение на которое мы отвечаем. В самой функции мы отправляем сообщение с текстом и ссылками на тот словарь из функции weather.  В конце отправляем сообщение со ссылкой на полный прогноз.

Далее делаем сообщение с погодой от яндекса:

def print_yandex_weather(dict_weather_yandex, message):
    day = {'night': 'ночью', 'morning': 'утром', 'day': 'днем', 'evening': 'вечером', 'fact': 'сейчас'}
    bot.send_message(message.from_user.id, f'А яндекс говорит:')
    for i in dict_weather_yandex.keys():
        if i != 'link':
            time_day = day[i]
            bot.send_message(message.from_user.id, f'Температура {time_day} {dict_weather_yandex[i]["temp"]}'
                                                   f', на небе {dict_weather_yandex[i]["condition"]}')

    bot.send_message(message.from_user.id, f' А здесь ссылка на подробности '
                                           f'{dict_weather_yandex["link"]}')

здесь так же скармливаем функции выход функции yandex_weather и сообщение на которое отвечаем. Здесь создаем словарь с переводом времени дня, затем отправляем сообщение, далее задаем цикл по ключам словаря из выхода функции yandex_weather если ключ не link то отправляем сообщение: температура {время дня}, на небе {состояние неба}. В конце отправляем ссылку на яндекс (это требование яндекса).

И наконец обобщающая функция:

def big_weather(message, city):
    latitude, longitude = geo_pos(city)
    cod_loc = code_location(latitude, longitude, token_accu)
    you_weather = weather(cod_loc, token_accu)
    print_weather(you_weather, message)
    yandex_weather_x = yandex_weather(latitude, longitude, token_yandex)
    print_yandex_weather(yandex_weather_x, message)

В нее мы погружаем сообщение и город. Определяем координаты, запрашиваем accuweather и пишем сообщение на основе данных от него, потом тоже самое от яндекса.

Нужна ещё функция сохранения городов:

def add_city(message):
    try:
        latitude, longitude = geo_pos(message.text.lower().split('город ')[1])
        global cities
        cities[message.from_user.id] = message.text.lower().split('город ')[1]
        with open('cities.json', 'w') as f:
            f.write(json.dumps(cities))
        return cities, 0
    except Exception as err:
        return cities, 1

Сперва проверяем существование такого города если все ок то все что после слова город сохраняем в словарь cities под ключом id пользователя. Можно было бы использовать имя, но это только если Вы уверены, что сервисом будут пользоваться люди с разными именами.

Так все вспомогательные функции мы описали теперь функции ответа на сообщения:

@bot.message_handler(command=['start', 'help'])
def send_welcome(message):
    bot.reply_to(message, f'Я погодабот, приятно познакомитсья, {message.from_user.first_name}')

Здесь мы просто представляемся при первом посещении.

А вот и самая большая функция:

bot.message_handler(content_types=['text'])
def get_text_messages(message):
    global cities
    if message.text.lower() == 'привет' or message.text.lower() == 'здорова':
        bot.send_message(message.from_user.id,
                         f'О великий и могучий {message.from_user.first_name}! Позвольте Я доложу '
                         f' Вам о погоде! Напишите  слово "погода" и я напишу погоду в Вашем'
                         f' "стандартном" городе или напишите название города в котором Вы сейчас')
    elif message.text.lower() == 'погода':
        if message.from_user.id in cities.keys():
            city = cities[message.from_user.id]
            bot.send_message(message.from_user.id, f'О великий и могучий {message.from_user.first_name}!'
                                                   f' Твой город {city}')
            big_weather(message, city)

        else:
            bot.send_message(message.from_user.id, f'О великий и могучий {message.from_user.first_name}!'
                                                   f' Я не знаю Ваш город! Просто напиши:'
                                                   f'"Мой город *****" и я запомню твой стандартный город!')
    elif message.text.lower()[:9] == 'мой город':
        cities, flag = add_city(message)
        if flag == 0:
            bot.send_message(message.from_user.id, f'О великий и могучий {message.from_user.first_name}!'
                                                   f' Теперь я знаю Ваш город! это'
                                                   f' {cities[str(message.from_user.id)]}')
        else:
            bot.send_message(message.from_user.id, f'О великий и могучий {message.from_user.first_name}!'
                                                   f' Что то пошло не так :(')
    else:
        try:
            city = message.text
            bot.send_message(message.from_user.id, f'Привет {message.from_user.first_name}! Твой город {city}')
            big_weather(message, city)
            except AttributeError as err:
            bot.send_message(message.from_user.id, f'{message.from_user.first_name}! Не вели казнить,'
                                                   f' вели слово молвить! Я не нашел такого города!'
                                                   f'И получил ошибку {err}, попробуй другой город')

В первой части если автор сообщения сказал «привет» или «здарова» мы просто здороваемся и напоминаем, то что мы ждем от автора сообщения.

 Если написал «погода» то уточняемся есть ли он в нашем списке и если есть, то сообщаем название города и выполняем функцию big_weather.

Если пользователя нет, то предлагаем ему задать его стандартный город.

Если пользователь написал «Мой город *****», то пользователя. И сообщаем что город записан, если что-то пошло не так, то сообщаем об этом.

Во всех остальных случаях мы сообщение воспринимаем как название города. Если города такого не находится то сообщаем об этом.

ну и собственно нам нужно записать всю программу (это наверное самое короткое):

bot = telebot.TeleBot(token)

with open('cities.json', encoding='utf-8') as f:
    cities = json.load(f)
    
bot.polling(none_stop=True)

Здесь мы создаем объект bot, потом открываем json файл с архивом городов и id пользователей. И наконец запускаем в бесконечном цикле бота.

На этом можно закончить, но в целом функционал можно расширять, можно было бы добавить гисметео или rp5, но у этих товарищей нет бесплатных версий прогноза L.

Вот ссылка на gitHub.

И теперь нам нужно все это запустить на сервере.

Для этого нам нужен собственно сам сервер я например арендовал на джино, это не реклама просто констатация факта. это обходится 3,19 рублей в день.

Для создания своего аккаунта достаточно иметь аккаунт в google. Для целей телеграмм бота достаточно самого дешевого сервера.

Выберем ось

И нажимаем создать сервер.

После выделения Вам сервера можно будет попасть в его меню.

Здесь можно посмотреть обзор сервера его загруженность и самое главное параметры для ssh соединения и задать пароль для root (это нужно сделать обязательно).

Для подключения по SSH нам нужна программа putty она бесплатная. Устанавливаем, запускаем и видим окно:

В host-name вписываем хост из данных от джино и подключаемся, появляется командная строка вводим логин root нажимаем enter после чего вводим пароль который мы задали ранее.

Так теперь нам нужно установить python для этого в командной строке пишем

sudo apt install python3

и обновляем pip

sudo apt install python3-pip

Далее устанавливаем все библиотеки которые устанавливали в начале:

pip install requests

pip install geopy

pip install pyTelegramBotAPI

Переходим в каталог cd /usr/bin/

Создаем каталог mkdir telegrambot/

Открываем командную строку в виндовс

pscp -P (здесь указываем порт джино) C:\(путь к проекту)\ root@"указываем имя сервера":/usr/bin/telegrambot/

Он попросит ввести пароль, вводим пароль от root.

после копирования переходим на окно putty проверяем что все скопировалось командной ls.

В начале статьи я писал что токены сохраним в переменные среды. Мне проще было сделать так:

  1. В командной строке винды пишем: pscp -P (здесь указываем порт джино) root@"указываем имя сервера":/etc/environment C:\(путь к проекту)\environment

  2. открываем блокнотом скачанный файл и вписываем наши перменные каждую на отдельной строке:

    token_bot=ваш токен для бота

    token_accu=ваш токен для accuweather

    token_yandex=ваш токен для яндекса

  3. Сохраняем

  4. Отправляем этот файл обратно: pscp -P (здесь указываем порт джино) C:\(путь к проекту)\environment root@"указываем имя сервера":/etc/environment

Закрываем putty и открываем его снова. Входим как и ранее. для проверки добавления переменных среды в консоли вводим команду export. Должны отобразиться все переменные среды, включая Ваши.

После чего запускаем python3 /usr/bin/telegrambot/main.py

Все, телеграм бот работает.

Теги:
Хабы:
-5
Комментарии26

Публикации

Истории

Работа

Python разработчик
142 вакансии
Data Scientist
63 вакансии

Ближайшие события