Scrapy 按顺序爬取 URL

27 投票
13 回答
39057 浏览
提问于 2025-04-16 20:46

我的问题其实很简单。我有一个爬虫程序在多个网站上抓取数据,我希望它能按照我在代码中写的顺序返回数据。下面是我的代码。

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

但是,结果却是随机的,比如它先返回第29个,然后是第28个,再然后是第30个。我尝试把调度器的顺序从DFO改成BFO,想看看是不是这个导致的问题,但结果没有任何变化。

13 个回答

8

谷歌小组的讨论建议在请求对象中使用优先级属性。Scrapy 默认保证网址是按照深度优先的顺序进行爬取的。但它并不保证网址会按照你在解析回调中生成的顺序被访问。

与其生成请求对象,不如返回一个请求的数组,从这个数组中逐个取出请求,直到数组为空。

你可以试试这样的做法吗?

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
23

Scrapy 现在的 Request 对象有了一个 priority 属性。

如果你在一个函数里有很多 Request 请求,想要优先处理某一个请求,你可以设置:

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 会先处理 priority=1 的请求。

21

start_urls 是用来定义一组网址的,这些网址会在 start_requests 方法中使用。当你下载每个网址的页面时,parse 方法会被调用来处理这些页面的响应。不过,你无法控制加载的时间,可能第一个网址的响应最后才会到达 parse 方法。

解决这个问题的方法是重写 start_requests 方法,在生成的请求中添加一个 meta 字段,并设置一个 priority 的键。在 parse 方法中提取这个 priority 值,并将其添加到 item 中。在处理管道中根据这个值进行相应的操作。(我不知道你为什么需要这些网址按这个顺序处理。)

另一种方法是让它变得有点同步——把这些起始网址存储起来。在 start_urls 中放入第一个网址。在 parse 方法中处理第一个响应并返回相应的 item,然后从存储中取下一个网址,并发出请求,回调到 parse 方法。

撰写回答