├── larepublica_scraper ├── .gitignore ├── configuracion_inicial.md └── scraper.py ├── LICENSE └── README.md /larepublica_scraper/.gitignore: -------------------------------------------------------------------------------- 1 | # Carpetas 2 | venv/ 3 | 18-10-2020/ 4 | 5 | # Archivos 6 | /larepublica_scraper.code-workspace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Franco Manca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /larepublica_scraper/configuracion_inicial.md: -------------------------------------------------------------------------------- 1 | # Configuración inicial del proyecto 2 | 3 | 1. Creamos una carpeta para el proyecto. En este caso larepublica_scraper 4 | 5 | 2. Iniciamos git con `git init`. Si lo ya tenemos iniciado, no lo hacemos. 6 | 7 | 3. Creamos el entorno virtual es: 8 | `$ python3 -m venv venv (Linux)` 9 | `$ py -m venv venv (Windows)` 10 | El ultimo venv es el nombre del entorno. 11 | 12 | 4. Creamos el archivo `.gitignore` porque no queremos trackearlo, ya que seria bastante pesado llevarlo al control de versiones. Escribimos en él la carpeta `/venv` 13 | 14 | 5. Activamos nuestro entorno virtual desde consola con: 15 | 16 | `$ ven\Scripts\activate (Windows)` 17 | `$ source venv/bin/activate (Linux)` 18 | 19 | 6. 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. 20 | 21 | 7. Instalamos las dependencias de este proyecto: 22 | - Request: Para realizar peticiones 23 | - Lxml: Para utilizar Xpath 24 | - Autopep8: Ayuda a formatear el código según los estilos oficiales dle lenguaej,e 25 | 26 | `$ pip install requests lxml autopep8` 27 | -------------------------------------------------------------------------------- /larepublica_scraper/scraper.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import lxml.html as html 3 | 4 | import os 5 | import datetime 6 | 7 | # Constantes 8 | HOME_URL = 'https://www.larepublica.co/' 9 | 10 | XPATH_LINK_TO_ARTICLE = '//div/a[contains(@class, "kicker")]/@href' 11 | XPATH_TITLE = '//div[@class="mb-auto"]/text-fill/a/text()' 12 | XPATH_SUMMARY = '//div[@class="lead"]/p/text()' 13 | XPATH_AUTHOR = '//div[@class="autorArticle"]/p/text()' 14 | XPATH_BODY = '//div[@class="html-content"]/p[not(@class)]/text()' 15 | 16 | 17 | def parse_notices(link, today): 18 | ''' Función para parsear el html. 19 | Recibe el link de lugar y la fecha para crear la carpeta''' 20 | try: 21 | response = requests.get(link) 22 | if response.status_code == 200: 23 | # Traigo el docuemnto html de la noticia. 24 | notice = response.content.decode('utf-8') 25 | parsed = html.fromstring(notice) 26 | 27 | #Quiero traer el título, el cuerpo y el resumen, hago una validación 28 | # try -except, estoy haciendolo para los índices de la lista. 29 | # Pueden haber noticias con nodos faltantes por lo que arrojará un error 30 | try: 31 | #Traemos el primer elemento de la lista. 32 | title = parsed.xpath(XPATH_TITLE)[0] 33 | print(title) 34 | # No es deseable tener comillas en los títulos porque presentan un error en OS. 35 | # Para solucionar esto, hacemos uso de que title es un str y del metodo replace() 36 | title = title.replace('\"', '') 37 | summary = parsed.xpath(XPATH_SUMMARY)[0] 38 | body = parsed.xpath(XPATH_BODY) 39 | except IndexError: 40 | return 41 | 42 | # Guardamos en un archivo 43 | # with es un manejador contextual. Si algo sucede y el script se cierra, mantiene las cosas 44 | # de manera segura y así no se corrompe el archivo. 45 | with open(f'{today}/{title}.txt', 'w', encoding='utf-8') as f: 46 | f.write(title) 47 | f.write('\n\n') 48 | f.write(summary) 49 | f.write('\n\n') 50 | for p in body: 51 | f.write(p) 52 | f.write('\n') 53 | else: 54 | raise ValueError(f'Error: {response.status_code}') 55 | except ValueError as ve: 56 | print(ve) 57 | 58 | 59 | def parse_home(): 60 | ''' Para extraer los link de las noticias''' 61 | 62 | # Creamos un bloque try para manejar los errores. Y manejar los Status Code. 63 | try: 64 | response = requests.get(HOME_URL) 65 | 66 | # Lógica para traer los links. 67 | if response.status_code == 200: 68 | # .content trae el HTML que necesita ser traducido con un decode para que python lo entienda 69 | # en terminos de caracteres, me devuelve un string que noes más que el HTML crudo. 70 | home = response.content.decode('utf-8') 71 | 72 | # En esta línea uso el parser para transformar el contentido 73 | # html a un archivo que sea de utilidad para las expresiones xpath 74 | parsed = html.fromstring(home) 75 | 76 | # En esta línea estoy usando el archivo parseado con la función xpath y le paso por parámetro mi constante 77 | # la cual almacena la expresión Xpath. 78 | links_to_notices = parsed.xpath(XPATH_LINK_TO_ARTICLE) 79 | 80 | # For debugg 81 | # print(len(links_to_notices)) 82 | # print(type(links_to_notices)) 83 | # print(links_to_notices) 84 | 85 | # Traigo una fecha con la función fecha. Y Today, la fecha del día de hoy. La variable today 86 | # se almacena un objeto de tipo fecha, pero nos interesa más tener una cadena de caracteres que contenga la fecha 87 | # en determinado formato que será guardado en la carpeta y con la función strftime logramos esto 88 | today = datetime.date.today().strftime("%d-%m-%Y") 89 | # Este condicional sirve para decirle que si no existe una carpeta con la fehca del día de hoy 90 | # me cree una carpeta. 91 | if not os.path.isdir(today): 92 | os.mkdir(today) 93 | # Creo la función para recorrer la lista de links y ejecuto en cada ciclo la función parse_notice() 94 | for link in links_to_notices: 95 | parse_notices(link, today) 96 | 97 | else: 98 | #Elevamos el error para ser capturado en el try-except, too lo que sea un error. 99 | raise ValueError(f'Error: {response.status_code}') 100 | except ValueError as ve: 101 | print(ve) 102 | 103 | def run(): 104 | ''' Función principal que se va a correr cuando ejecutemos el archivo''' 105 | parse_home() 106 | 107 | 108 | if __name__ == '__main__': 109 | run() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Fundamentos de Web Scraping con Python y Xpath

4 | 5 |
6 | 7 | ## Introducción al documento 8 | 9 | El contenido de este documento son **apuntes prácticos** del [Curso de Fundamentos de Web Scraping con Python y Xpath](https://platzi.com/cursos/web-scraping/) y busca ser una guía para futuros trabajos personales. El mismo está dictado por [Facundo García Martoni](https://twitter.com/facmartoni), Technical Mentor en Platzi. El curso es de [Platzi](https://platzi.com). 10 | 11 | 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. 12 | 13 | ## Objetivos del documento 14 | 15 | - Crear un scraper de noticias 16 | - Utilizar XML Path Language 17 | - Conocer los fundamentos de la web 18 | - Aprender los conceptos básicos de Web Scraping 19 | 20 | ## Tabla de contenido 21 | - [Fundamentos de Web Scraping con Python y Xpath](#fundamentos-de-web-scraping-con-python-y-xpath) 22 | - [Introducción al web scraping](#introducción-al-web-scraping) 23 | - [¿Qué es el web scraping?](#qué-es-el-web-scraping) 24 | - [¿Por qué aprender web scraping hoy?](#por-qué-aprender-web-scraping-hoy) 25 | - [Python: el lenguaje más poderoso para extraer datos](#python-el-lenguaje-más-poderoso-para-extraer-datos) 26 | - [Principales frameworks(librerias) en python](#principales-frameworkslibrerias-en-python) 27 | - [Herramientas de web scrapping](#herramientas-de-web-scrapping) 28 | - [Otras librerais y otros lenguajes](#otras-librerais-y-otros-lenguajes) 29 | - [Fundamentos de la web](#fundamentos-de-la-web) 30 | - [Entender HTTP](#entender-http) 31 | - [¿Qué es HTML?](#qué-es-html) 32 | - [Robots.txt: permisos y consideraciones al hacer web scraping](#robotstxt-permisos-y-consideraciones-al-hacer-web-scraping) 33 | - [XML Path Language](#xml-path-language) 34 | - [XML Path Language, ¿Que es?](#xml-path-language-que-es) 35 | - [Tipos de nodos en XPath](#tipos-de-nodos-en-xpath) 36 | - [Expresiones en XPath](#expresiones-en-xpath) 37 | - [Predicados en Xpath](#predicados-en-xpath) 38 | - [Operadores en Xpath](#operadores-en-xpath) 39 | - [Wildcards en Xpath](#wildcards-en-xpath) 40 | - [In-text search en Xpath](#in-text-search-en-xpath) 41 | - [XPath Axes](#xpath-axes) 42 | - [Resumen de Xpath](#resumen-de-xpath) 43 | - [Aplicando lo aprendido](#aplicando-lo-aprendido) 44 | - [Proyecto: scraper de noticias](#proyecto-scraper-de-noticias) 45 | - [Un proyecto para tu portafolio: scraper de noticias](#un-proyecto-para-tu-portafolio-scraper-de-noticias) 46 | - [Entorno de trabajo](#entorno-de-trabajo) 47 | - [Construcción de las expresiones de XPath](#construcción-de-las-expresiones-de-xpath) 48 | - [Obteniendo los links de los artículos con Python](#obteniendo-los-links-de-los-artículos-con-python) 49 | - [Guardando las noticias en archivos de texto](#guardando-las-noticias-en-archivos-de-texto) 50 | 51 | # Fundamentos de Web Scraping con Python y Xpath 52 | 53 | ## Introducción al web scraping 54 | 55 | ### ¿Qué es el web scraping? 56 | 57 | ![web_scrapping](https://imgur.com/A2tL463.png) 58 | 59 | **[Web scraping](https://es.wikipedia.org/wiki/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. 60 | 61 | **[Xpath](https://es.wikipedia.org/wiki/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. 62 | 63 | ### ¿Por qué aprender web scraping hoy? 64 | 65 | 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. 66 | 67 | 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. 68 | 69 | ### Python: el lenguaje más poderoso para extraer datos 70 | 71 | 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. 72 | 73 | #### Principales frameworks(librerias) en python 74 | 75 | - [Request](https://requests.readthedocs.io/es/latest/) 76 | - Es una librería que nos permite controlar HTTP. El conjunto de reglas o protocolos de comunicación. 77 | 78 | - "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. " 79 | 80 | - [Beautiful Soup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/#) 81 | - Es una libería de pyhton qué nos sirve para extraer información HTML y XML. 82 | - Recibe este nombre debido a un poema con el mismo nombre de Lewins Carroll en Alicia en el pais de la maravillas. 83 | 84 | >"Beautiful Soup, so rich and green, Waiting in a hot tureen! Who for such dainties would not stoop? Soup of the evening, beautiful Soup! " 85 | 86 | - [Selenium](https://selenium-python.readthedocs.io/) 87 | - Podemos crear navegadores fantasmas para controlar sitios web de manera automática. Bots. 88 | 89 | - [Scrapy](https://scrapy.org/) 90 | - Permite escribir reglas para extraer los datos, es extensible por diseño, 91 | es rápido y simple. Es usado por el UK para recolectar datos de la población. 92 | 93 | #### Herramientas de web scrapping 94 | 95 | Los siguientes son soluciones que no necesitan codear, y que en su mayoría tienen un propósito específico. 96 | 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. 97 | 98 | - [ParseHub](https://parsehub.com/) 99 | - [webscraper.io](https://www.webscraper.io/) 100 | 101 | #### Otras librerais y otros lenguajes 102 | 103 | - [Rvest](https://www.datanalytics.com/libro_r/web-scraping.html) Es una librería inspirada en Beautiful soup, diseñada para cosechar y recolectar datos de HTML. Se usa en R studio. 104 | 105 | - [Puppeteer](https://pptr.dev/) Es una librería de Js que puede usarse para diferentes propósitos entre los cuales el webscrapping es uno. 106 | 107 | ## Fundamentos de la web 108 | 109 | ### Entender HTTP 110 | 111 | [El protocolo HTTP](https://es.wikipedia.org/wiki/Protocolo_de_transferencia_de_hipertexto) es conjunto de reglas por el cual dos computadoras se comunican. Un cliente y un servidor. El cliente realiza peticiones a servidores. 112 | 113 | Una petición se vería de la siguiente manera: 114 | 115 | ```shell 116 | # Request 117 | GET / HTTP/1.1 118 | Host: developer.mozilla.org Accept-Language: fr 119 | 120 | # Response 121 | HTTP/1.1 200 OK 122 | Date: Sat, 09 Oct 2010 14:28:02 GMT 123 | Server: Apache 124 | Last-Modified: Tue, 01 Dec 2009 20:18:22 GMT ETag: "51142bc1-7449-479b075b2891b" 125 | Accept-Ranges: bytes Content-Length: 29769 Content-Type: text/html 126 | 184 | 185 | if response_platzi.status_code == 200: 186 | print("Aquí tienes lo que buscas") 187 | elif response_platzi.status_code == 400: 188 | print("Ups, no puedo darte nada en el momento. Nosotros nunca paramos de mejorar <3") 189 | ``` 190 | 191 | Un artículo para profundizar en cómo manejar la librería request y como manejar los status code [Request Tutorial](https://realpython.com/python-requests/#status-codes) 192 | 193 | ### ¿Qué es HTML? 194 | 195 | [HTML](https://es.wikipedia.org/wiki/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. 196 | 197 | **Etiquetas** está encerrado en angle brakets **< >**. Una etiqueta puede contener a otras etiquetas, las etiquetas tienen **atributos**. 198 | 199 | El conocimiento de los **atributos** es crucial porque con ellos podremos conectar el scraper para extraer información. 200 | 201 | La siguientes etiquetas son importantes para el web scraping y por ende se explican: 202 | 203 | - **[< script >](https://developer.mozilla.org/es/docs/Web/HTML/Elemento/script)**: Se utiliza para insertar o hacer referencia a un script o código que ejecuta una acción dentro de un docuemnto HTML. 204 | - **[< meta >](https://es.wikipedia.org/wiki/Etiqueta_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. 205 | - **[< iframe >](https://developer.mozilla.org/es/docs/Web/HTML/Elemento/iframe)**: Representa un contexto de navegación anidado, el cual permite incrustrar otra página HTML en la página actual. 206 | 207 | ### Robots.txt: permisos y consideraciones al hacer web scraping 208 | 209 | Los archivos [robots.txt](https://support.google.com/webmasters/answer/6062608?hl=es&ref_topic=6061961) 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. 210 | Principalmente, se utiliza para evitar que tu sitio web se sobrecargue con solicitudes. 211 | 212 | 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 213 | de estas directivas puede acarrear un problema legal con el sitio web al que estamos scrapeando. 214 | 215 | **robots.txt** Contiene entre otros elementos: 216 | 217 | **USER-AGENT**: Identificadores de quienes acceden a tu sitio web, puede ser un `archivo.py` hasta un googlebot. 218 | 219 | **DIRECTIVAS** 220 | 221 | - **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. 222 | - **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. 223 | 224 | -------------- 225 | Ejemplo: 226 | 227 | ```shell 228 | url/robots.txt 229 | Por ejemplo: 230 | 231 | # Robots.txt file from http://www.nasa.gov 232 | # 233 | # All robots will spider the domain 234 | 235 | User-agent: * 236 | Disallow: /worldbook/ 237 | Disallow: /offices/oce/llis/ 238 | 239 | ``` 240 | --------------- 241 | Para conocer más información de [robots.txt](https://ahrefs.com/blog/es/robots-txt/). 242 | 243 | ------- 244 | 245 | Resumen de la sección anterior para introducirnos en la siguiente: 246 | 247 | Se aprendio como esta conformada la estructura de un sitio web. 248 | 249 | 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. 250 | 251 | En este documento se define la estructura de un sitio web. Sabemos como esta conformada esta estructura y como contruir una. 252 | 253 | 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. 254 | 255 | Sabiendo lo anterior veremos Xpath, XML Path Language. 256 | 257 | ## XML Path Language 258 | 259 | ### XML Path Language, ¿Que es? 260 | 261 | **[XML, Xtensible markup lenguage](https://es.wikipedia.org/wiki/Extensible_Markup_Language)**. Sirvio para definir interfaces, es un lenguaje de nodos o etiquetas. 262 | 263 | Una técnica para extraer datos de allí es **[Xpath](https://es.wikipedia.org/wiki/XPath)**. 264 | 265 | Xpath es a HTML lo que las REGEX son a un texto. 266 | 267 | 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. 268 | 269 | ### Tipos de nodos en XPath 270 | 271 | 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. 272 | 273 | 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. 274 | 275 | [Toscrape](http://toscrape.com/) es un sandbox para practicar scraping. 276 | 277 | El siguiente esquema es un arbol de nodos con el que se trabajará para hacer scraping del sandbox toscrape. 278 | 279 | ![arbol_de_nodos](https://imgur.com/TSIFWUJ.png) 280 | 281 | ### Expresiones en XPath 282 | 283 | 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. 284 | 285 | - `/` 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. 286 | - `//` Sirve para acceder a todos los nodos con la etiqueta seleccionada. e.g `$x(//span)` muestra todas las etiquetas span. 287 | 288 | ```js 289 | //Fuente de trabajo Quotes to Scrape: 290 | 291 | url ="http://quotes.toscrape.com/" 292 | 293 | //Voy a la consola de modzilla y hago lo siguiente. 294 | 295 | //Quiero extraer el texto de mi nodo h1. 296 | 297 | $x('//h1/a/text()').map(x => x.wholeText) 298 | //Devuelve en consola: ["Quotes to Scrape"] 299 | //La función map pertenece a Js y la estoy usando 300 | //para que me muestre todo el texto de la selección de Xpath. 301 | ``` 302 | 303 | - `..` Sirve para acceder a los nodos padre de la etiqueta tag. e.g `$x(//span/..)` accede a todos los nodos padre de span. 304 | - `.` Hace referencia al nodo actual. e.g. `$x(//span/.)` es equivalent a `$x(//span`. 305 | - `@` Sirve para traer los atributos. e.g `$x(//span/@class` . Estoy trayendo todos los atributos class de los nodos span. 306 | 307 | ### Predicados en Xpath 308 | 309 | 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. 310 | 311 | - `$x('/html/body/div/div[1]')` nos devolveria el div `[1]` dentro de la anterior busqueda. 312 | - `$x('/html/body/div/div[last()]')` podemos pedir el ultimo con las sentencia last. 313 | - `$x('//span[@class]')` aqui solicitamos todos lo span que tengan al menos un elemento de tipo class. 314 | - `$x('//span[@class="text"]')` pedimos una clase determinada, en este ejemplo solo pedimos la clase text. 315 | 316 | Ejemplo de los anterior en la siguiente imagen: 317 | ![ejemplo_predicados](https://imgur.com/qV6yTvb.png) 318 | 319 | ### Operadores en Xpath 320 | 321 | Hay una forma de filtrar más avanzada y es con operadores lógicos. 322 | 323 | Operadores lógicos en Xpath : 324 | Cabe notar que los operadores se usan dentro del predicado. 325 | 326 | - `!=`: Operador de diferencia 327 | - `<>`: Operador de mayor - menor 328 | - `and`: Operador y 329 | - `or`: Opeador o 330 | - `not`: Operador negación 331 | 332 | Ejemplo: 333 | 334 | - Usando el operador != diferente, le estoy diciendo que me traiga todos los nodos span que tienen clase diferente a Texto. 335 | 336 | ```js 337 | $x('//span[@class!="text"]') 338 | ``` 339 | 340 | - Utilizando operadores and y or 341 | Que nos traiga todos los nodos span que tienen la clase 'text' y 'tag-item'. 342 | 343 | ```js 344 | $x('//span[@class="text" and @class="tag-item"]') 345 | 346 | $x('//span[@class="text" or @class="tag-item"]') 347 | ``` 348 | 349 | - Usando operador not me devuelve todos los nodos que no tienen el atributo class. 350 | 351 | ```js 352 | $x('//span[not(@class)]') 353 | ``` 354 | 355 | ### Wildcards en Xpath 356 | 357 | Son comodines que usamos cuando no tenemos claro lo que queremos extraer. 358 | 359 | - `/*` : Con asterisco le estoy diciendo que me traiga todos los nodos inmediatamente después de la expresión. 360 | - `//*` : En este caso le estoy diciendo que estoy saltando en todos los niveles en todas las direcciones. 361 | - `@*`: Traer todos los atributos de todos los nodos 362 | - `/node()` : Nos trae además de nodos el contenido, difiere de asterisco. 363 | 364 | Ejemplos: 365 | 366 | - `$x('/')`: Trae todo el documento porque representa la raíz de nuestro el html. 367 | - `$x('/*')` : * después de / pide que traiga todos los nodos que están debajo de / (* es el primer wildcard). 368 | - `$x('/html/*')`: Trae todos los nodos que están inmediatamente después de html. 369 | - `$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. 370 | - `$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”. 371 | - `$x('/html/body//div/@*')`: Todos los atributos (usando @*) de todos los div (usando //div) que están después de body. 372 | - `$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. 373 | 374 | node() a diferencia de * trae no solamente los nodos, sino también todo el contenido. 375 | 376 | ### In-text search en Xpath 377 | 378 | Para buscar cadenas de caracteres especificas dentro de un texto. 379 | 380 | Los ejemplos son sobre la pagina de prueba [topscrape](http://quotes.toscrape.com/). 381 | 382 | - `starts-with("ruta_de_busqueda",“Texto a buscar”)`: Empezar con, Si escribimos solo un punto, este hará referencia al nodo actual. 383 | 384 | 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. 385 | 386 | ```js 387 | $x('//small[@class="author" and starts-with(.,"A")]/text()').map(x => x.wholeText) 388 | 389 | //Devuelve (4) ["Albert Einstein", "Albert Einstein", "Albert Einstein", "André Gide"] 390 | ``` 391 | 392 | - `contains (., “Texto a buscar”)` : Sirve para buscar por un contenido en el texto. 393 | 394 | Ejemplo, con contains trae todos los autores que tengan en su nombre “Ro”: 395 | 396 | ```js 397 | $x('//small[@class="author" and contains(., "g")]/text()').map(x => x.wholeText) 398 | 399 | //Devuelve ["J.K. Rowling"] 400 | ``` 401 | 402 | > 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. 403 | 404 | - `end-with(.,"")`: Termina en. 405 | - `matches(.,"")`: Sirve para hacer una búsqueda en el texto de un nodo que coincida con una expresión regular. 406 | 407 | ### XPath Axes 408 | 409 | Un eje representa una relación entre el nodo actual. Es usado para localizar nodos relativos a el nodo en el DOM tree. 410 | 411 | ![axes](https://imgur.com/5ltleiq.png) 412 | 413 | - `self::div` -> se abrevia con . y se refiere al mismo nodo o div en este caso 414 | - `child::div` -> Trae los hijos del div 415 | - `descendant::div` -> Trae todos los nodos que están en niveles inferiores 416 | - `descendant-or-self::div` -> Trae la unión entre los descendientes y el mismo nodo div. 417 | 418 | Ejemplo de utilización: 419 | ```js 420 | $x('/html/body/div/self::div') 421 | $x('/html/body/div/descendant-or-self::div') 422 | ``` 423 | 424 | ### Resumen de Xpath 425 | 426 | ![resumen_xpath](https://imgur.com/XffXu5F.png) 427 | 428 | ### Aplicando lo aprendido 429 | 430 | En el siguiente enlace, [catálogos de libros](http://books.toscrape.com/), se hará scraping: 431 | 432 | - Extracción de los títulos de los libros en venta. 433 | 434 | ```js 435 | 436 | $x('//article[@class="product_pod"]/h3/a/@title').map(x => x.value) 437 | 438 | // Salida 439 | // 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", … ] 440 | 441 | ``` 442 | 443 | - Extracción del precio de los libros. 444 | 445 | ```js 446 | 447 | $x('//article[@class="product_pod"]/div[@class="product_price"]/p[@class="price_color"]/text()').map(x => x.wholeText) 448 | 449 | // Salida 450 | // Array(20) [ "£51.77", "£53.74", "£50.10", "£47.82", "£54.23", "£22.65", "£33.34", "£17.93", "£22.60", "£52.15", … ] 451 | 452 | ``` 453 | 454 | - Extracción de información de la barra lateral izquierda de categorias. 455 | 456 | ```js 457 | 458 | $x('//div[@class="side_categories"]/ul[@class="nav nav-list"]/li/ul/li/a/text()').map(x => x.wholeText) 459 | 460 | // Salida 461 | // 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 ", … ] 462 | ``` 463 | 464 | En el siguiente enlace, [libro del catalogo](http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html), se hará scraping: 465 | 466 | - Extracción de la descripción. 467 | 468 | ```js 469 | 470 | $x('//article[@class="product_page"]/p/text()').map(x => x.wholeText) 471 | 472 | //Salida 473 | //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" ] 474 | 475 | ``` 476 | 477 | - Extracción del stock disponible del libro. 478 | 479 | ```js 480 | 481 | $x('//article[@class="product_page"]//p[@class="instock availability"]/text()').map(x => x.wholeText) 482 | 483 | // Salida 484 | // Array [ "\n ", "\n \n In stock (22 available)\n \n" ] 485 | 486 | ``` 487 | 488 | ## Proyecto: scraper de noticias 489 | 490 | ### Un proyecto para tu portafolio: scraper de noticias 491 | 492 | Este proyecto es un scraper de noticias del diario [La Republica](https://www.larepublica.co/). 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. 493 | 494 | >Nota: Hay que observar https://www.larepublica.co/robots.txt para tener claro a qué podemos o no acceder con nuestro scraper 495 | 496 | #### Entorno de trabajo 497 | 498 | 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. 499 | 500 | 1. Creamos una carpeta para el proyecto. En este caso larepublica_scraper 501 | 502 | 2. Iniciamos git con `git init`. Si lo ya tenemos iniciado, no lo hacemos. 503 | 504 | 3. Creamos el entorno virtual es: 505 | `$ python3 -m venv venv (Linux)` 506 | `$ py -m venv venv (Windows)` 507 | El ultimo venv es el nombre del entorno. 508 | 509 | 4. Creamos el archivo `.gitignore` porque no queremos trackearlo, ya que seria bastante pesado llevarlo al control de versiones. Escribimos en él la carpeta `/venv` 510 | 511 | 5. Activamos nuestro entorno virtual desde consola con: 512 | 513 | `$ ven\Scripts\activate (Windows)` 514 | `$ source venv/bin/activate (Linux)` 515 | 516 | 6. 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. 517 | 518 | 7. Instalamos las dependencias de este proyecto: 519 | - Request: Para realizar peticiones 520 | - Lxml: Para utilizar Xpath 521 | - Autopep8: Ayuda a formatear el código según los estilos oficiales dle lenguaej,e 522 | 523 | `$ pip install requests lxml autopep8` 524 | 525 | ### Construcción de las expresiones de XPath 526 | 527 | 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: 528 | 529 | ```js 530 | //Links 531 | $x('//div/a[contains(@class, "kicker")]/@href').map(x => x.wholeText) 532 | //Titulo 533 | $x('//div[@class="mb-auto"]/h2/a/text()').map(x => x.wholeText) 534 | //Resumen 535 | $x('//div[@class="lead"]/p/text()').map(x => x.wholeText) 536 | //Autor 537 | $x('//div[@class="autorArticle"]/p/text()').map(x => x.wholeText) 538 | //Cuerpo 539 | $x('//div[@class="html-content"]/p[not(@class)]/text()').map(x => x.wholeText) 540 | ``` 541 | 542 | 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. 543 | 544 | ### Obteniendo los links de los artículos con Python 545 | 546 | Lo que haremos en esta sección, ya del proyecto del scraper, es obtener todos los links con python. 547 | 548 | Definimos dos funciones iniciales: 549 | 550 | - `parse_home()`: Función para extraer los link de las noticias. 551 | - `run()`: Función principal que se va a correr cuando ejecutemos el archivo. 552 | 553 | Script para obtener los links: [scraper.py](https://github.com/francomanca93/fundamentos-web-scraping-python-xpath/blob/main/larepublica_scraper/scraper.py) 554 | 555 | Debemos tener en cuenta lo siguiente a la hora de realizar el código, estos son errores Asociados y comentarios: 556 | 557 | 1. 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. 558 | 559 | 2. ¿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?. 560 | 561 | - **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. 562 | 563 | ### Guardando las noticias en archivos de texto 564 | 565 | Script donde guardamos las noticias en archivos de texto: [scraper.py](https://github.com/francomanca93/fundamentos-web-scraping-python-xpath/blob/main/larepublica_scraper/scraper.py) 566 | 567 | Vamos a realizar una lógica para ir de cada link al sitio de cada noticia y de ahí extraer: 568 | 569 | - Titulo 570 | - Resumen 571 | - Cuerpo 572 | 573 | Esto lo haremos creando una función aparte llamada: 574 | 575 | - `parse_notices(link, today)`: Función para parsear el html. Recibe el link de lugar y la fecha para crear la carpeta. 576 | --------------------------------------------------------------------------------