Ahora cuando posteo estos articulos también crean un tweet automático en https://twitter.com/ksergiocom
Quería explorar una forma de publicar tweets de forma automática. He decido crear un script de Python que utilize la API de Twitter para subir estos artículos de forma automática.
Para esto usaré cron para llamar el script cada período de tiempo, y este comprueba en la base de datos si existe algún artículo a publicar.
Esta será la estructura del proyecto. Para más detalle dejo aquí el repositorio.
El script es bastante sencillo. Por un lado busco los posts. Luego itero sobre ellos publicando aquellos que no están publicados, y por ultimo les actualizo el estado de publicado en twitter.
from lib.db import get_posts, set_post_as_publicado
from lib.twitter import publicar_tweet
posts = get_posts()
for post in posts:
publicar_tweet(post)
set_post_as_publicado(post)
Lo que respecta a la interacción con la base de datos. Nada peculiar, quizás la sintáxis de Mongo respecto a SQL, como por ejemplo; el modificador '$set' de mongo para actualizar unicamente este campo de forma aislada.
import os
from bson import ObjectId
from pymongo import MongoClient
MONGO_URI = os.getenv('MONGO_URI')
client = MongoClient(MONGO_URI)
posts_collection = client['express_ksergiocom'].get_collection('posts')
def get_posts():
posts = posts_collection.find({
"$or": [
{"publicadoTwitter": False},
{"publicadoTwitter": {"$exists": False}}
]
})
return posts
def set_post_as_publicado(post):
posts_collection.find_one_and_update({
'_id': ObjectId(post['_id'])
},{
'$set':{
'publicadoTwitter':True,
}
})
Para interactuar con la API de Twitter simplemente tendríamos que hacer requests a los determinados endpoints que nos idica la documentación de Twitter.
Primero habría que crearse una cuenta de desarollo y solicitar las keys necesarías para poderenviar peticiones autentificadas.
En Python he encontrado una librería llamada Tweepy que simplifica más aún el trabajo.
import os
import tweepy
client = tweepy.Client(
consumer_key = os.getenv('CONSUMER_KEY'),
consumer_secret = os.getenv('CONSUMER_SECRET'),
access_token = os.getenv('ACCESS_TOKEN'),
access_token_secret = os.getenv('ACCESS_TOKEN_SECRET'),
)
def publicar_tweet(post):
if post['publicadoTwitter']:
return
texto = f"{post['titulo']}. Leer en: https://www.ksergio.com/{post['slug']}"
client.create_tweet(text=texto)
Cron es un administrador o demonio que ejecuta procesos de forma periódica.
Utilizando una sintáxis concreta le podemos decir que ejecute nuestro script de forma recurrente.
Este es el aspecto para ejecutar el script con cron. Existen varías formas de hacerlo, pero ahora voy a explicar como lo integro con Docker para desplegarlo con un único comando.
0 * * * * /usr/local/bin/python3 /app/script/main.py
Por ultimo la raiz contiene varios archivos importantes.
- .env
- .dockerignore
- start.sh
- Dockerfile
- docker-compose.yml
El archivo .env contiene todas las variables de entorno que le voy a pasar al contenedor.
CONSUMER_KEY=CONSUMER_KEY
CONSUMER_SECRET=CONSUMER_SECRET
ACCESS_TOKEN=ACCESS_TOKEN
ACCESS_TOKEN_SECRET=ACCESS_TOKEN_SECRET
MONGO_URI=mongodb://localhost:27017
El archivo .dockerignore hace que no se pasen las carpetas venv y pycache que se se han generado en desarollo. No hace falta pasar esto al contenedor ya que no los va a usar.
El archivo ./start.sh tiene truco. El proceso lanzado en segundo plano no detecaba correcamente las variables de entorno. Por eso se las declaro directamente en el archivo de configuración de cron.
En realidad lo que hace es escribirlo todo en el archivo cronjob linea a linea y luego lo pasa con crontab al archivo de configuracion de cron
La linea final ejecuta cron y lo deja abierto en primer plano. Para que se ejecute sin parar. Si se parara el contenedor entero se detendría.
Otra peculiaridad es que redirecciono la salida a un archivo cron.log que estará en su propio volumen.
#!/bin/bash
MONGO_URI=$(printenv MONGO_URI)
CONSUMER_KEY=$(printenv CONSUMER_KEY)
CONSUMER_SECRET=$(printenv CONSUMER_SECRET)
ACCESS_TOKEN=$(printenv ACCESS_TOKEN)
ACCESS_TOKEN_SECRET=$(printenv ACCESS_TOKEN_SECRET)
echo "MONGO_URI=$MONGO_URI" > /app/cron/cronjob
echo "CONSUMER_KEY=$CONSUMER_KEY" >> /app/cron/cronjob
echo "CONSUMER_SECRET=$CONSUMER_SECRET" >> /app/cron/cronjob
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> /app/cron/cronjob
echo "ACCESS_TOKEN_SECRET=$ACCESS_TOKEN_SECRET" >> /app/cron/cronjob
echo "0 * * * * /usr/local/bin/python3 /app/script/main.py >> /app/cron/cron.log 2>&1" >> /app/cron/cronjob
crontab /app/cron/cronjob
cron -f
Para el Dockerfile voy a usar una imagen oficial de Python. También le voy a instalar cron para poder utilizarlo. Al final ejecutamos el script de bash start.sh
FROM python:3.8
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r script/requirements.txt
RUN apt-get update && apt-get -y install cron
RUN chmod +x start.sh
CMD ["./start.sh"]
Por ultimo el archivo docker-compose.yml que en este caso tiene una opción network_mode puesta a "host" para usar la misma red de la máquina anfitriona.
version: '3'
services:
twitter_script:
build:
context: .
volumes:
- ./cron/cron.log:/app/cron/cron.log
env_file:
- .env
network_mode: "host"
Ahora lo unico que falta sería pasar el contenido a donde queramos usarlo y ejecutar docker-compose up