Advertencia:
El contenido de este artículo está presentado solo con fines didácticos, como demostración de como utilizar herramientas GNU y expresiones regulares para procesar una gran cantidad de información de forma automática.
La propiedad intelectual de las imágenes en el sitio de Tumblr es de sus respectivos dueños.
Hace unos días se me ocurrió intentar bajar todas las imágenes de un blog de Tumblr, pero hacerlo a mano tomaría demasiado tiempo, por lo que empecé a analizar la forma de automatizar la tarea.
Ya me había fijado en que todas las imágenes que estaba bajando estaban alojadas en dominios que empiezan con dos números seguidos de .media.tumblr.com y los nombres empiezan con tumblr_. Veamos un ejemplo: la imágen del post http://ilovephotographyclub.tumblr.com/post/22189996450/via-on-my-way-to-heaven-by-farhadvm-on
es http://27.media.tumblr.com/tumblr_m3chaqtcbC1roly7jo1_1280.jpg.
Para este artículo voy a utilizar herramientas GNU, que pueden utilizarse tanto bajo las variantes *nix (Linux, FreeBSD, MacOS, etc.) como bajo Windows si instalamos cygwin (lo cual recomiendo encarecidamente).
Podemos utilizar wget para recuperar el contenido de la web y sed para parsear el código html de la página para recuperar las direcciones de la imágenes. Con la siguiente expresión regular '/="http:\/\/.*media\.tumblr\.com\/tumblr_.*"/ s/.*"(http:\/\/.*\.[a-zA-Z]{3,4})".*/\1/p' coincidimos las URL que nos interesan.
Ejecutando lo siguiente:
$ wget -qO- http://ilovephotographyclub.tumblr.com/post/22189996450/via-on-my-way-to-heaven-by-farhadvm-on | sed -rn -e '/="http:\/\/.*media\.tumblr\.com\/tumblr_.*"/ s/.*"(http:\/\/.*\.[a-zA-Z]{3,4})".*/\1/p' http://26.media.tumblr.com/tumblr_m3chaqtcbC1roly7jo1_250.jpg http://27.media.tumblr.com/tumblr_m3chaqtcbC1roly7jo1_1280.jpg
y terminamos con dos lineas que son los enlaces a las dos versiones de la imagen, una en baja resolución y la otra en una mayor resolución.
Ahora simplemente podemos bajar las imágenes con:
$ wget http://26.media.tumblr.com/tumblr_m3chaqtcbC1roly7jo1_250.jpg $ wget http://27.media.tumblr.com/tumblr_m3chaqtcbC1roly7jo1_1280.jpg
Hasta acá solo probamos nuestra teoría de obtener los enlaces de los posts, ahora veamos como podemos aplicar esto a todos los posts del blog.
Vamos a trabajar con la página principal del blog para parsearla y recuperar de ahí los enlaces a cada post en particular. Personalmente no encontré una forma de hacer esto en pocos pasos, especialmente después de probarlo con varios blogs. La siguiente linea de comando nos retorna la lista de posts que solo pertenecen al blog que estamos procesando (muchas veces hay referencias a otros blogs de donde proviene la imágen).
$ wget -qO- http://ilovephotographyclub.tumblr.com | sed -rn -e "/tumblr\.com\/post/ s/.*(\"http:\/\/.*\.tumblr\.com\/post.*\").*/\1/p" | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p' | sort | uniq
El comando recupera la página, filtra por los enlaces a posts y luego elimina el texto redundante que pasó por el primer filtro, también se aprovecha para eliminar referencias a la misma página (#), luego se ordena con sort para que uniq nos devuelva una lista única.
Ahora sería interesante aplicar esto a todos los posts del blog, si pudiéramos encontrar la forma de acceder a algún tipo de lista de los mismos. La página archive del blog nos permite acceder al historial el blog, pero muestra solo los últimos posts en orden descendente, al ir bajando -mediante javascript- va agregando dinámicamente el resto de los posts mas antiguos que no aparecieron en la página al cargarse. Si intentamos recuperar esta página con wget tenemos solo la página inicial y no todo el archivo por lo que no es práctico para nuestros intereses.
Otra forma de acceder al archivo de Tumblr es a través de páginas. Se pueden acceder ellas a través de la subcarpeta page seguida del número de página a la que queremos acceder. Ej: http://blog.tumblr.com/page/3 para acceder a la página 3.
Con esto podemos recorrer todas las páginas con un contador y un bucle, hasta que lleguemos al final de las páginas. Si solicitamos una página posterior a la última que tenga contenido, el sitio nos devuelve una página sin enlaces a post alguno, con formato pero vacía, podría decirse.
Ya no podemos probar este concepto directamente desde la linea de comandos, tendremos que utilizar un script.
#!/bin/bash PAGE_NUM=1 SALIR=0 BASE_URL="http://$1.tumblr.com" while [ $SALIR -eq 0 ]; do SITE="$BASE_URL/page/$PAGE_NUM" echo "procesando la página $PAGE_NUM [$SITE]" POST_LIST=`wget -qO- $SITE | sed -rn -e "/$1\.tumblr\.com\/post/ s/.*(\"http:\/\/.*\.tumblr\.com\/post.*\").*/\1/p" | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p' | sort | uniq` if [ -z "$POST_LIST" ]; then SALIR=1 else for POST in $POST_LIST; do echo $POST done let PAGE_NUM=$PAGE_NUM+1 fi done
Este script entra en un loop en el que incrementaremos nuestro contador de páginas, recuperaremos los enlaces a posts de cada página, si no podemos recuperar ningún enlace mas significa que llegamos al final de las páginas, entonces salimos del loop. La única acción del script es recorrer la lista y mostrar los enlaces. Debemos de pasar el nombre del blog como parámetro. Ej:
$ sh dwn_tumblr_test.sh ilovephotographyclub
Teniendo todo esto, es hora de programar un script que implemente todos los conceptos que probamos a lo largo del artículo.
#!/bin/bash BLOG=$1 LOG="$1.log" URL="http://$1.tumblr.com" ARCHIVE="$URL/archive" DUMP_DIR=$1 echo -e "Iniciando recuperacion del blog $1\n" > $LOG echo "URL: $URL" >> $LOG echo -e "Iniciando recuperacion del blog $1\n" echo "URL: $URL" # creamos la carpeta de salida if [ -e $1 ] && [ -d $1 ]; then echo "usando directorio $PWD/$1" >> $LOG echo "usando directorio $PWD/$1" else echo "directorio $PWD/$1 no existe, creando." >> $LOG echo "directorio $PWD/$1 no existe, creando." mkdir $1 >> $LOG fi echo "" >> $LOG PAGE_NUM=1 # el número de página que vamos a procesar SALIR=0 # el loop iteractuará mientras esta variable sea 0 while [ $SALIR -eq 0 ]; do PAGE_URL="$URL/page/$PAGE_NUM" echo "procesando la página $PAGE_NUM [$PAGE_URL]" echo "procesando la página $PAGE_NUM [$PAGE_URL]" >> $LOG POST_LIST=`wget -qO- $PAGE_URL | sed -rn -e "/$1\.tumblr\.com\/post/ s/.*(\"http:\/\/.*\.tumblr\.com\/post.*\").*/\1/p" | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p' | sort | uniq` if [ -z "$POST_LIST" ]; then SALIR=1 else # if [ ! -z "$POST_LIST" ] ... for POST in $POST_LIST; do # recuperamos una lista de los enlaces de las imágenes del post. normalmente hay varias versiones # de la imágen posteada en varias resoluciones IMG_URL_LIST=`wget -qO- $POST | sed -rn -e '/="http:\/\/.*media\.tumblr\.com\/tumblr_.*"/ s/.*("http:\/\/.*media\.tumblr\.com\/tumblr_.*").*/\1/p' | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p' | sort | uniq` # recorremos la lista de imágenes for IMG_URL in $IMG_URL_LIST; do echo " url: $IMG_URL" # recuperamos el nombre del archivo FILE_NAME=`basename $IMG_URL` echo " filename: $FILE_NAME" # para ahorrar tiempo solo bajamos el archivo si no existe en el directorio de salida if [ -e "$DUMP_DIR/$FILE_NAME" ]; then echo "url: $IMG_URL #filename: $FILE_NAME post:$POST" >> $LOG echo "# ya existe" else echo ">> bajando" echo "url: $IMG_URL >filename: $FILE_NAME post:$POST" >> $LOG wget -qO "$DUMP_DIR/$FILE_NAME" $IMG_URL >> $LOG fi done # for IMG_URL in $IMG_URL_LIST ... done # for POST in $POST_LIST ... let PAGE_NUM=$PAGE_NUM+1 fi # if [ ! -z "$POST_LIST" ] ... done # while [ $SALIR -eq 0 ] ...
Guardamos el script en un archivo y lo ejecutamos, siempre pasando el nombre del blog como parámetro:
$ sh dwn_tumblr.sh ilovephotographycluby al terminar tendremos un directorio con el nombre del blog con las imágenes y un archivo también con el mismo nombre pero con extensión .log con el detalle de todo lo descargado.
Todo el ejemplo aquí expuesto fue creado y probado con CygWin bajo Windows 7 x64.
Actualización 03/05/2012: En el último script, en la linea 42 se agregó "| sort | uniq" al código a fin de eliminar duplicados:
IMG_URL_LIST=`wget -qO- $POST | sed -rn -e '/="http:\/\/.*media\.tumblr\.com\/tumblr_.*"/ s/.*("http:\/\/.*").*/\1/p' | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p'`
por
IMG_URL_LIST=`wget -qO- $POST | sed -rn -e '/="http:\/\/.*media\.tumblr\.com\/tumblr_.*"/ s/.*("http:\/\/.*").*/\1/p' | sed -rn -e 's/"([^"|^#]*)(["#].*)/\1/p' | sort | uniq`
En las lineas 54 y 58 se agregó "post: $POST" al texto del echo, a fin de registrar de cual entrada se recuperó la imágen.