【问题标题】:Scrapy Crawl URLs in Order按顺序抓取 URL
【发布时间】:2011-09-27 19:44:10
【问题描述】:

所以,我的问题比较简单。我有一个爬虫爬取多个站点,我需要它按照我在代码中编写的顺序返回数据。贴在下面。

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,以防万一出现问题,但这并没有改变任何东西。

【问题讨论】:

  • 你能告诉我们你怎么称呼你的蜘蛛吗?
  • >我有一个蜘蛛爬多个网站,你的意思是多个起始网址?

标签: python sorting asynchronous hashmap scrapy


【解决方案1】:

我知道这是一个老问题,但我今天遇到了这个问题,对我在这个帖子中找到的解决方案并不完全满意。以下是我的处理方式。

蜘蛛:

import scrapy

class MySpider(scrapy.Spider):

    name = "mySpider"

    start_urls = None

    def parse(self, response):
        #your parsing code goes here

    def __init__(self, urls):
        self.start_urls = urls

还有蜘蛛跑者:

from twisted.internet import reactor, defer
import spiders.mySpider as ms
from scrapy.crawler import CrawlerRunner

urls = [
    'http://address1.com',
    'http://address2.com',
    'http://address3.com'
   ]

runner = CrawlerRunner()

@defer.inlineCallbacks
def crawl():
    for url in urls:
        yield runner.crawl(ms.MySpider, urls = [url])
    reactor.stop()

crawl()
reactor.run()

此代码使用作为参数传递的列表中的 url 调用蜘蛛,然后等待它完成,然后再使用下一个 url 再次调用蜘蛛

【讨论】:

    【解决方案2】:

    大多数答案建议一个一个地传递 url 或将并发限制为 1, 如果您要抓取多个网址,这会大大减慢您的速度。

    虽然我遇到了同样的问题,但我的解决方案是使用回调参数来存储抓取的数据 从所有 url 中,并使用初始 url 的顺序对其进行排序,然后按顺序返回所有抓取的数据,如下所示:

    import scrapy
    
    class MLBoddsSpider(scrapy.Spider):
       name = "sbrforum.com"
       allowed_domains = ["sbrforum.com"]
       to_scrape_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):
           data = {}
           for url in self.to_scrape_urls:
               yield scrapy.Request(url, self.parse, cb_kwargs=data)
    
       def parse(self, response, **kwargs):
           # scrape the data and add it to kwargs
           kwargs[response.url] = response.css('myData').get()
    
           # check if all urls has been scraped yet
           if len(kwargs) == len(self.to_scrape_urls):
               # return a sorted list of your data
               return [kwargs[url] for url in self.to_scrape_urls]
    

    【讨论】:

      【解决方案3】:

      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 的那个。

      【讨论】:

        【解决方案4】:

        有一种更简单的方法可以使 scrapy 遵循starts_url 的顺序:您可以取消注释并将settings.py 中的并发请求更改为1。

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

        【讨论】:

        • 或者您添加:custom_settings = { 'CONCURRENT_REQUESTS': '1' } 正下方 class DmozSpider(BaseSpider): name = "dmoz" 。这样你就不需要额外的settings.py 文件了。
        【解决方案5】:

        我个人喜欢@user1460015 在我设法解决解决方案后的实施。

        我的解决方案是使用Python的子进程逐个url调用scrapy url,直到处理完所有url。

        在我的代码中,如果用户没有指定他/她想要顺序解析url,我们可以正常启动spider。

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

        如果用户指定需要按顺序完成,我们可以这样做:

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

        请注意:此实现不处理错误。

        【讨论】:

          【解决方案6】:

          start_urls 定义了在start_requests 方法中使用的url。下载页面时,将调用您的 parse 方法并响应每个起始 URL。但是您无法控制加载时间 - 第一个开始 url 可能最后一个到 parse

          一种解决方案 -- 覆盖 start_requests 方法并将 metapriority 键添加到生成的请求中。在parse 中提取此priority 值并将其添加到item。在管道中根据这个值做一些事情。 (我不知道为什么以及在哪里需要按此顺序处理这些 url)。

          或者让它同步——将这些起始网址存储在某个地方。输入start_urls 其中的第一个。在parse 中处理第一个响应并生成项目,然后从您的存储中获取下一个 url 并通过回调parse 对其进行请求。

          【讨论】:

          • 所有很好的反馈,感谢大家的帮助。这个最接近我想做的事情。
          • 我有一个相关的问题。假设我想指定一个 URL 列表,其中第一个是网站的主页,下一个是网页列表。我该怎么办?
          • @PrakharMohanSrivastava,把它们放在start_urls?
          【解决方案7】:

          解决方案是顺序的。
          这个解决方案类似于@wuliang

          我从@Alexis de Tréglodé 方法开始,但遇到了一个问题:
          您的 start_requests() 方法返回 URL 列表的事实
          return [ Request(url = start_url) for start_url in start_urls ]
          导致输出不连续(异步)

          如果返回是单个响应,则通过创建替代 other_urls 可以满足要求。此外,other_urls 可用于添加从其他网页抓取的 URL。

          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
          

          【讨论】:

            【解决方案8】:

            当然,你可以控制它。 最高机密是如何喂养贪婪的引擎/调度器的方法。你的要求只是一点点。请参阅我添加了一个名为“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
            

            如果你想要一些更复杂的案例,请看我的项目: https://github.com/wuliang/TiebaPostGrabber

            【讨论】:

              【解决方案9】:

              google 小组讨论建议在请求对象中使用优先级属性。 Scrapy 保证默认情况下在 DFO 中抓取 url。但它不能确保按照它们在解析回调中产生的顺序访问 url。

              您希望返回一个 Requests 数组,而不是产生 Request 对象,从中弹出对象直到它为空。

              你能试试这样的吗?

              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
              

              【讨论】:

                【解决方案10】:

                我怀疑是否有可能实现你想要的,除非你使用scrapy internals。关于scrapy google groups有一些类似的讨论,例如

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

                还有一点可以提供帮助的是 设置 CONCURRENT_REQUESTS_PER_SPIDER 为 1,但不能完全确保 订单要么是因为 下载器有自己的本地队列 出于性能原因,所以最好 你可以做的是优先处理请求 但不能保证它的确切顺序。

                【讨论】:

                  【解决方案11】:

                  我相信

                  hxs.select('...')
                  

                  您将按照网站出现的顺序从网站上抓取数据。无论是那个还是scrapy,都会以任意顺序通过你的start_urls。要强制它以预定义的顺序遍历它们,请注意,如果您需要抓取更多网站,这将不起作用,那么您可以尝试以下操作:

                  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))
                  

                  然后编写一个 parse2,它做同样的事情,但附加一个带有 callback=self.parse3 的 url3.html 请求。这是一种可怕的编码风格,但我只是把它扔掉,以防你需要快速破解。

                  【讨论】:

                    【解决方案12】:

                    免责声明:没有专门使用scrapy

                    爬虫可能会根据超时和HTTP错误对请求进行排队和重新排队,如果你能从响应页面中获取日期会容易得多吗?

                    即添加另一个获取日期的 hxs.select 语句(只是看了一下,它肯定在响应数据中),并将其添加到项目字典中,根据它对项目进行排序。

                    这可能是一种更健壮的方法,而不是依赖于刮擦的顺序...

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 2012-06-18
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-11-28
                      • 2017-01-12
                      相关资源
                      最近更新 更多