2012-10-04 24 views
9

He conseguido codificar una oruga muy simple con Scrapy, con estas restricciones dadas:¿Por qué no funcionan mis reglas de Scrapy CrawlSpider?

  • tienda toda la información de enlace (por ejemplo: el ancla de texto, título de la página), por lo tanto, las devoluciones de llamada 2
  • Uso CrawlSpider a Aproveche las reglas, por lo tanto, no BaseSpider

Funciona bien, excepto que no implementa reglas si agrego una devolución de llamada a la primera solicitud.

Aquí está mi código: (obras, pero no correctamente, con un ejemplo 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 en 3 maneras:

  • 1: Para devolver una Solicitud con la url de inicio - reglas no se ejecutan
  • 2: Igual que el anterior, pero con una devolución de llamada a parse_links - mismo problema
  • 3: Llamada parse_linksdespués raspando la URL de inicio, mediante la implementación de parse_start_url, la función no se consiga llamar

Estos son los registros:

----------manual call of <200 http://www.cumulodata.com>) 

----------manual call done 

#No '----------- manual parsing links', so `parse_links` is never called! 

Versiones

  • Python 2.7.2
  • Scrapy 0.14.4

Respuesta

18

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 - Por desgracia, cuando se especifica una devolución de llamada para la primera solicitud, las reglas no se ejecutan. Esto está incorporado en Scrapy, y solo podemos administrar esto con una solución alternativa. Entonces hacemos un list(self.parse_links(response)) dentro de esta función. ¿Por qué el list()? Porque parse_links es un generador y los generadores son flojos. Entonces, debemos llamarlo explícitamente.

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

    a. Estamos agregando '/? 1' al final de la URL. Dado que parse_links devuelve direcciones URL duplicadas, Scrapy las filtra. Una manera más fácil de evitar eso es pasar dont_filter=True a Request(). Sin embargo, todas sus páginas están interrelacionadas (volver al índice de la página AA, etc.) y dont_filter aquí genera demasiadas solicitudes duplicadas & elementos.

    b. if not '/' in url.partition('//')[2] - Una vez más, esto se debe a la vinculación en su sitio web. Uno de los enlaces internos es 'www.cumulodata.com 'y otra a' www.cumulodata.com/ '. Dado que estamos agregando explícitamente un mecanismo para permitir duplicados, esto resultó en un artículo adicional. Como necesitábamos algo perfecto, implementé este truco.

  3. title = ''.join(link.select('./@title').extract()) - No desea devolver el nodo, sino los datos. Además: '' .join (list) es mejor que list [0] en caso de una lista vacía.

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

+0

Muchas gracias a Anuj por sus esfuerzos para resolver este problema. ¡Definitivamente no lo habría resuelto solo! Agregaré luego unos pocos pasos para deshacerme del parámetro? 1 justo antes de guardar el ítem, (probablemente en una tubería), y será perfecto. Implementé de hecho el sitio de prueba para duplicar lo que puede suceder en la vida real;) – arno

+0

Para entender completamente cuál era el problema, la principal dificultad planteada por mi crawlr es que estoy lanzando solicitudes en url que ya están en la cola procesada por el planificador , puesto allí por el propio CrawlSpider a través de las Reglas, ¿verdad? ¿Así que te deshiciste de esto al usar diferentes URL para el CrawlSpider (sin? 1) y para mi propio push de Solicitud (con el parámetro adicional? 1), ¿estoy en lo cierto? – arno

+0

Tiene razón, Scrapy no rastrea las URL duplicadas (por una buena razón). Como lo necesitábamos, modificamos la URL. Podría haber usado 'dont_filter = True' pero el sitio web de prueba está muy interconectado y produjo demasiados duplicados, por lo que creamos un conjunto adicional de URL. Ya conoce los otros dos problemas: 1. Las reglas no se ejecutan para una solicitud de inicio con devolución de llamada personalizada, 2. 'parse_links' no se estaba llamando correctamente. –

Cuestiones relacionadas