Advertisement
  1. Code
  2. Python

Web Scraping moderno con BeautifulSoup y Selenium

Scroll to top
Read Time: 8 min

Spanish (Español) translation by steven (you can also view the original English article)

Visión general

HTML es casi intuitivo. CSS es un gran avance que separa limpiamente la estructura de una página de su apariencia. JavaScript agrega algo de magia. Ésa es la teoría. El mundo real es un poco diferente.

En este tutorial, aprenderás cómo se procesa el contenido que ve en el navegador y cómo eliminarlo cuando sea necesario. En particular, aprenderás a contar los comentarios de Disqus. Nuestras herramientas serán Python y paquetes increíbles como "requests", BeautifulSoup y Selenium.

¿Cuándo deberías utilizar Web Scraping?

El web scraping es la práctica de buscar automáticamente el contenido de las páginas web diseñadas para interactuar con usuarios humanos, analizarlas y extraer cierta información (posiblemente navegando por enlaces a otras páginas). A veces es necesario si no hay otra forma de extraer la información necesaria. Idealmente, la aplicación proporciona una API dedicada para acceder a sus datos mediante programación. Hay varias razones por las que el web scraping debería ser tu último recurso:

  • Es frágil (las páginas web que estás raspando pueden cambiar con frecuencia).
  • Puede que esté prohibido (algunas aplicaciones web tienen políticas contra el scraping).
  • Puede ser lento y expansivo (si necesitas buscar y caminar a través de mucho ruido).

Comprensión de las páginas web del mundo real

Entendamos a qué nos enfrentamos, observando el resultado de algún código de aplicación web común. En el artículo Introducción a Vagrant, hay algunos comentarios de Disqus en la parte inferior de la página:

Understanding Real-World Web PagesUnderstanding Real-World Web PagesUnderstanding Real-World Web Pages

Para obtener estos comentarios, primero debemos encontrarlos en la página.

Ver código fuente

Todos los navegadores desde los albores de los tiempos (la década de 1990) han admitido la capacidad de ver el HTML de la página actual. Aquí hay un fragmento de la fuente de visualización de Introducción a Vagrant que comienza con una gran cantidad de JavaScript minificado y desagradable que no está relacionado con el artículo en sí. Aquí hay una pequeña porción:

Page SourcePage SourcePage Source

Aquí hay algo de HTML real de la página:

HTML From the PageHTML From the PageHTML From the Page

Esto parece bastante complicado, pero lo sorprendente es que no encontrarás los comentarios de Disqus en la fuente de la página.

El poderoso marco en línea

Resulta que la página es una mezcla de varias tecnologías y los comentarios de Disqus están incrustados como un elemento iframe (marco en línea). Puedes averiguarlo haciendo clic con el botón derecho en el área de comentarios y verás que hay información del marco y la fuente allí:

The Mighty Inline FrameThe Mighty Inline FrameThe Mighty Inline Frame

Eso tiene sentido. Incrustar contenido de terceros como iframe es una de las principales razones para usar iframes. Busquemos la etiqueta <iframe> en la fuente de la página principal. ¡Frustrado de nuevo! No hay una etiqueta <iframe> en la fuente de la página principal.

Marcado generado por JavaScript

El motivo de esta omisión es que la fuente de la página de visualización muestra el contenido que se obtuvo del servidor. Pero el DOM (modelo de objeto de documento) final que obtiene el navegador puede ser muy diferente. JavaScript se activa y puede manipular el DOM a voluntad. No se puede encontrar el iframe porque no estaba allí cuando se obtuvo la página del servidor.

Raspado estático frente a raspado dinámico

El raspado estático ignora JavaScript. Obtiene páginas web del servidor sin la ayuda de un navegador. Obtiene exactamente lo que ve en "ver código fuente de la página", y luego lo corta y lo separa. Si el contenido que estás buscando está disponible, no necesitas ir más lejos. Sin embargo, si el contenido es algo así como el iframe de comentarios de Disqus, necesitas raspado dinámico.

El raspado dinámico utiliza un navegador real (o un navegador sin cabeza) y permite que JavaScript haga lo suyo. Luego, consulta el DOM para extraer el contenido que está buscando. A veces es necesario automatizar el navegador simulando a un usuario para obtener el contenido que necesitas.

Raspado estático con "Requests" y BeautifulSoup

Veamos cómo funciona el scraping estático usando dos increíbles paquetes de Python: requests para buscar páginas web y BeautifulSoup para analizar páginas HTML.

Instalación de Requests y BeautifulSoup

Instala pipenv primero, y luego: pipenv install requests beautifulsoup4

Esto creará un entorno virtual para ti también. Si estás utilizando el código de gitlab, puede simplemente instalar pipenv mediante el comando pipenv install.

Obteniendo páginas

Obtener una página con "requests" es una línea: r = requests.get(url)

El objeto de respuesta tiene muchos atributos. Los más importantes son okcontent. Si la solicitud falla, r.ok será False y r.content contendrá el error. El contenido es un flujo de bytes. Por lo general, es mejor decodificarlo en utf-8 cuando se trata de texto:

1
>>> r = requests.get('http://www.c2.com/no-such-page')
2
>>> r.ok
3
False
4
>>> print(r.content.decode('utf-8'))
5
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
6
<html><head>
7
<title>404 Not Found</title>
8
</head><body>
9
<h1>Not Found</h1>
10
<p>The requested URL /ggg was not found on this server.</p>
11
<hr>
12
<address>
13
Apache/2.0.52 (CentOS) Server at www.c2.com Port 80
14
</address>
15
</body></html>

Si todo está bien, r.content contendrá la página web solicitada (igual que ver la fuente de la página).

Encontrar elementos con BeautifulSoup

La función get_page() a continuación obtiene una página web por URL, la decodifica en UTF-8 y la analiza en un objeto BeautifulSoup utilizando el analizador HTML.

1
def get_page(url):
2
    r = requests.get(url)
3
    content = r.content.decode('utf-8')
4
    return BeautifulSoup(content, 'html.parser')

Una vez que tenemos un objeto BeautifulSoup, podemos comenzar a extraer información de la página. BeautifulSoup proporciona muchas funciones de búsqueda para ubicar elementos dentro de la página y profundizar en elementos anidados en profundidad.

Las páginas de autor de Tuts+ contienen múltiples tutoriales. Aquí está mi página de autor. En cada página, hay hasta 12 tutoriales. Si tienes más de 12 tutoriales, puedes navegar a la página siguiente. El HTML de cada artículo está incluido en una etiqueta <article>. La siguiente función busca todos los elementos del artículo en la página, profundiza en sus enlaces y extrae el atributo href para obtener la URL del tutorial:

1
def get_page_articles(page):
2
    elements = page.findAll('article')
3
    articles = [e.a.attrs['href'] for e in elements]
4
    return articles

El siguiente código obtiene todos los artículos de mi página y los imprime (sin el prefijo común):

1
page = get_page('https://tutsplus.com/authors/gigi-sayfan')
2
articles = get_page_articles(page)
3
prefix = 'https://code.tutsplus.com/tutorials'
4
for a in articles:
5
    print(a[len(prefix):])
6
	
7
Output:
8
9
building-games-with-python-3-and-pygame-part-5--cms-30085
10
building-games-with-python-3-and-pygame-part-4--cms-30084
11
building-games-with-python-3-and-pygame-part-3--cms-30083
12
building-games-with-python-3-and-pygame-part-2--cms-30082
13
building-games-with-python-3-and-pygame-part-1--cms-30081
14
mastering-the-react-lifecycle-methods--cms-29849
15
testing-data-intensive-code-with-go-part-5--cms-29852
16
testing-data-intensive-code-with-go-part-4--cms-29851
17
testing-data-intensive-code-with-go-part-3--cms-29850
18
testing-data-intensive-code-with-go-part-2--cms-29848
19
testing-data-intensive-code-with-go-part-1--cms-29847
20
make-your-go-programs-lightning-fast-with-profiling--cms-29809

Raspado dinámico con Selenium

El raspado estático fue lo suficientemente bueno para obtener la lista de artículos, pero como vimos anteriormente, los comentarios de Disqus están incrustados como un elemento iframe en JavaScript. Para obtener los comentarios, necesitaremos automatizar el navegador e interactuar con el DOM de forma interactiva. Una de las mejores herramientas para el trabajo es Selenium.

El Selenium está orientado principalmente a las pruebas automatizadas de aplicaciones web, pero es excelente como herramienta de automatización de navegadores de uso general.

Instalación de Selenium

Escribe este comando para instalar Selenium: pipenv install selenium

Elige tu controlador web

Selenium necesita un controlador web (el navegador que automatiza). Para el web scraping, por lo general, no importa qué controlador elijas. Prefiero el controlador de Chrome. Sigue las instrucciones de esta guía de Selenium.

Chrome frente a PhantomJS

En algunos casos, es posible que prefieras utilizar un navegador sin cabeza, lo que significa que no se muestra la interfaz de usuario. En teoría, PhantomJS es solo otro controlador web. Pero, en la práctica, las personas informaron problemas de incompatibilidad en los que Selenium funciona correctamente con Chrome o Firefox y, a veces, falla con PhantomJS. Prefiero eliminar esta variable de la ecuación y usar un controlador web de navegador real.

Contando comentarios de Disqus

Hagamos un scraping dinámico y usemos Selenium para contar los comentarios de Disqus en los tutoriales de Tuts+. Aquí están las importaciones necesarias.

1
from selenium import webdriver
2
from selenium.webdriver.common.by import By
3
from selenium.webdriver.support.expected_conditions import (
4
    presence_of_element_located)
5
from selenium.webdriver.support.wait import WebDriverWait

La función get_comment_count() acepta un controlador y una URL de Selenium. Utiliza el método get() del controlador para obtener la URL. Esto es similar a request.get(), pero la diferencia es que el objeto controlador administra una representación en vivo del DOM.

Luego, obtiene el título del tutorial y ubica el iframe de Disqus usando su id principal disqus_thread y luego el iframe en sí:

1
def get_comment_count(driver, url):
2
    driver.get(url)
3
    class_name = 'content-banner__title'
4
    name = driver.find_element_by_class_name(class_name).text
5
    e = driver.find_element_by_id('disqus_thread')
6
    disqus_iframe = e.find_element_by_tag_name('iframe')
7
    iframe_url = disqus_iframe.get_attribute('src')

El siguiente paso es recuperar el contenido del propio iframe. Ten en cuenta que esperamos a que esté presente el elemento comment-count (recuento de comentarios) porque los comentarios se cargan dinámicamente y no necesariamente están disponibles todavía.

1
    driver.get(iframe_url)
2
    wait = WebDriverWait(driver, 5)
3
    commentCountPresent = presence_of_element_located(
4
        (By.CLASS_NAME, 'comment-count'))
5
    wait.until(commentCountPresent)
6
7
    comment_count_span = driver.find_element_by_class_name(
8
        'comment-count')
9
    comment_count = int(comment_count_span.text.split()[0])

La última parte es devolver el último comentario si no lo hice yo. La idea es detectar comentarios a los que aún no he respondido.

1
    last_comment = {}
2
    if comment_count > 0:
3
        e = driver.find_elements_by_class_name('author')[-1]
4
        last_author = e.find_element_by_tag_name('a')
5
        last_author = e.get_attribute('data-username')
6
        if last_author != 'the_gigi':
7
            e = driver.find_elements_by_class_name('post-meta')
8
            meta = e[-1].find_element_by_tag_name('a')
9
            last_comment = dict(
10
            	author=last_author,
11
                title=meta.get_attribute('title'),
12
                when=meta.text)
13
    return name, comment_count, last_comment

Conclusión

El raspado web es una práctica útil cuando se puede acceder a la información que necesitas a través de una aplicación web que no proporciona una API adecuada. Se necesita un trabajo no trivial para extraer datos de las aplicaciones web modernas, pero las herramientas maduras y bien diseñadas como requests, BeautifulSoup y Selenium hacen que valga la pena.

Además, no dudes en ver lo que tenemos disponible para la venta y para estudiar en Envato Market, y no dudes en hacer cualquier pregunta y brindar tus valiosos comentarios utilizando el feed a continuación.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.