¡Esta es una revisión vieja del documento!
Tabla de Contenidos
Extracción de contenidos web con Python
Módulo perteneciente al curso Python avanzado para proyectos de seguridad
Entre las técnicas que disponemos para extraer contenidos de la web podemos destacar:
- Screen scraping: Técnica que permite obtener información moviéndote por la pantalla, por ejemplo registrando los clicks del usuario.
- Web scraping: Trata es obtener la información de un recurso como por ejemplo de una página web en HTML y procesa esa información para extraer datos relevantes.
- Report mining: Técnica que también pretende obtener información, pero en este caso a partir de un archivo (HTML, RDF, CSV, etc). Con esta aproximación de definición podemos crear un mecanismo simple y rápido sin necesidad de escribir una API y como característica principal podemos indicar que el sistema no necesita de una conexión ya que al trabajar a partir de un fichero es posible extraer la información de forma offline y sin necesidad de utilizar ninguna API.
- Spider: Los spiders (crawlers/arañas) son scripts o programas que siguen unas reglas para moverse por un sitio web y tienen como objetivo recolectar información imitando la interacción que realizaría un usuario con el sitio web. La idea es que sea solo necesario escribir las reglas para extraer los datos que nos interesen y dejar que el spyder rastree todo el sitio web en busca de enlaces.
En esta unidad didáctica nos vamos a centrar en la técnica de webscraping que permite la recolección o extracción de datos de páginas web de forma automática. Es un campo muy activo y en continuo desarrollo que comparte objetivos con la web semántica, el procesamiento de textos automático, inteligencia artificial e interacción humano-computador.
El web scraping es una técnica que permite la extracción de información de sitios web, transformando datos no estructurados como los datos en formato HTML en datos estructurados.
IMAAAAAAAAAAAAAAAAAAAAAGEN
En esta unidad revisaremos el módulo lxml junto con los parsers xml, html y el módulo BeautifulSoup.
Complementamos el uso de estos módulos con el módulo requests para realizar peticiones y descargar el código HTML.
Por ejemplo, BeautifulSoup recibirá el contenido de la respuesta con el objetivo analizar el código HTML del sitio web y extraer los datos que nos interesen.
Parsers XML y HTML
Dentro del ecosistema de python encontramos diferentes módulos que nos puedes ayudar a parsear un documento que se encuentra en formato xml y html.
El módulo lxml es un módulo que une las librerías libxml2 para análisis de documentos XML y libxslt. Las principales características del módulo son:
- Soporte para documentos XML y HTML
- Dispone de una API basada en ElementTree
- Soporte para seleccionar elementos del documento mediante expresiones XPath
La instalación de este parser se puede hacer a través del repositorio oficial de python:
pip install lxml
En este primer ejemplo vamos a hacer uso del módulo lxml.etree que se trata de un submódulo dentro de la librería lxml, que proporciona métodos como XPath(), que soporta expresiones utilizando como sintaxis selectores XPath.
Con este ejemplo vemos el uso del parser lxml para leer un fichero html y extraer el texto de la etiqueta title del documento html a través de una expresión Xpath.
Puede encontrar el siguiente código en el fichero obtener_title_xpath.py:
#!/usr/bin/env python3 import re import requests from lxml import etree respuesta = requests.get('https://www.debian.org/releases/stable/index.en.html') parser = etree.HTML(respuesta.text) resultado = etree.tostring(parser,pretty_print=True, method="html") #print(resultado) obtener_texto_xpath = etree.XPath("//title/text()", smart_strings=False) texto = obtener_texto_xpath(parser)[0] print(texto)
Submódulo lxml.html
El módulo lxml también provee un submódulo de Python llamado lxml.html dedicado para trabajar con HTML.
En el siguiente ejemplo vamos a estudiar el mismo ejemplo que el caso anterior, esta vez utilizando el parser HTML.
Puede encontrar el siguiente código en el fichero obtener_title_xpath_parser_html.py:
#!/usr/bin/env python3 import re import requests from lxml import html respuesta = requests.get('https://www.debian.org/releases/stable/index.en.html') elementos = html.fromstring(respuesta.text) obtener_texto_xpath = elementos.xpath("//title/text()", smart_strings=False) texto = obtener_texto_xpath[0] print(texto)
Extraer etiquetas de un sitio web con el módulo lxml
Antes de comenzar a analizar el código HTML, necesitamos extraer el contenido analizar.
En este ejemplo vamos a obtener la versión y el nombre en clave de la última versión estable de Debian del sitio web de Debian: https://www.debian.org/releases/stable/index.en.html
La información que queremos se muestra en el título de la página y en el primer párrafo.
Primero lo que tenemos que hacer es descargar la página con el módulo requests:
import requests response = requests.get('https://www.debian.org/releases/stable/index.en.html')
Posteriormente, analizamos el código fuente en un árbol ElementTree. Esto es lo mismo que analizar XML con ElementTree de la biblioteca estándar, excepto que aquí usaremos el parser HTML disponible en el módulo lxml:
from lxml.etree import HTML root = HTML(response.content)
La función HTML() es un acceso directo que lee el código HTML que se le pasa y produce un árbol XML. Tenga en cuenta que estamos pasando response.content y no response.text. El módulo lxml produce mejores resultados cuando utiliza la respuesta sin procesar.
La implementación de ElementTree de la biblioteca lxml ha sido diseñada para ser 100 % compatible con la biblioteca estándar, por lo que podemos comenzar a explorar el documento de la misma manera que lo hicimos con XML. Por ejemplo podemos obtener las etiquetas raíz que se encuentran en un documento html.
>>> [e.tag for e in root] ['head', 'body']
Si nos interesa el contenido de texto del elemento <title> del documento html, podríamos hacerlo de la siguiente forma:
>>> root.find('head').find('title').text 'Debian -- Debian "stretch" Release Information '
Obtener formularios de un sitio web
En este ejemplo realizamos una petición al buscador duckduckgo y obtenemos el formulario que se utiliza para realizar las búsquedas.
Para ello accedemos al objeto forms que se estará contenido dentro de la respuesta de la url.
Puede encontrar el siguiente código en el fichero duckduckgo.py:
#!/usr/bin/env python3 from lxml.html import fromstring, tostring from lxml.html import parse, submit_form import requests response = requests.get('https://duckduckgo.com') form_page = fromstring(response.text) form = form_page.forms[0] print(tostring(form)) page = parse('http://duckduckgo.com').getroot() print(tostring(page)) page.forms[0].fields['q'] = 'python' busqueda = parse(submit_form(page.forms[0])).getroot() print(tostring(busqueda))
Este es el resultado de la primera parte del script, donde podemos ver el objeto de formulario de DuckDuckGo:
b'<form id="search_form_homepage" class="search search--home js-search-
form" name="x" method="POST" action="/html">\n\t\t\t<input
id="search_form_input_homepage" class="search__input js-search-input"
type="text" autocomplete="off" name="q" tabindex="1"
value="">\n\t\t\t<input id="search_button_homepage" class="search__button
js-search-button" type="submit" tabindex="2" value="S">\n\t\t\t<input
id="search_form_input_clear" class="search__clear empty js-search-clear"
type="button" tabindex="3" value="X">\n\t\t\t<div
id="search_elements_hidden" class="search__hidden js-search-
hidden"></div&gt;\n\t\t</form&gt;\n\n\t\t\t\t\t\t'
Expresiones XPath
Con el objetivo de optimizar la comprobación de los elementos html, necesitamos usar XPath, que es un lenguaje de consulta que se desarrolló específicamente para XML y es compatible con el módulo lxml. Para comenzar con XPath, use el shell de Python de la última sección y haga lo siguiente:
>>> root.xpath('body') [<Element body at 0x4477530>]
Esta es la forma más simple de una expresión XPath; lo que hace es buscar hijos del elemento actual que tienen nombres de etiquetas que coinciden con el nombre de la etiqueta especificada. El elemento actual es el que llamamos xpath(), en este caso, root. El elemento raíz es el elemento <html> de nivel superior en el documento HTML, por lo que el elemento devuelto es el elemento <body>
Las expresiones XPath pueden contener múltiples niveles de elementos donde las búsquedas comienzan desde el nodo en el que se realiza la llamada xpath(). Por ejemplo, podemos usar expresiones xpath para encontrar solo los elementos secundarios <div> dentro de <body>.
>>> root.xpath('body/div') [<Element div at 0x447a1e8>, <Element div at 0x447a210>, <Element div at 0x447a238>]
Las consultas anteriores son relativas al elemento al que llamamos con el método xpath(), pero podemos forzar una búsqueda desde la raíz del árbol añadiendo una barra diagonal al comienzo de la expresión. También podemos realizar una búsqueda en todos los descendientes de un elemento, con la ayuda de una barra doble.
>>> root.xpath('//h1') [<Element h1 at 0x447aa58>]
La verdadera potencia de XPath está en que podemos aplicar condiciones adicionales a los elementos en la expresión:
>>> root.xpath('//div[@id="content"]') [<Element div at 0x3d6d800>]
Los corchetes después de div, [@id = "content"], forman una condición que colocamos en los elementos <div>. El signo @ antes de la palabra clave id significa que id se refiere a un atributo, por lo que la condición significa: obtener aquellos elementos cuyo atributo id sea igual a "content". Así es como podemos encontrar nuestra etiqueta de contenido <div>.
Antes de mostrar un ejemplo completo, vamos a analizar algunos casos de uso. Podemos especificar solo un nombre de etiqueta, como se muestra aquí:
>>> root.xpath('//div[h1]') [<Element div at 0x3d6d800>]
En el ejemplo anterior la expresión xpath devuelve todos los elementos <div> que tienen un elemento hijo <h1>:
>>> root.xpath('body/div[2]') [<Element div at 0x3d6d800>]
En el ejemplo anterior estamos accediendo al segundo elemento hijo <div> de <body>. Poner un número como condición devolverá el elemento en esa posición en la lista. Hay que tener en cuenta que estos índices comienzan en 1, a diferencia de la indexación de Python que comienza en 0.
La especificación completa de XPath es un estándar del World Wide Web Consortium (W3C): https://www.w3.org/TR/xpath-3
Ejemplo de script utilizando expresiones xpath
En el siguiente script obtenemos la versión de debían utilizando expresiones xpath.
Puede encontrar el siguiente código en el fichero get_debian_version.py:
#!/usr/bin/env python3 import re import requests from lxml.etree import HTML response = requests.get('https://www.debian.org/releases/stable/index.en.html') root = HTML(response.content) title_text = root.find('head').find('title').text print(title_text) release = re.search('\u201c(.*)\u201d', title_text).group(1) p_text = root.xpath('//div[@id="content"]/p[1]')[0].text version = p_text.split()[1] print('Codename: {}\nVersion: {}'.format(release, version))
En el script anterior analizamos la página web extrayendo el texto que queremos con la ayuda de XPath. Hemos utilizado una expresión regular para extraer el nombre y el número de versión.
$ python3 get_debian_version.py Debian -- Debian “bookworm” Release Information Codename: bookworm Version: 12.4
Extracción de enlaces con el módulo lxml con expresiones xpath
Una de las principales funcionalidades que podríamos desarrollar es la extracción de diferentes elementos html.
Podríamos definir una clase llamada Scraping y definir un método por cada tipo de recurso a extraer.
En este caso estamos utilizando el parser xml y expresiones regulares del tipo xpath para obtener cada uno de los recursos a extraer.
Para el caso de extraer enlaces a partir de una url podemos hacer uso de la expresión xpath //a/@href.
Esto nos devolverá el valor del atributo href para todos aquellos elementos correspondientes a un enlace html.
Puede encontrar el siguiente código en el fichero obtener_links_lxml.py:
#!/usr/bin/env python3 import os import requests from lxml import html class Scraping: def scrapingLinks(self,url): print("Obtener links de la url:"+ url) try: response = requests.get(url) parsed_body = html.fromstring(response.text) # expresion regular para obtener links links = parsed_body.xpath('//a/@href') print('Links encontrados %s' % len(links)) for link in links: if(link.startswith("http")): print(link) else: print(url+link) except Exception as e: print("Error de conexión en: " + url) pass if __name__ == "__main__": target = "https://www.python.org" scraping = Scraping() scraping.scrapingLinks(target)
Desde nuestro programa principal main llamaríamos al método scrapingLinks Scraping pasándole por parámetro la url o target a analizar.
Como podemos ver en la salida, nos devuelve aquellos enlaces tanto absolutos como relativos a la página principal que estamos analizando.:
$ python3 obtener_links_lxml.py Obtener links de la url:https://www.python.org Links encontrados 206 https://www.python.org#content https://www.python.org#python-network https://www.python.org/ https://www.python.org/psf-landing/ https://docs.python.org https://pypi.org/ https://www.python.org/jobs/ https://www.python.org/community/ https://www.python.org#top https://www.python.org/ https://psfmember.org/civicrm/contribute/transact?reset=1&id=2 https://www.python.org#site-map https://www.python.org# https://www.python.orgjavascript:;
Extracción de documentos PDF con el módulo lxml con expresiones xpath
Para el caso de extraer documentos pdf a partir de una url podemos hacer uso de la expresión xpath //a[@href[contains(., ".pdf")]]/@href.
Esto nos devolverá el valor de atributo href para todos aquellos elementos correspondientes a un documento pdf.
Puede encontrar el siguiente código en el fichero obtener_pdfs_lxml.py:
#!/usr/bin/env python3 import os import requests from lxml import html class Scraping: def scrapingPDFs(self,url): print("Obtener pdfs de la url:"+ url) try: response = requests.get(url) parsed_body = html.fromstring(response.text) # expresion regular para obtener pdf pdfs = parsed_body.xpath('//a[@href[contains(., ".pdf")]]/@href') print('Pdfs encontrados %s' % len(pdfs)) #crear directorio para guardar los documentos os.system("mkdir pdfs") for pdf in pdfs: if pdf.startswith("http") == False: download = url + "/"+ pdf else: download = pdf print(download) # download document en el directorio creado r = requests.get(download) f = open('pdfs/%s' % download.split('/')[-1], 'wb') f.write(r.content) f.close() except Exception as e: print("Error de conexión en: " + url) pass if __name__ == "__main__": target = "https://docs.python-guide.org" scraping = Scraping() scraping.scrapingPDFs(target)
Desde nuestro programa principal main llamaríamos al método scrapingPDFs de la clase Scraping pasándole por parámetro la url o target a analizar.
Como podemos ver en la salida nos devuelve aquellos documentos pdfs que se encuentran en la página principal que estamos analizando.
Obtener pdfs de la url:https://docs.python-guide.org Pdfs encontrados 1 https://media.readthedocs.org/pdf/python-guide/latest/python-guide.pdf
Actividad práctica: Completar el código que permite la extracción de imágenes con el módulo lxml con expresiones xpath
Completar el código que permite la extracción de imágenes con el módulo lxml con expresiones xpath
#!/usr/bin/env python3 import os import requests from lxml import xxx class Scraping: def scrapingImages(self,xxx): print("Obtener imágenes de la url:"+ xxx) try: response = requests.xxx(xxx) parsed_body = html.xxx(response.xxx) # expresion regular para obtener imagenes images = parsed_body.xxx(xxx) print('Imágenes encontradas %s' % len(images)) #crear directorio para guardar las imagenes os.xxx("mkdir imagenes") for image in xxx: if xxx.startswith("http") == False: download = url + "/"+ xxx else: download = xxx print(download) # descargar las imagenes en el directorio creado r = requests.xxx(xxx) f = open('imagenes/%s' % xxx.split('/')[-1], 'wb') f.xxx(r.xxx) f.xxx() except Exception as e: print("Error de conexión en: " + url) pass if __name__ == "__main__": target = "https://www.python.org" scraping = Scraping() scraping.xxx(target)
Para el caso de extraer imágenes a partir de una url podemos hacer uso de la expresión xpath img/@src Esto nos devolverá el valor del atributo src para todos aquellos elementos correspondientes a una imagen html. Desde nuestro programa principal main llamaríamos al método scrapingImages de la clase Scraping pasándole por parámetro la url o target a analizar. Como podemos ver en la salida, nos devuelve aquellas imágenes que se encuentran en la página principal que estamos analizando. <code> Obtener imágenes de la url:https://www.python.org Imágenes encontradas 1 https://www.python.org//static/img/python-logo.png </code> ==== Solución ==== <code python> #!/usr/bin/env python3 import os import requests from lxml import html class Scraping: def scrapingImages(self,url): print(“Obtener imágenes de la url:”+ url) try: response = requests.get(url) parsed_body = html.fromstring(response.text) # expresion regular para obtener imagenes images = parsed_body.xpath('img/@src')
print('Imágenes encontradas %s' % len(images))
#crear directorio para guardar las imagenes
os.system("mkdir imagenes")
for image in images:
if image.startswith("http") == False:
download = url + "/"+ image
else:
download = image
print(download)
# descargar las imagenes en el directorio creado
r = requests.get(download)
f = open('imagenes/%s' % download.split('/')[-1], 'wb')
f.write(r.content)
f.close()
except Exception as e:
print("Error de conexión en: " + url)
pass
if name == “main”:
target = "https://www.python.org" scraping = Scraping() scraping.scrapingImages(target)
</code>
