Scrapy Crawl URLs en orden

Entonces, mi problema es relativamente simple. Tengo una araña que rastrea varios sitios y la necesito para devolver los datos en el orden en que los escribo en mi código. Se publica a continuación.

from scrapy.spider import BaseSpider from scrapy.selector import HtmlXPathSelector from mlbodds.items import MlboddsItem class MLBoddsSpider(BaseSpider): name = "sbrforum.com" allowed_domains = ["sbrforum.com"] start_urls = [ "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/" ] def parse(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]') items = [] for site in sites: item = MlboddsItem() item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract() item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract() items.append(item) return items 

Los resultados se devuelven en orden aleatorio, por ejemplo, devuelve el 29, luego el 28, luego el 30. He intentado cambiar la orden del progtwigdor de DFO a BFO, solo en caso de que ese fuera el problema, pero eso no cambió nada.

Related of "Scrapy Crawl URLs en orden"

start_urls define las urls que se usan en el método start_requests . Cuando se descarga la página, se llama a su método de parse con una respuesta para cada urls de inicio. Pero no puede controlar los tiempos de carga: la primera URL de inicio puede ser la última en parse .

Una solución: anule el método start_requests y agregue a las solicitudes generadas un meta con clave de priority . En el parse extraiga este valor de priority y añádalo al item . En la tubería hacer algo basado en este valor. (No sé por qué y dónde necesita que se procesen estas URL en este orden).

O haga que sea un poco síncrono: almacene estas URL de inicio en algún lugar. Pon en start_urls el primero de ellos. En el proceso de parse , parse la primera respuesta y obtenga el (los) elemento (s), luego tome la siguiente url de su almacenamiento y realice una solicitud con callback para el parse .

“Solicitud” de Scrapy tiene un atributo de prioridad ahora. http://doc.scrapy.org/en/latest/topics/request-response.html#request-objects Si tiene muchas “Solicitudes” en una función y desea procesar una solicitud en particular primero, puede configurar

def parse(self,response): url = http://www.example.com/first yield Request(url=url,callback = self.parse_data,priority=1) url = http://www.example.com/second yield Request(url=url,callback = self.parse_data)

Scrapy procesará el que tenga prioridad 1 primero.

La discusión del grupo de google sugiere usar el atributo de prioridad en el objeto de solicitud. Scrapy garantiza que las direcciones URL se rastrean en DFO de forma predeterminada. Pero no garantiza que las direcciones URL se visiten en el orden en que se obtuvieron en su callback de análisis.

En lugar de generar los objetos de solicitud, desea devolver una matriz de solicitudes desde las que se mostrarán los objetos hasta que esté vacío.

¿Puedes probar algo así?

 from scrapy.spider import BaseSpider from scrapy.http import Request from scrapy.selector import HtmlXPathSelector from mlbodds.items import MlboddsItem class MLBoddsSpider(BaseSpider): name = "sbrforum.com" allowed_domains = ["sbrforum.com"] def start_requests(self): start_urls = reversed( [ "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/" ] ) return [ Request(url = start_url) for start_url in start_urls ] def parse(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]') items = [] for site in sites: item = MlboddsItem() item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract() item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract() items.append(item) return items 

Dudo que sea posible lograr lo que quieres a menos que juegues con internos de scrapy. Hay algunas discusiones similares sobre grupos de google de chatarra, por ejemplo

http://groups.google.com/group/scrapy-users/browse_thread/thread/25da0a888ac19a9/1f72594b6db059f4?lnk=gst

Una cosa que también puede ayudar es establecer CONCURRENT_REQUESTS_PER_SPIDER en 1, pero tampoco garantizará completamente el pedido porque el descargador tiene su propia cola local por razones de rendimiento, por lo que lo mejor que puede hacer es priorizar las solicitudes pero no garantizar su orden exacto. .

La solución es secuencial.
Esta solución es similar a @wuliang

Comencé con el método @Alexis de Tréglodé pero llegué a un problema:
El hecho de que su método start_requests() devuelva una lista de URLS
return [ Request(url = start_url) for start_url in start_urls ]
hace que la salida sea no secuencial (asíncrona)

Si la devolución es una respuesta única, al crear una alternativa, other_urls puede cumplir los requisitos. Además, other_urls se puede usar para agregar URLs raspadas de otras páginas web.

 from scrapy import log from scrapy.spider import BaseSpider from scrapy.http import Request from scrapy.selector import HtmlXPathSelector from practice.items import MlboddsItem log.start() class PracticeSpider(BaseSpider): name = "sbrforum.com" allowed_domains = ["sbrforum.com"] other_urls = [ "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/", ] def start_requests(self): log.msg('Starting Crawl!', level=log.INFO) start_urls = "http://www.sbrforum.com/mlb-baseball/odds-scores/20110327/" return [Request(start_urls, meta={'items': []})] def parse(self, response): log.msg("Begin Parsing", level=log.INFO) log.msg("Response from: %s" % response.url, level=log.INFO) hxs = HtmlXPathSelector(response) sites = hxs.select("//*[@id='moduleData8460']") items = response.meta['items'] for site in sites: item = MlboddsItem() item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract() item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text()').extract() items.append(item) # here we .pop(0) the next URL in line if self.other_urls: return Request(self.other_urls.pop(0), meta={'items': items}) return items 

Por supuesto, puedes controlarlo. El máximo secreto es el método para alimentar al motor / progtwigdor codicioso. Tu requerimiento es solo uno pequeño. Por favor vea que agrego una lista llamada “task_urls”.

 from scrapy.spider import BaseSpider from scrapy.selector import HtmlXPathSelector from scrapy.http.request import Request from dirbot.items import Website class DmozSpider(BaseSpider): name = "dmoz" allowed_domains = ["sbrforum.com"] start_urls = [ "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/", ] task_urls = [ "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/", "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/" ] def parse(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]') items = [] for site in sites: item = Website() item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract() item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract() items.append(item) # Here we feed add new request self.task_urls.remove(response.url) if self.task_urls: r = Request(url=self.task_urls[0], callback=self.parse) items.append(r) return items 

Si desea un caso más complicado, consulte mi proyecto: https://github.com/wuliang/TiebaPostGrabber

Descargo de responsabilidad: no he trabajado con scrapy específicamente

El raspador puede ser poner en cola y volver a poner en cola las solicitudes según los tiempos de espera y los errores de HTTP. ¿Sería mucho más fácil si pudiera obtener la fecha en la página de respuestas?

Es decir, agregue otra statement hxs.select que capture la fecha (solo le eché un vistazo, definitivamente está en los datos de respuesta), y agregue eso al dictado del elemento, ordene los elementos según eso.

Este es probablemente un enfoque más robusto, en lugar de confiar en el orden de raspaduras …

Creo el

 hxs.select('...') 

Usted hará que raspará los datos del sitio en el orden en que aparece. O eso o desperdicio está pasando por tus start_urls en un orden arbitrario. Para forzarlo a pasar por ellos en un orden predefinido, y eso sí, esto no funcionará si necesita rastrear más sitios, luego puede intentar esto:

 start_urls = ["url1.html"] def parse1(self, response): hxs = HtmlXPathSelector(response) sites = hxs.select('blah') items = [] for site in sites: item = MlboddsItem() item['header'] = site.select('blah') item['game1'] = site.select('blah') items.append(item) return items.append(Request('url2.html', callback=self.parse2)) 

luego escriba un parse2 que haga lo mismo pero agregue una Solicitud de url3.html con callback = self.parse3. Este es un estilo de encoding horrible, pero lo estoy tirando en caso de que necesites un truco rápido.

Personalmente, me gusta la implementación de @ user1460015 después de que logré tener mi propia solución de trabajo.

Mi solución es usar el subproceso de Python para llamar a url scrapy por url hasta que todas las urls hayan sido atendidas.

En mi código, si el usuario no especifica que desea analizar las URL de forma secuencial, podemos iniciar la araña de una manera normal.

 process = CrawlerProcess({'USER_AGENT': 'Mozilla/4.0 (compatible; \ MSIE 7.0; Windows NT 5.1)'}) process.crawl(Spider, url = args.url) process.start() 

Si un usuario especifica que debe hacerse de manera secuencial, podemos hacer esto:

 for url in urls: process = subprocess.Popen('scrapy runspider scrapper.py -a url='\ + url + ' -o ' + outputfile) process.wait() 

Tenga en cuenta que: esta implementación no maneja errores.

Hay una manera mucho más fácil de hacer que Scrapy siga el orden de starts_url: simplemente puede descomentar y cambiar las solicitudes concurrentes en settings.py a 1.

 Configure maximum concurrent requests performed by Scrapy (default: 16) CONCURRENT_REQUESTS = 1