¿Por qué no funcionan mis reglas de Scrapy CrawlSpider?

He logrado codificar un rastreador muy simple con Scrapy, con estas restricciones dadas:

  • Almacene toda la información del enlace (por ejemplo: texto de anclaje, título de la página), por lo tanto, las 2 devoluciones de llamada
  • Utilice CrawlSpider para aprovechar las reglas, por lo tanto, no BaseSpider

Funciona bien, excepto que no implementa reglas si agrego una callback a la primera solicitud.

Aquí está mi código: (funciona pero no correctamente, con un ejemplo en vivo)

from scrapy.contrib.spiders import CrawlSpider,Rule from scrapy.selector import HtmlXPathSelector from scrapy.http import Request from scrapySpider.items import SPage from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor class TestSpider4(CrawlSpider): name = "spiderSO" allowed_domains = ["cumulodata.com"] start_urls = ["http://www.cumulodata.com"] extractor = SgmlLinkExtractor() def parse_start_url(self, response): #3 print('----------manual call of',response) self.parse_links(response) print('----------manual call done') # 1 return Request(self.start_urls[0]) # does not call parse_links(example.com) # 2 return Request(self.start_urls[0],callback = self.parse_links) # does not call parse_links(example.com) rules = ( Rule(extractor,callback='parse_links',follow=True), ) def parse_links(self, response): hxs = HtmlXPathSelector(response) print('----------- manual parsing links of',response.url) links = hxs.select('//a') for link in links: title = link.select('@title') url = link.select('@href').extract()[0] meta={'title':title,} yield Request(url, callback = self.parse_page,meta=meta) def parse_page(self, response): print('----------- parsing page: ',response.url) hxs = HtmlXPathSelector(response) item=SPage() item['url'] = str(response.request.url) item['title']=response.meta['title'] item['h1']=hxs.select('//h1/text()').extract() yield item 

He intentado resolver este problema de 3 maneras:

  • 1: Para devolver una solicitud con la url de inicio, las reglas no se ejecutan
  • 2: igual que el anterior, pero con una callback a parse_links – mismo problema
  • 3: Llamar a parse_links después de raspar la url de inicio, al implementar parse_start_url , la función no se llama

Aquí están los registros:

 ----------manual call of ) ----------manual call done #No '----------- manual parsing links', so `parse_links` is never called! 

Versiones

Aquí hay un raspador que funciona perfectamente:

 from scrapy.contrib.spiders import CrawlSpider,Rule from scrapy.selector import HtmlXPathSelector from scrapy.http import Request from scrapySpider.items import SPage from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor class TestSpider4(CrawlSpider): name = "spiderSO" allowed_domains = ["cumulodata.com"] start_urls = ["http://www.cumulodata.com/"] extractor = SgmlLinkExtractor() rules = ( Rule(extractor,callback='parse_links',follow=True), ) def parse_start_url(self, response): list(self.parse_links(response)) def parse_links(self, response): hxs = HtmlXPathSelector(response) links = hxs.select('//a') for link in links: title = ''.join(link.select('./@title').extract()) url = ''.join(link.select('./@href').extract()) meta={'title':title,} cleaned_url = "%s/?1" % url if not '/' in url.partition('//')[2] else "%s?1" % url yield Request(cleaned_url, callback = self.parse_page, meta=meta,) def parse_page(self, response): hxs = HtmlXPathSelector(response) item=SPage() item['url'] = response.url item['title']=response.meta['title'] item['h1']=hxs.select('//h1/text()').extract() return item 

Cambios :

  1. Implementado parse_start_url : desafortunadamente, cuando especifica una callback para la primera solicitud, las reglas no se ejecutan. Esto está incorporado en Scrapy, y solo podemos gestionar esto con una solución alternativa. Así que hacemos una list(self.parse_links(response)) dentro de esta función. ¿Por qué la list() ? Porque parse_links es un generador, y los generadores son perezosos. Así que tenemos que llamarlo explícitamente por completo.

  2. cleaned_url = "%s/?1" % url if not '/' in url.partition('//')[2] else "%s?1" % url – Hay un par de cosas que suceden aquí:

    a. Estamos agregando ‘/? 1’ al final de la URL: dado que parse_links devuelve URL duplicadas, Scrapy las filtra. Una forma más fácil de evitar eso es pasar dont_filter=True a Request (). Sin embargo, todas sus páginas están interconectadas (de vuelta al índice de pageAA, etc.) y un dont_filter aquí resulta en demasiadas solicitudes y artículos duplicados.

    segundo. if not '/' in url.partition('//')[2] – Nuevamente, esto se debe al enlace en su sitio web. Uno de los enlaces internos es a ‘www.cumulodata.com’ y otro a ‘www.cumulodata.com/’. Ya que estamos agregando explícitamente un mecanismo para permitir duplicados, esto resultó en un elemento adicional. Como necesitábamos perfecto, implementé este hack.

  3. title = ''.join(link.select('./@title').extract()) : no desea devolver el nodo, sino los datos. También: ” .join (lista) es mejor que la lista [0] en caso de una lista vacía.

Felicidades por la creación de un sitio web de prueba que planteó un problema curioso: ¡los duplicados son tanto necesarios como no deseados!