Dentro de Twitter: estructuras de datos
Esta es la cuarta entrada de este blog, dedicado a usar el API de Twitter y las librerías de Python para generar una imagen de la conversación que tenga un formato gráfico:
Tengo que recuperar los tuits con el API de Twitter y usar Python para filtrar la información, crear una lista ordenada y generar un gráfico con los nodos.
En esta entrada examinaremos la estructura de los datos que devuelve el API de Twitter, y cómo los manipulamos con Python. Supongo cierta familiaridad con Python; en caso contrario, hay todo tipo de tutoriales en la red y seguro que alguno se adapta a tu nivel. En caso de duda, escribe un comentario o un tuit e intentaré ayudarte.
La línea de tiempo (según el API de Twitter)
Si llegaste a ejecutar el último comando que te proponía en el post anterior, tuviste que recibir un gran flujo de datos parecido al que ves a continuación(vuelvo a mostrar todos los comandos. Recuerda que las claves que uses han de ser las tuyas):
>>> import twitter >>> token = "93826987-b17gKPaq4a5sNtbu3PfXPHdBcCm4ff533PSlMKGMV" >>> token_key = "wZZ5VUOPzOy1QABHiA4gUQEfrRoMxn4qlewNTJ1yxhZem" >>> con_secret = "gsIT85KKCwn96YZdEm5i3uGtq" >>> con_secret_key = "SvWhbZNUZIx9Kz3sMuMaditk3phMyhw2tLHrrxXLX7SaYm9Ntm" >>> auth = twitter.OAuth(token, token_key, con_secret, con_secret_key) >>> t = twitter.Twitter(auth=auth) >>> t.statuses.home_timeline() [{'retweet_count': 1, 'coordinates': None, 'retweeted': False, 'id_str': '501334102357073921', 'favorite_count': 1, 'favorited': False, 'id': 501334102357073921, 'user': {'profile_banner_url': 'https://pbs.twimg.com/profile_banners/100731315/1401451804', 'listed_count': 5080, 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1563598007/rt_sepa_mas_normal.png', 'verified': True, 'time_zone': 'Moscow', 'profile_link_color': '5CBA1D', 'url': 'http://t.co/D4csDmdhn8', 'created_at': 'Thu Dec 31 09:33:16 +0000 2009', 'statuses_count': 175603, 'contributors_enabled': False, 'profile_sidebar_fill_color': '042107', 'profile_background_color': '005E15', 'profile_text_color': 'FFFFFF', 'id': 100731315, 'is_translation_enabled': False, ... (abreviado)
Esta información está en un formato llamado JSON. JSON permite guardar datos estructurados de una forma muy sencilla. Se pueden usar dos tipos de estructuras:
- Las listas. Son colecciones de valores separados por comas y entre paréntesis cuadrados [ ]. Esta lista podría ser la información de inventario de un producto:
['2014-08-18', 250.99, 'Reloj de oro', 9, 'En stock']
- Los diccionarios. Los dicionarios son colecciones de parejas de valores, del estilo código-valor. La sintaxis usa corchetes { } para definir el diccionario y los dos puntos para separar la clave del valor. La lista anterior podría reescribirse como diccionario, si precedemos cada elemento con el nombre del concepto que representa:
{'fecha': '2014-08-18', 'precio': 250.99, 'descripcion: 'Reloj de oro', 'cantidad': 9, 'estado': 'En stock'}
El problema con el JSON que hemos recibido es que no es muy legible. A continuación he reorganizado el texto para que se vea más claro.
[ {'retweet_count': 1, 'coordinates': None, 'retweeted': False, 'id_str': '501334102357073921', 'favorite_count': 1, 'favorited': False, 'id': 501334102357073921, 'user': { 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/100731315/1401451804', 'listed_count': 5080, 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1563598007/rt_sepa_mas_normal.png' ...
Puesto que empieza con un paréntesis cuadrado, lo primero que debería quedar claro es que esta secuencia contiene una lista con uno o más elementos.
El segundo signo es un corchete, por tanto el primer elemento de la lista es un diccionario. Las claves de este diccionario son: ‘retweet_count’, ‘coordinates’, ‘retweeted’… Algunas de estas claves tienen un significado bastante explícito para el usuario de Twitter, mientras que hay otros más oscuros. Una de las cosas que tendremos que hacer es examinar estas estructuras para comprender cómo modelaron las cosas los ingenieros de Twitter.
La facilidad de JSON para anidar objetos dentro de otros objetos es lo que lo hace tan práctico para estructurar datos complejos. Además, se necesita muy poco formato extra para cumplir con la sintaxis, es un formato muy económico. Esto es una ventaja cuando se trata de mover datos por la red.
Listas y diccionarios en Python
Una buena noticia: Python es idéntico a JSON en lo que se refiere a su sintaxis de listas y diccionarios. De hecho, listas y diccionarios son tipos de datos nativos, igual que los números o las cadenas. Crear variables de tipo lista o diccionario es fácil:
>>> mi_lista = ['Diego Buendia', 'dbuendiab', '2009-12-01'] >>> print(mi_lista) ['Diego Buendia', 'dbuendiab', '2009-12-01'] >>> mi_diccionario = {'name': 'Diego Buendia', 'screen_name': 'dbuendiab', 'created_at': '2009-12-01'} >>> print(mi_diccionario) {'name': 'Diego Buendia', 'screen_name': 'dbuendiab', 'created_at': '2009-12-01'}
Explorando listas y diccionarios
El acceso a los elementos de listas y diccionarios se hace mediante una sintaxis de paréntesis cuadrados: variable[<indice-o-clave>].
Las listas indexan sus elementos por números consecutivos, empezando por el cero, y los diccionarios lo hacen mediante las claves que lo componen.
>>> mi_lista[0]
'Diego Buendia'
Los diccionarios, en cambio, lo hacen por claves:
>>> mi_diccionario['created_at']
'2009-12-01'
Una forma muy de Python de averigüar el tipo de dato en una variable es asumir que es una lista. Si no da error, es que lo era:
>>> mi_diccionario[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 0
Un error KeyError significa que esa clave no existe en el diccionario. Desafortunadamente, puede que estemos tratando realmente con un diccionario:
>>> d = {1: 'Francia', 2: 'Italia', 5: 'Alemania'} >>> d[2] 'Italia' >>> d[0] Traceback (most recent call last): File "", line 1, in KeyError: 0
¿Qué diferencia hay entre este diccionario y una lista? Bueno, la diferencia está en que los índices de una lista empiezan forzosamente por cero, mientras que el diccionario podría tenerlos arbitrarios (como el de Alemania, que es 5).
¿Hay una forma de saber si una variable contiene un diccionario o una lista sin examinarla? Pues sí. La función isinstance(<objeto>, <tipo>):
>>> isinstance(d, list) False >>> isinstance(d, dict) True
También nos interesa saber cómo recuperar todas las claves, o valores, de un diccionario:
>>> d.keys() [1, 2, 5] >>> d.values() ['Francia', 'Italia', 'Alemania']
Observarás que estas funciones tienen una sintaxis diferente a len() o dir(). En éstas, el argumento va dentro de los paréntesis, y en cambio keys() y values() están asociadas al objeto d mediante la notación ‘.’ (punto). Todo en Python deriva de una clase Object, y las clases son estructuras que contienen atributos (digamos variables asociadas) y métodos (funciones asociadas). La notación ‘.’ explicita esta dependencia.
Con estos recursos, es posible navegar y conocer poco a poco las estructuras de nuestro programa. Lo peor que puede pasar, y que de hecho pasa, es que al intentar mostrar un elemento inadvertidamente resulte ser un elemento de tamaño descomunal y se produzca un pantallazo continuo de datos. La solución es teclear pronto un <Ctrl>-C para interrumpir la salida.
La estructura del objeto Tweet de Twitter
Para terminar, vamos a recuperar el código con el que iniciábamos el presente post, pero ahora, para poder examinarlo con calma, guardaremos el timeline recuperado en una variable:
>>> import twitter >>> token = "93826987-b17gKPaq4a5sNtbu3PfXPHdBcCm4ff533PSlMKGMV" >>> token_key = "wZZ5VUOPzOy1QABHiA4gUQEfrRoMxn4qlewNTJ1yxhZem" >>> con_secret = "gsIT85KKCwn96YZdEm5i3uGtq" >>> con_secret_key = "SvWhbZNUZIx9Kz3sMuMaditk3phMyhw2tLHrrxXLX7SaYm9Ntm" >>> auth = twitter.OAuth(token, token_key, con_secret, con_secret_key) >>> t = twitter.Twitter(auth=auth) >>> tl = t.statuses.home_timeline()
Ya vimos que la variable tl tiene aspecto de lista. Puedes comprobar que tiene 20 elementos (es lo que devuelve el API de Twitter por defecto, si no se le dice nada al respecto).
>>> len(tl)
20
Extrae el primer elemento a una variable, para examinarla con más calma. Recordarás que los elementos de esta lista son diccionarios, que corresponden a tuits. Puedes mirar las claves del diccionario con keys(): lo que ves son los atributos (públicos) de un tuit, según el API de Twitter. Uso el bucle for de Python (for elem in lista:) para presentar los atributos con más pulcritud.
>>> tuit = tl[0] >>> for atributo in tuit.keys(): ... print(atributo) ... retweeted in_reply_to_status_id favorite_count entities possibly_sensitive contributors in_reply_to_user_id geo favorited truncated in_reply_to_screen_name retweet_count id text id_str in_reply_to_status_id_str lang source retweeted_status place in_reply_to_user_id_str extended_entities user created_at coordinates
He marcado en color aquellos campos que en mi opinión son más relevantes, para comentarlos a continuación. Si quieres conocer con más detalle esta y otras estructuras, puedes consultar la documentación de objetos del API de Twitter.
Campos esenciales
- id: campo numérico que identifica el tuit de una forma única. Este (y otros campos numéricos) tiene una versión en formato cadena (id_str) para evitar problemas en aquellos lenguajes que tienen problemas con los números muy grandes. No es el caso de Python, así que en adelante ignoraré las versiones _str.
- text: el contenido del tuit. O sea, lo que lee la gente, propiamente dicho.
- created_at: la fecha en que se publicó el tuit.
- user: user es un diccionario que corresponde al objeto User de Twitter. Contiene toda la información del usuario
Campos reply
Existen siempre (menos el último), pero sólo se rellenan cuando el tuit es contestación a otro previo. En caso contrario, contienen el valor None. Si es tuit de contestación, estos campos se rellenan con información del tuit al que ha contestado.
- in_reply_to_status_id: es el id del tuit original al que contestamos.
- in_reply_to_user_id: el id del usuario que creó el tuit original.
- in_reply_to_screen_name: el nombre Twitter de dicho usuario.
- retweeted_status: la copia del tuit original. Este campo no existe si el tuit no es contestación de otro.
Campos estadísticos
Corresponden al número de retweets y favorites que tenga el tuit.
- favorite_count: número de veces que fue marcado como favorito.
- retweet_count: número de veces que fue retuiteado.
La estructura del objeto User de Twitter
Selecciona el elemento user del tuit y guárdalo en una variable. Busca las keys(), como antes:
>>> user = tuit['user'] >>> for atributo in user: ... print(atributo) ... contributors_enabled profile_background_tile url profile_link_color is_translation_enabled screen_name entities time_zone utc_offset geo_enabled profile_background_image_url_https listed_count name protected verified location profile_background_image_url profile_sidebar_border_color profile_text_color default_profile_image id profile_use_background_image follow_request_sent following profile_image_url_https lang profile_sidebar_fill_color is_translator id_str description statuses_count favourites_count friends_count notifications followers_count profile_background_color profile_image_url profile_banner_url created_at default_profile
Campos esenciales
- id: código del usuario. Su DNI en Twitter.
- screen_name: el nombre que aparece en las menciones.
- name: el nombre que aparece en grande junto a la foto.
- created_at: fecha en la que el usuario se dio de alta en Twitter. Este campo no se ve en la web, a pesar de que es muy interesante (en mi opinión).
- description: texto que el usuario publica de sí mismo y que aparece en su perfil.
Campos estadísticos
- statuses_count: número de tuits publicados por este usuario.
- favourites_count: número de tuits marcados como favoritos por este usuario.
- friends_count: número de personas a las que este usuario sigue.
- followers_count: número de personas que siguen a este usuario.
- listed_count: número de listas en las que está presente este usuario.
Conclusión
Con esto termino el post de hoy. Resumo su contenido:
- Hemos examinado lo que nos devuelve el API de Twitter (en un caso concreto).
- Hemos hablado del formato JSON.
- Hemos visto las listas y diccionarios de Python, y cómo explorarlos.
- Hemos analizado las estructuras Tweet y User.
Ahora ya tienes unas nociones de las estructuras de datos que tiene Twitter, y de cómo se manejan desde Python. El trabajo del próximo día será estudiar el API de Twitter y su uso a través de la librería Python correspondiente. Hasta mañana.
Ah! Me gustaría pedirte tu opinión sobre este blog. En caso de que tengas alguna sugerencia o crítica me gustaría mucho conocerla.