¿Cómo puedo extraer una página con contenido dinámico (creado por JavaScript) en Python?
Estoy intentando desarrollar un raspador web simple. Quiero extraer texto sin formato sin formato HTML. Mi código funciona en HTML simple (estático), pero no cuando el contenido se genera mediante JavaScript incrustado en la página.
En particular, cuando leo urllib2.urlopen(request)
el contenido de la página, no muestra nada de lo que agregaría el código JavaScript, porque ese código no se ejecuta en ninguna parte. Normalmente lo ejecutaría el navegador web, pero eso no forma parte de mi programa.
¿Cómo puedo acceder a este contenido dinámico desde mi código Python?
Consulte también ¿Se puede utilizar scrapy para extraer contenido dinámico de sitios web que utilizan AJAX? para obtener respuestas específicas de Scrapy.
EDITAR Septiembre de 2021: phantomjs
ya no se mantiene
EDITAR 30/dic/2017: esta respuesta aparece en los principales resultados de las búsquedas de Google, así que decidí actualizarla. La vieja respuesta aún está al final.
dryscape ya no se mantiene y la biblioteca que recomiendan los desarrolladores de dryscape es solo Python 2. Descubrí que usar la biblioteca Python de Selenium con Phantom JS como controlador web es lo suficientemente rápido y fácil para realizar el trabajo.
Una vez que haya instalado Phantom JS , asegúrese de que el phantomjs
binario esté disponible en la ruta actual:
phantomjs --version
# result:
2.1.1
#Ejemplo Para dar un ejemplo, creé una página de muestra con el siguiente código HTML. ( enlace ):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Javascript scraping test</title>
</head>
<body>
<p id='intro-text'>No javascript support</p>
<script>
document.getElementById('intro-text').innerHTML = 'Yay! Supports javascript';
</script>
</body>
</html>
sin javascript dice: No javascript support
y con javascript:Yay! Supports javascript
#Scraping sin soporte JS:
import requests
from bs4 import BeautifulSoup
response = requests.get(my_url)
soup = BeautifulSoup(response.text)
soup.find(id="intro-text")
# Result:
<p id="intro-text">No javascript support</p>
#Scraping con soporte JS:
from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(my_url)
p_element = driver.find_element_by_id(id_='intro-text')
print(p_element.text)
# result:
'Yay! Supports javascript'
También puede utilizar el dryscrape de la biblioteca Python para eliminar sitios web controlados por JavaScript.
#Scraping con soporte JS:
import dryscrape
from bs4 import BeautifulSoup
session = dryscrape.Session()
session.visit(my_url)
response = session.body()
soup = BeautifulSoup(response)
soup.find(id="intro-text")
# Result:
<p id="intro-text">Yay! Supports javascript</p>
No obtenemos los resultados correctos porque cualquier contenido generado por JavaScript debe representarse en el DOM. Cuando recuperamos una página HTML, recuperamos el DOM inicial, no modificado por JavaScript.
Por lo tanto, debemos representar el contenido de JavaScript antes de rastrear la página.
Como el selenio ya se menciona muchas veces en este hilo (y también se mencionó lo lento que se vuelve a veces), enumeraré otras dos posibles soluciones.
Solución 1: Este es un tutorial muy bueno sobre cómo usar Scrapy para rastrear contenido generado en JavaScript y vamos a seguirlo.
Qué necesitaremos:
Docker instalado en nuestra máquina. Esta es una ventaja sobre otras soluciones hasta este momento, ya que utiliza una plataforma independiente del sistema operativo.
Instale Splash siguiendo las instrucciones enumeradas para nuestro sistema operativo correspondiente.
Citando la documentación de presentación:Splash es un servicio de renderizado de JavaScript. Es un navegador web liviano con una API HTTP, implementado en Python 3 usando Twisted y QT5.
Básicamente, usaremos Splash para representar contenido generado en Javascript.
Ejecute el servidor de presentación:
sudo docker run -p 8050:8050 scrapinghub/splash
.Instale el complemento scrapy-splash :
pip install scrapy-splash
Suponiendo que ya tenemos un proyecto Scrapy creado (si no, hagamos uno ), seguiremos la guía y actualizaremos
settings.py
:Luego vaya a su proyecto scrapy
settings.py
y configure estos middlewares:DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashCookiesMiddleware': 723, 'scrapy_splash.SplashMiddleware': 725, 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810, }
La URL del servidor Splash (si está utilizando Win u OSX, esta debería ser la URL de la máquina acoplable: ¿Cómo obtener la dirección IP de un contenedor Docker desde el host? ):
SPLASH_URL = 'http://localhost:8050'
Y finalmente también debes establecer estos valores:
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
Finalmente, podemos usar un
SplashRequest
:En una araña normal tienes objetos de solicitud que puedes usar para abrir URL. Si la página que desea abrir contiene datos generados por JS, debe usar SplashRequest (o SplashFormRequest) para representar la página. He aquí un ejemplo sencillo:
class MySpider(scrapy.Spider): name = "jsscraper" start_urls = ["http://quotes.toscrape.com/js/"] def start_requests(self): for url in self.start_urls: yield SplashRequest( url=url, callback=self.parse, endpoint='render.html' ) def parse(self, response): for q in response.css("div.quote"): quote = QuoteItem() quote["author"] = q.css(".author::text").extract_first() quote["quote"] = q.css(".text::text").extract_first() yield quote
SplashRequest representa la URL como html y devuelve la respuesta que puede usar en el método de devolución de llamada (análisis).
Solución 2: Llamemos a esto experimental en este momento (mayo de 2018)...
Esta solución es solo para la versión 3.6 de Python (por el momento).
¿ Conoce el módulo de solicitudes (bueno, quién no)?
Ahora tiene un hermano pequeño que rastrea la web: request-HTML :
Esta biblioteca pretende hacer que el análisis de HTML (por ejemplo, el raspado de la web) sea lo más simple e intuitivo posible.
Instalar solicitudes-html:
pipenv install requests-html
Realice una solicitud a la URL de la página:
from requests_html import HTMLSession session = HTMLSession() r = session.get(a_page_url)
Representa la respuesta para obtener los bits generados por Javascript:
r.html.render()
Finalmente, el módulo parece ofrecer capacidades de scraping .
Alternativamente, podemos probar la forma bien documentada de usar BeautifulSoup con el r.html
objeto que acabamos de renderizar.
Quizás el selenio pueda hacerlo.
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
Si alguna vez ha utilizado el Requests
módulo para Python, recientemente descubrí que el desarrollador creó un nuevo módulo llamado Requests-HTML
que ahora también tiene la capacidad de representar JavaScript.
También puede visitar https://html.python-requests.org/ para obtener más información sobre este módulo, o si solo está interesado en renderizar JavaScript, puede visitar https://html.python-requests.org/? #javascript-support para aprender directamente cómo usar el módulo para representar JavaScript usando Python.
Básicamente, una vez que haya instalado correctamente el Requests-HTML
módulo, el siguiente ejemplo, que se muestra en el enlace anterior , muestra cómo puede utilizar este módulo para extraer un sitio web y representar el JavaScript contenido en el sitio web:
from requests_html import HTMLSession
session = HTMLSession()
r = session.get('http://python-requests.org/')
r.html.render()
r.html.search('Python 2 will retire in only {months} months!')['months']
'<time>25</time>' #This is the result.
Hace poco me enteré de esto por un vídeo de YouTube. ¡Haga clic aquí! para ver el vídeo de YouTube, que demuestra cómo funciona el módulo.
Parece que se puede acceder a los datos que realmente estás buscando a través de una URL secundaria llamada por algún JavaScript en la página principal.
Si bien puedes intentar ejecutar JavaScript en el servidor para manejar esto, un enfoque más simple podría ser cargar la página usando Firefox y usar una herramienta como Charles o Firebug para identificar exactamente cuál es esa URL secundaria. Luego, puede consultar esa URL directamente para obtener los datos que le interesan.