Usando InitSpider con splash: ¿solo analizando la página de inicio de sesión?

Esta es una especie de pregunta de seguimiento para una pregunta que hice anteriormente .

Estoy tratando de raspar una página web que tengo que iniciar sesión para llegar primero. Pero después de la autenticación, la página web que necesito requiere que se ejecute un poco de Javascript antes de poder ver el contenido. Lo que he hecho es seguir las instrucciones aquí para instalar splash e intentar renderizar el Javascript. Sin embargo…

Antes de cambiar a splash, la autenticación con InitSpider de InitSpider estaba bien. Pasaba por la página de inicio de sesión y estaba limpiando la página de destino (excepto que el Javascript no funciona, obviamente). Pero una vez que agrego el código para pasar las solicitudes a través de splash, parece que no estoy analizando la página de destino.

Araña abajo. La única diferencia entre la versión splash (aquí) y la versión no splash es la función def start_requests() . Todo lo demás es igual entre los dos.

 import scrapy from scrapy.spiders.init import InitSpider from scrapy.spiders import Rule from scrapy.linkextractors import LinkExtractor class BboSpider(InitSpider): name = "bbo" allowed_domains = ["bridgebase.com"] start_urls = [ "http://www.bridgebase.com/myhands/index.php" ] login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F" # authentication def init_request(self): return scrapy.http.Request(url=self.login_page, callback=self.login) def login(self, response): return scrapy.http.FormRequest.from_response( response, formdata={'username': 'USERNAME', 'password': 'PASSWORD'}, callback=self.check_login_response) def check_login_response(self, response): if "recent tournaments" in response.body: self.log("Login successful") return self.initialized() else: self.log("Login failed") print(response.body) # pipe the requests through splash so the JS renders def start_requests(self): for url in self.start_urls: yield scrapy.Request(url, self.parse, meta={ 'splash': { 'endpoint': 'render.html', 'args': {'wait': 0.5} } }) # what to do when a link is encountered rules = ( Rule(LinkExtractor(), callback='parse_item'), ) # do nothing on new link for now def parse_item(self, response): pass def parse(self, response): filename = 'test.html' with open(filename, 'wb') as f: f.write(response.body) 

Lo que está sucediendo ahora es que test.html , el resultado de parse() , ahora es simplemente la página de inicio de sesión en lugar de la página a la que debo redirigirme después de iniciar sesión.

    Esto está indicando en el registro: normalmente, vería la línea “Iniciar sesión correctamente” desde check_login_response() , pero como puede ver a continuación, parece que ni siquiera estoy llegando a ese paso. ¿Esto se debe a que scrapy ahora también está poniendo las solicitudes de autenticación a través de salpicaduras, y se está quedando colgado? Si ese es el caso, ¿hay alguna forma de omitir el splash solo para la parte de autenticación?

     2016-01-24 14:54:56 [scrapy] INFO: Spider opened 2016-01-24 14:54:56 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2016-01-24 14:54:56 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023 2016-01-24 14:55:02 [scrapy] DEBUG: Crawled (200)  (referer: None) 2016-01-24 14:55:02 [scrapy] INFO: Closing spider (finished) 

    Estoy bastante seguro de que no estoy usando splash correctamente. ¿Alguien puede indicarme alguna documentación donde pueda averiguar qué está pasando?

    No creo que solo Splash manejaría bien este caso en particular.

    Aquí está la idea de trabajo:

    • use PhantomJS y el navegador sin cabeza PhantomJS para iniciar sesión en el sitio web
    • pasar las cookies del navegador de PhantomJS a Scrapy

    El código:

     import scrapy from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BboSpider(scrapy.Spider): name = "bbo" allowed_domains = ["bridgebase.com"] login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F" def start_requests(self): driver = webdriver.PhantomJS() driver.get(self.login_page) driver.find_element_by_id("username").send_keys("user") driver.find_element_by_id("password").send_keys("password") driver.find_element_by_name("submit").click() driver.save_screenshot("test.png") WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.LINK_TEXT, "Click here for results of recent tournaments"))) cookies = driver.get_cookies() driver.close() yield scrapy.Request("http://www.bridgebase.com/myhands/index.php", cookies=cookies) def parse(self, response): if "recent tournaments" in response.body: self.log("Login successful") else: self.log("Login failed") print(response.body) 

    Impresiones Login successful y el HTML de la página de “manos”.

    Actualizar

    Entonces, parece que start_requests dispara antes del inicio de sesión.

    Aquí está el código de InitSpider, menos comentarios.

     class InitSpider(Spider): def start_requests(self): self._postinit_reqs = super(InitSpider, self).start_requests() return iterate_spider_output(self.init_request()) def initialized(self, response=None): return self.__dict__.pop('_postinit_reqs') def init_request(self): return self.initialized() 

    InitSpider llama a las principales start_requests con initialized .

    Sus start_requests es una versión modificada del método de la clase base. Así que tal vez algo como esto funcione.

     from scrapy.utils.spider import iterate_spider_output ... def start_requests(self): self._postinit_reqs = my_start_requests() return iterate_spider_output(self.init_request()) def my_start_requests(self): for url in self.start_urls: yield scrapy.Request(url, self.parse, meta={ 'splash': { 'endpoint': 'render.html', 'args': {'wait': 0.5} } }) 

    Necesitas return self.initialized()

    Puede obtener todos los datos sin necesidad de js, hay enlaces disponibles para los navegadores que no tienen habilitado javascript, las direcciones URL son la misma barra ?offset=0 . Solo necesita analizar las consultas del url del torneo en el que está interesado y crear una solicitud de formulario.

     import scrapy from scrapy.spiders.init import InitSpider from urlparse import parse_qs, urlparse class BboSpider(InitSpider): name = "bbo" allowed_domains = ["bridgebase.com"] start_urls = [ "http://www.bridgebase.com/myhands/index.php" ] login_page = "http://www.bridgebase.com/myhands/myhands_login.php?t=%2Fmyhands%2Findex.php%3F" def start_requests(self): return [scrapy.FormRequest(self.login_page, formdata={'username': 'foo', 'password': 'bar'}, callback=self.parse)] def parse(self, response): yield scrapy.Request("http://www.bridgebase.com/myhands/index.php?offset=0", callback=self.get_all_tournaments) def get_all_tournaments(self, r): url = r.xpath("//a/@href[contains(., 'tourneyhistory')]").extract_first() yield scrapy.Request(url, callback=self.chosen_tourney) def chosen_tourney(self, r): url = r.xpath("//a[contains(./text(),'Speedball')]/@href").extract_first() query = urlparse(url).query yield scrapy.FormRequest("http://webutil.bridgebase.com/v2/tarchive.php?offset=0", callback=self.get_tourney_data_links, formdata={k: v[0] for k, v in parse_qs(query).items()}) def get_tourney_data_links(self, r): print r.xpath("//a/@href").extract() 

    Hay numerosos enlaces en la salida, para las manos que obtenga el tview.php?-t=.... , puede solicitar que cada uno se una a http://webutil.bridgebase.com/v2/ y le dará un tabla de todos los datos que es fácil de analizar, también hay enlaces a tourney=4796-1455303720-&username=... asociados con cada mano en las tablas, un fragmento de la salida del enlace tview:

     class="bbo_tr_t"> 
    Title#4796 Ind. ACBL Fri 2pm
    HostACBL
    Tables9
    Section 1
    NameScore (IMPs)RankPrizePoints
    colt2242.8810.90
    francha35.5220.63
    MSMK34.3830.45

    El rest del análisis lo dejaré para ti.