2011-03-08 18 views
12

Estoy usando scrapy para rastrear un sitio. El sitio tiene 15 listados por página y luego tiene un botón siguiente. Me estoy encontrando con un problema donde se llama a mi Solicitud del siguiente enlace antes de terminar de analizar todas mis listas en tramitación. Aquí está el código para mi araña:rastreo recursivo con Python y Scrapy

class MySpider(CrawlSpider): 
    name = 'mysite.com' 
    allowed_domains = ['mysite.com'] 
    start_url = 'http://www.mysite.com/' 

    def start_requests(self): 
     return [Request(self.start_url, callback=self.parse_listings)] 

    def parse_listings(self, response): 
     hxs = HtmlXPathSelector(response) 
     listings = hxs.select('...') 

     for listing in listings: 
      il = MySiteLoader(selector=listing) 
      il.add_xpath('Title', '...') 
      il.add_xpath('Link', '...') 

      item = il.load_item() 
      listing_url = listing.select('...').extract() 

      if listing_url: 
       yield Request(urlparse.urljoin(response.url, listing_url[0]), 
           meta={'item': item}, 
           callback=self.parse_listing_details) 

     next_page_url = hxs.select('descendant::div[@id="pagination"]/' 
            'div[@class="next-link"]/a/@href').extract() 
     if next_page_url: 
      yield Request(urlparse.urljoin(response.url, next_page_url[0]), 
          callback=self.parse_listings) 


    def parse_listing_details(self, response): 
     hxs = HtmlXPathSelector(response) 
     item = response.request.meta['item'] 
     details = hxs.select('...') 
     il = MySiteLoader(selector=details, item=item) 

     il.add_xpath('Posted_on_Date', '...') 
     il.add_xpath('Description', '...') 
     return il.load_item() 

Estas líneas son el problema. Como dije antes, se están ejecutando antes de que la araña haya terminado de rastrear la página actual. En cada página del sitio, esto causa que solo 3 de 15 de mis listas se envíen a la canalización.

 if next_page_url: 
      yield Request(urlparse.urljoin(response.url, next_page_url[0]), 
          callback=self.parse_listings) 

Esta es mi primera araña y podría ser una falla de diseño de mi parte, ¿hay una mejor manera de hacerlo?

+1

Hola. ¿Tienes el código funcionando? Me gustaría que la araña se arrastre a la página siguiente, pero parece que no puede encontrar ningún tutorial sobre ella. Tu código de trabajo podría ser útil. ¡Gracias! – Victor

+0

No, no. Incluso me puse en contacto con los creadores de scrapy, pero no fueron de ayuda. – imns

+0

Acabo de hacer una búsqueda con diferentes palabras clave, y encontré esto: http://abuhijleh.net/2011/02/13/guide-scrape-multi-pages-content-with-scrapy/ Espero que ayude. Aún no he escrito mi propio rastreador. Si he hecho algo, publicaré algo. – Victor

Respuesta

1

ver más abajo para una respuesta actualizada, en la sección EDIT 2 (actualizado sexto de octubre de, 2017)

¿Hay alguna razón específica que está utilizando el rendimiento? El rendimiento devolverá un generador, que devolverá el objeto Solicitud cuando se invoque .next().

Cambie sus declaraciones yield a return declaraciones y las cosas deberían funcionar como se esperaba.

He aquí un ejemplo de un generador:

In [1]: def foo(request): 
    ...:  yield 1 
    ...:  
    ...:  

In [2]: print foo(None) 
<generator object foo at 0x10151c960> 

In [3]: foo(None).next() 
Out[3]: 1 

EDIT:

Cambiar la función def start_requests(self) utilizar el parámetro follow.

return [Request(self.start_url, callback=self.parse_listings, follow=True)] 

EDIT 2:

A partir de Scrapy v1.4.0, publicado el 05.18.2017, ahora se recomienda el uso de response.follow en lugar de crear scrapy.Request objetos directamente.

Desde el release notes:

Hay un nuevo método para la creación de response.follow solicitudes; ahora es una forma recomendada de crear Solicitudes en arañas Scrapy. Este método hace que sea más fácil escribir las arañas correctas; response.follow tiene varias ventajas sobre crear scrapy.Request objetos directamente:

  • que maneja URL relativos;
  • funciona correctamente con URL no ascii en páginas que no sean UTF8;
  • Además de las URL absolutas y relativas admite los selectores; para elementos también puede extraer sus valores href.

Por lo tanto, para el programa operativo anterior, cambie el código de:

next_page_url = hxs.select('descendant::div[@id="pagination"]/' 
           'div[@class="next-link"]/a/@href').extract() 
    if next_page_url: 
     yield Request(urlparse.urljoin(response.url, next_page_url[0]), 
         callback=self.parse_listings) 

a:

next_page_url = hxs.select('descendant::div[@id="pagination"]/' 
           'div[@class="next-link"]/a/@href') 
    if next_page_url is not None: 
     yield response.follow(next_page_url, self.parse_listings) 
+1

porque si uso return, solo rastrea una lista y se detiene. No itera sobre cada listado y crea una solicitud para él. Verá, obtengo información sobre mí en la página que tiene las 15 listas, pero luego tengo que ir a rastrear la página individual de esa lista para obtener el resto de la información que necesito. El rendimiento funciona de maravilla, hasta que quiera agregar la funcionalidad para rastrear la "página siguiente". – imns

+0

Ah, está bien. Voy a actualizar mi respuesta entonces. –

+2

follow no parece que sea un argumento de Request(), recibí un error 'got an unexpected keyword argument 'follow'' – imns

0

Es posible que desee buscar en dos cosas.

  1. El sitio web que está rastreando puede bloquear el agente de usuario que ha definido.
  2. Prueba agregar un DOWNLOAD_DELAY a tu araña.
4

Raspar en lugar de araña?

Dado que su problema original requiere la navegación repetida de un conjunto de contenidos consecutivos y repetidos en lugar de un árbol de contenido de tamaño desconocido, utilice mecanizar (http://wwwsearch.sourceforge.net/mechanize/) y beautifulsoup (http : //www.crummy.com/software/BeautifulSoup/).

Aquí hay un ejemplo de crear instancias de un navegador usando mecanizar. Además, usar br.follow_link (text = "foo") significa que, a diferencia de xpath en su ejemplo, los enlaces se seguirán sin importar la estructura de los elementos en la ruta del ancestro. Es decir, si actualizan su HTML, su script se rompe. Un acoplamiento más flexible le ahorrará algo de mantenimiento. Aquí está un ejemplo:

br = mechanize.Browser() 
br.set_handle_equiv(True) 
br.set_handle_redirect(True) 
br.set_handle_referer(True) 
br.set_handle_robots(False) 
br.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1)Gecko/20100101 Firefox/9.0.1')] 
br.addheaders = [('Accept-Language','en-US')] 
br.addheaders = [('Accept-Encoding','gzip, deflate')] 
cj = cookielib.LWPCookieJar() 
br.set_cookiejar(cj) 
br.open("http://amazon.com") 
br.follow_link(text="Today's Deals") 
print br.response().read() 

Además, en los próximos 15 "" href probablemente hay algo que indica la paginación por ejemplo, & índice = 15. Si el número total de elementos en todas las páginas está disponible en la primera página, a continuación:

soup = BeautifulSoup(br.response().read()) 
totalItems = soup.findAll(id="results-count-total")[0].text 
startVar = [x for x in range(int(totalItems)) if x % 15 == 0] 

A continuación, sólo iterar sobre startVar y crear la url, añadir el valor de startVar a la url, br.open() se y raspe los datos. De esta manera, no tiene que "buscar" programáticamente el "siguiente" enlace en la página y ejecutar un clic sobre él para avanzar a la siguiente página: ya conoce todas las URL válidas. Minimizar la manipulación controlada por código de la página solo a los datos que necesita acelerará su extracción.

+0

Esto está fuera de tema, él ya está usando Scrapy. –

3

Hay dos maneras de hacer esto de forma secuencial:

  1. mediante la definición de una lista listing_url en la clase.
  2. definiendo el listing_url dentro del parse_listings().

La única diferencia es verbage. Además, supongamos que hay cinco páginas para obtener listing_urls. Por lo tanto, ponga page=1 en clase también.

En el método parse_listings, solo realice una solicitud una vez. Coloque todos los datos en el meta que necesita para realizar un seguimiento. Dicho esto, use parse_listings solo para analizar la 'página principal'.

Una vez que llegue al final de la línea, devuelva sus artículos. Este proceso es secuencial.

class MySpider(CrawlSpider): 
    name = 'mysite.com' 
    allowed_domains = ['mysite.com'] 
    start_url = 'http://www.mysite.com/' 

    listing_url = [] 
    page = 1 

    def start_requests(self): 
     return [Request(self.start_url, meta={'page': page}, callback=self.parse_listings)] 

    def parse_listings(self, response): 
     hxs = HtmlXPathSelector(response) 
     listings = hxs.select('...') 

     for listing in listings: 
      il = MySiteLoader(selector=listing) 
      il.add_xpath('Title', '...') 
      il.add_xpath('Link', '...') 

     items = il.load_item() 

     # populate the listing_url with the scraped URLs 
     self.listing_url.extend(listing.select('...').extract()) 

     next_page_url = hxs.select('descendant::div[@id="pagination"]/' 
            'div[@class="next-link"]/a/@href').extract() 

     # now that the front page is done, move on to the next listing_url.pop(0) 
     # add the next_page_url to the meta data 
     return Request(urlparse.urljoin(response.url, self.listing_url.pop(0)), 
          meta={'page': self.page, 'items': items, 'next_page_url': next_page_url}, 
          callback=self.parse_listing_details) 

    def parse_listing_details(self, response): 
     hxs = HtmlXPathSelector(response) 
     item = response.request.meta['item'] 
     details = hxs.select('...') 
     il = MySiteLoader(selector=details, item=item) 

     il.add_xpath('Posted_on_Date', '...') 
     il.add_xpath('Description', '...') 
     items = il.load_item() 

     # check to see if you have any more listing_urls to parse and last page 
     if self.listing_urls: 
      return Request(urlparse.urljoin(response.url, self.listing_urls.pop(0)), 
          meta={'page': self.page, 'items': items, 'next_page_url': response.meta['next_page_url']}, 
          callback=self.parse_listings_details) 
     elif not self.listing_urls and response.meta['page'] != 5: 
      # loop back for more URLs to crawl 
      return Request(urlparse.urljoin(response.url, response.meta['next_page_url']), 
          meta={'page': self.page + 1, 'items': items}, 
          callback=self.parse_listings) 
     else: 
      # reached the end of the pages to crawl, return data 
      return il.load_item() 
1

Puede ceder solicitudes o elementos cuantas veces lo necesite.

def parse_category(self, response): 
    # Get links to other categories 
    categories = hxs.select('.../@href').extract() 

    # First, return CategoryItem 
    yield l.load_item() 

    for url in categories: 
     # Than return request for parse category 
     yield Request(url, self.parse_category) 

encontré que aquí - https://groups.google.com/d/msg/scrapy-users/tHAAgnuIPR4/0ImtdyIoZKYJ

0

que acaba de arreglar este mismo problema en mi código. Utilicé la base de datos SQLite3 que viene como parte de Python 2.7 para solucionarlo: cada elemento sobre el que se recopila obtiene su línea única en una tabla de base de datos en el primer paso de la función de análisis, y cada instancia de la devolución de llamada agrega cada datos del artículo a la tabla y línea para ese artículo. Mantenga un contador de instancias para que la última rutina de análisis de devolución de llamada sepa que es la última y escriba el archivo CSV de la base de datos o lo que sea. La devolución de llamada puede ser recursiva, ya que se le dice en meta qué esquema de parse (y, por supuesto, con qué elemento) se envió para que funcione. Funciona para mí como un encanto. Tienes SQLite3 si tienes Python. Aquí estaba mi puesto cuando descubrí limitación de scrapy en este sentido: Is Scrapy's asynchronicity what is hindering my CSV results file from being created straightforwardly?

Cuestiones relacionadas