python

Gestionando sitio o blog con API de WordPress y Python

Introducción

WordPress tiene una API que podemos consumir para gestionar completamente un blog. Todas las peticiones se hacen a un mismo archivo PHP. Pero hoy no hablaremos de PHP, sino de Python y una librería que nos va a facilitar el trabajo.

Me gustaría recalcar que esta api sirve tanto para sitios que están en wordpress.com (por ejemplo, algo como misitio.wordpress.com) y para sitios que no están en wordpress.com pero usan wordpress, por ejemplo, este sitio que, aunque está alojado en un servidor distinto, utiliza wordpress.

Comprobación rápida

Para comprobar si nuestro sitio puede ser manejado con la api, vamos a comprobar si existe un archivo llamado xmlrpc.php.

Para ello iremos a la dirección de nuestro sitio y adjuntaremos /xmlrpc.php

En este caso probaré con un blog gratuito que he creado en wordpress.com, pero esto aplica para cualquier sitio que tenga wordpress instalado.

En mi caso, la ruta normal es parzibyte.wordpress.com pero la del archivo es parzibyte.wordpress.com/xmlrpc.php

Navegando, tenemos que encontrar algo así:

Si es que sale así, entonces podemos comenzar a trabajar. El mensaje que sale indica que sólo se aceptan peticiones POST, y en este caso estamos haciendo una de tipo GET. Muy bien, ahora podemos proseguir.

Requisitos

Los requisitos no son muchos, ni son engorrosos. Además, si antes hemos programado con Python esto será pan comido.

  • Python superior a la versión 3
  • Pip o easy install

Tip: Puedes seguir este tutorial fácil para ver cómo instalar dichos requisitos 😉

Instalando python-wordpress-xmlrpc

Una vez que ya tenemos PIP instalado, en una terminal vamos a escribir:

<span class="pre">pip</span> <span class="pre">install</span> <span class="pre">python-wordpress-xmlrpc</span>

Esperaremos a que se descargue y listo

Puedes consultar la documentación oficial si quieres ver alternativas.

Hola mundo

Es hora de crear nuestro hola mundo. Para ello, abriremos nuestro editor de código favorito, crearemos un archivo nuevo y escribiremos lo siguiente:

from wordpress_xmlrpc import Client
from wordpress_xmlrpc.methods.users import GetUserInfo
usuario = "tu_usuario"
contraseña = "tu_contraseña"
sitio = "tusitiodewordpress.com/xmlrpc.php" #Recuerda que debes llamar al archivo xmlrpc.php
cliente = Client(sitio, usuario, contraseña)
datos_usuario = cliente.call(GetUserInfo())
print("Tu nombre de usuario es {}".format(datos_usuario))

Guardamos, y en la terminal escribimos python nombre_del_archivo.py

Obviamente vamos a remplazar los datos que aparecen ahí por los verdaderos. Si ponemos el usuario o la contraseña equivocada, aparecerá algo así:

En cambio, si todo va bien, algo así se verá. En este caso puede que nos parezca redundante, pero no es así, ya que el nombre de usuario ha sido obtenido directamente de wordpress

Listando posts

Una vez que ya nos pudimos autenticar, es hora de trabajar como se debe. Comenzaremos leyendo los posts que tenemos en nuestro sitio. Para ello, veamos el siguiente código:

from wordpress_xmlrpc import Client
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
entradas = cliente.call(posts.GetPosts())
if len(entradas) > 0:
    for entrada in entradas:
        print(entrada)
else:
    print("No hay entradas para mostrar")

En él, simplemente obtenemos las entradas llamando al método GetPosts(). Se supone que si nos autenticamos correctamente, debe mostrar las entradas que tenemos. Por el momento sólo muestra las primeras 10 entradas y el título de ellas.

Cabe mencionar que muestra tanto las que son borradores y las que están publicadas.

Al ejecutar el script se debe ver algo así:

Y si voy al verdadero sitio, veré esto:

Por lo que se comprueba que los datos de la API son reales.

Listando más propiedades de los posts

Hasta ahora sólo mostramos el título (que es lo que se imprime por defecto al imprimir el objeto), pero ciertamente las entradas tienen otras propiedades como id, estado, contenido, etcétera. Así que ahora vamos a imprimir 10 posts (al igual que hace un momento) pero también imprimiremos las propiedades más importantes.

Como sólo tengo 1 post, sólo se imprimirá uno. Pero el punto es demostrar las propiedades

El código es el siguiente:

from wordpress_xmlrpc import Client
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
entradas = cliente.call(posts.GetPosts())
if len(entradas) > 0:
    for entrada in entradas:
        print("""
        ID: {}

        Título: {}

        Contenido: {}

        Estado: {}
        """.format(entrada.id, entrada.title, entrada.content, entrada.post_status))
        input("Presiona ENTER para ver la siguiente entrada")
else:
    print("No hay entradas")

Lo ejecutamos y debe aparecer algo así:

En este caso es la primera entrada y la única que existe. Tiene un id, contenido, título, y estado. Por cierto, no sólo existen esas propiedades. Podemos usar todas las que se muestran en esta lista de la documentación oficial.

Creando un post

Crear o insertar una nueva entrada o post es igual de sencillo que todo lo que hemos hecho hasta ahora. Vamos a crear uno que diga Hola mundo. Por cierto, todos los posts que publiquemos desde la api serán marcados como borradores (por lo que no serán publicados) pero tenemos un pequeño truco detallado más abajo para publicarlos instantáneamente.

Muy bien, para insertar podemos usar lo siguiente:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
nueva_entrada = WordPressPost()
nueva_entrada.title = "El título de la entrada"
nueva_entrada.content = "Hola, yo soy el contenido. Claro que <strong>puedo</strong> llevar contenido HTML"
id_entrada_publicada = cliente.call(posts.NewPost(nueva_entrada))
print("Correcto! Se publicó la entrada, y su id es {}".format(id_entrada_publicada))

Y al ejecutarlo, vemos esto:

Si vamos al sitio de wordpress, vemos lo siguiente:

Maravilloso, tiene el título que nosotros pusimos en el script. Y si la abrimos para ver detalles, podemos ver en la url que el id efectivamente es el 17. Y que sí podemos incluir contenido HTML:

La única desventaja es que fue publicada como borrador. Ahora veremos cómo convertir instantáneamente ese borrador en una entrada pública.

Cambiar estado de borrador a entrada publicada

Como dijimos, cuando publicamos con la api, la entrada se queda como borrador. Pues bien, como nos devuelve el id de la entrada podemos obtenerla para editarla, cambiar su estado y guardar cambios. Así:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
nueva_entrada = WordPressPost()
nueva_entrada.title = "Soy otra entrada pública"
nueva_entrada.content = "Hola, yo soy el contenido. Claro que <strong>puedo</strong> llevar contenido HTML"
id_entrada_publicada = cliente.call(posts.NewPost(nueva_entrada))
print("Correcto! Se guardó la entrada como borrador, y su id es {}".format(id_entrada_publicada))
print("Publicando entrada...")
nueva_entrada.post_status = 'publish'
resultado = cliente.call(posts.EditPost(id_entrada_publicada, nueva_entrada))
if resultado is True:
    print("Entrada publicada")
else:
    print("Algo salió mal")

La entrada fue publicada correctamente:

Incluso se puede visitar aquí

Etiquetas y categorías

Al publicar un post también podemos agregar categorías y etiquetas. Simplemente establecemos la propiedad “term_names” de la nueva entrada. Para las etiquetas usamos post_tag y para las categorías category. Cada clave necesita un arreglo.

El código queda así:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
nueva_entrada = WordPressPost()
nueva_entrada.title = "Entrada con categorías"
nueva_entrada.content = "Soy otra entrada publicada con la api de wp :)<br>En este caso llevo etiquetas y categorías"
nueva_entrada.terms_names = {
            'post_tag': ['Soy la primera etiqueta', 'Otra etiqueta por aquí', 'Etiquetas infinitas'],
            'category': ['Yo soy una categoría', 'Otra categoría', 'Categorías infinitas'],
    }
id_entrada_publicada = cliente.call(posts.NewPost(nueva_entrada))
print("Correcto! Se guardó la entrada como borrador, y su id es {}".format(id_entrada_publicada))
print("Publicando entrada...")
nueva_entrada.post_status = 'publish'
resultado = cliente.call(posts.EditPost(id_entrada_publicada, nueva_entrada))
if resultado is True:
    print("Entrada publicada")
else:
    print("Algo salió mal")

Voy a ejecutar el script:

Podemos ver la entrada aquí, y si observamos bien, veremos que tomó las etiquetas y categorías que le pusimos:

Así es como podemos seguir jugando con toda la api.

Eliminando entrada

Para terminar este CRUD, falta mostrar cómo eliminar. Para ello necesitamos el id. En este caso eliminaré la entrada que dice “Primera entrada del blog”. Listaré todos los posts y veré cuál id tiene:

Es el id 4, con ello puedo llamar a DeletePost y pasarle ese id como argumento. Como medida de seguridad, haré que el sistema pregunte si realmente quiero realizar esa acción y que muestre los detalles del post a eliminar usando el método GetPost.

Así que el código queda así:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts
usuario = ""
contraseña = ""
sitio = ""
cliente = Client(sitio, usuario, contraseña)
id_post_eliminar = 4
detalles_post = cliente.call(posts.GetPost(id_post_eliminar))
respuesta_de_usuario = int(input("""
¿Realmente desea eliminar el post?
Detalles:
ID: {}
Título: {}

Escriba 1 si desea confirmar la operación:
""".format(detalles_post.id, detalles_post.title)))
if respuesta_de_usuario == 1:
    resultado = cliente.call(posts.DeletePost(id_post_eliminar))
    if resultado is True:
        print("Eliminado correctamente")
    else:
        print("Algo salió mal")

Al correrlo y confirmar sale esto:

Si visito la página, veré que el post se ha movido a la papelera. No sé si exista un método para eliminarlo permanentemente, pero con moverlo a la papelera basta. Ya que luego podemos vaciarla en una sola operación.

Script interactivo

Me tomé el tiempo de crear un script interactivo que junta todos estos métodos y permite editar, eliminar, insertar y listar lo básico.

Si no queremos ingresar nuestros datos cada que corramos el script, podemos remplazar los campos que dicen “usuario por defecto” y los otros dos (los de contraseña y sitio).

Si estamos en un sistema  que no es Windows, ponemos el comando que usamos para limpiar la pantalla en lugar de “cls”.

Y bien, aquí el código:

from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods import posts
from wordpress_xmlrpc.methods.users import GetUserInfo
import os
import re
comando_para_limpiar_pantalla = "cls" #Si estás en otro sistema cámbialo al comando correcto (en Linux es clear)


def limpiar_pantalla():
    os.system(comando_para_limpiar_pantalla)

def limpiar_html(html):
    cleanr = re.compile('<.*?>')
    cleantext = re.sub(cleanr, '', html).strip()
    return cleantext

def pedir_datos_nueva_entrada():
    limpiar_pantalla()
    nueva_entrada = WordPressPost()
    nueva_entrada.title = input("Ingresa el título de la entrada: ")
    nueva_entrada.content = input("Ingresa todo el contenido de la entrada: ")
    etiquetas = []
    categorias = []
    eleccion = input("¿Deseas agregar etiquetas? [S/N] ")
    if eleccion.lower() == "s":
        etiquetas = input("Ingresa las etiquetas separadas con comas: ").split(",")
    eleccion = input("¿Deseas agregar categorías? [S/N] ")
    if eleccion.lower() == "s":
        categorias = input("Ingresa las categorías separadas con comas: ").split(",")
    nueva_entrada.terms_names = {
            'post_tag': etiquetas,
            'category': categorias,
    }
    print("Publicando entrada...")
    id_entrada_publicada = cliente.call(posts.NewPost(nueva_entrada))
    limpiar_pantalla()
    print("Correcto! Se guardó la entrada como borrador, y su id es {}".format(id_entrada_publicada))
    eleccion = input("¿Publicar inmediatamente? [S/N] ")
    if eleccion.lower() == "s":
        print("Publicando entrada...")
        nueva_entrada.post_status = 'publish'
        resultado = cliente.call(posts.EditPost(id_entrada_publicada, nueva_entrada))
        if resultado is True:
            input("Entrada publicada")
        else:
            input("Algo salió mal")
    imprimir_menu_opciones()


def mostrar_todas_las_entradas():
    offset = 0
    increment = 20
    while True:
        limpiar_pantalla()
        print("Mostrando entradas desde {} hasta {}".format(offset + 1, increment))
        entradas = cliente.call(posts.GetPosts({'number': increment, 'offset': offset}))
        if len(entradas) == 0:
                break  # no more posts returned
        mostrar_tabla_posts(entradas)
        eleccion = input("¿Ver más? [S/N] ")
        if eleccion.lower() != "s":
            break
        offset = offset + increment
    imprimir_menu_opciones()


def mostrar_tabla_posts(entradas):
    print("+{:-<20}+{:-<30}+{:-<50}+".format("", "", "", "", ""))
    print("|{:<20}|{:<30}|{:<50}|".format("ID", "Título", "Contenido"))
    print("+{:-<20}+{:-<30}+{:-<50}+".format("", "", "", "", ""))
    for entrada in entradas:
        print("|{:<20}|{:<30}|{:<50}|".format(entrada.id, entrada.title[0:30], limpiar_html(entrada.content)[0:50]))
    print("+{:-<20}+{:-<30}+{:-<50}+".format("", "", "", "", ""))


def pedir_id_para_eliminar():
    limpiar_pantalla()
    id_entrada_eliminar = int(input("Introduce el ID de la entrada que deseas eliminar [-1 para salir] "))
    if id_entrada_eliminar != -1:
        print("Eliminando entrada con el id {}".format(id_entrada_eliminar))
        resultado = cliente.call(posts.DeletePost(id_entrada_eliminar))
        if resultado is True:
            input("Eliminado correctamente")
        else:
            input("Algo salió mal")
    imprimir_menu_opciones()


def pedir_id_para_editar():
    limpiar_pantalla()
    id_entrada_editar = int(input("Introduce el ID de la entrada que deseas editar [-1 para salir] "))
    if id_entrada_editar != -1:
        entrada_para_editar = cliente.call(posts.GetPost(id_entrada_editar))
        entrada_original = entrada_para_editar
        entrada_para_editar.title = input("Introduce el nuevo título: [vacío si no quieres cambiarlo] ") or entrada_original.title
        entrada_para_editar.content = input("Introduce el nuevo contenido: [vacío si no quieres cambiarlo] ") or entrada_original.content
        print("Guardando cambios...")
        resultado = cliente.call(posts.EditPost(id_entrada_editar, entrada_para_editar))
        if resultado is True:
            input("Cambios guardados")
        else:
            input("Algo salió mal")
    imprimir_menu_opciones()


def imprimir_menu_opciones():
    limpiar_pantalla()
    eleccion = int(input("""Elige una opción:
    [ 1 ] - Agregar nueva entrada
    [ 2 ] - Ver todas las entradas
    [ 3 ] - Eliminar entrada
    [ 4 ] - Editar entrada
    [ -1 ] - Salir
"""))
    if eleccion == 1:
        pedir_datos_nueva_entrada()
    elif eleccion == 2:
        mostrar_todas_las_entradas()
    elif eleccion == 3:
        pedir_id_para_eliminar()
    elif eleccion == 4:
        pedir_id_para_editar()
    else:
        exit()

usuario = input("Nombre de usuario: ") or "usuario por defecto"
contraseña = input("Contraseña: ") or "Contraseña por defecto"
sitio = input("Sitio completo (con http(s) y xmlrpc.php al final): ") or "sitio por defecto"
cliente = Client(sitio, usuario, contraseña)

#Obtener la información del usuario para lanzar una excepción si la autenticación falla
cliente.call(GetUserInfo())
imprimir_menu_opciones()

Algunas capturas:

Conclusión

Con este script de python podemos llevar a cabo cualquier operación que podemos hacer a mano. Si buscamos la manera, podremos optimizar esto y hacer que nos ahorre tiempo en nuestra vida cotidiana. Por ejemplo, podemos establecer estilos predeterminados con html, publicarlas de una base de datos, etcétera.

Se puede hacer todo lo que sea imaginable.

 

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Tengo una duda: Y si quiero mostrar solo las entradas con una categoría?

    Tengo: entradas = cliente.call(posts.GetPosts(
    {'number': noticias_mostradas, 'offset': 0, 'post_status': 'publish', 'category': 'categoria uno'}))

    Pero no me las filtra por categoría.

    ¿como se puede hacer?
    No veo como hacerlo.

    Muchas gracias,

  • Hola buenas, este artículo es oro puro para lo que quiero hacer.

    De momento he hecho un script para web scrapping.

    Mi duda es, como puedo hacer para que ese scrypt actualice una entrada periodicamente cada vez que se ejecute el script?? (es para scrapping de ofertas de empleo, 1 actualizacion cada hora)

    Solo tengo esa duda como lo hago para editar las entradas.

    Y ya que pregunto, me gustaría que se añadiese un bloque de html personalizado para poder hacer una caja de empleo.

    me queda claro que wordpress dispone de consola con python instalable y que podria correr los scripts desde ahí.

    Gracias

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.