Python

Python

Tutorial Python: Web Scraping de HTML

Beautiful Soup y web scraping de HTML. Aprende a extraer enlaces, tablas e imágenes de manera eficiente mejorando tus proyectos de scraping.

Aprende Python y certifícate

Extracción de enlaces y URLs

Una de las tareas más comunes en el web scraping es la extracción de enlaces y sus URLs de un documento HTML. Con Beautiful Soup, este proceso se simplifica gracias a sus métodos de navegación y búsqueda en el árbol DOM.

Para obtener todos los enlaces de una página, se utiliza el método find_all() buscando las etiquetas <a>, que representan los enlaces en HTML:

from bs4 import BeautifulSoup

html_doc = """
<html>
    <head><title>Página de ejemplo</title></head>
    <body>
        <p>Enlaces útiles:</p>
        <a href="https://www.ejemplo.com">Ejemplo</a>
        <a href="https://www.prueba.com">Prueba</a>
    </body>
</html>
"""

soup = BeautifulSoup(html_doc, 'html.parser')
enlaces = soup.find_all('a')

En este código, hemos creado un objeto BeautifulSoup y recopilado todos los enlaces en la variable enlaces. Cada elemento en enlaces es un objeto Tag que representa una etiqueta <a> del documento.

Para extraer las URLs de estos enlaces, accedemos al atributo href de cada elemento:

for enlace in enlaces:
    url = enlace.get('href')
    texto = enlace.text
    print(f"Texto: {texto}, URL: {url}")

Este bucle recorrerá cada enlace, obteniendo tanto el texto visible como la URL asociada. El método get() es seguro, ya que si el atributo no existe, devolverá None en lugar de causar un error.

Es posible filtrar enlaces que cumplan ciertas condiciones. Por ejemplo, si deseamos obtener solo los enlaces que apuntan a dominios específicos:

import re

enlaces_ejemplo = soup.find_all('a', href=re.compile(r'ejemplo\.com'))
for enlace in enlaces_ejemplo:
    print(enlace.get('href'))

Aquí utilizamos el módulo re para emplear expresiones regulares en la búsqueda, permitiendo localizar enlaces cuyo atributo href contenga "ejemplo.com".

Además, podemos buscar enlaces con una clase o identificador específico:

html_doc = """
<html>
    <body>
        <a href="https://www.ejemplo.com" class="importante">Enlace Importante</a>
        <a href="https://www.otro.com">Otro Enlace</a>
    </body>
</html>
"""

soup = BeautifulSoup(html_doc, 'html.parser')
enlace_importante = soup.find('a', class_='importante')
print(enlace_importante.get('href'))

Nótese que utilizamos class_ en lugar de class debido a que class es una palabra reservada en Python. De esta manera, podemos acceder a enlaces con una clase específica.

También es posible utilizar selectores CSS para una búsqueda más avanzada:

enlaces = soup.select('a.importante')
for enlace in enlaces:
    print(enlace['href'])

El método select() permite utilizar selectores CSS, como en este caso que seleccionamos todas las etiquetas <a> con la clase "importante".

Si necesitamos extraer todos los enlaces absolutos de una página, es importante tener en cuenta si las URLs son relativas. Para convertirlas en absolutas, podemos utilizar la librería urllib.parse:

from urllib.parse import urljoin

base_url = 'https://www.ejemplo.com'
for enlace in enlaces:
    url = urljoin(base_url, enlace.get('href'))
    print(url)

Con urljoin(), combinamos la URL base con el valor del atributo href, obteniendo así la URL completa.

La extracción de enlaces y URLs con Beautiful Soup es un proceso sencillo y eficiente. Gracias a sus métodos de búsqueda y navegación, podemos personalizar nuestras búsquedas y extraer la información necesaria de manera precisa.

Manejo de tablas y listas en HTML

El web scraping a menudo implica la extracción de información de tablas y listas presentes en páginas web. Beautiful Soup ofrece herramientas eficientes para navegar y extraer datos estructurados de estas etiquetas HTML.

Las tablas en HTML se definen con la etiqueta <table> y se componen de filas (<tr>) y celdas (<td> para datos y <th> para encabezados). Para extraer información de una tabla, primero localizamos la tabla de interés y luego iteramos sobre sus filas y celdas.

Por ejemplo, dado el siguiente fragmento de HTML:

<table id="tabla_datos">
    <tr>
        <th>Nombre</th>
        <th>Edad</th>
    </tr>
    <tr>
        <td>Ana</td>
        <td>28</td>
    </tr>
    <tr>
        <td>Luis</td>
        <td>34</td>
    </tr>
</table>

Podemos extraer los datos de la tabla utilizando el siguiente código:

from bs4 import BeautifulSoup

html_doc = '''
<table id="tabla_datos">
    <tr>
        <th>Nombre</th>
        <th>Edad</th>
    </tr>
    <tr>
        <td>Ana</td>
        <td>28</td>
    </tr>
    <tr>
        <td>Luis</td>
        <td>34</td>
    </tr>
</table>
'''

soup = BeautifulSoup(html_doc, 'html.parser')
tabla = soup.find('table', id='tabla_datos')
filas = tabla.find_all('tr')

for fila in filas:
    celdas = fila.find_all(['th', 'td'])
    datos = [celda.get_text(strip=True) for celda in celdas]
    print(datos)

En este ejemplo, localizamos la tabla con id='tabla_datos' y luego extraemos todas las filas. Para cada fila, obtenemos las celdas y extraemos el texto de cada una, obteniendo una lista de datos por fila.

Para manejar listas en HTML, que se definen con <ul> (lista no ordenada) y <ol> (lista ordenada), el proceso es similar. Consideremos el siguiente ejemplo:

<ul class="lista-elementos">
    <li>Elemento 1</li>
    <li>Elemento 2</li>
    <li>Elemento 3</li>
</ul>

El código para extraer los elementos de la lista sería:

lista = soup.find('ul', class_='lista-elementos')
items = lista.find_all('li')

for item in items:
    texto = item.get_text(strip=True)
    print(texto)

Aquí, buscamos la lista con la clase 'lista-elementos' y luego iteramos sobre cada elemento <li>, extrayendo su texto.

Cuando trabajamos con tablas más complejas, es posible que necesitemos manejar celdas combinadas (rowspan y colspan). Aunque Beautiful Soup no procesa automáticamente estas propiedades, podemos escribir lógica adicional para interpretarlas correctamente.

Para extraer datos específicos de una tabla, podemos utilizar selectores CSS:

celdas_edad = tabla.select('tr td:nth-of-type(2)')
for celda in celdas_edad:
    edad = celda.get_text(strip=True)
    print(edad)

Este código selecciona todas las celdas que contienen la edad, utilizando el selector nth-of-type(2) para obtener el segundo <td> de cada fila.

Al manejar listas anidadas, es importante considerar la estructura jerárquica. Por ejemplo:

<ul class="menu">
    <li>
        Opción 1
        <ul>
            <li>Subopción 1.1</li>
            <li>Subopción 1.2</li>
        </ul>
    </li>
    <li>Opción 2</li>
</ul>

Para extraer todos los elementos, incluyendo los anidados, podemos utilizar una función recursiva:

def extraer_items(lista):
    items = lista.find_all('li', recursive=False)
    for item in items:
        texto = item.contents[0].strip()
        print(texto)
        sublista = item.find('ul')
        if sublista:
            extraer_items(sublista)

menu = soup.find('ul', class_='menu')
extraer_items(menu)

Este código primero imprime el texto de cada elemento <li> y luego verifica si contiene una sublista <ul>, llamando a la función de nuevo si es así.

Si deseamos convertir los datos de una tabla en un diccionario o lista de registros, podemos hacerlo de la siguiente manera:

cabeceras = [th.get_text(strip=True) for th in tabla.find_all('th')]
registros = []

for fila in tabla.find_all('tr')[1:]:
    celdas = fila.find_all('td')
    valores = [celda.get_text(strip=True) for celda in celdas]
    registro = dict(zip(cabeceras, valores))
    registros.append(registro)

print(registros)

Este enfoque es útil para estructurar los datos y facilitar su posterior manipulación o almacenamiento.

Es fundamental prestar atención a los detalles, como los espacios en blanco y los caracteres especiales, utilizando métodos como get_text(strip=True) para obtener el texto limpio de las etiquetas.

Al extraer datos de listas y tablas, podemos combinar Beautiful Soup con otras librerías, como pandas, para procesar y analizar la información:

import pandas as pd

df = pd.DataFrame(registros)
print(df)

Esto nos permite convertir nuestros datos en un DataFrame, facilitando operaciones de análisis y visualización.

Descarga y procesamiento de imágenes y multimedia

En el proceso de web scraping, es común encontrar la necesidad de extraer y descargar imágenes u otros contenidos multimedia de páginas web. Beautiful Soup facilita la localización y extracción de estas etiquetas en el código HTML, permitiéndonos obtener las URLs para su posterior descarga y procesamiento.

Para extraer todas las imágenes de una página web, buscamos las etiquetas <img>, que representan las imágenes en HTML:

from bs4 import BeautifulSoup

html_doc = '''
<html>
    <body>
        <h1>Galería de Imágenes</h1>
        <img src="imagen1.jpg" alt="Imagen 1">
        <img src="/media/imagen2.png" alt="Imagen 2">
        <img src="https://www.ejemplo.com/imagenes/imagen3.gif" alt="Imagen 3">
    </body>
</html>
'''

soup = BeautifulSoup(html_doc, 'html.parser')
imagenes = soup.find_all('img')

En este código, utilizamos find_all('img') para obtener una lista de todas las etiquetas <img> presentes en el documento. Cada elemento en imagenes es un objeto Tag que representa una imagen específica.

Para extraer las URLs de las imágenes, accedemos al atributo src de cada etiqueta:

for img in imagenes:
    url_imagen = img.get('src')
    texto_alt = img.get('alt')
    print(f"Texto alternativo: {texto_alt}, URL de la imagen: {url_imagen}")

Este bucle nos permite obtener tanto la URL de la imagen como su texto alternativo. Es recomendable utilizar get() en lugar de acceder directamente al atributo, ya que así evitamos errores si el atributo no existe.

Es habitual que las URLs de las imágenes sean relativas, por lo que necesitamos convertirlas en URLs absolutas para poder descargarlas correctamente. Para ello, empleamos la función urljoin del módulo urllib.parse:

from urllib.parse import urljoin

base_url = 'https://www.ejemplo.com/'

for img in imagenes:
    url_relativa = img.get('src')
    url_absoluta = urljoin(base_url, url_relativa)
    print(f"URL absoluta de la imagen: {url_absoluta}")

Con urljoin, combinamos la URL base con la ruta relativa de la imagen, obteniendo la URL completa necesaria para su descarga.

Para descargar las imágenes, utilizamos la biblioteca requests:

import requests

for img in imagenes:
    url_relativa = img.get('src')
    url_absoluta = urljoin(base_url, url_relativa)
    nombre_imagen = url_absoluta.split('/')[-1]

    respuesta = requests.get(url_absoluta)

    if respuesta.status_code == 200:
        with open(nombre_imagen, 'wb') as archivo:
            archivo.write(respuesta.content)
        print(f"Imagen {nombre_imagen} descargada correctamente.")
    else:
        print(f"Error al descargar {nombre_imagen}")

En este ejemplo, por cada imagen:

  • Construimos la URL absoluta de la imagen.
  • Extraemos el nombre del archivo a partir de la URL.
  • Realizamos una petición GET para descargar la imagen.
  • Si la respuesta es exitosa (status_code 200), guardamos el contenido en un archivo binario.
  • Informamos si hubo algún error durante la descarga.

Para manejar otros tipos de contenido multimedia, como videos o audios, el proceso es similar. Las etiquetas <video> y <audio> también contienen un atributo src o tienen etiquetas <source> anidadas:

# Extraer y descargar videos
videos = soup.find_all('video')

for video in videos:
    fuente = video.find('source')
    if fuente:
        url_relativa = fuente.get('src')
    else:
        url_relativa = video.get('src')

    if url_relativa:
        url_absoluta = urljoin(base_url, url_relativa)
        nombre_video = url_absoluta.split('/')[-1]

        respuesta = requests.get(url_absoluta)

        if respuesta.status_code == 200:
            with open(nombre_video, 'wb') as archivo:
                archivo.write(respuesta.content)
            print(f"Video {nombre_video} descargado correctamente.")
        else:
            print(f"Error al descargar {nombre_video}")

Aquí, buscamos primero si existe una etiqueta <source> dentro del <video>. Si no, utilizamos el atributo src directamente del <video>. Luego, procedemos de la misma manera que con las imágenes.

Para archivos multimedia de gran tamaño, es recomendable descargar el contenido en streaming para no saturar la memoria del sistema:

for img in imagenes:
    url_relativa = img.get('src')
    url_absoluta = urljoin(base_url, url_relativa)
    nombre_imagen = url_absoluta.split('/')[-1]

    with requests.get(url_absoluta, stream=True) as respuesta:
        if respuesta.status_code == 200:
            with open(nombre_imagen, 'wb') as archivo:
                for trozo in respuesta.iter_content(chunk_size=8192):
                    archivo.write(trozo)
            print(f"Imagen {nombre_imagen} descargada en streaming correctamente.")
        else:
            print(f"Error al descargar {nombre_imagen}")

Este método descarga el archivo en fragmentos pequeños, escribiendo cada trozo directamente en el archivo y evitando cargar todo el contenido en memoria.

Es esencial implementar un correcto adecuado de excepciones para gestionar posibles errores durante la descarga:

for img in imagenes:
    try:
        url_relativa = img.get('src')
        url_absoluta = urljoin(base_url, url_relativa)
        nombre_imagen = url_absoluta.split('/')[-1]

        respuesta = requests.get(url_absoluta)
        respuesta.raise_for_status()

        with open(nombre_imagen, 'wb') as archivo:
            archivo.write(respuesta.content)
        print(f"Imagen {nombre_imagen} descargada exitosamente.")
    except requests.exceptions.RequestException as e:
        print(f"Error al descargar {nombre_imagen}: {e}")

Al utilizar respuesta.raise_for_status(), provocamos una excepción si la petición HTTP no fue exitosa, lo que nos permite capturar y manejar el error correctamente.

Además de descargar, podemos procesar las imágenes utilizando librerías como Pillow:

from PIL import Image
from io import BytesIO

for img in imagenes:
    try:
        url_relativa = img.get('src')
        url_absoluta = urljoin(base_url, url_relativa)

        respuesta = requests.get(url_absoluta)
        respuesta.raise_for_status()

        imagen = Image.open(BytesIO(respuesta.content))
        imagen = imagen.convert('L')  # Convertir a escala de grises
        nombre_imagen = url_absoluta.split('/')[-1]
        imagen.save(f"gris_{nombre_imagen}")
        print(f"Imagen {nombre_imagen} procesada y guardada como gris_{nombre_imagen}.")
    except Exception as e:
        print(f"Error al procesar {nombre_imagen}: {e}")

En este caso, después de descargar la imagen, la convertimos a escala de grises y la guardamos con un nombre distinto, demostrando cómo podemos manipular imágenes tras su descarga.

Para asegurarnos de que todas las descargas y procesos se realizan de manera responsable, es importante:

  • Respetar los términos y condiciones del sitio web.
  • Validar que tenemos permiso para descargar y utilizar el contenido multimedia.
  • Implementar pausas entre las descargas para no sobrecargar el servidor:
import time

for img in imagenes:
    # Código de descarga...
    time.sleep(1)  # Esperar 1 segundo entre descargas

Finalmente, al trabajar con contenido multimedia, debemos estar atentos a los metadatos y otros atributos que pueden ser de interés:

for img in imagenes:
    titulo = img.get('title')
    clase = img.get('class')
    print(f"Imagen con título: {titulo}, clase: {clase}")

La extracción de estos atributos nos permite obtener información adicional sobre las imágenes, lo cual puede ser útil en diferentes contextos.

Gestión de contenido dinámico y JavaScript básico

Al realizar web scraping, es común enfrentarse a páginas web cuyo contenido es generado dinámicamente mediante JavaScript. Beautiful Soup, al procesar el HTML estático obtenido de la respuesta HTTP inicial, no ejecuta JavaScript. Por ello, es necesario emplear estrategias para extraer la información deseada de este tipo de páginas.

Una técnica eficaz es analizar las peticiones de red que realiza la página al cargar el contenido dinámico. Muchas veces, los datos se obtienen a través de solicitudes XHR o fetch a APIs que devuelven información en formato JSON. Utilizando herramientas de desarrollo del navegador, como la pestaña "Red" (o Network), podemos identificar estas peticiones y obtener sus URL.

Por ejemplo, si una página carga una lista de productos mediante una petición a una API, podemos replicar esta solicitud:

import requests

url_api = 'https://www.ejemplo.com/api/productos'
respuesta = requests.get(url_api)
datos = respuesta.json()

for producto in datos['items']:
    nombre = producto['nombre']
    precio = producto['precio']
    print(f"Producto: {nombre}, Precio: {precio}")

Aquí, realizamos una petición directa a la API y procesamos la respuesta JSON, facilitando la extracción de datos sin necesidad de ejecutar JavaScript.

Otra estrategia es extraer los datos embebidos dentro de etiquetas <script> en el HTML. Algunas páginas incluyen variables JavaScript que contienen información útil. Podemos localizar estas etiquetas y extraer el contenido relevante:

from bs4 import BeautifulSoup
import json
import re

html_doc = '''
<html>
<head></head>
<body>
<script type="text/javascript">
    window.datosProductos = {
        "items": [
            {"nombre": "Producto A", "precio": 25},
            {"nombre": "Producto B", "precio": 30}
        ]
    };
</script>
</body>
</html>
'''

soup = BeautifulSoup(html_doc, 'html.parser')
script = soup.find('script', text=re.compile('window\.datosProductos'))
contenido = script.string
json_texto = re.search(r'window\.datosProductos\s*=\s*(\{.*\});', contenido, re.DOTALL).group(1)
datos = json.loads(json_texto)

for producto in datos['items']:
    nombre = producto['nombre']
    precio = producto['precio']
    print(f"Producto: {nombre}, Precio: {precio}")

En este caso, utilizamos expresiones regulares para extraer el objeto JSON de la variable JavaScript y luego lo procesamos con el módulo json.

Cuando los datos se cargan mediante solicitudes POST o GET con parámetros específicos, podemos simular estas peticiones para acceder al contenido:

import requests

url_busqueda = 'https://www.ejemplo.com/buscar'
parametros = {'q': 'ordenadores', 'categoria': 'tecnologia'}

respuesta = requests.get(url_busqueda, params=parametros)
soup = BeautifulSoup(respuesta.text, 'html.parser')

productos = soup.find_all('div', class_='producto-item')

for producto in productos:
    titulo = producto.find('h3').get_text(strip=True)
    precio = producto.find('span', class_='precio').get_text(strip=True)
    print(f"Título: {titulo}, Precio: {precio}")

Al incluir los parámetros de búsqueda adecuados, obtenemos el HTML con los resultados que nos interesan y podemos extraer la información utilizando Beautiful Soup.

Es esencial considerar las cabeceras HTTP al realizar peticiones. Algunas páginas responden de manera diferente según el User-Agent o requieren ciertas cabeceras para devolver el contenido correcto:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
    'Accept-Language': 'es-ES,es;q=0.9'
}

respuesta = requests.get(url_busqueda, params=parametros, headers=headers)

Estableciendo las cabeceras apropiadas, podemos emular el comportamiento de un navegador real y mejorar el éxito de nuestras peticiones.

En situaciones donde el contenido dinámico es complejo, puede ser útil revisar si la página utiliza frameworks como React o Vue.js. A veces, estos frameworks incluyen los datos en forma de render props o atributos de datos en las etiquetas, que podemos aprovechar:

<div id="root" data-inicial='{"usuario":"Juan","edad":30}'></div>

Podemos extraer este atributo y cargarlo como JSON:

div = soup.find('div', id='root')
data_inicial = div.get('data-inicial')
datos = json.loads(data_inicial)

print(f"Usuario: {datos['usuario']}, Edad: {datos['edad']}")

Al acceder a los atributos de datos, obtenemos información directamente sin necesidad de procesar el contenido generado por JavaScript.

Otra opción es aprovechar los sitemaps o archivos robots.txt de los sitios web, que a veces contienen enlaces directos a recursos o APIs útiles para la extracción de datos.

Es importante mantener una actitud ética y responsable al realizar web scraping. Debemos respetar los términos de servicio y las normativas legales aplicables, evitando acceder a información no autorizada o sensible.

Para mejorar la eficiencia de nuestro código, es recomendable implementar cacheo de solicitudes y manejar adecuadamente las excepciones:

try:
    respuesta = requests.get(url_api, timeout=5)
    respuesta.raise_for_status()
except requests.exceptions.Timeout:
    print("La solicitud ha excedido el tiempo de espera.")
except requests.exceptions.HTTPError as err:
    print(f"Error HTTP: {err}")

Manejando las posibles excepciones, nuestro programa será más robusto y podrá gestionar errores sin detener su ejecución.

Finalmente, aunque Beautiful Soup no ejecuta JavaScript, combinándolo con estas técnicas podemos extraer gran parte del contenido dinámico de las páginas web. La clave es entender cómo se carga la información y encontrar formas de acceder a ella mediante peticiones directas o analizando el HTML estático disponible.

Resolución de codificaciones y caracteres especiales

Al realizar web scraping, es frecuente encontrarse con problemas relacionados con las codificaciones de caracteres y la gestión de caracteres especiales. Las páginas web pueden utilizar diferentes codificaciones, como UTF-8, ISO-8859-1 o Windows-1252, lo que puede causar que el texto extraído contenga caracteres ilegibles o símbolos extraños si no se maneja adecuadamente.

Para asegurar que Beautiful Soup interprete correctamente el contenido de la página, es fundamental gestionar la codificación de manera correcta. Cuando se utiliza la biblioteca requests para obtener el contenido HTML, esta intenta detectar la codificación automáticamente, pero no siempre es precisa.

Por ejemplo:

import requests
from bs4 import BeautifulSoup

url = 'https://www.ejemplo.com'
respuesta = requests.get(url)
contenido = respuesta.content
soup = BeautifulSoup(contenido, 'html.parser')
texto = soup.get_text()

En este caso, el objeto respuesta contiene los datos de la página web. Sin embargo, si la codificación no se detecta correctamente, el texto extraído puede contener errores. Para evitar esto, podemos establecer explícitamente la codificación antes de procesar el contenido:

respuesta.encoding = 'utf-8'
soup = BeautifulSoup(respuesta.text, 'html.parser')

Al asignar 'utf-8' a respuesta.encoding, indicamos a requests que interprete el contenido utilizando dicha codificación, facilitando que Beautiful Soup procese correctamente los caracteres especiales.

En algunas ocasiones, es útil verificar cuál es la codificación detectada por requests:

print(respuesta.apparent_encoding)

Este atributo muestra la codificación que requests ha inferido. Podemos utilizar esta información para ajustar la codificación si es necesario.

Además, al crear el objeto BeautifulSoup, podemos especificar la codificación mediante el parámetro from_encoding:

soup = BeautifulSoup(contenido, 'html.parser', from_encoding='utf-8')

Es importante notar que Beautiful Soup suele manejar automáticamente la detección de codificaciones, y en versiones recientes este parámetro puede ser menos necesario.

Si trabajamos con páginas que declaran una codificación incorrecta en sus metadatos, podemos utilizar la biblioteca chardet para detectar la codificación real:

import chardet

detector = chardet.detect(contenido)
codificacion = detector['encoding']
print(f'Codificación detectada: {codificacion}')

Con la codificación detectada, podemos decodificar el contenido y procesarlo:

contenido_decodificado = contenido.decode(codificacion, errors='replace')
soup = BeautifulSoup(contenido_decodificado, 'html.parser')

El parámetro errors='replace' sustituye los caracteres que no pueden ser decodificados, evitando que el proceso falle.

Al extraer texto que contiene caracteres especiales como acentos, eñes o símbolos, es crucial asegurar que estos se manejen correctamente. Por ejemplo, al escribir el contenido en un archivo:

with open('contenido.txt', 'w', encoding='utf-8') as archivo:
    archivo.write(soup.get_text())

Al especificar encoding='utf-8' en la función open, nos aseguramos de que el texto se guarde con la codificación adecuada, preservando los caracteres especiales.

Cuando nos encontramos con entidades HTML, como &aacute; para á, Beautiful Soup las maneja automáticamente y las decodifica al carácter correspondiente. Sin embargo, si necesitamos acceder a las entidades, podemos utilizar el módulo html:

import html

texto_bruto = soup.prettify()
texto_con_entidades = html.escape(texto_bruto)

Con html.escape(), convertimos los caracteres especiales a sus entidades HTML correspondientes.

En caso de que el contenido incluya secuencias de escape o caracteres Unicode, podemos normalizarlos utilizando el módulo unicodedata:

import unicodedata

texto = soup.get_text()
texto_normalizado = unicodedata.normalize('NFKD', texto)

La función normalize() nos permite convertir caracteres compuestos en sus equivalentes descompuestos, facilitando el procesamiento y comparación de textos.

Es posible que algunos textos contengan caracteres no imprimibles o espacios en blanco especiales que pueden afectar al análisis de datos. Para limpiar estos caracteres, podemos aplicar expresiones regulares:

import re

texto_limpio = re.sub(r'\s+', ' ', texto).strip()

Este código reemplaza múltiples espacios en blanco por un solo espacio, y elimina espacios al inicio y al final con strip().

Al trabajar con diferentes codificaciones, es fundamental estar atentos a las excepciones que puedan surgir. Por ejemplo, si intentamos decodificar con una codificación incorrecta, obtendremos un UnicodeDecodeError. Para manejar estas situaciones, podemos utilizar bloques try-except:

try:
    contenido_decodificado = contenido.decode('utf-8')
except UnicodeDecodeError:
    contenido_decodificado = contenido.decode('latin-1', errors='replace')

De esta forma, si la decodificación con 'utf-8' falla, intentamos con 'latin-1', reemplazando los caracteres problemáticos.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con los certificados de CertiDevs.

20 % DE DESCUENTO

Plan mensual

19.00 /mes

15.20 € /mes

Precio normal mensual: 19 €
58 % DE DESCUENTO

Plan anual

10.00 /mes

8.00 € /mes

Ahorras 132 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Web Scraping de HTML

Evalúa tus conocimientos de esta lección Web Scraping de HTML con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Diccionarios en Python

Python
Código

Módulo math

Python
Puzzle

Excepciones

Python
Test

Gestor de tareas CRUD

Python
Proyecto

Funciones Python

Python
Puzzle

Módulo datetime

Python
Test

Polimorfismo

Python
Test

Módulo os

Python
Test

Diccionarios

Python
Puzzle

Operadores

Python
Test

Estructuras de control

Python
Código

Instalación de Python y creación de proyecto

Python
Test

Estructuras de control

Python
Puzzle

Encapsulación

Python
Puzzle

Herencia de clases

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Funciones lambda

Python
Puzzle

Diccionarios

Python
Test

Funciones lambda

Python
Test

Tuplas

Python
Puzzle

Operadores

Python
Código

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Módulo csv

Python
Test

Módulo json

Python
Test

Tipos de datos

Python
Código

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Funciones

Python
Test

Funciones Python

Python
Código

Variables

Python
Test

Módulo csv

Python
Puzzle

Introducción a Python

Python
Test

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Listas

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

OOP en python

Python
Proyecto

Listas

Python
Puzzle

Encapsulación

Python
Test

Clases y objetos

Python
Test

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Python y certifícate

Certificados de superación de Python

Supera todos los ejercicios de programación del curso de Python y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender cómo obtener enlaces y URLs de páginas HTML.
  • Aprender a extraer datos de tablas y listas.
  • Manejo de imágenes y contenido multimedia.
  • Técnicas para gestionar contenido dinámico y JavaScript básico.
  • Resolver problemas de codificación y caracteres especiales.