() translation by (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:

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:

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

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í:

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 ok
y content
. 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.