Mostrando las entradas con la etiqueta automation. Mostrar todas las entradas
Mostrando las entradas con la etiqueta automation. Mostrar todas las entradas

miércoles, 2 de mayo de 2012

Descargar todas las imágenes de un blog de Tumblr



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 ilovephotographyclub
y 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.

miércoles, 18 de abril de 2012

Automata with GeneXus Ev1 on Linux and Windows, and I


Today I will begin a series of four publications in which I will try to give a glimpse into how to program an automata in Genexus Ev1, periodically run it on Linux or Windows and send logs and/or resulting reports by mail.

This technique we used in production at one company where I worked as a developer to implement a data integrity verification process.

In this first issue will discuss the creation of the automaton with GeneXus Evolution 1 and the Java generator for Windows, but in fact could be programmed with any language that can throw a PDF with the name and directory you specify.


In the second issue we will deal with running it periodically under Linux using nothing but standard GNU applications that come with most distributions.

In the third post will do the same but under Windows, using some third party applications to achieve the same effect as inLinux.

In the fourth and final issue I will modify the script for Linux to use the same applications that we will use in Windows, as an exercise in adaptation from one platform to another.


Requirements of the automata

We need to create a program without a user interface that receives parameters from the command line, create files in a directory passed as parameter, as its output goes to standard output text to the OS.

Access to databases or other procedures will depend in each case we want to implement this technique, so they are optional as requirement.


Hands On

The main procedure

As a first step we create a KB on GeneXus. For the purposes of this project we choose Java Environment in Prototyping Environment and chose Win as target, the rest of the details aren't relevant but to follow the example would be advisable to name chequeodesatendido to the KB.


Now create a procedure called chequeodesatendido, this will be the main procedure of our automata. Change in its properties  Main program  to True and Call protocol to Command line.



Add some code:

Rules
parm(in:&date_ini, in:&date_end, in:&filename);

Source

if &date_ini.IsEmpty() or &date_end.IsEmpty() or &filename.IsEmpty() 
  msg("Faltan parametros.")
  msg("Se debe proveer fecha inicial, fecha final y nombre del archivo de salida")
endif

msg("date ini: " + &date_ini.ToFormattedString() + 
  " | date_end: " + &date_end.ToFormattedString() + 
  " | filename: " + &filename
)

reporte.call(&date_ini, &date_end, &filename)

As can be seen the code is quite simple, just as an example.Msg is output to the console, which under Linux can be redirected to a file easily under Windows can not find yet how to do the same.

This is where we run or call the code that performs some verification, correction, closing process, etc.. on our data.




Report

Create a procedure called report and modify its properties. By coincidence the properties matches the above procedure, change  Main program  to True and Call  protocol  to Command line.
Rules
parm(in:&date_ini, in:&date_end, in:&filename);
output_file(&filename, 'PDF');

Source
print printBlock1
return

Layout

For our example create just a band called printBlock1 and add the variables that received as parameter. In production this is where the report to be sent as a result should be generated.



Now define chequeodesatendido as the Startup object and create the project.


Deploy it

The easiest way to package the files needed to run our project is doing a "deploy", so you run the Deployment Wizard



On the first screen our two procedures appears in the list of Available mains, pass them to the right under Mains to deploy.



We turn to the second screen and don't touch anything there, just hit Next to reach the third screen. Once there we check the checkbox Transfer 
location  files then we enter a directory where the wizard will place the files, finally click Finish.



Now opens the Genexus Web Start Deployment window, change VM: to Sun, specify a name in Application name:, I used again chequeodesatendido, now click on Build Archives.



Now, if all went well, we should have all the files needed to run our program in the directory you specified in the third window of the Genexus Deployment Wizard. We should have a Shared folder and another with the name you specified in the Application name: in the GeneXus Web Start Deployment. Create a folder named reportes, which is where we'll ask our automata to send its reports.


Last actions

From here, in theory, we are able to test our automata, but there are still a couple of details that the wizard didn't cover, no idea why. For some reason the wizard doesn't copy the package iText.jar that is necessary to generate the report, so we must copy it manually from our KB. Copy it from the folder JavaModel on the KB to Shared on our deployment. 

Under Windows 7, I gess it's should be the same under Vista, at the first run the progam attempts to copy the file winjutil.dll to the bin folder of the JRE, but will fail due to permissions. There are two ways to solve this problem, the first is run once our project as Administrator, the other is to copy the file from the KB JavaModel our JRE's bin folder.



Testing the automata

Create a file names  named test.cmd and add the following code:


@echo off

rem reemplazamos los backslach "/" de la fecha por el signo menos "-"
for /f "tokens=1-3 delims=/" %%a in ("%date%") do set FECHA=%%a-%%b-%%c
set ARCHIVO_SALIDA=cr_%FECHA%
set DIRECTORIO_SALIDA=%CD%\reportes
set GXCLASSPATH="shared/.;shared/gxclassp.jar;shared/iText.jar;chequeodesatendido/chequeodesatendido_GXWS.jar"

java -cp %GXCLASSPATH% achequeodesatendido "%FECHA%" "%FECHA%" "%DIRECTORIO_SALIDA%\%ARCHIVO_SALIDA%"


Execute test.cmd, after that whe should have a PDF file in the reportes folder.

That's all for the first issue, the second part in a couple of days.