Web scraping de páginas y archivos con el requests y python

El módulo requests te permite descargar fácilmente archivos de la web sin tener que preocuparte por cuestiones complicadas como errores de red, problemas de conexión y compresión de datos. El módulo requests no viene con Python, así que tendrás que instalarlo primero. Desde la línea de comandos, ejecute pip install --user requests.

El módulo requests fue escrito porque el módulo urllib2 de Python es demasiado complicado de usar. De hecho, toma un marcador permanente y tacha todo este párrafo. Olvida que he mencionado urllib2. Si necesitas descargar cosas de la web, simplemente usa el módulo requests.

A continuación, haz una simple prueba para asegurarte de que el módulo requests se ha instalado correctamente. Introduzca lo siguiente en el shell interactivo:

import requests

Si no aparece ningún mensaje de error, entonces el módulo requests se ha instalado con éxito.

Descarga de una página web con la función requests.get()

La función requests.get() toma una cadena de una URL para descargar. Llamando a type() sobre el valor de retorno de requests.get(), puedes ver que devuelve un objeto Response, que contiene la respuesta que el servidor web dio para tu petición. Explicaré el objeto Response con más detalle más adelante, pero por ahora, introduce lo siguiente en el shell interactivo mientras tu ordenador está conectado a internet:

>>> import requests
➊ >>> res = requests.get('https://automatetheboringstuff.com/files/rj.txt')
>>> type(res)
<class 'requests.models.Response'>
➋ >>> res.status_code == requests.codes.ok
True
>>> len(res.text)
178981
>>> print(res.text[:250])
The Project Gutenberg EBook of Romeo and Juliet, by William Shakespeare

This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever.  You may copy it, give it away or
re-use it under the terms of the Proje

La URL va a una página web de texto para la obra completa de Romeo y Julieta ➊. Puede saber que la solicitud de esta página web tuvo éxito comprobando el atributo status_code del objeto Response. Si es igual al valor de requests.codes.ok, entonces todo ha ido bien ➋. (Por cierto, el código de estado para «OK» en el protocolo HTTP es 200. Es posible que ya esté familiarizado con el código de estado 404 para «Not Found»). Puede encontrar una lista completa de códigos de estado HTTP y sus significados en https://es.wikipedia.org/wiki/Anexo:C%C3%B3digos_de_estado_HTTP.

Si la solicitud tuvo éxito, la página web descargada se almacena como una cadena en el atributo text del objeto Response. Este atributo contiene toda la obra en una cadena realmente larga; la llamada a len(res.text) muestra que tiene más de 178.000 caracteres. Finalmente, al llamar a print(res.text[:250]) se muestran sólo los primeros 250 caracteres.

Si la solicitud falló y mostró un mensaje de error, como «“Failed to establish a new connection” o “Max retries exceeded”, entonces revise su conexión a Internet. La conexión a los servidores puede ser bastante complicada, y no puedo dar una lista completa de posibles problemas aquí. Puedes encontrar las causas comunes de tu error haciendo una búsqueda en la web del mensaje de error entre comillas.

Comprobación de errores

Como ha visto, el objeto Response tiene un atributo status_code que puede ser comprobado con requests.codes.ok (una variable que tiene el valor entero 200) para ver si la descarga tuvo éxito. Una forma más sencilla de comprobar el éxito es llamar al método raise_for_status() en el objeto Response. Esto lanzará una excepción si hubo un error al descargar el archivo y no hará nada si la descarga tuvo éxito. Introduzca lo siguiente en el shell interactivo:

>>> res = requests.get('https://inventwithpython.com/page_that_does_not_exist')
>>> res.raise_for_status()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

  File "C:\Users\Al\AppData\Local\Programs\Python\Python37\lib\site-packages\requests\models
.py", line 940, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://inventwithpython
.com/page_that_does_not_exist.html

El método raise_for_status() es una buena manera de asegurar que un programa se detenga si se produce una mala descarga. Esto es algo bueno: usted quiere que su programa se detenga tan pronto como ocurra algún error inesperado. Si una descarga fallida no es un problema para su programa, puede envolver la línea raise_for_status() con sentencias try y except para manejar este caso de error sin que se caiga.

import requests
res = requests.get('https://inventwithpython.com/page_that_does_not_exist')
try:
    res.raise_for_status()
except Exception as exc:
    print('There was a problem: %s' % (exc))

Esta llamada al método raise_for_status() hace que el programa muestre lo siguiente:

Ha habido un problema: 404 Client Error: No se encuentra para la url: https://
inventwithpython.com/page_that_does_not_exist.html

Llame siempre a raise_for_status() después de llamar a requests.get(). Siempre quiere estar seguro de que la descarga ha funcionado realmente antes de que su programa continúe.

Guardar los archivos descargados en el disco duro

A partir de aquí, puede guardar la página web en un archivo de su disco duro con la función estándar open() y el método write(). Sin embargo, hay algunas pequeñas diferencias. En primer lugar, debe abrir el archivo en modo binario de escritura pasando la cadena 'wb' como segundo argumento a open(). Incluso si la página está en texto plano (como el texto de Romeo y Julieta que descargaste antes), necesitas escribir datos binarios en lugar de datos de texto para mantener la codificación Unicode del texto.

Para escribir la página web en un archivo, puede utilizar un bucle for con el método iter_content() del objeto Response.

>>> import requests
>>> res = requests.get('https://automatetheboringstuff.com/files/rj.txt')
>>> res.raise_for_status()
>>> playFile = open('RomeoAndJuliet.txt', 'wb')
>>> for chunk in res.iter_content(100000):
        playFile.write(chunk)

100000
78981
>>> playFile.close()

El método iter_content() devuelve "chunk" (trozos) del contenido en cada iteración a través del bucle. Cada trozo es del tipo de datos bytes, y usted puede especificar cuántos bytes contendrá cada trozo. Cien mil bytes es generalmente un buen tamaño, así que pase 100000 como argumento a iter_content().

El archivo RomeoAndJuliet.txt existirá ahora en el directorio de trabajo actual. Tenga en cuenta que mientras el nombre del archivo en el sitio web era rj.txt, el archivo en su disco duro tiene un nombre diferente. El módulo de peticiones simplemente se encarga de descargar el contenido de las páginas web. Una vez que la página es descargada, es simplemente un dato en su programa. Incluso si perdieras la conexión a Internet después de descargar la página web, todos los datos de la página seguirían estando en tu ordenador.

El método write() devuelve el número de bytes escritos en el archivo. En el ejemplo anterior, había 100.000 bytes en el primer trozo, y la parte restante del archivo sólo necesitaba 78.981 bytes.

Para repasar, este es el proceso completo para descargar y guardar un archivo:

  • Llama a requests.get() para descargar el archivo.
  • Llame a open() con 'wb' para crear un nuevo archivo en modo binario de escritura.
  • Haga un bucle sobre el método iter_content() del objeto Response.
  • Llame a write() en cada iteración para escribir el contenido en el archivo.
  • Llame a close() para cerrar el archivo.

¡Eso es todo lo que hay en el módulo requests! El bucle for y el método iter_content() pueden parecer complicados comparados con el flujo de trabajo open()/write()/close() que has estado usando para escribir archivos de texto, pero es para asegurar que el módulo requests no consuma demasiada memoria incluso si descargas archivos masivos. Puedes conocer otras características del módulo requests en https://requests.readthedocs.org/.

Deja una respuesta