В этом посте я покажу вам способ сделать автоматический твит о прогрессе в испытании #100DaysOfCode Challenge. После этого у вас останется больше времени на разработку. Супер, да?
Это день 007 нашего испытания 100 Days of Code. Весь код проекта хранится здесь.
Подготовка
Итак, нам понадобятся pytz, tweepy и requests. Вы можете установить их все разом командой:
pip install -r requirements.txt
если вы склонировали репозиторий. Также рекомендую использовать virtualenv для изоляции окружений.
Вам также понадобятся Consumer Key/Secret и Access Token (Secret) из Twitter. Я добавил их в .bashrc, который я загружаю через os.environ в config.py. Там же запускается обработчик логгирования, записывающий исходящие твиты и все возможные ошибки.
Главный скрипт
Согласно PEP8, вначале импортируем стандартную библиотеку, затем внешние модули, а ниже — модули проекта:
import datetime
import os
import re
import sys
import requests
import pytz
from config import logging, api
Мой сервер запущен в часовом поясе Mountain Time, а мне необходимо было работать с поясами в EMEA. В этом нам поможет pytz, с ним очень легко работать с любыми часовыми поясами:
tz = pytz.timezone('Europe/Amsterdam')
now = datetime.datetime.now(tz)
start = datetime.datetime(2017, 3, 29, tzinfo=tz) # = PyBites 100 days :)
По PEP8 константы зададим символами в верхнем регистре с разделителем в виде подчёркивания. Очень удобный расчёт дат:
CURRENT_CHALLENGE_DAY = str((now - start).days).zfill(3)
LOG = 'https://raw.githubusercontent.com/pybites/100DaysOfCode/master/LOG.md'
LOG_ENTRY = re.compile(r'\[(?P
REPO_URL = 'https://github.com/pybites/100DaysOfCode/tree/master/'
TWEET_LEN = 140
TWEET_LINK_LEN = 23
Ну и куда без requests? Так одной строкой я получаю файл LOG.md из репозитория:
def get_log():
return requests.get(LOG).text.split('\n')
Получим название скрипта и строку с соответствующей датой из LOG.md (сегодня = ‘007’):
def get_day_progress(html):
lines = [line.strip()
for line in html
if line.strip()]
for line in lines:
day_entry = line.strip('|').split('|')[0].strip()
if day_entry == CURRENT_CHALLENGE_DAY:
return LOG_ENTRY.search(line).groupdict()
Создаём твит. Я добавил немного кода для сокращения названия скрипт, если он превышает допустимый размер твита:
def create_tweet(m):
ht1, ht2 = '#100DaysOfCode', '#Python'
title = m['title']
day = m['day']
url = REPO_URL + day
allowed_len = TWEET_LEN + len(url) - TWEET_LINK_LEN
fmt = '{} - Day {}: {} {} {}'
tweet = fmt.format(ht1, day, title, url, ht2)
surplus = len(tweet) - allowed_len
if surplus > 0:
new_title = title[:-(surplus + 4)] + '...'
tweet = tweet.replace(title, new_title)
return tweet
Метод tweet_status() отправляет твит. Здесь мы используем импортированный объект api (из config.py) для отправки твита, а также запишем в лог информацию об успешной отправке или об ошибке:
def tweet_status(tweet):
try:
api.update_status(tweet)
logging.info('Posted to Twitter')
except Exception as exc:
logging.error('Error posting to Twitter: {}'.format(exc))
Будем запускать наш скрипт из main. Также я добавил несколько переменных для проверок:
if __name__ == '__main__':
import socket
local = 'MacBook' in socket.gethostname()
test = local or 'dry' in sys.argv[1:]
В режиме тестирования я использую локальный файл LOG:
if test:
log = os.path.basename(LOG)
with open(log) as f:
html = f.readlines()
else:
html = get_log()
Если по какой-то причине я не смогу получить данные из get_day_progress(), скрипт прекратит работу и в лог запишется ошибка:
m = get_day_progress(html)
if not m:
logging.error('Error getting day progress from log')
sys.exit(1)
Создаём твит. В режиме тестирования просто запишем его в лог, иначе — отправляем:
tweet = create_tweet(m)
if test:
logging.info('Test: tweet to send: {}'.format(tweet))
else:
tweet_status(tweet)
Деплой
Есть несколько вещей, которые необходимо сделать для работы нашей программы: source .bashrc для загрузки переменных среды, экспортировать PYTHONPATH, задать полный путь до python3. И как сказано здесь: «Cron ничего не знает о вашей оболочке; он запускается системой, поэтому у него минимум данных о среде.»
$ crontab -l
...
34 14 * * * source $HOME/.bashrc && export PYTHONPATH=$HOME/bin/python3/lib/python3.5/site-packages && cd $HOME/code/100days/007 && $HOME/bin/python3/bin/python3.5 100day_autotweet.py
Результат
Какое совпадение: твит о прогрессе за сегодня как раз ушёл 🙂
Логгирование
Очень полезная фишка модуля логгирования — автоматическое получение лога всех внешних модулей. Посмотрите в лог, там намного больше, чем пишет моя программа:
$ vi 100day_autotweet.log
...
...
14:34:02 tweepy.binder INFO PARAMS: {'status': b'#100DaysOfCode - Day 007: script to automatically tweet 100DayOfCode progress tweet https://github.com/pybites/100DaysOfCode/tree/master/007 #Python'}
...
many more log entries ...
...
14:34:02 requests.packages.urllib3.connectionpool DEBUG https://api.twitter.com:443 "POST /1.1/statuses/update.json?status={33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}23100DaysOfCode+-+Day+007{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}3A+script+to+automatically+tweet+100DayOfCode+progress+tweet+https{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}3A{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2F{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2Fgithub.com{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2Fpybites{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2F100DaysOfCode{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2Ftree{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2Fmaster{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}2F007+{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}23Python HTTP/1.1" 200 2693
14:34:02 root INFO Posted to Twitter ==> my message
Конечно, это можно отключить, повысив уровень логгирования (INFO или ещё выше) в logging.basicConfig (в config.py). Ну и почитайте документацию про это.
По материалам «How we Automated our 100DaysOfCode Daily Tweet».
Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.
Пишите: @ighar. Buy me a coffee, please :).