Interactuar con webs en Python

Existen dos módulos principales para leer datos de URLs en Python: urllib y urllib2. En esta lección aprenderemos a utilizar urllib2 ya que es mucho más completo, aunque urllib tiene funcionalidades propias que no se pueden encontrar en urllib2, por lo que también lo tocaremos de pasada.

urllib2 puede leer datos de una URL usando varios protocolos como HTTP, HTTPS, FTP, o Gopher.

Se utiliza una función urlopen para crear un objeto parecido a un fichero con el que leer de la URL. Este objeto cuenta con métodos como read, readline, readlines y close, los cuales funcionan exactamente igual que en los objetos file, aunque en realidad estamos trabajando con un wrapper que nos abstrae de un socket que se utiliza por debajo.

El método read, como recordareis, sirve para leer el «archivo» completo o el número de bytes especificado como parámetro, readline para leer una línea, y readlines para leer todas las líneas y devolver una lista con ellas.

También contamos con un par de métodos geturl, para obtener la URL de la que estamos leyendo (que puede ser útil para comprobar si ha habido una redirección) e info que nos devuelve un objeto con las cabeceras de respuesta del servidor (a las que también se puede acceder mediante el atributo headers).

import urllib2

try:
    f = urllib2.urlopen("http://www.python.org")
    print f.read()
    f.close()
except HTTPError, e:
    print "Ocurrió un error"
    print e.code
except URLError, e:
    print "Ocurrió un error"
    print e.reason

Al trabajar con urllib2 nos podemos encontrar, como vemos, con errores de tipo URLError. Si trabajamos con HTTP podemos encontrarnos también con errores de la subclase de URLError HTTPError, que se lanzan cuando el servidor devuelve un código de error HTTP, como el error 404 cuando no se encuentra el recurso. También podríamos encontrarnos con errores lanzados por la librería que urllib2 utiliza por debajo para las transferencias HTTP: httplib; o con excepciones lanzadas por el propio módulo socket.

La función urlopen cuenta con un parámetro opcional data con el que poder enviar información a direcciones HTTP (y solo HTTP) usando POST (los parámetros se envían en la propia petición), por ejemplo para responder a un formulario. Este parámetro es una cadena codificada adecuadamente, siguiendo el formato utilizado en las URLs:

‘password=contrase%A4a&usuario=manuel’

Lo más sencillo para codificar la cadena es utilizar el método urlencode de urllib, que acepta un diccionario o una lista de tuplas (clave, valor) y genera la cadena codificada correspondiente:

import urllib, urllib2

params = urllib.urlencode({"usuario": "manuel", "password": "contraseña"})
f = urllib2.urlopen("http://ejemplo.com/login", params)

Si lo único que queremos hacer es descargar el contenido de una URL a un archivo local, podemos utilizar la función urlretrieve de urllib en lugar de leer de un objeto creado con urlopen y escribir los datos leídos.

La función urlretrieve toma como parámetros la URL a descargar y, opcionalmente, un parámetro filename con la ruta local en la que guardar el archivo, un parámetro data similar al de urlopen y un parámetro reporthook con una función que utilizar para informar del progreso.

A excepción de las ocasiones en las que se utiliza el parámetro data las conexiones siempre se realizan utilizando GET (los parámetros se envían en la URL). Para enviar datos usando GET basta con concatenar la cadena resultante de urlencode con la URL a la que nos vamos a conectar mediante el símbolo «?».

params = urllib.urlencode({"usuario": "manuel", "password": "contraseña"})

f = urllib2.urlopen("http://ejemplo.com/login" + "?" + params)

En urllib también se utiliza una función urlopen para crear nuestros pseudo-archivos, pero a diferencia de la versión de urllib, la función urlopen de urllib2 también puede tomar como parámetro un objeto Request, en lugar de la URL y los datos a enviar.

La clase Request define objetos que encapsulan toda la información relativa a una petición. A través de este objeto podemos realizar peticiones más complejas, añadiendo nuestras propias cabeceras, como el User-Agent.

El constructor más sencillo para el objeto Request no toma más que una cadena indicando la URL a la que conectarse, por lo que utilizar este objeto como parámetro de urlopen sería equivalente a utilizar una cadena con la URL directamente.

Sin embargo el constructor de Request también tiene como parámetros opcionales una cadena data para mandar datos por POST, un diccionario headers con las cabeceras y un par de campos origin_req_host y unverifiable, que quedan fuera del propósito de la lección.

Veamos cómo añadir nuestras propias cabeceras utilizando como ejemplo la cabecera User-Agent. El User-Agent es una cabecera que sirve para identificar el navegador y sistema operativo que estamos utilizando para conectarnos a esa URL. Por defecto urllib2 se identifica como «Python-urllib/2.5»; si quisiéramos identificarnos como un Linux corriendo Konqueror por ejemplo, usaríamos un código similar al siguiente:

ua = "Mozilla/5.0 (compatible; Konqueror/3.5.8; Linux)"
h = {"User-Agent": ua}
r = urllib2.Request("http://www.python.org", headers=h)
f = urllib2.urlopen(r)
print f.read()

Para personalizar la forma en que trabaja urllib2 podemos instalar un grupo de manejadores (handlers) agrupados en un objeto de la clase OpenerDirector (opener o abridor), que será el que se utilice a partir de ese momento al llamar a urlopen.

Para construir un opener se utiliza la función build_opener a la que se le pasa los manejadores que formarán parte del opener. El opener se encargará de encadenar la ejecución de los distintos manejadores en el orden dado. También se puede usar el constructor de OpenerDirector, y añadir los manejadores usando su método add_handler.

Para instalar el opener una vez creado se utiliza la función install_opener, que toma como parámetro el opener a instalar. También se podría, si sólo queremos abrir la URL con ese opener una sola vez, utilizar el método open del opener.

urllib2 cuenta con handlers que se encargan de manejar los esquemas disponibles (HTTP, HTTPS, FTP), manejar la autenticación, manejar las redirecciones, etc.

Para añadir autenticación tendríamos que instalar un opener que incluyera como manejador HTTPBasicAuthHandler, ProxyBasicAuthHandler, HTTPDigestAuthHandler y/o ProxyDigestAuthHandler.

Para utilizar autenticación HTTP básica, por ejemplo, usaríamos HTTPBasicAuthHandler:

aut_h = urllib2.HTTPBasicAuthHandler()
aut_h.add_password("realm", "host", "usuario", "password")

opener = urllib2.build_opener(aut_h)
urllib2.install_opener(opener)

f = urllib2.urlopen("http://www.python.org")

Si quisiéramos especificar un proxy en el código tendríamos que utilizar un opener que contuviera el manejador ProxyHandler. El manejador por defecto incluye una instacia de ProxyHandler construido llamando al inicializador sin parámetros, con lo que se lee la lista de proxies a utilizar de la variable de entorno adecuada. Sin embargo también podemos construir un ProxyHandler pasando como parámetro al inicializador un diccionario cuyas claves son los protocolos y los valores, la URL del proxy a utilizar para dicho protocolo.

proxy_h = urllib2.ProxyHandler({"http" : "http://miproxy.net:123"})

opener = urllib2.build_opener(proxy_h)
urllib2.install_opener(opener)

f = urllib2.urlopen("http://www.python.org")

Para que se guarden las cookies que manda HTTP utilizamos el manejador HTTPCookieProcessor.

cookie_h = urllib2.HTTPCookieProcessor()

opener = urllib2.build_opener(cookie_h)
urllib2.install_opener(opener)

f = urllib2.urlopen("http://www.python.org")

Si queremos acceder a estas cookies o poder mandar nuestras propias cookies, podemos pasarle como parámetro al inicializador de HTTPCookieProcessor un objeto de tipo CookieJar del módulo cookielib.

Para leer las cookies mandadas basta crear un objeto iterable a partir del CookieJar (también podríamos buscar las cabeceras correspondientes, pero este sistema es más claro y sencillo):

import urllib2, cookielib

cookie_j = cookielib.CookieJar()

cookie_h = urllib2.HTTPCookieProcessor(cookie_j)

opener = urllib2.build_opener(cookie_h)
opener.open("http://www.python.org")

for num, cookie in enumerate(cookie_j):
    print num, cookie.name
    print cookie.value
    print

En el improbable caso de que necesitáramos añadir una cookie antes de realizar la conexión, en lugar de conectarnos para que el sitio la mande, podríamos utilizar el método set_cookie de CookieJar, al que le pasamos un objeto de tipo Cookie. El constructor de Cookie, no obstante, es bastante complicado.



23 comentarios en «Interactuar con webs en Python»

  1. Orale!

    Este articulo es perfecto! hace tiempo aprendi a hacer todo este relajo en python pero basandome en muuuchos articulos y python.org esta muy completo, obviamente si quieres implementarlo deberas echarle un poco de coco.

    Muchas gracias!

  2. Justamente ayer empece a leer de esto para hacer unas pruebas.

    ¿Sabes como usar certificados de seguridad para accesar a un sitio https?

  3. Pingback: Caso el Bruto.es « Fitorec – Pensamientos Libres!

  4. Este artículo es excelente describe exactamente el problema que me había planteado
    Envio de variables post, Edicion de cabeceras HTTP.

    simplemente EXCELENTE

  5. Hola,oye,io tengo cierto problema y es que a la pagina que deseo accesar esta en aspx :|,tengo que seleccionar de unas listas algunas opciones para poder obtener la tabla generada y guardarla en archivo,espero me puedas ayudar ya que se me hace algo confuso esto,de antemano muchas gracias!!!

  6. emmmmmmmm.. mejor cambio de pregunta
    (ya encontré su respuesta)

    Cómo puedo redireccionar desde python, así como se hace en php, header(«Location: …»)

    Cualquier idea es bienvenida.
    Saludos.

  7. La verdad que esto me vino genial hace un tiempo para poder extraer datos de una web en la que debía logearme.

    Pero ahora necesito poder ejecutar una función javascript de dicha web, sólo eso, no he de recoger nada de lo que genera dicha función, sólo ejecutarla, esto es posible con python?

    Un saludo, y gracias.

  8. Buenos dias,

    Quiero leer datos de varias webs, que voy generando de forma dinamica. Leo la primera, pero al hacer la segunda, no se muestra su contenido, tiene que ver con

    datos = html.fromstring(sitio.content)

    ¿alguien puede ayudarme?

Responder a CAX Cancelar respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.