El contenido de este documento son apuntes prácticos del Curso de Fundamentos de Web Scraping con Python y Xpath y busca ser una guía para futuros trabajos personales. El mismo está dictado por Facundo García Martoni, Technical Mentor en Platzi. El curso es de Platzi.
Con el curso se trata de aprende las bases de la extracción de datos en Internet y decubrir cómo funciona una aplicación de Web Scraping internamente. Se desarrollan scripts a través de herramientas como Python y las DevTools del navegador.
- Crear un scraper de noticias
- Utilizar XML Path Language
- Conocer los fundamentos de la web
- Aprender los conceptos básicos de Web Scraping
- Fundamentos de Web Scraping con Python y Xpath
Web scraping es una técnica usada por data scientists y backend developers para extraer información de internet, accede a esto usando el protocolo de tranferencias de hipertexto (HTTP) o a través de un navegador. Los datos extraídos usualmente son guardados en una base de datos, incluso en una hoja de cálculo para posteriores análisis. Puede hacerse de manera automática (bot) o manualmente.
Xpath es un lenguaje que sirve para apuntar a las partes de un documento XML. Xpath modela un documento XML como un árbol de nodos. Existen diferentes tipos de nodos: elementos, atributos, texto.
Las agencias de seguridad, aplicaciones que comparan precios más baratos entre hoteles, apliaciones de ecommerce que comparan precios entre diferentes competidores usan web scraping. Las agencias de marketing para analziar el contenido de tweets que se vuelven virales. En general el web scraping es una habilidad muy valiosa para cuando no tienes acceso a una API.
Es posible realizar web scraping con diferentes lenguajes de programación, como R o Js (y sus respectivas librerías) sin embargo Py es por excelencia el lenguaje de programación para esta tarea. Cuenta con la comunidad más grande para implementarlo.
Python es el lenguaje que mas soporte tiene en el mundo open source y en general para realizar este tipo de técnicas. Existen una cantidad de módulos para realizar por ti mismo web scrapping. Python es uno de los lenguajes que esta mas especializado para hacer ciencia de datos. Por lo tanto para los cienctificos de datos esto es una ventaja grande. Si eres backend developer y trabajas con Django puedes nuclear el conocimiento de web scraping con Django y realizar un proyecto sin irse de lenguaje en lenguaje.
-
-
Es una librería que nos permite controlar HTTP. El conjunto de reglas o protocolos de comunicación.
-
"El gobierno de su Majestad, Amazon, Google, Twilio, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundClound, Kippt, Readability y algunas organizaciones Federales de los Estados Unidos de América utilizan Requests internamente. Ha sido descargado más de 8,000,000 de veces desde PyPI. "
-
-
-
Es una libería de pyhton qué nos sirve para extraer información HTML y XML.
-
Recibe este nombre debido a un poema con el mismo nombre de Lewins Carroll en Alicia en el pais de la maravillas.
"Beautiful Soup, so rich and green, Waiting in a hot tureen! Who for such dainties would not stoop? Soup of the evening, beautiful Soup! "
-
-
- Podemos crear navegadores fantasmas para controlar sitios web de manera automática. Bots.
-
- Permite escribir reglas para extraer los datos, es extensible por diseño, es rápido y simple. Es usado por el UK para recolectar datos de la población.
Los siguientes son soluciones que no necesitan codear, y que en su mayoría tienen un propósito específico. Enfocados ecomerce o a funciones como tomar screenshots de PDFs. Automatizar y agendar actividades, y las soluciones están dadas como pluggins en el navegador hasta servicios.
-
Rvest Es una librería inspirada en Beautiful soup, diseñada para cosechar y recolectar datos de HTML. Se usa en R studio.
-
Puppeteer Es una librería de Js que puede usarse para diferentes propósitos entre los cuales el webscrapping es uno.
El protocolo HTTP es conjunto de reglas por el cual dos computadoras se comunican. Un cliente y un servidor. El cliente realiza peticiones a servidores.
Una petición se vería de la siguiente manera:
# Request
GET / HTTP/1.1
Host: developer.mozilla.org Accept-Language: fr
# Response
HTTP/1.1 200 OK
Date: Sat, 09 Oct 2010 14:28:02 GMT
Server: Apache
Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT ETag: "51142bc1-7449-479b075b2891b"
Accept-Ranges: bytes Content-Length: 29769 Content-Type: text/html
<!DOCTYPE html... (here comes the 29769 bytes of the requested web page)
HEADERS
Permiten al cliente y el servidor passar información adicional con un request o response HTTP.
Pueden agruparse en las siguientes categorías:
- Generales : Aplica para request y responses pero no tiene relación con la data transmitida en el cuerpo
- Request : Contienen más información acerca del recurso a ser fetch (extraer)
- Response : Contiene información adicional sobre respuestas. Como ubicación o el Server provider.
- Entity : Contien información acerca del recurso del cuerpo.
Existen muchas cabeceras o headers como:
- Accept
- Authorization
- Link
- Location
- Save-Data
Puedes consultar aquí toda la documentación sobre las cabeceras o Headers
HTTP nos permite transportar, HTML, CSS, webAPIs, Js. Se vale de protcolos como IP, TCP, UDP para comunicarse con el servidor, mediante TLS se hace la encriptación. Y el DNS asigna nombres a direcciones IP.
STATUS CODE:
Los estados son la forma en que el servidor da respuesta de las peticiones.
- Respuestas informativas (100–199).
- Respuestas satisfactorias (200–299).
- Redirecciones (300–399).
- Errores de los clientes (400–499).
- Errores de los servidores (500–599).
Documentación de Mozilla sobre STATUS CODE
MANEJO DE STATUS CODES
Una opción rápida para manejar los STATUS CODE es usar la librería Request.
- Abre un ambiente virtual.
- En la carpeta de trabajo:
pip install request
Luego en pyhton:
# Una idea sobre el manejo de los status Code.
import requests
response_platzi = requests.get('https://api.platzi.com')
print(response_platzi)
# <Response [404]>
if response_platzi.status_code == 200:
print("Aquí tienes lo que buscas")
elif response_platzi.status_code == 400:
print("Ups, no puedo darte nada en el momento. Nosotros nunca paramos de mejorar <3")
Un artículo para profundizar en cómo manejar la librería request y como manejar los status code Request Tutorial
HTML es una lenguaje que permite definir la estructura de una página web. Estrucutra, estilo, partes interactivas. En el contexto de webscraping HTML es muy importante.
Etiquetas está encerrado en angle brakets < >. Una etiqueta puede contener a otras etiquetas, las etiquetas tienen atributos.
El conocimiento de los atributos es crucial porque con ellos podremos conectar el scraper para extraer información.
La siguientes etiquetas son importantes para el web scraping y por ende se explican:
- < script >: Se utiliza para insertar o hacer referencia a un script o código que ejecuta una acción dentro de un docuemnto HTML.
- < meta >: Los metadatos son atributos que no se muestran en la página web, pero que sirven para identificar cosas como el autor de la página, el lenguaje en que está escrito, palabras clave para que los motores de búsqueda las indexen etc. Aporta información extra al documento. Aunque no son visibles al usuario de un sitio web si se pueden analizar de forma automática por código.
- < iframe >: Representa un contexto de navegación anidado, el cual permite incrustrar otra página HTML en la página actual.
Los archivos robots.txt exiten como una forma de administrar una página web. Proporciona información a los rastreadores de los buscadores sobre las páginas o los archivos que pueden solicitar o no de tu sitio web. Principalmente, se utiliza para evitar que tu sitio web se sobrecargue con solicitudes.
En el contexto de webscraping, le dice al scraper que puede y no extraer. Es decir hasta donde puede llegar. Ya que infrigir en la violación de estas directivas puede acarrear un problema legal con el sitio web al que estamos scrapeando.
robots.txt Contiene entre otros elementos:
USER-AGENT: Identificadores de quienes acceden a tu sitio web, puede ser un archivo.py
hasta un googlebot.
DIRECTIVAS
- ALLOW: Utiliza esta directiva para permitir a los motores de búsqueda rastrear un subdirectorio o una página, incluso en un directorio que de otro modo no estaría permitido.
- DISALLOW: Utiliza esta directiva para indicar a los motores de búsqueda que no accedan a archivos y páginas que se encuentren bajo una ruta específica.
Ejemplo:
url/robots.txt
Por ejemplo:
# Robots.txt file from http://www.nasa.gov
#
# All robots will spider the domain
User-agent: *
Disallow: /worldbook/
Disallow: /offices/oce/llis/
Para conocer más información de robots.txt.
Resumen de la sección anterior para introducirnos en la siguiente:
Se aprendio como esta conformada la estructura de un sitio web.
Sabemos que es HTTP, el protocolo de transferencia de hipertexto, el cual nos permite comunicar un cliente y un servidor en la red. En esta comunicación el servidor nos envia un documento de tipo HTML.
En este documento se define la estructura de un sitio web. Sabemos como esta conformada esta estructura y como contruir una.
Se analizo el archivo robots.txt, el cual define las reglas para poder extrar información o no, dependiendo sea el caso, de un sitio web.
Sabiendo lo anterior veremos Xpath, XML Path Language.
XML, Xtensible markup lenguage. Sirvio para definir interfaces, es un lenguaje de nodos o etiquetas.
Una técnica para extraer datos de allí es Xpath.
Xpath es a HTML lo que las REGEX son a un texto.
Es decir, Xpath es un lenguaje de patrones, expresiones que nos permitirá extraer datos de un HTML, ya que HTML y XML son muy parecidos. Puntualmente sirve para apuntar a partes de un documento XML.
Un nodo es lo mismo que la etiqueta y su contenido. Cuando hablemos de nodos nos estaremos refiriendo a una etiqueta HTML y todo lo que contiene dentro de si misma. Un nodo puede contener a otros nodos.
En otras palabras Xpath es un lenguaje que nos permitirá movernos entre nodos y navegar en los diferentes niveles de profundidad deseados con el fin extraer información. Para describir los nodos y relaciones con Xpath se usan una sintaxis de ejes.
Toscrape es un sandbox para practicar scraping.
El siguiente esquema es un arbol de nodos con el que se trabajará para hacer scraping del sandbox toscrape.
Para escribir expresiones se usara lo siguiente $x('')
. Entre las comillas se van a escribir las expresiones, las expresiones tienen diferentes símbolos que tienen una utilidad.
/
hace referencia a la raíz, o tambien significa un salto entre nodos. e.g$x(/html/body')
Muestra todo lo que hay dentro del body de html.//
Sirve para acceder a todos los nodos con la etiqueta seleccionada. e.g$x(//span)
muestra todas las etiquetas span.
//Fuente de trabajo Quotes to Scrape:
url ="http://quotes.toscrape.com/"
//Voy a la consola de modzilla y hago lo siguiente.
//Quiero extraer el texto de mi nodo h1.
$x('//h1/a/text()').map(x => x.wholeText)
//Devuelve en consola: ["Quotes to Scrape"]
//La función map pertenece a Js y la estoy usando
//para que me muestre todo el texto de la selección de Xpath.
..
Sirve para acceder a los nodos padre de la etiqueta tag. e.g$x(//span/..)
accede a todos los nodos padre de span..
Hace referencia al nodo actual. e.g.$x(//span/.)
es equivalent a$x(//span
.@
Sirve para traer los atributos. e.g$x(//span/@class
. Estoy trayendo todos los atributos class de los nodos span.
Cuando necesitamos un numero especifico dentro ese numero de nodos podemos utilizar [ ]
como si fuera una lista, entonce llamamos el numero en el que esta ordenado esa etiqueta.
$x('/html/body/div/div[1]')
nos devolveria el div[1]
dentro de la anterior busqueda.$x('/html/body/div/div[last()]')
podemos pedir el ultimo con las sentencia last.$x('//span[@class]')
aqui solicitamos todos lo span que tengan al menos un elemento de tipo class.$x('//span[@class="text"]')
pedimos una clase determinada, en este ejemplo solo pedimos la clase text.
Ejemplo de los anterior en la siguiente imagen:
Hay una forma de filtrar más avanzada y es con operadores lógicos.
Operadores lógicos en Xpath : Cabe notar que los operadores se usan dentro del predicado.
!=
: Operador de diferencia<>
: Operador de mayor - menorand
: Operador yor
: Opeador onot
: Operador negación
Ejemplo:
- Usando el operador != diferente, le estoy diciendo que me traiga todos los nodos span que tienen clase diferente a Texto.
$x('//span[@class!="text"]')
- Utilizando operadores and y or Que nos traiga todos los nodos span que tienen la clase 'text' y 'tag-item'.
$x('//span[@class="text" and @class="tag-item"]')
$x('//span[@class="text" or @class="tag-item"]')
- Usando operador not me devuelve todos los nodos que no tienen el atributo class.
$x('//span[not(@class)]')
Son comodines que usamos cuando no tenemos claro lo que queremos extraer.
/*
: Con asterisco le estoy diciendo que me traiga todos los nodos inmediatamente después de la expresión.//*
: En este caso le estoy diciendo que estoy saltando en todos los niveles en todas las direcciones.@*
: Traer todos los atributos de todos los nodos/node()
: Nos trae además de nodos el contenido, difiere de asterisco.
Ejemplos:
$x('/')
: Trae todo el documento porque representa la raíz de nuestro el html.$x('/*')
: * después de / pide que traiga todos los nodos que están debajo de / (* es el primer wildcard).$x('/html/*')
: Trae todos los nodos que están inmediatamente después de html.$x('//*')
: // es la expresión para saltar todos los niveles y con el * en todas las direcciones. Trae todos los nodos y todos los atributos de estos nodos.$x('//span[@class="text]/@*')
: Trae todos los span, que tengan como clase “text”, con @* trae todos los atributos. Dicho de otra forma, trae todos los atributos de todos los nodos de tipo span de clase “text”.$x('/html/body//div/@*')
: Todos los atributos (usando @*) de todos los div (usando //div) que están después de body.$x('//span[@class="text" and @itemprop="text"]/node()')
: Trae todos los spam que sean de clase “text” que tengan un atributo @itemprop “text” y de ahí (usando node()) traer todo lo que esté dentro de los spam que cumplen las condiciones.
node() a diferencia de * trae no solamente los nodos, sino también todo el contenido.
Para buscar cadenas de caracteres especificas dentro de un texto.
Los ejemplos son sobre la pagina de prueba topscrape.
starts-with("ruta_de_busqueda",“Texto a buscar”)
: Empezar con, Si escribimos solo un punto, este hará referencia al nodo actual.
Ejemplo, busca todos los nodos de tipo small (//small
), que cumplan clase “author”
y empiecen con “A”
(el .
inicial es para buscar en el nodo actual), luego /text()
para que nos devuelva el texto encontrado y con map lo podamos ver de manera textual.
$x('//small[@class="author" and starts-with(.,"A")]/text()').map(x => x.wholeText)
//Devuelve (4) ["Albert Einstein", "Albert Einstein", "Albert Einstein", "André Gide"]
contains (., “Texto a buscar”)
: Sirve para buscar por un contenido en el texto.
Ejemplo, con contains trae todos los autores que tengan en su nombre “Ro”:
$x('//small[@class="author" and contains(., "g")]/text()').map(x => x.wholeText)
//Devuelve ["J.K. Rowling"]
Nota: Debido a las versiones del lenguaje Xpath en los navegadores, 1.0, las funciones end-with y matches no están disponibles, pero una en código con python corren sin problemas.
end-with(.,"")
: Termina en.matches(.,"")
: Sirve para hacer una búsqueda en el texto de un nodo que coincida con una expresión regular.
Un eje representa una relación entre el nodo actual. Es usado para localizar nodos relativos a el nodo en el DOM tree.
self::div
-> se abrevia con . y se refiere al mismo nodo o div en este casochild::div
-> Trae los hijos del divdescendant::div
-> Trae todos los nodos que están en niveles inferioresdescendant-or-self::div
-> Trae la unión entre los descendientes y el mismo nodo div.
Ejemplo de utilización:
$x('/html/body/div/self::div')
$x('/html/body/div/descendant-or-self::div')
En el siguiente enlace, catálogos de libros, se hará scraping:
- Extracción de los títulos de los libros en venta.
$x('//article[@class="product_pod"]/h3/a/@title').map(x => x.value)
// Salida
// Array(20) [ "A Light in the Attic", "Tipping the Velvet", "Soumission", "Sharp Objects", "Sapiens: A Brief History of Humankind", "The Requiem Red", "The Dirty Little Secrets of Getting Your Dream Job", "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull", "The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics", "The Black Maria", … ]
- Extracción del precio de los libros.
$x('//article[@class="product_pod"]/div[@class="product_price"]/p[@class="price_color"]/text()').map(x => x.wholeText)
// Salida
// Array(20) [ "£51.77", "£53.74", "£50.10", "£47.82", "£54.23", "£22.65", "£33.34", "£17.93", "£22.60", "£52.15", … ]
- Extracción de información de la barra lateral izquierda de categorias.
$x('//div[@class="side_categories"]/ul[@class="nav nav-list"]/li/ul/li/a/text()').map(x => x.wholeText)
// Salida
// Array(50) [ "\n \n Travel\n \n ", "\n \n Mystery\n \n ", "\n \n Historical Fiction\n \n ", "\n \n Sequential Art\n \n ", "\n \n Classics\n \n ", "\n \n Philosophy\n \n ", "\n \n Romance\n \n ", "\n \n Womens Fiction\n \n ", "\n \n Fiction\n \n ", "\n \n Childrens\n \n ", … ]
En el siguiente enlace, libro del catalogo, se hará scraping:
- Extracción de la descripción.
$x('//article[@class="product_page"]/p/text()').map(x => x.wholeText)
//Salida
//Array [ "It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary with this special edition. Silverstein's humorous and creative verse can amuse the dowdiest of readers. Lemon-faced adults and fidgety kids sit still and read these rhythmic words and laugh and smile and love th It's hard to imagine a world without A Light in the Attic. This now-classic collection of poetry and drawings from Shel Silverstein celebrates its 20th anniversary with this special edition. Silverstein's humorous and creative verse can amuse the dowdiest of readers. Lemon-faced adults and fidgety kids sit still and read these rhythmic words and laugh and smile and love that Silverstein. Need proof of his genius? RockabyeRockabye baby, in the treetopDon't you know a treetopIs no safe place to rock?And who put you up there,And your cradle, too?Baby, I think someone down here'sGot it in for you. Shel, you never sounded so good. ...more" ]
- Extracción del stock disponible del libro.
$x('//article[@class="product_page"]//p[@class="instock availability"]/text()').map(x => x.wholeText)
// Salida
// Array [ "\n ", "\n \n In stock (22 available)\n \n" ]
Este proyecto es un scraper de noticias del diario La Republica. Vamos a extraer de este periodico, encabezados y cuerpo de las noticias diariamente y almancenarlos para un posterior análisis. Como análisis de Texto, marketing, análisis de sentimientos y demás.
Nota: Hay que observar https://www.larepublica.co/robots.txt para tener claro a qué podemos o no acceder con nuestro scraper
Es necesario tener buenas prácticas para desarrollar, en python como todo lenguaje tiene sus propias. A continuación la configuración inicial de este proyecto.
-
Creamos una carpeta para el proyecto. En este caso larepublica_scraper
-
Iniciamos git con
git init
. Si lo ya tenemos iniciado, no lo hacemos. -
Creamos el entorno virtual es:
$ python3 -m venv venv (Linux)
$ py -m venv venv (Windows)
El ultimo venv es el nombre del entorno. -
Creamos el archivo
.gitignore
porque no queremos trackearlo, ya que seria bastante pesado llevarlo al control de versiones. Escribimos en él la carpeta/venv
-
Activamos nuestro entorno virtual desde consola con:
$ ven\Scripts\activate (Windows)
$ source venv/bin/activate (Linux)
-
Si trabajas en VsCode. Creamos un Workspace para tener una estructura más organizada “save as workspace” y así tener un workspace para que VS tenga idea de qué tenemos en esta capeta.
-
Instalamos las dependencias de este proyecto:
- Request: Para realizar peticiones
- Lxml: Para utilizar Xpath
- Autopep8: Ayuda a formatear el código según los estilos oficiales dle lenguaej,e
$ pip install requests lxml autopep8
En esta sección lo que se hace es contruir las expresiones de XPath. Lo que se hace es navegar a través de la pagina, y buscar los elementos adecuados para armar las mismas. Las empresiones quedan como sigue:
//Links
$x('//div/a[contains(@class, "kicker")]/@href').map(x => x.wholeText)
//Titulo
$x('//div[@class="mb-auto"]/h2/a/text()').map(x => x.wholeText)
//Resumen
$x('//div[@class="lead"]/p/text()').map(x => x.wholeText)
//Autor
$x('//div[@class="autorArticle"]/p/text()').map(x => x.wholeText)
//Cuerpo
$x('//div[@class="html-content"]/p[not(@class)]/text()').map(x => x.wholeText)
Una vez probadas las expresiones y obteniendo la información que queremos desde la consola del navegador lo único que extraemos de las lineas de código son las expresiones xpath para utilizarlas en la sección siguiente junto con python.
Lo que haremos en esta sección, ya del proyecto del scraper, es obtener todos los links con python.
Definimos dos funciones iniciales:
parse_home()
: Función para extraer los link de las noticias.run()
: Función principal que se va a correr cuando ejecutemos el archivo.
Script para obtener los links: scraper.py
Debemos tener en cuenta lo siguiente a la hora de realizar el código, estos son errores Asociados y comentarios:
-
Primera recomendación es que uses el print statement para debuggear tus código línea por línea. Es una práctica que resulta muy útil.
-
¿Tienes una lista vacía?, ¿No te trae los links al ejecutar scraper.py, pero tu expresion Xpath retorna en la consola de Chrome lo que buscas?.
- R: Cambia tu expresión Xpath. Es posible que varias cosas estén ocurriendo y una de las más probables es la expresión Xpath, recuerda que tienes muchas formas de llegar al mismo nodo.
Script donde guardamos las noticias en archivos de texto: scraper.py
Vamos a realizar una lógica para ir de cada link al sitio de cada noticia y de ahí extraer:
- Titulo
- Resumen
- Cuerpo
Esto lo haremos creando una función aparte llamada:
parse_notices(link, today)
: Función para parsear el html. Recibe el link de lugar y la fecha para crear la carpeta.