Publicando en WordPress desde Python

Es curioso como siendo WordPress el CMS más usado la información que se encuentra para automatizar la publicación de Posts desde fuentes externas sea tan compleja y poco amigable.
Como casi todo lo que voy mirando, responde a una necesidad de alguno de mis pequeños proyectos. En este caso necesitaba generar contenido para www.uadla.com, la web de ofertas que voy manteniendo. Se ha creado un proceso que va buscando las mejores ofertas en la base de datos de uadla, y publica un par de ellas en el blog. También un par de veces al día se conecta con las tiendas y publica las ofertas o cupones descuentos que estas lanzan.

Entonces podríamos resumir las necesidades de mi sistema automático de publicación en:
-Un sistema con pocas necesidades de escalar, unos 3 o 5 post al día.
-Capaz de crear post nuevos.
-Capaz de subir imagenes… y poco más!

Las posibilidades que nos ofrece WordPress son muchísimas, pero me he limitado a las que más necesitaba. Puedes manejar las cuentas de los usuarios, consultas las estadisticas, obtener el contenido, realizar busquedas… casi todo lo que puede hacer el administrador se puede hacer con algún API.

¿Cuántas opciones hay?

Pues bastantes más de las que nos podamos imaginar. Voy a enumerar algunas de las que me he ido encontrando:
WP REST API. Un pluguin para Worpress que mediante REST nos ofrece un API para que podamos interactuar. La información es realmente buena, van por su versión V2 y se actualiza cada poco. A estas alturas (Junio 2016) es una opción válida y actual. Dispone de diferentes formas de autenticación.

WORDPRESS REST API. Vale, tenemos el plugin, y WordPress también tiene un API REST. La mayor diferencia es que no necesitamos el plugin y que este API solo soporta autenticación por OATH2. Un proceso un poco complicado.

XML RPC WordPress API. ¿Porque?!!!! ¿Porque tenemos una tercera opción? A saber, quién lo puede saber. Pues ni idea. Ofrece acceso a buenas partes de WordPress y su sistema de autenticación es el más sencillo de todos, aunque también el más inseguro.

y no son las únicas opciones, también el famosísimo plugin JetPack que nos ofrece un API.

Bueno, despues de ver todo esto, de estar peleandome con la información disponible y de confundir el plugin con el API, el API de WordPress con el XML-RPC y liarme un poco acabe usando el API xml-rpc, me acabo pareciendo el más fácil de usar, aunque ya aviso que no es la opción más segura. La verdad es que hay cierta controversia sobre si WordPress debería permitir o no este tipo de llamadas, pero las permite y para mi blog, con poca visibilidad y que no creo que sea carne de ataques ta me va bién. Aunque… algún ataque si que he tenido, no usando el xml-rpc, sino un agujero de una versión antigua de PHP de un blog abandonado en el mismo servidor compartido…. pero esa es otra historia. Vamos a ver como funciona este API para poder publicar Post e imagenes en WordPress desde python, es mi caso desde Google App Engine usando python 2.7.

Conectando con el servidor.

from xmlrpclib import xmlrpclib
conexión = xmlrpclib.Server("nombredetublog/xmlrpc.php")

llamamos a la función Server de xmlrpclib, solo tiene un parámetro y es elpath de la hoja xmlrpc.php de nuestro blog. Por ejemplo si nuestro blog esta en la raíz de nuestro WEB y se la dirección es: tendremos que hacer la llamada de esta forma:

conexion = xmlrpclib.Server("http:miweb.com/xmlrpc.php")

Esta función, nos devuelve una conexión con el blog, que la usaremos para llamar a las distintas funciones después.

Incorporando un post

post_id = self.wp_server.metaWeblog.newPost(0, usuario, password, data, post_status)

El primer parámetro no se usa en WordPress… vamos a ver, y ¿como es eso? Si no se usa en WordPress, ¿que hace aquí? La razón es que he usado la función de metaweblog, que funciona tanto en WordPress, como del Blogger u otros sistemas de CMS diferentes a WordPress, no hay ningún secreto, en los otros sistemas indica el Id del blog que queremos usar, ya que una conexión puede valer para más de un blog. Nosotros le pasamos un 0 y lo ignoramos.
Los siguientes dos parámetros se explican por si solos, esta función necesita un usuario y un password que estén dados de alta en WordPress. Por dios, no uséis un password con permisos de administrador, que sea usuario con los permisos justos. Si queréis que los post se queden en borrador un perfil con el rol de Colaborador, y si queréis que los posts se publiquen automáticamente le dais un perfil de Autor. Pero en ningún caso uséis una cuenta de administrador, ya que si alguien accede al código u obtiene las credenciales de otra forma tomaría el control de vuestro Blog.
Después viene el parámetro más interesante, y es un diccionario que contendrá entre otras cosas el contenido a publicar.
Por último una variable que indica si queremos que el post se guarde como borrador, en este caso le pasaremos un 0, o se publique directamente, con lo que el valor tendría que ser un 1.

Dando formato al diccionario data

Lo mejor para saber qué parámetros se le pueden pasa es mirar la documentación oficial de WordPress, pero vamos a definir los más importantes, o los que yo he tenido que usar para mi sistema automatizado de publicación.

data = {'title': "titulo del post", 'wp_post_thumbnail':identificadorNumericoImagen, 'description': "contenido del post", 'categories': ["cat1",cat2"], 'mt_keywords': ["tag1","tag2"]}

La variable data es el cuarto parámetro que tenemos que pasarle a la función metaWeblog.newPost. Hay que tener en cuenta que las categorías que le pasemos tienen que existir en nuestro Blog, sino simplemente se ignoraran, en cambio podemos usar los tags que queramos que apareceran todos.

Al post se le puede asignar una imagen por defecto, esta imagen tiene que estar previamente subida en WordPress, así que podemos usar una imagen que ya tengamos en la biblioteca, averiguando previamente el ID que le asigna WordPress, o subirla usando la función que nos ofrece el API xml-rpc.

conexion.wp.uploadFile(0, self.wp_user, self.wp_password, data)

Como vemos usamos la conexión que hemos obtenido previamente mediante la función xmlrpclib.Server. El primer parámetro es el Id de Blog, ignorado por WordPress y que podemos pasar un precioso 0. Los dos siguientes son un usuario y password, recordemos que tenemos que usar un usuario con los permisos justos, no usemos un administrador, en este caso un autor o colaborador es suficiente. El ultimo parámetro es la imagen a subir, el contenido de la imagen, de abrir el fichero y guardar su contenido en la imagen que pasaremos como parámetro al método.

Vale, ya se puede adivinar que es todo más o menos sencillo, pero vamos a hacerlo más fácil, vamos a poner un ejemplo completo de creación de un Post, con titulo, contenido, categorías, tags y una imagen por defecto.

Ejemplo de automatización de Post para WordPress con xml-rpc desde Python

# coding:utf-8
import urllib2
from StringIO import StringIO
from xmlrpclib import xmlrpclib
#creamos la conexión con nuestro Blog 
wp_server = xmlrpclib.Server("http://www.miblog.com/xmlrpc.php")
#subimos una imagen a WordPress para usarla como imagen por defecto
data_img = {'name': "nombre.jpg",'type': 'image/jpg',}
#asignamos una imagen ya existente en Internet 
data_img['bits'] = xmlrpclib.Binary(urllib2.urlopen("http://www.lavidacotidiana.es/wp-content/uploads/2015/04/caracol.jpg").read())
response = wp_server.wp.uploadFile(0, 'miusuario', 'mipassword', data_img)
#obtenemos el identificador en nuestro blog de la imagen recien subida
ps_imageDefault = response['id']
#llenamos el parametro data con lo que sera el contenido del post
data_post = {'title': "Titulo del post", 'wp_post_thumbnail':ps_imageDefault, 'description': "contenido del post", 'categories': ["Deportes", "Ofertas"], 'mt_keywords': ["tag1", "tag2"]}
#llamamos a la funcion de creación del post, el último parametro indica que el post quedará en borrador
post_id = wp_server.metaWeblog.newPost(0, 'miusuario', 'mipassword', data_post, 0)

Bueno, pues este es un ejemplo muy sencillo, pero claro los post no tienen solo una frase, y a veces contienen más imágenes que la de por defecto. Así que vamos a darle un poco de forma para que sea mas sencillo de usar, y ya de paso ponemos un ejemplo completo de como usarlo.

Clase que nos encapsula el API xml-rpc de WordPress

class WPAuto():
wp_blog = ""
wp_user = ""
wp_password = ""
ps_content = []
ps_title = ""
ps_categories = []
ps_tags = []
ps_imageDefault = 0
wp_server = ""</code>
def __init__(self, blog, user, password):
urlfetch.set_default_fetch_deadline(60)
self.wp_blog=blog
self.wp_user=user
self.wp_password=password
self.wp_server = xmlrpclib.Server(self.wp_blog);</code>
def initPost(self):
self.ps_content = []
self.ps_title = ""
self.ps_categories = []
self.ps_tags = []
self.ps_imageDefault = 0</code>
def newBlog2Connect(self, blog, user, password):
self.wp_blog=blog
self.wp_user=user
self.wp_password=password
self.wp_server = xmlrpclib.Server(self.wp_blog);</code>
def setTitle(self, title):
self.ps_title = title</code>
def setDefaultImageById(self, idImageDefault):
self.ps_imageDefault = idImageDefault</code>
def setDefaultImageByUrl(self, urlImageDefault, imageName="pictureDe.jpg"):
try:
data = {'name': imageName,'type': 'image/jpg',}
urlfetch.set_default_fetch_deadline(60)
data['bits'] = xmlrpclib.Binary(urllib2.urlopen(urlImageDefault).read())
response = self.wp_server.wp.uploadFile(0, self.wp_user, self.wp_password, data)
self.ps_imageDefault = response['id']
return response['url'], response['id']
except Exception as e:
logging.exception("Exception setDefaultImageByUrl")
logging.exception(e)
def addSentenceContent(self, sentence):
self.ps_content.append(sentence)
def addImageContent(self, imageUrl, imageId, deepLink = None, altText=""):
imgData = self.wp_server.wp.getMediaItem(0, self.wp_user, self.wp_password, imageId)
sentence = ""
if deepLink is not None: 
sentence = "&amp;lta href='" + deepLink + "'&amp;gt"
try:	
sentence = sentence + "&amp;ltimg src='" + imageUrl + "' class='aligncenter size-full' alt='"+altText +"' width='" +str(imgData['metadata']['sizes']['medium']['width'])+"' height='"+str(imgData['metadata']['sizes']['medium']['height'])+"'/&amp;gt"
except:
sentence = sentence + "&amp;ltimg src='" + imageUrl + "' class='aligncenter size-full' alt='"+altText +"' width='360' height='300'/&amp;gt"
if deepLink is not None: 
sentence = sentence + "&amp;lt/a&amp;gt"
self.addSentenceContent(sentence)&amp;lt/code&amp;gt
def addCategorie(self, categorie):
self.ps_categories.append(categorie)
def addTag(self, tag):
self.ps_tags.append(tag)
def publishPost(self, post_status=0):
content = ""
for sentence in self.ps_content:
content = content + sentence + "&amp;ltbr/&amp;gt"
data = {'title': self.ps_title, 'wp_post_thumbnail':self.ps_imageDefault, 'description': content, 'categories': self.ps_categories, 'mt_keywords': self.ps_tags}
post_id = self.wp_server.metaWeblog.newPost(0, self.wp_user, self.wp_password, data, post_status)

Muy muy sencillo, sin ningún secreto. Vamos a poner un ejemplo, pero casi que no hace falta. Vamos a ver como usar esta clase para crear un post que tenga una imagen por defecto, un contenido formado por cuatro lineas y la misma imagen por defecto insertada. Este post como nos ha quedado muy bonito vamos a publicarlo en otro blog diferente.

aWP = WPAuto('http://www.miblog.com/xmlrpc.php', 'miusuario', 'mipassword')
aWP.initPost()
urlImage, idImage = aWP.setDefaultImageByUrl("http://www.lavidacotidiana.es/wp-content/uploads/2015/04/caracol.jpg", "caracol.jpg" )
aWP.setTitle("Mega post hiperinteresante")
aWP.addSentenceContent("Aqui tenemos la primera parrafada que le ponemos al post")
aWP.addSentenceContent("Hemos usado el api xml-rpc para publicarlo")
aWP.addSentenceContent("Vamos a poner una imagen")
aWP.addImageContent(urlImage, idImage, "http://google.es", "imagen de un caracol")
aWP.addSentenceContent("Bonito caracol que linka a google.es")
aWP.addCategorie("Ofertas")
aWP.addCategorie("Caracoles")
aWP.addTag("Caracol")
aWP.addTag("Campo")
aWP.addTag("Python")
#publicamos el post!
aWP.publishPost(1)
#lo reaprovechamos para otro blog
aWP.newBlog2Connect('http://www.otroblog.com/xmlrpc.php', 'miusuario', 'mipassword')
urlImage, idImage = aWP.setDefaultImageByUrl("http://www.lavidacotidiana.es/wp-content/uploads/2015/04/caracol.jpg", "caracol.jpg")
aWP.publishPost(1)

Como se puede ver el uso de la clase es bastante sencillo, se trata de pasar el blog y las credenciales cuando se crea la variable, ir incorporando contenido con la función addSentence(), las categorias los tags, las imágenes que queremos y cuando esta todo listo llamamos a publishPost(). Si queremos publicar el mismo Post en otro blog solo tenemos que llamar a la función newBlog2Connect(), subir la imagen al nuevo blog y llamar de nuevo a publishPost(). En el caso de quere hacer mas de un post para el mismo blog lo mejor es llamar a la función initPost() para borrar el contenido pero mantener la conexión con el blog.

Sentiros totalmente libres, si lo necesitáis y consideráis necesario, de realizar un copy / paste y usar el código. Tenia pensado subirlo a gitHub, pero esta poco pulido, no hay tratamiento de errores, no hay comentarios y es tan simple que me da hasta vergüenza, así que por ahora aquí se queda, a lo mejor lo subo si tengo tiempo de mejorarlo y documentarlo un poco.

Comentarios.