Alejandro Santos: Eclipse Luna on Debian Wheezy Crash


The crash is related to a version mismatch between GTK2, GTK3 and Debian's GLIBC. The relevant bug is here: https://bugs.eclipse.org/bugs/show_bug.cgi?id=430736

To force the use of GTK2 on Eclipse Luna you can try:
$ export SWT_GTK3=0
For a more permanent solution you can set the GTK version on the eclipse.ini file:
 --launcher.GTK_version
2
This option should be inserted before the --launcher.appendVmargs option.

And now you should be able to use Eclipse Luna on Wheezy.

Hernán Grecco: PyVISA 1.5 is out

PyVISA is a Python wrapper for the VISA library that enables controlling all kinds of measurement equipment through GPIB, RS232, USB and Ethernet. It has served the instrumentation community very well since 2005 (that's Python 2.3!) and still does.

However, Python and the different supported platforms have changed a lot in the recent years. We thought that PyVISA could use an update. Within the Lantz Project we did a small proof of principle of such update in visalib. Now we are taking what worked well and use it into PyVISA 1.5 (without changing the API!). In other words, PyVISA 1.5 brings several important changes in the underlying architecture without forcing you to change the programs.

Some time ago I posted that we were going beta. Now PyVISA 1.5 is finally released.

The new architecture is summarized here and the comparison with the previous one is here. Briefly you get Python 3 support, Mac OS X support, a better way to find libraries in your platform, an isolated ctypes wrapper. But the most important change is that the VISA library is not opened on import anymore. You can still use the instrument and get_instruments_list helpers (although we encourage not to do it!), but under the hood they will only instantiate a VisaLibrary object when you need them. We think that this will lead to more explicit and clear code, easier to deploy and to upgrade. There other small goodies around. Take a look at the docs.

You can install PyVISA or upgrade your previous version using pip:
 
$ pip install -U pyvisa

This release is only possible thanks to the contribution of a lot of people that contributed bug reports, testing and code. Thanks to everybody!

We are moving forward to make PyVISA even better. Submit your bug reports, comments and suggestions in the Issue Tracker. We will address them promptly.

Read the docs: http://pyvisa.readthedocs.org/
or fork the code: https:/https://github.com/hgrecco/pyvisa/

Gabriel Patiño: Mapas topograficos propios

Hace un tiempo había subido unos mapas topográficos que hice a otro blog, pero se cayó el servicio donde estaban, asi que los pongo de nuevo, esta vez en un server propio.

Los mapas son imagenes diseñadas para imprimir en tamaño A3. Pueden mandar a imprimirlos a una imprenta o imprimirlo en dos A4 y pegarlas.

Va la lista de mapas que tengo por el momento:



La verdad que estaba bueno hacer esto, deberia volver a hacer algunos de la zona (SMA).

Martín Gaitán: Deploy de Django con Circus, Chaussette y Nginx

Aunque hay un pequeño mito al respecto, poner en producción una aplicación web hecha en Python no es tan complejo. Esa facilidad se debe a la estandarización de la pasarela WSGI, que define cómo se comunica (o se debería comunicar) una "app" (sea hecha con un framework o no) con un servidor web.

Si bien Nginx, el servidor web que está desplazando a Apache como el más popular tiene un módulo que implementa el estándar WSGI de manera nativa, la arquitectura más típica es utilizarlo como proxy reverso, conectado a un servidor WSGI especializado como Gunicorn que interactua con la aplicación web (posiblemente a través de multiples instancias o workers). Como queremos que nuestra app funcione permanentemente, el proceso WSGI y otros que sean necesarios (por ejemplo Redis) se demonizan de manera que sepan restaurarse automáticamente si mueren y sea posible monitorearlos: para eso suele usarse supervisor.

https://raw.githubusercontent.com/mozilla-services/circus/dff6cf3a348fecc0b58bd08cae91b1508aed14c2/docs/source/classical-stack.png

La arquitectura de deployment más común para una aplicación web Python

Si bien esta arquitectura está bastamente probada, hay una opción mejor.

https://circus.readthedocs.org/en/0.11.1/_images/circus-medium.png

El circo y el soquete

Circus y Chaussette son proyectos desarrollados por Tarek Ziadé y el equipo de Mozilla Sevices.

Consejo

Tarek es un pythonista francés, core committer de Python y muchos proyectos relacionados. Recibió el reconocimiento de la PSF por sus aportes y es autor del gran libro Expert Python Programming

Una arquitectura de producción análoga a la descripta arriba, pero basada en Circus, se ve así:

https://raw.githubusercontent.com/mozilla-services/circus/dff6cf3a348fecc0b58bd08cae91b1508aed14c2/docs/source/circus-stack.png

Circus maneja procesos demonizados igual que Supervisor, pero además puede bindear sockets y manejarlos de la misma manera. Este desacople de la gestión de sockets del webserver WSGI permite muchas posibilidades, tanto en la gestión como en la escalabilidad de la arquitectura.

La capa WSGI en este esquema la aporta Chaussette, que tiene la particularidad que, en vez de abrir un socket nuevo, utiliza el que Circus abrió (y controla) para el worker. Además, aunque trae una implementación de WSGI built-in, puede usar muchos backends más eficientes o con características particulares como meinheld, gevent, gevent-socketio, entre muchos otros.

A diferencia de Supervisor que se basa en el protocolo XML-RPC para inspeccionar los procesos que controla, Circus utiliza un canal pub/sub basado en el mucho más moderno ZeroMQ (lo mismo que usa IPython Notebook) que permite un monitoreo realtime y una API de plugins mucho más simple. Otra diferencia, nada menor, es que Circus funciona con Python 2 o 3 (supervisor no es compatible con Python 3).

Y de yapa: Circus se puede usar como una biblioteca de muy alto nivel para la gestión no bloqueante de procesos. Se puede pensar con un wrapper de subprocess y/o multiprocess, que aporta información de monitoreo y estadísticas, control de flujo, una capa de señales (hooks) muy completa y más.

Desplegando Django

Para ejemplificar, voy utilizar un proyecto Django que estoy desarrollando (muy lentamente): nikolahub.

Circus se configura con un archivo con formato .ini. El mio, que bauticé circus.ini quedó así:

[circus]
check_delay = 5
endpoint = tcp://127.0.0.1:5555
pubsub_endpoint = tcp://127.0.0.1:5556
stats_endpoint = tcp://127.0.0.1:5557

[socket:nikolahub]
host = 127.0.0.1
port = 8080

[watcher:nikolahub]
cmd = /virtualenvs/nikolahub/bin/chaussette --fd $(circus.sockets.nikolahub) nikolahub.wsgi.application
use_sockets = True
numprocesses = 3

[env:nikolahub]
PYTHONPATH = /projects/nikolahub

La sección watcher indica lanza el comando a controlar, en este caso levantando 3 workers de la aplicación -django. Notar que como tengo instalado Chaussette dentro del virtualenv, uso el path absoluto al ejecutable. El fragmento --fd $(circus.sockets.nikolahub) se expande implícitamente asignando el pid que obtuvo el fork (el proceso hijo) de circus.

Si quisieramos usar otro servidor web, sólo hay que indicar cual con el parámetro --backend Por ejemplo:

cmd = /virtualenvs/nikolahub/bin/chaussette --backend gevent --fd $(circus.sockets.nikolahub) nikolahub.wsgi.application

Podemos probar si todo funciona:

(nikolahub)tin@morochita:$ circusd circus.ini
2014-06-12 04:36:16 circus[1141] [INFO] Starting master on pid 1141
2014-06-12 04:36:16 circus[1141] [INFO] sockets started
2014-06-12 04:36:16 circus[1141] [INFO] Arbiter now waiting for commands
2014-06-12 04:36:16 circus[1141] [INFO] nikolahub started
2014-06-12 04:36:16 circus[1141] [INFO] circusd-stats started
2014-06-12 04:36:17 circus[1150] [INFO] Starting the stats streamer
2014-06-12 04:36:17 [1149] [INFO] Application is <django.core.handlers.wsgi.WSGIHandler object at 0xa06f60c>
2014-06-12 04:36:17 [1149] [INFO] Serving on fd://5
2014-06-12 04:36:17 [1149] [INFO] Using <class chaussette.backend._wsgiref.ChaussetteServer at 0x9f2d6ec> as a backend
2014-06-12 04:36:17 [1148] [INFO] Application is <django.core.handlers.wsgi.WSGIHandler object at 0x939b60c>
2014-06-12 04:36:17 [1148] [INFO] Serving on fd://5
2014-06-12 04:36:17 [1148] [INFO] Using <class chaussette.backend._wsgiref.ChaussetteServer at 0x92596ec> as a backend

Tendremos la aplicación servida en el puerto 8080 de localhost. Demonizarlo es sólo un flag:

(nikolahub)tin@morochita:$ circud --daemon circus.ini

Para implementar nginx como proxy reverso armé un archivo nginx.conf:

server {
    listen 80;
    server_name nikolahub.nqnwebs.com;

    location /static/ {
            alias /projects/nikolahub/static/;
    }

    location /media/ {
        alias /projects/nikolahub/media/;
    }

    location / {
        proxy_pass http://localhost:8080/;
        proxy_pass_header Server;
        proxy_set_header Host $host;
        proxy_redirect off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_connect_timeout 600;
        proxy_send_timeout 600;
        proxy_read_timeout 600;
    }
}

Luego agregamos el sitio:

$ ln -s /etc/nginx/sites-available/nikolahub nginx.conf
$ ln -s /etc/nginx/sites-enable/nikolahub nginx.conf
$ sudo service nginx restart
http://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Reverse_proxy_h2g2bob.svg/400px-Reverse_proxy_h2g2bob.svg.png

Esto pone a nginx como "frontend" de la aplicación, sirviendo los directorios con contenido estático y pasando el resto de las peticiones al puerto 8080 que administra Circus.

Ya tenemos nuestro sitio en producción.

El dueño del circo y los monitos

De ahora en más, podremos usar las herramientas que provee Circus.

circusctl es el dueño del circo. Puede parar, reiniciar, cambiar la cantidad de workers, abrir una consola ipython para interactuar o inspeccionar y mucho mas. Se puede usar como subcomandos (circusctl <subcmd> <watcher>) o usar la consola interactiva.

Por ejemplo, si quisiera ver cuantos procesos workers tengo y agregar uno más, podría hacer así:

(nikolahub)tin@morochita:$ circusctl numprocesses nikolahub
3
(nikolahub)tin@morochita:$ circusctl incr nikolahub
ok
(nikolahub)tin@morochita:$ circusctl numprocesses nikolahub
4

Lo mismo y más se puede hacer desde una consola IPython.

(nikolahub)tin@morochita:$ circusctl ipython
Python 2.7.4 (default, Apr 19 2013, 18:32:33)
Type "copyright", "credits" or "license" for more information.

IPython 2.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: arbiter.numprocesses()
Out[1]: 4

circus-top es un monitor realtime, estilo top. Escucha las estadísticas que produce circusd-stats.

(nikolahub)tin@morochita:$ circus-top
/images/circus-top.png

circus-top en acción. Muestra los procesos watcher y los recursos que cosumen.

Todo esto puede verse y manejarse cómodamente a través de circus-web, un dashboard web que permite monitorear y administrar circus, con gráficos realtime y muy fácil de usar.

https://circus.readthedocs.org/en/0.11.1/_images/web-watchers.png

Desde las últimas versiones, circus-web se refactorizó para basarla en Tornado (originalmente usaba bottle) y hay que instalarlo aparte.

$ pip install circus-web

Conclusiones

Circus es una herramienta que simplifica el stack de deployment de una aplicación web WSGI. La API de alto nivel, una arquitectura mucho más moderna y simple y el aval de ser desarrollada (y usada exhaustivamente) por Mozilla, son avales poderosos para probarla.

Como escribió el Marcos Luc "la función ya debería empezar (...) Bueno nena, buena suerte, cada vez la red te teme más..."

Marcos Dione: optimizing-mapnik-rendering-with-metatiles

One of the most cited ways to accelerate rendering maps with Mapnik is metatiling. This technique is simple: instead of rendering each individual 256x256 PNG file that normally backs a slippy map, render NxN times that (normally with N=8) and split afterwards. mapnik-stylesheet's generate_tiles.py was not capable of doing it, but I hacked a little bit and got it to do it, so after integrating openstreetmap-carto v2.14.1 and fixing some local bugs, I gave it another spin.

This time I also imported the whole Europe data, and expanded the terrain to cover anything above 60°N, taking the height files form De Ferranti's viewfinderpanoramas. This meant that whatever rendering times I got this time, they are not completely comparable to what I obtained before because it's simply handling way more data, specially relative to the terrain data.

So, first, a short revisit to importing. Here's the summary:

mdione@diablo:/var/lib/data/mdione/src/projects/osm/data/osm$ osm2pgsql \
    --create --drop --database gis --slim --flat-nodes /home/mdione/src/projects/osm/nodes.cache \
    --cache 2048 --number-processes 4 --unlogged europe-latest.osm.pbf
osm2pgsql SVN version 0.82.0 (64bit id space)

Node-cache: cache=2048MB, maxblocks=262145*8192, allocation method=11
Mid: loading persistent node cache from /home/mdione/src/projects/osm/nodes.cache
Allocated space for persistent node cache file
Maximum node in persistent node cache: 0
Mid: pgsql, scale=100 cache=2048

Reading in file: europe-latest.osm.pbf
Processing: Node(1225816k 799.6k/s) Way(151242k 17.71k/s) Relation(1872470 338.91/s)  parse time: 15596s [4h20m]
Node stats: total(1225816996), max(2884224404) in 1533s [0h26m]
Way stats: total(151242682), max(284701738) in 8538s [2h22m]
Relation stats: total(1872475), max(3776697) in 5525s [1h32m]

Going over pending ways...
Maximum node in persistent node cache: 2884632575
        110980610 ways are pending
Using 4 helper-processes [but only 1 is used, the others failed, dunno why]
Mid: loading persistent node cache from /home/mdione/src/projects/osm/nodes.cache
Maximum node in persistent node cache: 2884632575
Process 0 finished processing 110980610 ways in 34202 sec [9h30m]
110980610 Pending ways took 34202s at a rate of 3244.86/s

Going over pending relations...
Maximum node in persistent node cache: 2884632575
        0 relations are pending
Using 4 helper-processes
Process 2 finished processing 0 relations in 2 sec
Process 3 finished processing 0 relations in 2 sec
Process 0 finished processing 0 relations in 3 sec
Process 1 finished processing 0 relations in 3 sec
0 Pending relations took 3s at a rate of 0.00/s

node cache: stored: 203128264(16.57%), storage efficiency: 75.67% (dense blocks: 138131, sparse nodes: 63494657), hit rate: 17.62%

Stopped table: planet_osm_rels in 1s
Stopped table: planet_osm_nodes in 1s
Stopped table: planet_osm_ways in 3s

All indexes on  planet_osm_roads created  in 4679s [1h18m]
All indexes on  planet_osm_point created  in 6093s [1h41m]
All indexes on  planet_osm_line created  in 9703s [2h42m]
All indexes on  planet_osm_polygon created  in 12735s [3h32m]

Osm2pgsql took 62780s overall [17h26m]

Only ~3h30m more than importing a part of Europe, I think it's manageable. I still don't know what to make out of the cache/storage efficiency line, but it's clearly related to the small size of the cache.

About the rendering, I only rendered up to zoom level 11, as I discussed before, but that limit was mostly due to the fact that going all the way down to 14 took too much time. But after viewing how much less rendering took this time, I most probably revert that change. Take a look at the graph:

This graph shows the average and total time for rendering up to zoom level 11 with single tiling (actually it's the old data I showed previously) and metatiling 8x8. The total time uses the logarithmic scale on the right. The average in the second case is the division between the total time and the amount of final individual tiles, which are produced by cutting the metatile in as many single tiles as the minimum between 2^z and 64, so zoom levels 0-2 don't produce spurious images. The times are amazing. Notice that from time to time the total time took more in the metatile version than in the single tile version, but the are two factors that impact here. One is that due to metatiling, I'm rendering tiles that with single tiling I wouldn't due to the fact that now I can only cut in increments of 8 instead of one. Two, now I'm rendering the whole Europe instead of just a big part of it. This makes the amount of tiles produced to be between 6 to 10 times more.

Still, take a look at the average rendering times for metatiling, and specially at the last column in the table, which is not graphed: it's the proportion between the average tile rendering time, meta to single. It's mostly below the 10%, except for a couple of zoom levels that either produce few tiles (ZL 2, 16 tiles) or stay under 15% (ZL 4). This is ridiculously low, which means fast. I will first finish rendering my zone down to ZL 18 and then render the rest down to ZL 14 to have more complete, comparable graphs.


openstreetmap gis

Gonzalo Martinez: Como configurar un RAID0 en AWS

La problemática empieza por mejorar la performance de IO en el server de Base de datos no es un problema actualmente pero tampoco queremos que lo sea

Primero que todo configuramos para nuestro renovado server una de las instancias con mejor performance de memoria según Amazon las instancias R3 [0]

Las instancias R3 están optimizadas para aplicaciones con un uso intenso de la memoria y ofrecen el coste más bajo por GiB de RAM entre los tipos de instancias de Amazon EC2.
Estos servidores tienen un Almacenamiento de Instancia [1] respaldado en Disco SSD pero estos son temporales por consiguiente si se Para y luego se Inicia nuevamente todo lo que habia en ese disco simplemente desaparece. Por eso decidimos seguir respaldando la DB en un EBS [2] y para mejorar un poco más la performance decidimos poner dos EBSs en RAID0

Entonces empecemos contando que es un EBS.  Amazon Dice:
Amazon Elastic Block Store (Amazon EBS) proporciona volúmenes de almacenamiento de nivel de bloque persistentes y diseñados para utilizarlos con las instancias de Amazon EC2 en la nube de AWS.
En resumen son digamos unos discos rigidos a pedido que van y vienen por la nube y que se pueden adjuntar a cualquier tipo de Instancia de EC2 y que son de almacenamiento permanente.

Ahora vamos con un poco sobre algo que siempre me costó entender ¿que es un RAID? [3]. Básicamente es un conjunto de discos independientes que se usan en conjunto generalmente para dar mayor redudancia a las implementaciones de Almacenamiento. Releyendo para escribir este Post me acuerdo cual es el motivo de que nunca lo terminara de entender y el motivo es que tiene muchos niveles y algunos resultan realmente complejos. Nivel se le llama a cada tipo de configuración que existente. Obviamente nosotros vamos a explicar algunas de las más caracteristicas.

El RAID0 (Data Striping) es la útilización de un conjunto de discos fisicos como si fueran uno solo. En verdad lo que se hace es distribuir equitativamente los datos en uno y otro disco por lo que esta configuración no le agrega redundancia al almacenamiento pero si nos ayudará en la mejora del rendimiento de lectura y escritura. En cuanto al tamaño este es limitado por el más pequeño de los discos si se tiene 2 discos de 100 GB el tamaño se duplicaría entonces pasariamos a tener un disco de 200GB ya que cada uno aporta 100GB a la distribución, pero si se tiene un disco de 300GB y uno de 100GB la distribución se hace sobre el más pequeño y cada uno aporta 100GB por lo que se obtiene un disco de 200GB perdiendo 200GB del disco más grande.


El RAID1 (Mirroring) crea una copia exacta de cada dato que va a un disco en uno o más. Esto es bueno para entornos donde es más importante la velocidad de lectura que la capacidad ya que (en su implementación más básica) se usarían dos discos de 100 GB cada uno y el máximo de almacenamiento es tanto como el más pequeño de los discos. Además tener un disco copiado exactamente le agrega redundancia al conjunto lo que es muy útil en ambientes de alta disponibilidad ya que si un disco falla el otro puede tomar su lugar sin demasiado problema.



Hay más? Si mucho más y es un tema largo y se puede volver bastante complejo. Leer en Wikipedia te va a dar una buena mirada [3].

Ahora vamos a lo nuestro como configurar un RAID0 para una instancia EC2 sobre Volúmenes EBS primero adjuntamos dos volumenes EBS a nuestra Instancia esto se puede hacer durante el lanzamiento de la Instancia en la Sección de "Add Storage" o posteriormente desde el Panel de Volúmenes de EC2 creando un Volumen y luego adjuntandolo a la Instancia. En este caso vamos a usar dos volúmenes de 30 GB.

Nosotros somos gente grosa así que usamos el tipo de servidores que sostiene al 95% de Internet servidores Linux entonces vamos a usar el comando "lsblk" que según man "lista los dispositivos de bloque"

vamos a  ver algo como esto

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 8G  0 disk
└─sda1   8:7    0  8G  0 part /
sdb      8:0    0 30G  0 disk
sdc      8:0    0 30G  0 disk

El disco sda de 8G es el que se monta como root por default en todas las intancias EC2 y el sdb y sdc son los que se van a usar para hacer el RAID0.

$ sudo mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=numero_de_volumenes dispositivo_1 dispositivo_2

Ejemplo:
$ sudo mdadm --create --verbose /dev/md0 --level=stripe --raid-devices=2 sdb sdc

Esto básicamente lo que hace es crear un nuevo dispositivo llamado md0 con los bloques sdb y sdc usando el nivel "stripe" que también podría ser "0" o "raid0"
-l, --level=
              Set  RAID  level.  When used with --create, options are: linear,
              raid0, 0, stripe, raid1, 1, mirror, raid4, 4, raid5,  5,  raid6,
              6, raid10, 10, multipath, mp, faulty, container.  Obviously some
              of these are synonymous.
Luego crearemos un sistema de archivos y un punto de montaje para montar el dispositivo.

$ sudo mkfs.ext4 /dev/md0
$ sudo mkdir /mnt/md0
$ sudo mount -t ext4 /dev/md0 /mnt/md0

Una vez montado ya podremos usarlo pero tendremos algunos problemas (no aparecerá el punto de montaje) si paramos y prendemos la instancia para eso se debe agregar la siguiente linea al archivo /etc/fstab. 

/dev/md0 /mnt/md0 ext4 defaults 0 0

Como dice el man de fstab 

The file fstab contains descriptive information about the various file systems. fstab is only read by programs, and not written; it is  the duty  of  the system administrator to properly create and maintain this file.

Y listo ya tenemos nuestro raid0 configurado en nuestra instancia ec2 o en verdad en cualquier linux.

Pueden probar como mejora el performance de IO con diferentes herramientas hdparm [4] o bonnie++ [5]

Más data de como hacer esta configuración y sus ventajas y desventajas en el siguiente link [6]

[0] http://aws.amazon.com/es/ec2/instance-types/
[1] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html
[2] http://aws.amazon.com/es/ebs/
[3] http://es.wikipedia.org/wiki/RAID
[4] http://es.wikipedia.org/wiki/Hdparm
[5] http://en.wikipedia.org/wiki/Bonnie++
[6] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/raid-config.html

Martín Gaitán: Los números del Mundial

Un amigo me dijo ayer que la fiebre del mundial ya no le afecta como antes. Cuando era chico, decía, la ansiedad por la navidad, los reyes y sobre todo, los mundiales, no lo dejaba dormir.

A mí Papá Noel y los Reyes me tenían bastante sin cuidado (que sólo ocasionalmente venían a mi casa, sobre todo cuando le hacíamos caso a mi hermano mayor, que nos instruía que a Baltazar había que dejarle una cerveza negra bien fría, para pasar el calor de las madrugadas de enero). Pero me sigue pasando lo mismo con el mundial de fútbol: fiebre. No hay acontecimiento que ansíe más que ese.

En el 86 yo ya caminaba solito, pero mis recuerdos mundialistas arrancan recién en el '90 con el gol de Camerún a Pumpido (que todavia tenía todos los dedos, aunque no se notaba). Tan poco futbolera era mi casa pero tanto yo, que tengo la imágen nítida: cuando fue el gol del Canni a Brasil yo estaba en la rotiseria de de Don Duca, a 3 cuadras de mi casa, esperando que me entregaran un pollo asado que me habían mandado a comprar. Al escuchar el griterío del barrio alguien me dejó pasar, y pude ver el gol en la repetición en un televisorcito diminuto que tenían sobre una mesada mugrienta. Volví llorando, emocionado, con un olor a pollo que no olvidaré más.

Y ahora, por fin, está llegando ese olorcito de nuevo. En el interín me empapo de nombres y jugadores, estadísticas e historias de color. La fiebre no me deja dormir.

En esa procrastinación encontré el artículo de Wikipedia con la lista de todas las selecciones y, como una ducha de agua fria, intenté sacarle alguna información.

En tu cabeza hay un (scrapper de) gol

Usé la misma técnica que en el análisis de las elecciones de Córdoba: la extensión de IPython para usar el orm de Django que hice y PyQuery para scrappear.

In [1]:
%load_ext django_orm_magic

Los modelos me quedaron así

In [2]:
%%django_orm

from django.db import models

class Country(models.Model):
    name = models.CharField(max_length="100")

    def __unicode__(self):
        return self.name

class City(models.Model):
    country = models.ForeignKey('Country')
    name = models.CharField(max_length="100")

    def __unicode__(self):
        return "{0}, {1}".format(self.name, self.country)

class Team(models.Model):
    country = models.ForeignKey('Country')
    group = models.CharField(max_length="100")

    def __unicode__(self):
        return unicode(self.country)

class Club(models.Model):
    name = models.CharField(max_length="100")
    country = models.ForeignKey('Country')

    def __unicode__(self):
        return self.name

class Player(models.Model):

    full_name = models.CharField(max_length=100)
    date_of_birth = models.DateField(null=True, blank=True)
    team = models.ForeignKey('Team')
    url = models.URLField(max_length=200, null=True, blank=True)

    place_of_birth = models.ForeignKey('City', null=True, blank=True)
    height = models.FloatField(null=True, blank=True)
    position = models.CharField(max_length=2)
    current_club = models.ForeignKey('Club')
    last_season_apps = models.IntegerField(null=True, blank=True)
    last_season_goals = models.IntegerField(null=True, blank=True)
    national_team_apps = models.IntegerField(null=True, blank=True)
    national_team_goals = models.IntegerField(null=True, blank=True)

    def __unicode__(self):
        return self.full_name

Lo que necesité es parsear cada una de las tablas asociadas a una selección. Por ejemplo, la de Brasil

In [3]:
from pyquery import PyQuery
from IPython.display import HTML, Image
pq = PyQuery('http://en.wikipedia.org/wiki/2014_FIFA_World_Cup_squads')
pq.make_links_absolute()
Out[3]:
[<html.client-nojs>]
In [4]:
brazil = pq('table:first').html()
HTML(brazil)
Out[4]:
No. Pos. Player DoB/Age Caps Club
1 1GK Jefferson (1983-01-02)2 January 1983 (aged 31) 9 Brazil Botafogo
2 2DF Dani Alves (1983-05-06)6 May 1983 (aged 31) 74 Spain Barcelona
3 2DF Thiago Silva (c) (1984-09-22)22 September 1984 (aged 29) 45 France Paris Saint-Germain
4 2DF David Luiz (1987-04-22)22 April 1987 (aged 27) 35 England Chelsea
5 3MF Fernandinho (1985-05-04)4 May 1985 (aged 29) 6 England Manchester City
6 2DF Marcelo (1988-05-12)12 May 1988 (aged 26) 30 Spain Real Madrid
7 4FW Hulk (1986-07-25)25 July 1986 (aged 27) 34 Russia Zenit Saint Petersburg
8 3MF Paulinho (1988-07-25)25 July 1988 (aged 25) 25 England Tottenham Hotspur
9 4FW Fred (1983-10-03)3 October 1983 (aged 30) 33 Brazil Fluminense
10 4FW Neymar (1992-02-05)5 February 1992 (aged 22) 48 Spain Barcelona
11 3MF Oscar (1991-09-09)9 September 1991 (aged 22) 30 England Chelsea
12 1GK Júlio César (1979-09-03)3 September 1979 (aged 34) 79 Canada Toronto
13 2DF Dante (1983-10-18)18 October 1983 (aged 30) 12 Germany Bayern Munich
14 2DF Maxwell (1981-08-27)27 August 1981 (aged 32) 8 France Paris Saint-Germain
15 2DF Henrique (1986-10-14)14 October 1986 (aged 27) 5 Italy Napoli
16 3MF Ramires (1987-03-24)24 March 1987 (aged 27) 42 England Chelsea
17 3MF Luiz Gustavo (1987-07-23)23 July 1987 (aged 26) 18 Germany VfL Wolfsburg
18 3MF Hernanes (1985-05-29)29 May 1985 (aged 29) 24 Italy Internazionale
19 3MF Willian (1988-08-09)9 August 1988 (aged 25) 6 England Chelsea
20 3MF Bernard (1992-09-08)8 September 1992 (aged 21) 10 Ukraine Shakhtar Donetsk
21 4FW (1987-03-20)20 March 1987 (aged 27) 16 Brazil Atlético Mineiro
22 1GK Victor (1983-01-21)21 January 1983 (aged 31) 6 Brazil Atlético Mineiro
23 2DF Maicon (1981-07-26)26 July 1981 (aged 32) 71 Italy Roma

Entonces hice esta funcioncita para guardar todos esos datos en mis modelos

In [5]:
def parse_squad(squad, group):
    country, _ = Country.objects.get_or_create(name=pq(squad).prev().prev().prev().text())
    print "Parsing", country
    team, _ = Team.objects.get_or_create(country=country, group=group)

    for row in pq('tr', squad)[2:]:

        position = pq('td:eq(1)', row).text()[-2:]
        full_name = pq('td:eq(2)', row).text()
        print full_name
        url = pq('td:eq(2) a', row).attr('href')

        club_country, _ = Country.objects.get_or_create(name=pq('td:eq(5) span.flagicon a', pq('tr', squad)[6]).attr('title'))
        club, _ = Club.objects.get_or_create(name=pq('td:eq(5)', pq('tr', squad)[6]).text(), country=club_country)
        print club
        Player.objects.create(full_name=full_name, url=url, position=position, team=team, current_club=club)


In [6]:
for i, squad in enumerate(pq('table:not(.sortable)')[:32]):
    parse_squad(squad, "ABCDEFGH"[i / 4])
Parsing Brazil
Parsing Cameroon
Parsing Croatia
Parsing Mexico
Parsing Australia
Parsing Chile
Parsing Netherlands
Parsing Spain
Parsing Colombia
Parsing Côte d'Ivoire
Parsing Greece
Parsing Japan
Parsing Costa Rica
Parsing England
Parsing Italy
Parsing Uruguay
Parsing Ecuador
Parsing France
Parsing Honduras
Parsing Switzerland
Parsing Argentina
Parsing Bosnia and Herzegovina
Parsing Iran
Parsing Nigeria
Parsing Germany
Parsing Ghana
Parsing Portugal
Parsing United States
Parsing Algeria
Parsing Belgium
Parsing Russia
Parsing South Korea
In [7]:
Player.objects.all()
Out[7]:
[<Player: Júlio César>, <Player: Jefferson>, <Player: Victor>, <Player: Daniel Alves>, <Player: Maicon>, <Player: Thiago Silva ( captain )>, <Player: David Luiz>, <Player: Marcelo>, <Player: Dante>, <Player: Maxwell>, <Player: Henrique>, <Player: Ramires>, <Player: Oscar>, <Player: Paulinho>, <Player: Hernanes>, <Player: Luiz Gustavo>, <Player: Bernard>, <Player: Fernandinho>, <Player: Willian>, <Player: Neymar>, '...(remaining elements truncated)...']

Por suerte Wikipedia reune a muchos enfermitos como yo, y cada jugador tiene su propio artículo con una ficha más o menos estandarizada que también es fácil obtener. Por ejemplo, la del mejor jugador del planeta:

In [8]:
messi = PyQuery(Player.objects.get(full_name__contains='Messi').url)
messi.make_links_absolute()
HTML(messi('table.infobox').html())
Out[8]:
Lionel Messi Lionel Messi Player of the Year 2011.jpg
Messi playing for Barcelona at the 2011 FIFA Club World Cup Personal information Full name Lionel Andrés Messi[1] Date of birth (1987-06-24) 24 June 1987 (age 26)[1] Place of birth Rosario, Argentina[1] Height 1.69 m (5 ft 7 in)[1] Playing position Forward Club information Current club Barcelona Number 10 Youth career 1995–2000 Newell's Old Boys 2000–2003 Barcelona Senior career* Years Team Apps (Gls) 2003–2004 Barcelona C 10 (5) 2004–2005 Barcelona B 22 (6) 2004– Barcelona 276 (243) National team 2004–2005 Argentina U20 18 (14) 2007–2008 Argentina U23 5 (2) 2005– Argentina 83 (37) * Senior club appearances and goals counted for the domestic league only and correct as of 01:38, 17 May 2014 (UTC).

† Appearances (Goals).

‡ National team caps and goals correct as of 11 September 2013
In [9]:
messi('table.infobox').text()
Out[9]:
u"Lionel Messi Messi playing for Barcelona at the 2011 FIFA Club World Cup Personal information Full name Lionel Andr\xe9s Messi [ 1 ] Date of birth ( 1987-06-24 ) 24 June 1987 (age\xa026) [ 1 ] Place of birth Rosario , Argentina [ 1 ] Height 1.69\xa0m (5\xa0ft 7\xa0in) [ 1 ] Playing position Forward Club information Current club Barcelona Number 10 Youth career 1995\u20132000 Newell's Old Boys 2000\u20132003 Barcelona Senior career* Years Team Apps \u2020 (Gls) \u2020 2003\u20132004 Barcelona C 10 (5) 2004\u20132005 Barcelona B 22 (6) 2004\u2013 Barcelona 276 (243) National team \u2021 2004\u20132005 Argentina U20 18 (14) 2007\u20132008 Argentina U23 5 (2) 2005\u2013 Argentina 83 (37) Honours Competitor for Argentina Men's Football Olympic Games Gold 2008 Beijing Olympic Team Copa Am\xe9rica Runner-up 2007 Venezuela Team FIFA U-20 World Cup Winner 2005 Netherlands U-20 Team U-20 South American Championship Third 2005 Colombia U-20 Team * Senior club appearances and goals counted for the domestic league only and correct as of 01:38, 17 May 2014 (UTC). \u2020 Appearances (Goals). \u2021 National team caps and goals correct as of 11 September 2013 Lionel Messi Born Lionel Andr\xe9s Messi ( 1987-06-24 ) 24 June 1987 (age\xa026) Rosario , Santa Fe , Argentina Residence Barcelona , Catalonia , Spain Nationality Argentinian Ethnicity Argentinian & Italian Occupation Association Footballer Salary \u20ac 16\xa0million Religion Roman Catholic Spouse(s) Antonella Roccuzzo Children Thiago Messi Parents Jorge Horacio Messi (father) Celia Maria Cuccittini (mother) Relatives Maxi Biancucchi (cousin) Emanuel Biancucchi (cousin) Website Official Website"
In [10]:
import re
fecha = re.findall(r'\d{4}\-\d{2}\-\d{2}', messi('table.infobox').text())[0]
fecha
Out[10]:
u'1987-06-24'

Luego de un poquito de experimentación con expresiones regulares (no hagan esto en su casa, amigos), llegué a otra funcioncita para extraer esas fichas y completar datos de los jugadores

In [11]:
from datetime import datetime

def fill_player(player):
    # print 'Retriving data for %s (%d)' % (player, player.id)
    pq = PyQuery(player.url)
    pq.make_links_absolute()
    info = pq('table.infobox').text()
    player.date_of_birth = datetime.strptime(re.findall(r'\d{4}\-\d{2}\-\d{2}', info)[0], "%Y-%m-%d").date()
    try:
        player.height = re.findall(r'(\d{1}\.\d{2})', info)[0]
    except IndexError:
        player.height = float(re.findall(r'Height (\d{3})', info)[0])/100

    try:
        player.last_season_apps, player.last_season_goals = re.findall(r'(\d+) \((\d+)\) National team', info)[0]
    except:
        pass
    try:
        player.national_team_apps, player.national_team_goals =  re.findall(r'(\d+) \((\d+)\)', info)[-1]
    except:
        pass
    player.save()
In [12]:
for player in Player.objects.exclude(url__contains='edit'):
    fill_player(player)

Y ahora sí, podemos escarbar algunos números

Cuestión de años

¿Quién es el jugador más viejo de Brasil 2014? ¿Quién el más jóven?

In [13]:
from datetime import date
viejo = Player.objects.filter(date_of_birth__isnull=False).order_by('date_of_birth')[0]
joven = Player.objects.filter(date_of_birth__isnull=False).order_by('-date_of_birth')[0]
In [14]:
today = date.today()
age = lambda player: (today - player.date_of_birth).days / 365.25
print((viejo.full_name, viejo.team, viejo.date_of_birth, age(viejo)))
print((joven.full_name, joven.team, joven.date_of_birth, age(joven)))
(u'Faryd Mondrag\xf3n', <Team: Colombia>, datetime.date(1971, 6, 21), 42.95687885010267)
(u'Fabrice Olinga', <Team: Cameroon>, datetime.date(1996, 5, 12), 18.064339493497606)

El veterano arquero colombiano Faryd Mondragón, con casi 43 pirulos, es el jugador más viejo del mundial. Por su parte, Olinga, delantero de Camerún, es el más jóven con 18 años recién cumplidos.

También podemos hacer un grafico de las edades de las selecciones. La edad promedio, y la desviación entre el más pibe y el más jovato de cada equipo

In [15]:
%matplotlib inline

import numpy as np
import matplotlib.pyplot as plt
plt.xkcd()

def team_age(team):
    players = team.player_set.filter(date_of_birth__isnull=False)
    media = sum(map(age, players)) / players.count()
    younger = age(players.order_by('-date_of_birth')[0])
    older = age(players.order_by('date_of_birth')[0])
    return np.array([media, younger, older])


teams = map(unicode, Team.objects.all())
y_pos = np.arange(len(teams))
data = np.vstack(map(team_age, Team.objects.all())).T
plt.figure(figsize=(5,10))
plt.xlim([16, 45])
plt.barh(y_pos, data[0], xerr=[data[0] - data[1], data[2] - data[0]], align='center', alpha=0.3)
plt.yticks(y_pos, teams)
plt.show()

Otra para la prensa cholula: ¿Qué judadores cumplen años durante el mundial?

In [16]:
from django.db.models import Q

mundial = Q()
for i in xrange(12,31):
    mundial = mundial | Q(date_of_birth__day=i, date_of_birth__month=6)
for i in xrange(1,14):
    mundial = mundial | Q(date_of_birth__day=i, date_of_birth__month=7)

for p in Player.objects.filter(mundial):
    print "%s de %s cumple el %d/%d" % (p, p.team, p.date_of_birth.day, p.date_of_birth.month)
Aurélien Chedjou de Cameroon cumple el 20/6
Jean-Armel Kana-Biyik de Cameroon cumple el 3/7
Dejan Lovren de Croatia cumple el 5/7
Guillermo Ochoa de Mexico cumple el 13/7
Miguel Layún de Mexico cumple el 25/6
Eugene Galeković de Australia cumple el 12/6
Matthew Špiranović de Australia cumple el 27/6
Jason Davidson de Australia cumple el 29/6
James Troisi de Australia cumple el 3/7
Ben Halloran de Australia cumple el 14/6
Cristopher Toselli de Chile cumple el 15/6
Mauricio Isla de Chile cumple el 12/6
José Rojas de Chile cumple el 23/6
Jordy Clasie de Netherlands cumple el 27/6
Alberto Moreno de Spain cumple el 5/7
Faryd Mondragón de Colombia cumple el 21/6
Fredy Guarín de Colombia cumple el 30/6
James Rodríguez de Colombia cumple el 12/7
Sayouba Mandé de Côte d'Ivoire cumple el 15/6
Cheick Tioté de Côte d'Ivoire cumple el 21/6
Orestis Karnezis de Greece cumple el 11/7
José Holebas de Greece cumple el 27/6
Kostas Manolas de Greece cumple el 14/6
Kostas Katsouranis de Greece cumple el 21/6
Andreas Samaris de Greece cumple el 13/6
Shusaku Nishikawa de Japan cumple el 18/6
Keisuke Honda de Japan cumple el 13/6
Heiner Mora de Costa Rica cumple el 20/6
Joel Campbell de Costa Rica cumple el 26/6
Frank Lampard de England cumple el 20/6
Jordan Henderson de England cumple el 17/6
Luke Shaw de England cumple el 12/7
Alberto Aquilani de Italy cumple el 7/7
Antonio Cassano de Italy cumple el 12/7
Fernando Muslera de Uruguay cumple el 16/6
Juan Carlos Paredes de Ecuador cumple el 8/7
João Rojas de Ecuador cumple el 14/6
Gökhan Inler ( captain ) de Switzerland cumple el 27/6
Hugo Campagnaro de Argentina cumple el 27/6
Éver Banega de Argentina cumple el 29/6
José Ernesto Sosa de Argentina cumple el 19/6
Lionel Messi ( Captain ) de Argentina cumple el 24/6
Asmir Begović de Bosnia and Herzegovina cumple el 20/6
Sead Kolašinac de Bosnia and Herzegovina cumple el 20/6
Srđan Stanić de Bosnia and Herzegovina cumple el 6/7
Ghasem Haddadifar de Iran cumple el 12/7
Ashkan Dejagah de Iran cumple el 5/7
Harrison Afful de Ghana cumple el 24/6
Jonathan Mensah de Ghana cumple el 13/7
Nick Rimando de United States cumple el 17/6
Geoff Cameron de United States cumple el 11/7
DeAndre Yedlin de United States cumple el 9/7
Islam Slimani de Algeria cumple el 18/6
Koen Casteels de Belgium cumple el 25/6
Kevin De Bruyne de Belgium cumple el 28/6
Vasili Berezutski de Russia cumple el 20/6
Roman Shirokov de Russia cumple el 6/7
Alan Dzagoev de Russia cumple el 17/6
Kwak Tae-Hwi de South Korea cumple el 8/7
Hwang Seok-Ho de South Korea cumple el 27/6
Son Heung-Min de South Korea cumple el 8/7
Park Chu-Young de South Korea cumple el 10/7
Lee Chung-Yong de South Korea cumple el 2/7
Kim Jin-Su de South Korea cumple el 13/6
Lungos y petisos
In [17]:
alto = Player.objects.filter(height__isnull=False).order_by('-height')[0]
petiso = Player.objects.filter(height__isnull=False).order_by('height')[0]
print "%s de %s es el jugador más alto con %.2f m" % (alto, alto.team, alto.height)
print "%s de %s es el jugador más  con %.2f m" % (petiso, petiso.team, petiso.height)
Lacina Traoré de Côte d'Ivoire es el jugador más alto con 2.03 m
Edgar Salli de Cameroon es el jugador más  con 1.63 m
In [18]:
def team_height(team):
    players = team.player_set.filter(height__isnull=False)
    media = sum([p.height for p in players]) / players.count()
    petiso = players.order_by('height')[0].height
    alto = players.order_by('-height')[0].height
    return np.array([media, petiso, alto])

data = np.vstack(map(team_height, Team.objects.all())).T
plt.figure(figsize=(5,10))
plt.barh(y_pos, data[0], xerr=[data[0] - data[1], data[2] - data[0]], align='center', alpha=0.3)
plt.xlim([1.55, 2.1])
plt.title(u'Altura promedio, maximo y minimo de las selecciones')
plt.yticks(y_pos, teams)
plt.show()
Cuestión de experiencia
In [19]:
def team_apps(team):
    players = team.player_set.filter(national_team_apps__isnull=False)
    media = sum([p.national_team_apps for p in players]) / players.count()
    nuevito = players.order_by('national_team_apps')[0].national_team_apps
    experimentado = players.order_by('-national_team_apps')[0].national_team_apps
    return np.array([media, nuevito, experimentado])

data = np.vstack(map(team_apps, Team.objects.all())).T
plt.figure(figsize=(5,10))
plt.barh(y_pos, data[0], xerr=[data[0] - data[1], data[2] - data[0]], align='center', alpha=0.3)
plt.title(u'Partidos jugados con la seleccion')
plt.yticks(y_pos, teams)
plt.show()
La temible delantera
In [20]:
def team_ataque(team):
    players = team.player_set.filter(position='FW', national_team_goals__isnull=False)
    media = sum([p.national_team_goals for p in players]) / players.count()
    funes_mori = players.order_by('national_team_goals')[0]
    messi = players.order_by('-national_team_goals')[0]
    return (np.array([media, funes_mori.national_team_goals, messi.national_team_goals]), funes_mori, messi)

rows = []
for team in Team.objects.all():
    t, funes_mori, messi = team_ataque(team)
    rows.append(t)
    print "%s. Máximo goleador: %s (%d). Más perro: %s (%d)" % (team, messi, messi.national_team_goals, funes_mori,
                                                                funes_mori.national_team_goals)

data = np.vstack(rows).T
plt.figure(figsize=(5,10))
plt.barh(y_pos, data[0], xerr=[data[0] - data[1], data[2] - data[0]], align='center', alpha=0.3)
plt.title(u'Goles de delanteros en la selección')
plt.yticks(y_pos, teams)
plt.show()
Brazil. Máximo goleador: Neymar (30). Más perro: Jô (5)
Cameroon. Máximo goleador: Samuel Eto'o (55). Más perro: Fabrice Olinga (1)
Croatia. Máximo goleador: Eduardo (29). Más perro: Ante Rebić (1)
Mexico. Máximo goleador: Javier Hernández (35). Más perro: Raúl Jiménez (4)
Australia. Máximo goleador: Tim Cahill (31). Más perro: Mathew Leckie (1)
Chile. Máximo goleador: Alexis Sánchez (22). Más perro: Fabián Orellana (2)
Netherlands. Máximo goleador: Robin van Persie (42). Más perro: Jeremain Lens (7)
Spain. Máximo goleador: Fernando Torres (36). Más perro: Diego Costa (0)
Colombia. Máximo goleador: Radamel Falcao (20). Más perro: Víctor Ibarbo (1)
Côte d'Ivoire. Máximo goleador: Didier Drogba (63). Más perro: Giovanni Sio (0)
Greece. Máximo goleador: Theofanis Gekas (24). Más perro: Giorgos Samaras (8)
Japan. Máximo goleador: Shinji Okazaki (38). Más perro: Manabu Saitō (1)
Costa Rica. Máximo goleador: Álvaro Saborío (32). Más perro: Jairo Arrieta (4)
England. Máximo goleador: Wayne Rooney (38). Más perro: Daniel Sturridge (2)
Italy. Máximo goleador: Mario Balotelli (12). Más perro: Alessio Cerci (0)
Uruguay. Máximo goleador: Luis Suárez (38). Más perro: Christian Stuani (2)
Ecuador. Máximo goleador: Felipe Caicedo (15). Más perro: Armando Wila (0)
France. Máximo goleador: Karim Benzema (19). Más perro: Loïc Rémy (4)
Honduras. Máximo goleador: Carlo Costly (30). Más perro: Rony Martínez (1)
Switzerland. Máximo goleador: Mario Gavranović (4). Más perro: Haris Seferović (1)
Argentina. Máximo goleador: Lionel Messi ( Captain ) (37). Más perro: Rodrigo Palacio (2)
Bosnia and Herzegovina. Máximo goleador: Edin Džeko (33). Más perro: Edin Višća (0)
Iran. Máximo goleador: Reza Ghoochannejhad (9). Más perro: Reza Norouzi (0)
Nigeria. Máximo goleador: Victor Obinna (12). Más perro: Michel Babatunde (0)
Germany. Máximo goleador: Miroslav Klose (68). Más perro: Kevin Volland (0)
Ghana. Máximo goleador: Asamoah Gyan (39). Más perro: Jordan Ayew (2)
Portugal. Máximo goleador: Cristiano Ronaldo ( captain ) (49). Más perro: Éder (0)
United States. Máximo goleador: Clint Dempsey (36). Más perro: Aron Jóhannsson (2)
Algeria. Máximo goleador: El Arbi Hillel Soudani (10). Más perro: Islam Slimani (1)
Belgium. Máximo goleador: Romelu Lukaku (8). Más perro: Divock Origi (0)
Russia. Máximo goleador: Aleksandr Kerzhakov (25). Más perro: Maksim Kanunnikov (0)
South Korea. Máximo goleador: Park Chu-Young (24). Más perro: Kim Shin-Wook (3)

Si termino con otras cosas urgentes (la lista es larga), haré otras cuentas y grafiquitos. Por ejemplo, se puede ver cuales són los clubes y ligas con más jugadores mundialistas, los jugadores que juegan para un país distinto a aquel en el que nacieron (por ejemplo, Gabriel Paletta, ex jugador de Boca y de la selección sub 20 argentina, juega para Italia), y etcétera.

Algo más interesante sería cruzar datos con bases de datos de juegos como el FIFA o los que trackean información de transferencias, que permitiría no sólo saber cuan cara es una selección (para que mi padre se indigne justificadamente con el "sucio negocio del fútbol" y me mande a comprar pollo en pleno partido) sino en qué condiciones llega cada selección, qué características tienen sus delanteros (los jueguitos asignan coeficientes de habilidad, velocidad, puntería, etc.) y muchísimo más.

Mientras tanto, se pueden bajar la base de datos para usar estos modelitos que yo armé sin tener que correr los scrappers que pueden tardar un rato.

Salud, y que gane Argentina, pero sobre todo, que haya muchos momentos como este:

Joaquin Tita: Internationalization

Daily, millions of users consume a huge amount of information available in Internet. The explosion of mobile devices has done nothing but encourage the devouring attitude. Few years ago, the only method accessible was using a desktop computer. Nowadays, users in their way to work or back to home, access different to websites looking for information to satisfy their desires.

Companies have seen this opportunity and have turned to distribute content worldwide. One of the big issues is that users decline to be limited to a fixed category no matter if it is cultural, political or language. Customers demand that their ideas, culture and beliefs are respected. The fact is that these customers are no longer situated in a specific area or country; furthermore, they are located all over the globe. News, politics, shopping, games, are some of the main areas that an average users prefer. If you want to succeed and increase your the number of loyal clients, you have to think globally.

One popular way to reach more users is simply to internationalize your software products. By internationalize we mean, ensure that it will work well for, can be easily adapted for, users with different cultures, regions or languages. In the software world it’s commonly named as “i18n”.

Localization versus Internationalization

Sometimes, these two terms are often considered as synonyms across different websites but the fact is they aren’t.

Localization refers to the process of adapting a product, application or document content to meet the market needs from the view of culture, language and other related requirements (also called “l10n”).

Some customizations that are of concern:
  • Numeric.
  • Date and time formats.
  • Use of currency.
  • Keyboard usage.
  • Collation and sorting.
  • Symbols, icons and colors.
  • Text and graphics containing references to objects or actions (in distinct cultures may have different meanings).
  • Varying legal requirements

According to W3C, internationalization is the design and the development of a product, application or document content that enables easy localization for target audiences that vary in culture, region or language.
Typical activities that are considered:
  • Designing and developing in a way that removes barriers to localization or international deployment (using Unicode, handling correctly legacy characters, issues when concatenating strings, avoiding dependencies in code of the user interface, etc.).
  • Providing support for features that may not be used until localization occurs (supporting bidirectional text or identifying the language, adding specific CSS primitives for supporting vertical text or other non-Latin typographic features).
  • Enabling code to support local, regional, language, or culturally related preferences (include date and time formats, local calendars, number formats and numeral systems, sorting and presentation of lists, handling of personal names and forms of address).
  • Separating localizable elements from source or content, such that localized alternatives can be loaded or selected based on the user's international preferences as needed.

These items do not necessarily include localization of the content; they are good practices that ensures that if a migration needs to take place it can be done easily (without a catastrophic refactoring).
Internationalization is a fundamental step in the designing and developing process for delivering content globally. Anyway, it is much more expensive and time consuming readapting a linguistically and culturally deliverable than to include this process at first in the development process.
Some general internationalization guidelines follows to illustrate the power and scope of this process.
Text and Words Guidelines

  • Use simple sentences. Depending of your needs and target market the language might be different, but it is recommendable to use English if you think in something global. Furthermore, it is easier to translate from English to other languages. In addition, use a restricted set of vocabulary and follow Noun-Verb-Object sentence structure. Users don’t have all the same language proficiency. You want attract them, not disinterest or bore them.
  • Try to avoid acronyms, abbreviations and slang. All are difficult and sometimes confusing to translate.
  • Avoid using three noun words (stringing three nouns together).
  • Don’t use local or computer jargon (for example: AI for artificial intelligence, App for application, Bug for programming error, etc.).
  • Avoid culturally specific examples. In some countries they are not seen with good eyes.
  • All references to national, racial, religious and sexist stereotypes should be avoided. Even if they are jokes.
  • A telegraphic writing style is not recommended (terse style where words such as “and,” “the,” and “is,” are omitted).
  • Write in a formal or semi-formal style. An over-friendly style is not recommended as it can be considered condescending and irritating for the reader.
  • Adhere to local user language idioms and cultural contexts. There are some words that have different meaning in distinct countries.
  • Preserve the original word if it can’t be translated. For example: “Disk Drive” and “zooming” don’t exist in Thai.
  • Some words translated to other languages need more screen space. For instance, in Gmail “Inbox” is translated to Spanish as “Bandeja de Entrada”. Take care of extra space for this kind of issues (horizontally and vertically speaking).  The National Language Technical Center from IBM proposed a guideline for calculating the extra space required from the character number.
  • The main choices at the moment of selecting alternative languages should be: German in Europe, Arabic in Middle East, Japanese in Far East as suggested Microsoft. This company also applied this guideline in the following order due to the complexity of each one: Japanese, Arabic and German.
  • Place icon captions outside of the graphic to save redrawing the entire icon per translation. Avoiding this, the only thing needed is to translate the text to the desired language.
  • Adhere to local formats for date, time, money, measurements, addresses, and telephone numbers.

Image and Symbols

  • Adhere to local cultural and social norms. The meaning of the images may confuse the users. Objects like mailboxes, trashcans are different in some countries. Develop proper images for the cultures where they will be used.
  • Use internationally accepted symbols. The International Standard Association (ISO) has developed standard shapes for different purposes. Don’t reinvent the wheel.
  • Develop generic images. If a user uses many versions of the same product and images are diametrically different, he will be totally lost. Try to standardize the images in your product.
  • Be extremely careful with:
    • Religious symbols (crosses and stars).
    • The human body.
    • Women.
    • Hand gestures.
    • Flags.
    • Controversial geographic maps.
    • The cross and check for check boxes (the O mark used in Korea and Japan).
    • Review proposed graphical images early in the design cycle.

    Color, Sequence, and Functionality

    • Adhere to local color connotations and conventions. The association of colors is different in many countries. In Spain, mailboxes are yellow but in England and Argentina they are red or blue. The following chart some of the interpretations of colors in different countries:
    • Provide the proper information sequence. The information shown in the screen has a logical flow that in some countries may be different. Clear examples are cultures where they read from right to left like Arabic countries or from top to down like Japan or Korea.
    • Provide the proper functionality. Some functionalities provided in the product can be accepted in some countries but in others not. All the functionalities should be reviewed to ensure that they don’t generate any cultural trouble.
    • Remove all references to features not supported. All internationalization functionalities that are not available or not supported should be eliminated. Otherwise, they will generate noise and confuse the users.

    From any point of view, internationalization should be considered from the starting point. Modern web application frameworks such as Django Framework and Spring Framework provide internationalization features to help the developers to build powerful web applications without suffering a complete refactoring. Furthermore, the cost and time of starting from scratch rather than trying to internationalize the product once deployed is quite minimum. Even though a global scope is not aimed, things in the future might change. Expect for the unexpected...just in case.

    References

    • W3C Website - http://www.w3.org/International/
    • Designing Web Usability, Jakob Nielsen, Peachpit Press
    • The Essential Guide to User Interface Design: An Introduction to GUI Design Principles and Techniques, 3rd Edition,Wilbert O. Galitz, John Wiley & Sons

    Joaquin Tita: The Rabbit Problem

    Problem
    A certain man put a pair of rabbits in a place surrounded on all sides by a wall.
    How many pairs of rabbits can be produced from that pair in a year if it is supposed that every month
    each pair begets a new pair which from the second month on becomes productive?

    Leonardo da Pisa 

    Damián Avila: Zen themes updated

    OK, time to recap some things... As you know, Nikola 7.0.0 was released some weeks ago. It has a lot of improvements, bug fixes and new features. I recommend you to download and try it! As part of the release, we paid attention to update all the plugins and themes inside the Nikola Github organization (don't forget you can contribute with your own plugins and themes!). So, I updated my own themes, in particular, the Zen ones.

    Read more… (2 min remaining to read)