14 заметок с тегом

языки_программирования

Python. Выгрузка бота на сервер

В прошлой статье мы собрали бота в докер контейнер и заставили его постоянно перезапускаться при ошибках. В этой статье зарегистрируемся на ДокерХабе — репозитории докер образов и зальём бота на сервер.

Докер хаб

Заходим на страницу регистрации и делаем учётку.
На главной странице нажимаем Create repository и заполняем данные о репозитории.

Чтобы обновлять код бота, нужно будет привязывать ГитХаб или вручную выгружать образы

Подготавливаем сервер

Теперь нужно арендовать сервер, чтобы там жил бот. Нужно чтобы сервер был с виртуализацией XEN или KVM, потому что другие типы не поддерживают докер. Сервак можно арендовать на Reg.ru или AdminVPS. Оплачиваете и ждёте данные для входа на сервер: айпишник, логин и пароль.

Подключаетесь к серверу через терминал по ssh, вот так

ssh ваш_логин@айпишник

# Например ssh xalion@151.248.126.105

После этого вводите пароль и попадаете в домашнюю директорию пользователя.
Перед пайтоном мы писали как работать в терминале.

Докер на сервере

Теперь нужно установить докер на сервер. Мы приведем пример установки для убунту, если у вас дебиан или центОС, посмотрите документацию по установке докера, пример по убунту взят оттуда же.

Скорее всего вы залогинитесь под рут пользователем. Если нет, то к началу каждого примера добавляйте sudo.

  1. Для начала проверим, нет ли старых версий докера.
apt-get remove docker docker-engine docker.io containerd runc
  1. Теперь обновляем библиотеку пакетов
apt-get update
  1. Устанавливаем нужные пакеты для установки докера
apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
  1. Добавляем GPG-ключ докера
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

Можно проверить ключ, если хотите. Он должен быть вот таким: 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88

apt-key fingerprint 0EBFCD88
  1. На виртуальном сервере тип архитектуры будет x86_64 / amd64.
    Добавляем в нашу библиотеку пакетов репозиторий для установки докера. Теперь система будет следить за его обновлениями.
add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
  1. Обновляем библиотеку пакетов
apt-get update
  1. И устанавливаем докер
apt-get install docker-ce docker-ce-cli containerd.io

Если на этом этапе появились ошибки — напишите хостеру, они помогут решить проблему.
Если ошибок нет и вывод выглядит нормальным, проверяем работу докера.

  1. Запускаем контейнер hello-world, в локальном хранилище его образа нет, поэтому докер скачает его с ДокерХаба, создаст контейнер и запустит
docker run hello-world

Подгружаем бота

Когда сервак готов, нужно загрузить туда образ бота, который мы собрали в прошлой статье.
Придумываем удобный для вас тэг на локальный образ и пушим его в репозиторий.

docker tag имя_локального_образа:тэг полное_имя_репозитория_на_сайте:тэг
# Например
docker tag shoper_bot:latest xalion/shoper_bot:latest

После, пушим в репозиторий

docker push полное_имя_репозитория_на_сайте:тэг

Когда бот залит в репозиторий, нужно подгрузить его на сервер.
Возвращаемся на сервер и пишем

docker pull полное_имя_репозитория_на_сайте

Когда образ скачан на сервер, делаем из него контейнер и запускаем

К следующей статье мы хотим сделать бота, который помогает составить список покупок с напоминаниями.
Мы можем не успеть сделать всё, поэтому в следующую субботу выйдет статья про алгоритмы и математику.

11 мая   python   языки_программирования

Python. Делаем лог и демонизируем бота

С прошлой статьи у нас осталась проблема — бота нужно было каждый раз перезапускать руками, чтобы он искал новые прокси. Это решается через докер — программу, которая берёт себе кусок памяти загружает какое-нибудь приложение и изолирует его от внешней памяти и приложений. Эта память называется контейнером.

И мы захотели сделать функцию для создания отчётов об ошибках, их называют логами. Для логера сделаем отдельный файл с функциями для записи отчётов.
Сначала сделаем логер, чтобы смотреть ошибки бота.

Логер

Для личного пользования, логеры можно не делать, но мы решили красиво оформить отчёты об ошибках. Если вам это не нужно, листайте до докера.

Для ведения лога будем использовать встроенную библиотеку logging.
Создаём файл loger.py. Импортируем библиотеку и устанавливаем параметры записи в лог.

import logging

# В параметрах указываем имя файла для ведения лога и минимальный тип сообщений в нём.
logging.basicConfig(filename="history_work.log", level=logging.INFO)

Для лога существует пять типов сообщений: DEBUG, INFO, WARNING, ERROR и CRITICAL. У них иерархическая система, поэтому если указать уровень ERROR, то выводиться будет и CRITICAL.
В конфиге мы указали, что в лог будем записывать сообщения типа INFO, WARNING, ERROR и CRITICAL.

Библиотека умеет записывать ошибки самостоятельно, но ошибки связанные с прокси нам нужны в красивом формате, чтобы было читаемо. Поэтому напишем две функции: первая для читаемого вывода текущего подключения, вторая для вывода ошибки подключения.

Запись информации о подключённом прокси

def write_info(data_proxy):
    # Проверяем, что прокси пришёл в полном формате
    try:
        logging.info('''
        Работаю на ip {ip}
        Порт {port}
        Страна {country}
        Последняя проверка {last_check}
        '''.format(ip=data_proxy['ip'], port=data_proxy['port'],
                   country=data_proxy['country'], last_check=data_proxy['last_check']))

    # Если прокси не в полном формате, значит бот попал в бан и использует резервный прокси из файла
    except KeyError:
        # С сайта придет ответ с причиной бана
        response = requests.get('http://pubproxy.com/api/proxy?type=https')
        # Записываем в лог
        logging.error('''Прокси не пришел, бот попал в бан.
        Ответ сайта: {}'''.format(response.text))



Запись об ошибке подключения к прокси

def write_error(description_error):
    logging.error('''
    Прокси отвалился с ошибкой {error}
    Подбираю новый
    '''.format(error=description_error))

Контролируем размер лога

При каждом реконнекте, бот будет делать новую запись в лог. Со временем лог вырастет в размерах, поэтому его нужно очищать.
Сделаем так: если файл лога будет больше 1 МБайта, то его удаляем. С новой записью, если файла нет, он создаёт новый. Мегабайта хватит для четырёх тысяч записей.
Чтобы это работало нам нужны: функция getsize из os.path, функции run из subprocess и библиотека для времени и даты datetime.

Функция для авто-чистки лога.

def check_log_size():
    # Проверяем размер файла
    # Если он больше или равен 1 Мб или же 1 048 576 байт, то удаляем лог
    if (getsize('history_work.log') >= 1048576):
        # Запускаем процесс для удаления файла
        run(['rm', 'history_work.log'])

        # Получаем сегодняшнюю дату и настраиваем её формат вывода
        # %d.%m.%Y в %H:%M — формат вывода день.месяц.год в час:минута
        last_cleaning_log = datetime.datetime.today().strftime("%d.%m.%Y в %H:%M")

        # Записываем в файл
        file = open('last_cleaning_log.txt', 'w')
        file.write(str(last_cleaning_log))
        file.close()



Чтобы авто-чистка работала, нужно вызвать функцию check_log_size() в файле loger.py.
Функция будет запускаться каждый раз при загрузке модуля, а модуль будет загружаться при старте или рекконекте бота.
Получившийся файл выглядит так.

Прикручиваем к главному файлу

Теперь соединим логирование и вывод информации о последней чистке лога с мейн кодом бота.
Импортируем логер в мейн код и добавляем логирование в блок поиска новых прокси.

try:
    # Запускаем бота
    bot.polling()

# Если прокси отваливается
except OSError as e:
    # Тормозим бота
    bot.stop_polling()
    
    loger.write_error(type(e).__name__)  # Записываем в лог имя ошибки подключения к прокси    
    sleep(5)  # Ждём пять секунд, чтобы не словить бан за слишком частые запросы    

    proxy = proxy_changer.get_proxy()  # Запрашиваем новый прокси
    ip_port = proxy['ip_port'] # Обновляем адрес прокси, чтобы бот выводил текущий адрес

    tb.apihelper.proxy = {'https': 'https://{}'.format(proxy['ip_port'])}  # Ставим прокси
    proxy_changer.write_proxy(proxy)  # Перезаписываем адрес в файл
    loger.write_info(proxy) # Записываем в лог данные нового прокси

    # Запускаем бота
    bot.polling()



Вывод даты последней чистки добавим в команду /log

@bot.message_handler(commands=['log'])
def send_log(message):
    log = open('history_work.log', 'r')
    bot.send_document(message.chat.id, log)

    # Проверяем есть ли файл
    try:
        file = open('last_cleaning_log.txt', 'r')

    # Если нет — считаем, что чистки ещё не было
    except FileNotFoundError:
        bot.send_message(message.chat.id, 'Чистки ещё не было')

    # Если есть, то читаем из файла дату последней чистки лога и отправляем её
    else:
        date_last_cleaning_log = file.read()
        file.close()

        bot.send_message(message.chat.id, '''
        Последняя чистка была {}'''.format(date_last_cleaning_log))

Обновлённый файл с мейн кодом бота теперь выглядит так

Теперь нужно всё это демонизировать при помощи докера.

Докер

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

Полное изучение докера — тема для отдельного цикла статей, сейчас изучим только то, что нам нужно.
Из всех понятий докера нам понадобятся два: образ и контейнер.
Образ — самостоятельная файловая система.
Контейнер — запущенный процесс операционной системы на образе.

Скачать докер можно с репозитория докер-образов. Там есть вариант под винду, мак и несколько линукс систем. Устанавливаем и запускаем.

Сборка образа

Теперь нужно собрать бота в докер-образ.
Чтобы это работало, нужно собрать все файлы для бота в одну папку и создать там Dockerfile.
Мы подготовили репозиторий с каркасом бота.

Открываем Dockerfile через блокнот или атом и пишем

# Качаем в контейнер рабочий пайтон 
FROM python:3.7.3-alpine3.9

RUN mkdir /shoper_bot  # Создаём директорию бота
COPY . /shoper_bot  # Копируем все файлы из текущей папки в папку в контейнере 
WORKDIR /shoper_bot  # Устанавливаем рабочую директорию

# Устанавливаем pytelegrambotapi и requests
RUN pip3 install --no-cache-dir pytelegrambotapi
RUN pip3 install --no-cache-dir requests

# Указываем команды для выполнения после запуска контейнера
CMD ["python3", "bot.py"]

Получается вот такой файл.

Теперь его надо собрать, проверить и запустить через терминал или CMD. Точка в конце команды нужна, чтобы докер искал Dockerfile в текущей директории.

docker build -t имя_докер-образа .

После создания, проверяем образ

docker images

У вас должно быть два образа: пайтон и бот.

После этого создаём контейнер и запускаем его через команду run.
При запуске контейнера, придумываем ему имя и ставим параметр рестарта на постоянный перезапуск при завершении бота из-за ошибок прокси.

docker run --name имя_контейнера --restart="always" имя_образа

Ждём несколько секунд. Скорее всего бот отвалится с ошибкой прокси и вас выкинет из сессии.
Если нет — просто нажмите Ctrl+C и перезапустите контейнер: docker restart имя_контейнера.
Проверяем, запущен ли контейнер

docker ps

Теперь когда бот будет вылетать из-за нескольких ошибок прокси, докер будет перезапускать контейнер до тех пор, пока бот не поймает рабочий прокси.



Следующая статья будет про  выгрузку бота на сервер и докер-хаб.

4 мая   python   языки_программирования

Python. Подбор рабочих прокси

В прошлой статье мы написали каркас бота, который здоровался на любое принятое сообщение. В коде есть проблема — нужно вручную менять прокси.
Бесплатные прокси либо не работают, либо работают через раз, поэтому бот часто падал. Лучший вариант для бота из России — переехать на VDS/VPS где-то за границей. VDS или же VPS — выделенные виртуальные серверы, которые работают на базе физического. Заказываете сервер, оплачиваете, загружаете бота, демонизируете и забываете про прокси, РКН не банит обычные иностранные IP.

Но мы решили сначала показать, что бесплатный вариант тоже возможен. Для этого мы сделали программу для подбора прокси, докер-контейнер для автоперезагрузки и соединили это всё с ботом. В этой статье будет про переборщика прокси.

Прокси-ченджер

Когда прокси, к которым подключен бот перестают работать, возникает ошибка тайм-аута, коннекта или другая сетевая ошибка и всё падает.
Чтобы такого не было, нужно заменить старый прокси на новый, а потом проверить, работает ли он, если нет — заново подбирать прокси.

Запрашиваем адрес

Мы нашли сайт PubProxy с API для получения прокси, чтобы было легче отлавливать список свободных адресов. Чтобы получить свободный адрес, нужно сделать запрос к API вот так:

http://pubproxy.com/api/proxy

К запросу можно добавлять фильтры для типа протокола, аптайма и других параметров. Просто дописываем к запросу и ставим между ними амперсанд &:

http://pubproxy.com/api/proxy?type=https  # искать адреса с протоколами https
http://pubproxy.com/api/proxy?type=socks5&speed=7  # искать socks5 с аптаймом не выше 7

Забираем адрес с сайта

Нам нужны https прокси, поэтому в параметрах будем указывать только их. Чтобы делать запросы из кода нужна библиотека requests, она ставится через pip3.

Для виндовс
pip install requests

Для линукса/мака
pip3 install requests

Импортируем её в код и отправляем запрос к API.

import requests 
url = 'http://pubproxy.com/api/proxy?type=https'  # url – просто строка с запросом
response = requests.get(url)  # запрос к API

Сайт даёт ответ в формате JSON — формат данных из пар ключ:значение. Единственный ключ data и ветви значений айпи, порты и все остальные.

Ключ — data, он состоит из списка на один элемент. Этот элемент — словарь

На сайте стоит ограничение для запросов, чтобы сервер не упал от большого количества одновременных запросов. Если за сутки превысить какое-то количество запросов, ваш айпишник забанят на несколько деньков.
Чтобы бот не упал из-за бана, добавим обработку исключения. При бане, API не отдаёт json-файл, поэтому декодер падает с исключением.
Обработка исключения:

try:
        json_proxy = json.loads(response.text)  # проверяем, в бане ли мы. Если в бане – json-файл не придет и будет исключение 
    except json.decoder.JSONDecodeError:  
        last_proxy = {'ip_port': read_proxy()}  # тогда бот будет читать последний доступный IPшник

Пакуем адрес в код

Из всего ответа нам нужно только пять значений. ipPort чтобы приконектить бота, ip, port, country и last_checked — для ведения лога. Лог — это история работы программы, об этом расскажем в следующей статье.

Ответ с сайта будем паковать в словарь, про них мы писали в статье про типы данных в пайтоне.
Чтобы забрать нужные данные из ответа, пишем:

# создаём словарь с данными о прокси
    proxy_data = dict.fromkeys(['ip_port', 'ip', 'port', 'country', 'last_check'])

    proxy_data['ip_port'] = json_proxy['data'][0]['ipPort']
    proxy_data['ip'] = json_proxy['data'][0]['ip']
    proxy_data['port'] = json_proxy['data'][0]['port']
    proxy_data['country'] = json_proxy['data'][0]['country']
    proxy_data['last_check'] = json_proxy['data'][0]['last_checked']

В итоге у нас получается словарь такого типа: {’ip_port’: ’39.137.69.6:80’, ’ip’: ’39.137.69.6’, ’port’: ’80’, ’country’: ’CN’, ’last_check’: ’2019-04-25 18:15:31’}. По ключу country хранится двухбуквенный код страны. Например, CN — код Китая.

Теперь обернём это всё в одну функцию.

def get_proxy():
    url = 'http://pubproxy.com/api/proxy?type=https'
    response = requests.get(url)

    # если адрес забанили или превышен лимит на запросы
    try:
        json_proxy = json.loads(response.text)
    except json.decoder.JSONDecodeError:
        last_proxy = {'ip_port': read_proxy()}
        return last_proxy

    # если не забанили, то создаём словарь с данными о прокси
    proxy_data = dict.fromkeys(['ip_port', 'ip', 'port', 'country', 'last_check'])

    proxy_data['ip_port'] = json_proxy['data'][0]['ipPort']
    proxy_data['ip'] = json_proxy['data'][0]['ip']
    proxy_data['port'] = json_proxy['data'][0]['port']
    proxy_data['country'] = json_proxy['data'][0]['country']
    proxy_data['last_check'] = json_proxy['data'][0]['last_checked']

    return proxy_data

Записываем IPшники в отдельный файл

Теперь, чтобы при новом запуске бот не слезал с рабочего прокси, добавим запись и чтение прокси из файла.
Функция записи прокси в файл

def write_proxy(proxy):  # функция принимает словарь 
    file = open('proxy.txt', 'w')
    file.write('{}'.format(proxy['ip_port']))  # на место фигурных скобок вставится айпи и порт из словаря
    file.close()

Функция чтения прокси из файла для бота

def read_proxy():
    file = open('proxy.txt', 'r')
    ip_port = file.read()
    file.close()

    return ip_port

Теперь соберём это всё в один файл. Назовём его proxy_changer.py

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

Бот

Сначала читаем прокси из файла, коннектим бота к нему, потом соединяемся с ботом и убираем у него обработку сообщения пользователя в нескольких потоках.

ip_port = proxy_changer.read_proxy()  # читаем айпи и порт прокси из файла
tb.apihelper.proxy = {'https': 'https://{}'.format(ip_port)}  # соединяемся с прокси чтобы обойти блокировку
bot = tb.TeleBot('Ваш токен', threaded=False)  # соединяемся с ботом и убираем многопоточность

Бот рассчитан на пользование только его создателем, поэтому добавим функцию вывода текущего прокси. Для этого напишем хендлер — функция для обработки конкретных сообщений и команд, например /proxy.

@bot.message_handler(commands=['proxy'])
def proxy_message(message):
    bot.send_message(message.chat.id, '''
    Сейчас я на ip {}'''.format(ip_port))

format — функция для форматирования строки. В строке ставятся фигурные скобки, в них format подставит значения своих аргументов.

Обработка ошибки коннекта

Может быть так получится, что прокси слетит не сразу, а во время диалога.
Будем отлавливать OSError, потому что все ошибки коннектов наследуются от него.

try:
    bot.polling()  # бот запущен и ждёт сообщений
except OSError:
    bot.stop_polling()
    sleep(5)  # ждём пять секунд, чтобы не словить бан за слишком частые запросы

    proxy = proxy_changer.get_proxy()  # забираем прокси с сайта 
    ip_port = proxy['ip_port']  # обновляем адрес прокси, чтобы бот выводил текущий адрес
    tb.apihelper.proxy = {'https': 'https://{}'.format(proxy['ip_port'])}  # ставим прокси
    proxy_changer.write_proxy(proxy)  # перезаписываем в файл
   
    bot.polling()  # снова запускаем бота

Теперь засовываем это всё в один файл bot.py и подгружаем туда proxy_changer.py. Вот так

Создаём файл proxy.txt и записываем туда любой айпишник. Можете взять этот адрес: 145.239.92.81:3128. На момент выхода статьи он работает.

По итогу должно получиться так: файл с ботом, файл с перебором IPшников и текстовый файл для прокси

Тестим

Запускаем файл с ботом, если прокси нерабочий — получаем одну из ошибок коннекта.

Proxy Error, Connect TimeOut и другие

Запускаем опять, пока бот не поймает рабочий прокси и оживёт. У нас это получилось на десятом перезапуске.



По десять раз перезапускать бота руками — не круто, поэтому в следующей статье мы упакуем бота в докер-контейнер и заставим докер перезапускать его при каждом завершении. Потом добавим логирование, чтобы видеть историю работы бота.

27 апреля   python   языки_программирования
Ранее Ctrl + ↓