所以,我的问题相对简单。我有一个蜘蛛爬行多个站点,我需要它返回的数据在我写它在我的代码的顺序。
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
方法,并添加一个meta
与priority
密钥生成的请求。在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]