Uso de APIs web RESTful con Python requests


Uno de los métodos que se están imponiendo en Internet para la comunicación de aplicaciones con servicios web disponibles es una API web tipo RESTful o REST, este método está desplazando paulatinamente a XML-RPC que está en progresivo desuso o SOAP, protocolo que es una recomendación de la W3C para ofrecer servicios web.

Las API REST está imponiéndose a otros métodos por su sencillez, y a pesar de que no son un protocolo bien establecido como SOAP, tienen un conjunto de aspectos que las definen:

  • Se utiliza una URI para definir el servicio web
  • Los datos se transfieren utilizando un formato como estandarizado (XML y JSON son los dos más extendidos actualmente)
  • Se utilizan métodos estándar HTTP: GET, POST, PUT o DELETE

En esta entrada veremos mediante algunos ejemplos la forma de utilizar APIs REST desde una aplicación Python, utilizando la biblioteca requests.

Método GET y respuesta en formato XML

El primer ejemplo vamos a hacerlo utilizando el servicio web de Yahoo de información meteorológica, básicamente se utiliza la URL http://weather.yahooapis.com/forecastrss en la que se pasa mediante el método GET como parámetro la localidad de la que queremos obtener información. Podemos hacerlo previamente con wget para Sevilla (España), que tiene el parámetro WOEID 774508 propio de Yahoo:

usuario@equipo:~$ wget -O salida.xml http://weather.yahooapis.com/forecastrss?w=774508

El fichero salida.xml contiene la información meteorológica en formato RSS (un tipo de XML):

<xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
  <channel>
    <title>Yahoo! Weather - Seville, ES</title>
    <link>http://us.rd.yahoo.com/dailynews/rss/weather/Seville__ES/*http://weather.yahoo.com/forecast/SPXX0074_f.html</link>
    <description>Yahoo! Weather for Seville, ES</description>
    <language>en-us</language>
    <lastBuildDate>Tue, 19 Feb 2013 7:00 pm CET</lastBuildDate>
    <ttl>60</ttl>
    <yweather:location city="Seville" region=""   country="Spain"/>
    <yweather:units temperature="F" distance="mi" pressure="in" speed="mph"/>
    <yweather:wind chill="55"   direction="160"   speed="6" />
    <yweather:atmosphere humidity="94"  visibility="6.21"  pressure="29.77"  rising="0" />
    <yweather:astronomy sunrise="8:07 am"   sunset="7:06 pm"/>
    <image>
      <title>Yahoo! Weather</title>
      <width>142</width>
      <height>18</height>
      <link>http://weather.yahoo.com</link>
      <url>http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif</url>
    </image>
    <item>
      <title>Conditions for Seville, ES at 7:00 pm CET</title>
      <geo:lat>37.39</geo:lat>
      <geo:long>-6</geo:long>
      <link>http://us.rd.yahoo.com/dailynews/rss/weather/Seville__ES/*http://weather.yahoo.com/forecast/SPXX0074_f.html</link>
      <pubDate>Tue, 19 Feb 2013 7:00 pm CET</pubDate>
      <yweather:condition  text="Partly Cloudy"  code="30"  temp="55"  date="Tue, 19 Feb 2013 7:00 pm CET" />
      <description><![CDATA[
      <img src="http://l.yimg.com/a/i/us/we/52/30.gif"/><br />
      <b>Current Conditions:</b><br />
      Partly Cloudy, 55 F<BR />
      <BR /><b>Forecast:</b><BR />
      Tue - Rain. High: 58 Low: 42<br />
      Wed - Partly Cloudy. High: 62 Low: 46<<br /> />
      <br />
      <a href="http://us.rd.yahoo.com/dailynews/rss/weather/Seville__ES/*http://weather.yahoo.com/forecast/SPXX0074_f.html">Full Forecast at Yahoo! Weather</a><BR/><BR/>
(provided by <a <hr />
ef="http://www.weather.com" >The Weather Channel</a>)<br/>
      ]]></description>
      <yweather:forecast day="Tue" date="19 Feb 2013" low="42" high="58" text="Rain" code="12" />
      <yweather:forecast day="Wed" date="20 Feb 2013" low="46" high="62" text="Partly Cloudy" code="30" />
      <guid isPermaLink="false">SPXX0074_2013_02_20_7_00_CET</guid>
    </item>
  </channel>
</rss>

<!-- api6.weather.ch1.yahoo.com Tue Feb 19 18:51:26 PST 2013 -->

El código Python que realizaría el mismo proceso sería (será preciso en algunos casos instalar el paquete requests de Python):

import requests
# Creamos la petición HTTP con GET:
r = requests.get("http://weather.yahooapis.com/forecastrss", params = {"w":"774508"})
# Imprimimos el resultado si el código de estado HTTP es 200 (OK):
if r.status_code == 200:
    print r.text

Es habitual definir múltiples parámetros en la petición, por ejemplo en este caso podemos solicitar que nos devuelvan los datos utilizando la escala de grados centígrados en lugar de farenheit. En ese caso la URL de la petición sería http://weather.yahooapis.com/forecastrss?w=774508&u=c, para lo que modificaríamos la línea de la petición GET por:

r = requests.get("http://weather.yahooapis.com/forecastrss", params = {"w":"774508", "u":"c"})

Método GET y respuesta en formato JSON

Uno de los éxitos de twitter y de otras aplicaciones similares es que permiten utilizar directamente la API web del servicio y desarrollar aplicaciones a medida o integradas en otras. Veamos por ejemplo cómo podemos obtener información de un tweet concreto desde Python con una petición GET y una respuesta en formato json:

import requests
r = requests.get("https://api.twitter.com/1/statuses/show.json", params = {"id":"303946689553776640"})
if r.status_code == 200:
    print r.text

En este caso obtenemos la salida en formato json:

{"created_at":"Tue Feb 19 19:18:32 +0000 2013",
 "id":303946689553776640,
 "id_str":"303946689553776640",
 "text":"#Debian Installer 7.0 Release Candidate 1 published - http:\/\/t.co\/6euTRCHj",
 "source":"\u003ca href=\"http:\/\/identi.ca\" rel=\"nofollow\"\u003eidentica\u003\/a\u003e",
 "truncated":false,
 "in_reply_to_status_id":null,
 "in_reply_to_status_id_str":null,
 "in_reply_to_user_id":null,
 "in_reply_to_user_id_str":null,
 "in_reply_to_screen_name":null,
 "user":{"id":24253645,
         "id_str":"24253645",
         "name":"The Debian Project",
         "screen_name":"debian",
         "location":"Your computers.",
         "url":"http:\/\/debian.org\/",
         "description":"The Universal Operating System; run by @raphaelhertzog",
         "protected":false,
         "followers_count":38707,
         "friends_count":5328,
         "listed_count":2137,
         "created_at":"Fri Mar 13 21:03:58 +0000 2009",
         "favourites_count":0,
         "utc_offset":3600,
         "time_zone":"Paris",
         "geo_enabled":false,
         "verified":false,
         "statuses_count":1488,
         "lang":"en",
         "contributors_enabled":false,
         "is_translator":false,
         "profile_background_color":"171717",
         "profile_background_image_url":"http:\/\/a0.twimg.com\/profile_background_images\/201654640\/spacefun-wallpaper.png",
         "profile_background_image_url_https":"https:\/\/si0.twimg.com\/profile_background_images\/201654640\/spacefun-wallpaper.png",
         "profile_background_tile":true,"profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/1311499343\/debian-square_normal.png",
         "profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/1311499343\/debian-square_normal.png",
         "profile_link_color":"554D25",
         "profile_sidebar_border_color":"FFFFFF",
         "profile_sidebar_fill_color":"FFFFFF",
         "profile_text_color":"333333",
         "profile_use_background_image":true,
         "default_profile":false,
         "default_profile_image":false,
         "following":null,
         "follow_request_sent":null,
         "notifications":null},
 "geo":null,
 "coordinates":null,
 "place":null,
 "contributors":null,
 "retweet_count":116,
 "favorited":false,
 "retweeted":false,
 "possibly_sensitive":false}

Método POST con respuesta en formato JSON

Vamos a utilizar en este ejemplo la API de OpenStack y vamos a utilizar un método POST en el que tendremos que enviar varios parámetros específicos en la solicitud (nombre de usuario, contraseña y nombre del proyecto) y obtendremos como respuesta un token de sesión, en esta implementación que se ha hecho en la API de OpenStack no se realiza una autenticación simple por HTTP, sino que se pasa el nombre de usuario y la contraseña como datos de la petición POST, en otras APIs sí se utiliza autenticación simple HTTP que debe especificarse con el parámetro auth. Sí incluimos como novedad respecto a los ejemplos anteriores el parámetro headers que se utiliza cuando es necesario añadir alguna cabecera específica.

import requests
from getpass import getpass
# Definimos la URL
url = "http://127.0.0.1:35357/v2.0/tokens"
# Solicitamos los datos del usuario
user = raw_input("username: ")
passwd = getpass ("password: ")
proy = raw_input("proyecto: ")
# Definimos la cabecera y el diccionario con los datos
cabecera1 = {'Content-type': 'application/json'}
datos = '{"auth":{"passwordCredentials":{"username": "%s", "password": "%s"}, "tenantName":"%s"}}' % (user, passwd, proy)
solicitud = requests.post(url+'tokens', headers = cabecera1, data = datos)
if solicitud.status_code == 200:
    print solicitud.text

Que produce la salida:

{"access": {"token": {"expires": "2013-02-20T22:04:51Z",
                      "id": "00000000007a4761b7589fd178000000", 
                      "tenant": {"enabled": true,
                      "id": "00000000003c8a98f89a895c6b200000c", 
                      "name": "proy-usuario", 
                      "description": "Proyecto de usuario"}},
...}

Gracias a estos tres ejemplos vemos que es relativamente sencillo utilizar una API REST, aunque la dificultad viene en algunas ocasiones de la falta de documentación de la API o la utilización de mecanismos no estándar. Un aspecto que no se trata en esta entrada y que en ocasiones es algo más tedioso es el procesamiento de los datos obtenidos en XML o JSON, ya que en muchas ocasiones es preciso escudriñar completamente el fichero obtenido, para lo que se utilizan bibliotecas de Python como lxml (para XML) o json (para JSON).

, , ,

  1. #1 por Dago Gallo el 4-03-15 - 12:45 am

    Muchas gracias por la informacion, fue de mucha utilidad (y)

    Me gusta

  2. #2 por Cuetox el 21-04-16 - 11:58 pm

    GRACIAS, ME FUNCIONO SOLO QUE SIN DECLARAR LOS HEADERS

    Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: