Scrapy按顺序抓取网址

时间:2011-07-04 00:09:17

标签: python sorting asynchronous hashmap scrapy

所以,我的问题相对简单。我有一个蜘蛛爬行多个站点,我需要它按照我在代码中写入的顺序返回数据。它发布在下面。

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

10 个答案:

答案 0 :(得分:18)

start_urls定义了start_requests方法中使用的网址。调用您的parse方法,并在下载页面时为每个起始网址添加响应。但是你无法控制加载时间 - 第一个启动网址可能是parse的最后一个。

解决方案 - 覆盖start_requests方法,并使用meta密钥向生成的请求添加priority。在parse中提取此priority值并将其添加到item。在管道中做一些基于此值的事情。 (我不知道为什么以及在哪里需要按此顺序处理这些网址。)

或者让它变得同步 - 将这些启动网址存储在某个地方。放入start_urls第一个。在parse处理第一个响应并生成项目,然后从您的存储中获取下一个网址并通过回调parse发出请求。

答案 1 :(得分:12)

Scrapy'Request'现在有一个优先级属性。http://doc.scrapy.org/en/latest/topics/request-response.html#request-objects如果你在函数中有很多'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将首先处理优先级为1的那个。

答案 2 :(得分:7)

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

您希望返回一个请求数组,而不是生成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

答案 3 :(得分:2)

我怀疑除非你玩scrapy内部,否则是否有可能达到你想要的效果。关于scrapy谷歌群体有一些类似的讨论,例如

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

  

有一件事也可以帮到我   设置CONCURRENT_REQUESTS_PER_SPIDER   到1,但它不会完全确保   订单要么是因为   downloader有自己的本地队列   出于性能原因,所以最好   您可以做的是优先处理请求   但不能确保其确切的顺序。

答案 4 :(得分:2)

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

我从@AlexisdeTréglodé方法开始,但遇到了一个问题:
您的start_requests()方法返回URLS列表的事实 return [ Request(url = start_url) for start_url in start_urls ]
导致输出非顺序(异步)

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

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:0)

免责声明:未专门针对scrapy工作

刮刀可能会根据超时和HTTP错误对请求进行排队和重新排队,如果您可以从响应页面获取日期会更容易吗?

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

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

答案 8 :(得分:0)

我相信

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请求。这是一种可怕的编码风格,但我只是把它丢掉,以防你需要快速破解。

答案 9 :(得分:0)

在我成功完成解决方案之后,我个人喜欢@ user1460015的实现。

我的解决方案是使用Python的子进程通过url调用scrapy url,直到所有url都被处理完毕。

在我的代码中,如果用户没有指定他/她想要按顺序解析网址,我们可以正常方式启动蜘蛛。

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

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