抓取顺序url



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

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

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

start_urls定义了start_requests方法中使用的url。当下载页面时,调用parse方法并为每个起始url提供响应。但是你不能控制加载时间——第一个开始的url可能是parse的最后一个。

一个解决方案——覆盖start_requests方法,并添加一个metapriority密钥生成的请求。在parse中提取priority的值,并将其添加到item中。在管道中基于这个值执行一些操作。(我不知道为什么和在哪里你需要这些url按这个顺序处理)

或者让它同步——把这些起始url存储在某个地方。把start_urls放在它们中的第一个。在parse处理第一个响应并产生项目(s),然后从您的存储中获取下一个url,并对parse进行回调请求。

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

不是生成Request对象,而是返回一个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

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

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

我怀疑是否有可能实现你想要的,除非你玩scrapy内部。在一些杂乱的google群组中也有类似的讨论,例如

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

还有一件事也有帮助设置CONCURRENT_REQUESTS_PER_SPIDER到1,但不能完全保证顺序可能是因为Downloader有自己的本地队列出于性能原因,所以最好你能做的就是优先处理这些请求但不能保证它的确切顺序。

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

我开始使用@Alexis de trsamuglod方法,但遇到了一个问题:
您的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

将此添加到settings

SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue' 
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

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

scraper可能会根据超时和HTTP错误对请求进行排队和重新排队,如果您可以从响应页面获得日期,则会容易得多。

。再加一个hxs。获取日期的Select语句(刚刚看了一下,它肯定在响应数据中),并将其添加到项字典中,并基于此对项进行排序。

这可能是一个更健壮的方法,而不是依赖于刮刀的顺序…

当然,你可以控制它。最高机密是如何提供贪婪引擎/调度的方法。你的要求很简单。请参见我添加了一个名为"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

我知道这是一个老问题,但我今天在这个问题上挣扎,并没有完全满意我在这个线程中发现的解决方案。我是这样处理的。

蜘蛛:

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

再次调用爬行器

我相信

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,做同样的事情,但附加一个请求url .html与callback=self.parse3。这是可怕的编码风格,但我只是把它扔出来,以防你需要快速破解。

我个人喜欢@user1460015的实现后,我设法有我自己的工作解决方案。

我的解决方案是使用Python的子进程逐个url调用scrapy url,直到所有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()

注意:这个实现不处理错误。

大多数答案建议逐个传递url或将并发性限制为1,这将大大降低你的速度,如果你抓取多个url。

当我遇到同样的问题时,我的解决方案是使用回调参数来存储抓取的数据从所有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]

最新更新