ksergio.com

I love coding

← Volver

Tweets automáticos con Python

15/1/2024

Twitter-python-logos

¿Pero por qué?

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.

imagen de la estructura de carpetas

Lógica con Python

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)
    

Ejecutar el script con CRON

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

Ailsar el proyecto en su propio entorno

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