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.
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.
Los requisitos no son muchos, ni son engorrosos. Además, si antes hemos programado con Python esto será pan comido.
Tip: Puedes seguir este tutorial fácil para ver cómo instalar dichos requisitos 😉
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.
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í:
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.
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.
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.
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í
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:
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:
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:
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.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
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
Hola. Para consultas personalizadas puede escribirme en https://parzibyte.me/#contacto
Muy interesante. Muy buen trabajo. Mil gracias.
Le invito a seguirme si el contenido fue de su agrado
Saludos :)