javascript - page - scrapy splash without docker
¿Se puede usar scrapy para eliminar contenido dinámico de sitios web que usan AJAX? (7)
Recientemente he estado aprendiendo Python y estoy inclinando mi mano en la construcción de un web-scraper. No es nada lujoso en absoluto; su único propósito es obtener los datos de un sitio web de apuestas y poner estos datos en Excel.
La mayoría de los problemas son solucionables y estoy teniendo un buen lío. Sin embargo, estoy alcanzando un obstáculo masivo sobre un problema. Si un sitio carga una tabla de caballos y enumera los precios de apuestas actuales, esta información no se encuentra en ningún archivo fuente. La clave es que estos datos son en vivo algunas veces, y los números se actualizan obviamente desde algún servidor remoto. El HTML de mi PC simplemente tiene un agujero en el que los servidores están procesando toda la información interesante que necesito.
Ahora mi experiencia con el contenido web dinámico es baja, así que esto es algo que me cuesta entender.
Creo que Java o Javascript es una clave, esto aparece a menudo.
El raspador es simplemente un motor de comparación de probabilidades. Algunos sitios tienen API pero necesito esto para aquellos que no lo hacen. Estoy usando la biblioteca de scrapy con Python 2.7
Me disculpo si esta pregunta es demasiado abierta. En resumen, mi pregunta es: ¿cómo se puede utilizar el scrapy para raspar estos datos dinámicos para poder usarlos? ¿Para que pueda raspar los datos de estas apuestas en tiempo real?
Saludos a la gente :)
¿Cómo se puede usar el filtro para raspar estos datos dinámicos para poder usarlo?
Me pregunto por qué nadie ha publicado la solución solo con Scrapy.
Echa un vistazo a la publicación del blog del equipo de Scrapy RETIRANDO PÁGINAS INFINITAS DE DESPLAZAMIENTO. El ejemplo elimina http://spidyquotes.herokuapp.com/scroll sitio web que utiliza desplazamiento infinito.
La idea es usar las herramientas de desarrollo de su navegador y observar las solicitudes de AJAX, luego, basándose en esa información, crear las solicitudes de Scrapy .
import json
import scrapy
class SpidyQuotesSpider(scrapy.Spider):
name = ''spidyquotes''
quotes_base_url = ''http://spidyquotes.herokuapp.com/api/quotes?page=%s''
start_urls = [quotes_base_url % 1]
download_delay = 1.5
def parse(self, response):
data = json.loads(response.body)
for item in data.get(''quotes'', []):
yield {
''text'': item.get(''text''),
''author'': item.get(''author'', {}).get(''name''),
''tags'': item.get(''tags''),
}
if data[''has_next'']:
next_page = data[''page''] + 1
yield scrapy.Request(self.quotes_base_url % next_page)
Aquí hay un ejemplo simple de usar scrapy con solicitud de Ajax. Deje que vea el sitio http://www.rubin-kazan.ru/guestbook.html Todos los mensajes se cargan con una solicitud ajax. Mi objetivo es obtener estos mensajes con todos sus atributos (autor, fecha, ...).
Cuando analizo el código fuente de la página, no puedo ver todos estos mensajes porque la página web usa la tecnología ajax. Pero puedo usar Firebug de Mozila Firefox (o un instrumento de analogía en otro navegador) para analizar la solicitud Http que genera los mensajes en la página web.
Para este propósito, no recargo todas las páginas, solo la parte de la página que contiene mensajes. Para este propósito hago clic en un número arbitrario de página en la parte inferior y observo la solicitud HTTP que es responsable del cuerpo del mensaje
Después de terminar, analizo los encabezados de la solicitud (debo citar que esta URL la extraeré de la página fuente de la sección var, vea el código a continuación).
y el contenido de datos de formulario de solicitud (el método Http es "Publicar")
y el contenido de la respuesta, que es un archivo Json,
que presentan toda la información que estoy buscando.
A partir de ahora debo implementar todo este conocimiento en scrapy. Vamos a definir la araña para este propósito.
class spider(BaseSpider):
name = ''RubiGuesst''
start_urls = [''http://www.rubin-kazan.ru/guestbook.html'']
def parse(self, response):
url_list_gb_messages = re.search(r''url_list_gb_messages="(.*)"'', response.body).group(1)
yield FormRequest(''http://www.rubin-kazan.ru'' + url_list_gb_messages, callback=self.RubiGuessItem, formdata={''page'': str(page + 1), ''uid'': ''''})
def RubiGuessItem(self, response):
json_file = response.body
En la función de análisis, tengo la respuesta para la primera solicitud. En RubiGuessItem tengo el archivo json con toda la información.
Estaba usando un middleware de descarga personalizado, pero no estaba muy contento con él, ya que no logré que la memoria caché funcionara con él.
Un mejor enfoque fue implementar un controlador de descarga personalizado.
Hay un ejemplo de trabajo here . Se parece a esto:
# encoding: utf-8
from __future__ import unicode_literals
from scrapy import signals
from scrapy.signalmanager import SignalManager
from scrapy.responsetypes import responsetypes
from scrapy.xlib.pydispatch import dispatcher
from selenium import webdriver
from six.moves import queue
from twisted.internet import defer, threads
from twisted.python.failure import Failure
class PhantomJSDownloadHandler(object):
def __init__(self, settings):
self.options = settings.get(''PHANTOMJS_OPTIONS'', {})
max_run = settings.get(''PHANTOMJS_MAXRUN'', 10)
self.sem = defer.DeferredSemaphore(max_run)
self.queue = queue.LifoQueue(max_run)
SignalManager(dispatcher.Any).connect(self._close, signal=signals.spider_closed)
def download_request(self, request, spider):
"""use semaphore to guard a phantomjs pool"""
return self.sem.run(self._wait_request, request, spider)
def _wait_request(self, request, spider):
try:
driver = self.queue.get_nowait()
except queue.Empty:
driver = webdriver.PhantomJS(**self.options)
driver.get(request.url)
# ghostdriver won''t response when switch window until page is loaded
dfd = threads.deferToThread(lambda: driver.switch_to.window(driver.current_window_handle))
dfd.addCallback(self._response, driver, spider)
return dfd
def _response(self, _, driver, spider):
body = driver.execute_script("return document.documentElement.innerHTML")
if body.startswith("<head></head>"): # cannot access response header in Selenium
body = driver.execute_script("return document.documentElement.textContent")
url = driver.current_url
respcls = responsetypes.from_args(url=url, body=body[:100].encode(''utf8''))
resp = respcls(url=url, body=body, encoding="utf-8")
response_failed = getattr(spider, "response_failed", None)
if response_failed and callable(response_failed) and response_failed(resp, driver):
driver.close()
return defer.fail(Failure())
else:
self.queue.put(driver)
return defer.succeed(resp)
def _close(self):
while not self.queue.empty():
driver = self.queue.get_nowait()
driver.close()
Supongamos que su raspador se llama "raspador". Si coloca el código mencionado dentro de un archivo llamado handlers.py en la raíz de la carpeta "scraper", puede agregarlo a su settings.py:
DOWNLOAD_HANDLERS = {
''http'': ''scraper.handlers.PhantomJSDownloadHandler'',
''https'': ''scraper.handlers.PhantomJSDownloadHandler'',
}
Y voilà, el DOM analizado JS, con caché de scrapy, reintentos, etc.
Los navegadores basados en Webkit (como Google Chrome o Safari) tienen herramientas de desarrollador integradas. En Chrome, puede abrirlo Menu->Tools->Developer Tools
. La pestaña Network
permite ver toda la información sobre cada solicitud y respuesta:
En la parte inferior de la imagen, puede ver que he filtrado la solicitud hacia abajo a XHR
; estas son solicitudes hechas por código de JavaScript.
Consejo: el registro se borra cada vez que carga una página, en la parte inferior de la imagen, el botón de punto negro conservará el registro.
Después de analizar las solicitudes y las respuestas, puede simular estas solicitudes desde su rastreador web y extraer datos valiosos. En muchos casos, será más fácil obtener sus datos que analizar HTML, porque esos datos no contienen lógica de presentación y están formateados para ser accedidos por código JavaScript.
Firefox tiene una extensión similar, se llama firebug . Algunos argumentarán que firebug es aún más poderoso, pero me gusta la simplicidad de webkit.
Manejo la solicitud de Ajax usando Selenium y el controlador web Firefox. No es tan rápido si necesita el rastreador como daemon, pero es mucho mejor que cualquier solución manual. Escribí un breve tutorial here para referencia
Muchas veces, al rastrear, nos encontramos con problemas en los que el contenido que se representa en la página se genera con Javascript y, por lo tanto, scrapy no puede rastrearlo (por ejemplo, solicitudes ajax, locura jQuery).
Sin embargo, si usa Scrapy junto con el marco de prueba web Selenium, podremos rastrear todo lo que se muestre en un navegador web normal.
Algunas cosas a tener en cuenta:
Debe tener instalada la versión Python de Selenium RC para que esto funcione, y debe haber configurado Selenium correctamente. También esto es solo un rastreador de plantilla. Podrías estar mucho más loco y más avanzado con las cosas, pero solo quería mostrar la idea básica. Como el código se encuentra ahora, se realizarán dos solicitudes para una url determinada. Una solicitud es hecha por Scrapy y la otra es hecha por Selenium. Estoy seguro de que hay formas de evitar esto, por lo que es posible que solo haga que Selenium lo solicite por única vez, pero no me molesté en implementarlo y, al hacer dos solicitudes, también puedo rastrear la página con Scrapy.
Esto es bastante poderoso porque ahora tiene todo el DOM renderizado disponible para que pueda rastrear y aún puede usar todas las características agradables de rastreo en Scrapy. Esto hará que el rastreo sea más lento, por supuesto, pero dependiendo de cuánto necesites el DOM renderizado, valdrá la pena la espera.
from scrapy.contrib.spiders import CrawlSpider, Rule from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from scrapy.selector import HtmlXPathSelector from scrapy.http import Request from selenium import selenium class SeleniumSpider(CrawlSpider): name = "SeleniumSpider" start_urls = ["http://www.domain.com"] rules = ( Rule(SgmlLinkExtractor(allow=(''/.html'', )), callback=''parse_page'',follow=True), ) def __init__(self): CrawlSpider.__init__(self) self.verificationErrors = [] self.selenium = selenium("localhost", 4444, "*chrome", "http://www.domain.com") self.selenium.start() def __del__(self): self.selenium.stop() print self.verificationErrors CrawlSpider.__del__(self) def parse_page(self, response): item = Item() hxs = HtmlXPathSelector(response) #Do some XPath selection with Scrapy hxs.select(''//div'').extract() sel = self.selenium sel.open(response.url) #Wait for javscript to load in Selenium time.sleep(2.5) #Do some crawling of javascript created content with Selenium sel.get_text("//div") yield item # Snippet imported from snippets.scrapy.org (which no longer works) # author: wynbennett # date : Jun 21, 2011
Referencia: http://snipplr.com/view/66998/
Otra solución sería implementar un controlador de descarga o descargar middleware de controlador. El siguiente es un ejemplo de middleware que usa selenio con un controlador de phantomjs headdriver sin cabeza:
class JsDownload(object):
@check_spider_middleware
def process_request(self, request, spider):
driver = webdriver.PhantomJS(executable_path=''D:/phantomjs.exe'')
driver.get(request.url)
return HtmlResponse(request.url, encoding=''utf-8'', body=driver.page_source.encode(''utf-8''))
Quería poder decirles a las diferentes arañas qué middleware usar, así que implementé este contenedor:
def check_spider_middleware(method):
@functools.wraps(method)
def wrapper(self, request, spider):
msg = ''%%s %s middleware step'' % (self.__class__.__name__,)
if self.__class__ in spider.middleware:
spider.log(msg % ''executing'', level=log.DEBUG)
return method(self, request, spider)
else:
spider.log(msg % ''skipping'', level=log.DEBUG)
return None
return wrapper
settings.py:
DOWNLOADER_MIDDLEWARES = {''MyProj.middleware.MiddleWareModule.MiddleWareClass'': 500}
para que el contenedor funcione, todas las arañas deben tener como mínimo:
middleware = set([])
para incluir un middleware:
middleware = set([MyProj.middleware.ModuleName.ClassName])
La principal ventaja de implementarlo de esta manera en lugar de en la araña es que solo terminas haciendo una solicitud. En la solución de AT, por ejemplo: el controlador de descargas procesa la solicitud y luego entrega la respuesta a la araña. La araña luego realiza una nueva solicitud en su función parse_page: son dos solicitudes para el mismo contenido.
¡Aclamaciones!