mocopera
Estoy tratando de desarrollar un web scraper simple. Quiero extraer texto sin formato sin marcado HTML. Mi código funciona en HTML simple (estático), pero no cuando JavaScript incrustado en la página genera contenido.
En particular, cuando uso urllib2.urlopen(request)
para leer el contenido de la página, no muestra nada que agregaría el código JavaScript, porque ese código no se ejecuta en cualquier lugar. Normalmente lo ejecutaría el navegador web, pero eso no es parte de mi programa.
¿Cómo puedo acceder a este contenido dinámico desde mi código de Python?
Ver también ¿Se puede usar scrapy para extraer contenido dinámico de sitios web que usan AJAX? para respuestas específicas de Scrapy.
avi
EDITAR septiembre de 2021: phantomjs
tampoco se mantiene más
EDIT 30/dic/2017: Esta respuesta aparece en los mejores resultados de las búsquedas de Google, así que decidí actualizarla. La respuesta anterior sigue estando al final.
dryscape ya no se mantiene y la biblioteca que los desarrolladores de dryscape recomiendan es solo Python 2. Descubrí que usar la biblioteca python de Selenium con Phantom JS como un controlador web es lo suficientemente rápido y fácil para hacer el trabajo.
Una vez que haya instalado Fantasma JSAsegúrate que 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>
#Raspado 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 puedes usar la biblioteca de Python raspado en seco para raspar sitios web controlados por javascript.
#Raspado 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>
-
Lamentablemente, no hay soporte para Windows.
– Expenzor
17 de abril de 2017 a las 14:39
-
@Expenzor
Estoy trabajando en ventanas. PhantomJS funciona bien.– Aakash Choubey
12 de enero de 2018 a las 10:43
-
Vale la pena señalar que PhantomJS ha sido descontinuado y ya no está en desarrollo activo a la luz de que Chrome ahora es compatible con headless. Se sugiere el uso de Chrome/Firefox sin interfaz.
– Sytech
23 de marzo de 2018 a las 20:42
-
Recibo la siguiente advertencia:
Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
. ¿Quizás @sytech estaba hablando del soporte de Selenium para eso?– jpmc26
30 de abril de 2018 a las 4:37
-
Es tanto el soporte de selenio como el propio PhantomJS. github.com/ariya/phantomjs/issues/15344
– Sytech
30 de abril de 2018 a las 12:34
Juan Moutafis
No obtenemos los resultados correctos porque cualquier contenido generado por JavaScript debe procesarse en el DOM. Cuando buscamos una página HTML, buscamos el DOM inicial, sin modificar 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 muy buen tutorial sobre cómo usar Scrapy para rastrear contenido generado por javascript y vamos a seguir precisamente eso.
Lo que necesitaremos:
-
Estibador instalado en nuestra máquina. Esta es una ventaja sobre otras soluciones hasta este momento, ya que utiliza una plataforma independiente del sistema operativo.
-
Instalar bienvenida siguiendo las instrucciones enumeradas para nuestro sistema operativo correspondiente.
Citando de la documentación de bienvenida: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.
Esencialmente, vamos a usar Splash para renderizar contenido generado por Javascript.
-
Ejecute el servidor de bienvenida:
sudo docker run -p 8050:8050 scrapinghub/splash
. -
Instala el scrapy-splash enchufar:
pip install scrapy-splash
-
Suponiendo que ya tenemos un proyecto Scrapy creado (si no, hagamos uno), seguiremos la guía y actualizaremos la
settings.py
:Luego ve a tu 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 usa Win u OSX, esta debería ser la URL de la máquina docker: ¿Cómo obtener la dirección IP de un contenedor Docker del host?):
SPLASH_URL = 'http://localhost:8050'
Y finalmente necesitas establecer estos valores también:
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
-
Finalmente, podemos usar un
SplashRequest
:En una araña normal, tiene objetos de solicitud que puede usar para abrir direcciones URL. Si la página que desea abrir contiene datos generados por JS, debe usar SplashRequest (o SplashFormRequest) para representar la página. Aquí hay un ejemplo simple:
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 por el momento (mayo de 2018)…
Esta solución es para la versión 3.6 de Python. solo (por el momento).
Sabe usted la peticiones módulo (bueno, ¿quién no)?
Ahora tiene un hermano pequeño que rastrea la web: solicitudes-HTML:
Esta biblioteca tiene la intención de hacer que el análisis de HTML (por ejemplo, raspar la web) sea lo más simple e intuitivo posible.
-
Instalar solicitudes-html:
pipenv install requests-html
-
Haz una solicitud a la url de la página:
from requests_html import HTMLSession session = HTMLSession() r = session.get(a_page_url)
-
Procesa la respuesta para obtener los bits generados por Javascript:
r.html.render()
Finalmente, el módulo parece ofrecer capacidades de raspado.
Alternativamente, podemos probar la forma bien documentada de usar BeautifulSoup con el r.html
objeto que acabamos de renderizar.
-
¿Puede explicar cómo obtener el contenido HTML completo, con los bits JS cargados, después de llamar a .render()? Estoy atascado después de ese punto. No veo todos los iframes que se inyectan en la página normalmente desde JavaScript en el
r.html.html
objeto.– fIwJlxSzApHEZIL
13 de diciembre de 2018 a las 20:24
-
@ anon58192932 Dado que en este momento esta es una solución experimental y no sé qué es exactamente lo que está tratando de lograr como resultado, realmente no puedo sugerir nada … Puede crear una nueva pregunta aquí en SO si no lo ha hecho resolvió una solución todavía
– John Moutafis
2 de enero de 2019 a las 13:57
-
Recibí este error: RuntimeError: no se puede usar HTMLSession dentro de un bucle de eventos existente. Utilice AsyncHTMLSession en su lugar.
– Josué Stafford
23 de abril de 2019 a las 15:59
-
@HuckIt parece ser un problema conocido: github.com/psf/requests-html/issues/140
– John Moutafis
15 de octubre de 2019 a las 12:22
-
Probé el primer método, pero todavía no puedo ver el contenido js renderizado. ¿Puedes decirme qué me estoy perdiendo?
– AlixaProDev
12 de julio de 2022 a las 16:41
Tal vez selenio lo puede hacer.
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get(url)
time.sleep(5)
htmlSource = driver.page_source
-
Selenium es realmente pesado para este tipo de cosas, sería innecesariamente lento y requeriría una cabeza de navegador si no usa PhantomJS, pero esto funcionaría.
– Joshua Hedges
28 de julio de 2017 a las 16:27
-
@JoshuaHedges Puede ejecutar otros navegadores más estándar en modo sin cabeza.
– reynoldsnlp
9 de enero de 2020 a las 0:55
-
options = webdriver.ChromeOptions() options.add_argument('--headless') driver = webdriver.Chrome(options=options)
– fantabuloso
15 oct 2020 a las 14:50
SShah
Si alguna vez has usado el Requests
módulo para python antes, recientemente descubrí que el desarrollador creó un nuevo módulo llamado Requests-HTML
que ahora también tiene la capacidad de renderizar JavaScript.
También puedes 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 renderizar JavaScript usando Python.
Esencialmente, una vez que haya instalado correctamente el Requests-HTML
módulo, el siguiente ejemplo, que es se muestra en el enlace de arribamuestra cómo puede usar este módulo para raspar 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 video de YouTube. ¡Haga clic aquí! para ver el video de YouTube, que demuestra cómo funciona el módulo.
Parece que se puede acceder a los datos que realmente está buscando a través de una URL secundaria llamada por algún javascript en la página principal.
Si bien podría 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 bicho de fuego para identificar exactamente cuál es esa URL secundaria. Luego, puede consultar esa URL directamente para obtener los datos que le interesan.
-
@Kris En caso de que alguien tropiece con esto y quiera probarlo en lugar de algo tan pesado como el selenio, aquí hay un breve ejemplo. Este abrirá la página de detalles de la pieza para una tuerca hexagonal en el sitio web de McMaster-Carr. El contenido de su sitio web se obtiene principalmente mediante Javascript y tiene muy poca información de página nativa. Si abre las herramientas de desarrollo de su navegador, navega a la pestaña Red y actualiza la página, puede ver todas las solicitudes realizadas por la página y encontrar los datos relevantes (en este caso, el html de detalle de la pieza).
– BasuraDemonio
13 de agosto de 2018 a las 18:02
-
Este es una URL diferente que se encuentra en la pestaña Red de devtool de Firefox que, si se sigue, contiene el html para la mayor parte de la información de la pieza y expone algunos de los parámetros necesarios para navegar fácilmente a otra información de la pieza para facilitar el raspado. Este ejemplo en particular no es particularmente útil ya que el precio es generado por otra función de Javascript, pero debería servir como una introducción para cualquiera que quiera seguir el consejo de Stephen.
– BasuraDemonio
13 de agosto de 2018 a las 18:10
Robbie
Esta parece ser una buena solución también, tomada de un gran entrada de blog
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from lxml import html
#Take this class for granted.Just use result of rendering.
class Render(QWebPage):
def __init__(self, url):
self.app = QApplication(sys.argv)
QWebPage.__init__(self)
self.loadFinished.connect(self._loadFinished)
self.mainFrame().load(QUrl(url))
self.app.exec_()
def _loadFinished(self, result):
self.frame = self.mainFrame()
self.app.quit()
url="http://pycoders.com/archive/"
r = Render(url)
result = r.frame.toHtml()
# This step is important.Converting QString to Ascii for lxml to process
# The following returns an lxml element tree
archive_links = html.fromstring(str(result.toAscii()))
print archive_links
# The following returns an array containing the URLs
raw_links = archive_links.xpath('//div[@class="campaign"]/a/@href')
print raw_links
-
@Kris En caso de que alguien tropiece con esto y quiera probarlo en lugar de algo tan pesado como el selenio, aquí hay un breve ejemplo. Este abrirá la página de detalles de la pieza para una tuerca hexagonal en el sitio web de McMaster-Carr. El contenido de su sitio web se obtiene principalmente mediante Javascript y tiene muy poca información de página nativa. Si abre las herramientas de desarrollo de su navegador, navega a la pestaña Red y actualiza la página, puede ver todas las solicitudes realizadas por la página y encontrar los datos relevantes (en este caso, el html de detalle de la pieza).
– BasuraDemonio
13 de agosto de 2018 a las 18:02
-
Este es una URL diferente que se encuentra en la pestaña Red de devtool de Firefox que, si se sigue, contiene el html para la mayor parte de la información de la pieza y expone algunos de los parámetros necesarios para navegar fácilmente a otra información de la pieza para facilitar el raspado. Este ejemplo en particular no es particularmente útil ya que el precio es generado por otra función de Javascript, pero debería servir como una introducción para cualquiera que quiera seguir el consejo de Stephen.
– BasuraDemonio
13 de agosto de 2018 a las 18:10
seco
Selenium es el mejor para raspar contenido JS y Ajax.
Revisa este artículo para extraer datos de la web usando Python
$ pip install selenium
A continuación, descargue el controlador web de Chrome.
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("https://www.python.org/")
nav = browser.find_element_by_id("mainnav")
print(nav.text)
Fácil, ¿verdad?
-
el selenio es mejor, pero algunos sitios parecen detectar el uso de selenio.
– Jawad Ahmad Kan
26 de agosto de 2020 a las 5:21
-
Creo que también necesitarás el controlador de cromo en su RUTA para que esto funcione.
– wsams
1 de noviembre de 2022 a las 16:08
Parece que podrías necesitar algo más pesado, prueba Selenium o Watir.
– Wim
8 de noviembre de 2011 a las 11:16
He hecho esto con éxito en Java (he usado el kit de herramientas Cobra lobobrowser.org/cobra.jsp) Ya que quieres hackear en python (siempre es una buena opción) te recomiendo estas dos opciones: – packtpub.com/article/web-scraping-with-python-part-2 – blog.databigbang.com/web-scraping-ajax-and-javascript-sites
– bpgergo
8 de noviembre de 2011 a las 11:34
Tenga en cuenta que la respuesta mejor calificada se actualizó por última vez en 2017 y está desactualizada a partir de 2021, ya que PhantomJS y dryscrape han quedado obsoletos. Recomiendo leer todo el hilo antes de probar una de las técnicas que recomienda.
– ggorlen
30 de marzo de 2021 a las 21:46