Scrapy 按顺序抓取 URL
- 2025-03-06 08:52:00
- admin 原创
- 70
问题描述:
所以,我的问题相对简单。我有一个蜘蛛爬行多个网站,我需要它按照我在代码中编写的顺序返回数据。它发布在下面。
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,以防万一出现问题,但这并没有改变任何东西。
解决方案 1:
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
。
解决方案 2:
start_urls
`start_requests定义方法中使用的 URL 。
parse下载页面时,会调用您的方法,并为每个起始 URL 提供响应。但您无法控制加载时间 - 第一个起始 URL 可能排在最后一个
parse`。
解决方案——覆盖start_requests
方法并向生成的请求添加一个meta
带有priority
键的。parse
提取此priority
值并将其添加到item
。在管道中根据此值执行某些操作。(我不知道为什么以及在哪里需要按此顺序处理这些 URL)。
或者让它有点同步——将这些起始 URL 存储在某处。放入start_urls
其中的第一个。parse
处理第一个响应并产生项目,然后从存储中获取下一个 URL 并使用回调发出请求parse
。
解决方案 3:
google 小组讨论建议在 Request 对象中使用优先级属性。Scrapy 默认保证在 DFO 中抓取 URL。但它不能确保按照解析回调中产生的顺序访问 URL。
您不想产生 Request 对象,而是想要返回一个 Requests 数组,从该数组中弹出对象直到其为空。
你能尝试这样的事吗?
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
解决方案 4:
有一种更简单的方法可以让scrapy遵循 starts_url 的顺序:您只需取消注释并将并发请求更改为settings.py
1 即可。
Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 1
解决方案 5:
我怀疑除非你使用 scrapy 内部组件,否则是否能实现你想要的效果。在 scrapy google 群组中也有一些类似的讨论,例如
还有一个有帮助的方法是将 CONCURRENT_REQUESTS_PER_SPIDER 设置为 1,但是它也不能完全确保顺序,因为出于性能原因,下载程序有自己的本地队列,所以您能做的最好的就是对请求进行优先排序,但不能确保其确切顺序。
解决方案 6:
解决方案是顺序的。
此解决方案类似于@wuliang
我开始使用@Alexis de Tréglodé 方法,但遇到了一个问题:您的方法返回 URL 列表
这一事实
导致输出不连续(异步)start_requests()
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
解决方案 7:
在设置中添加
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
解决方案 8:
免责声明:没有专门使用过 scrapy
抓取工具可能会根据超时和 HTTP 错误对请求进行排队和重新排队,如果您可以从响应页面获取日期,那会容易得多?
即添加另一个获取日期的 hxs.select 语句(刚看了一下,它肯定在响应数据中),并将其添加到项目字典中,并基于此对项目进行排序。
这可能是一种更为强大的方法,而不是依赖于抓取的顺序......
解决方案 9:
当然,你可以控制它。最大的秘密是如何喂饱贪婪的引擎/调度器。你的要求只是一点点。请看我添加了一个名为“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
解决方案 10:
我知道这是一个老问题,但我今天被这个问题困扰了,并且对我在这个帖子中找到的解决方案并不完全满意。以下是我处理它的方法。
蜘蛛:
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 再次调用蜘蛛
解决方案 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 来执行相同的操作,但附加一个带有回调=self.parse3 的 url3.html 请求。这是一种糟糕的编码风格,但我只是想把它扔掉,以防你需要快速破解。
解决方案 12:
在我设法找到自己的解决方案后,我个人很喜欢@user1460015 的实现。
我的解决方案是使用 Python 的子进程逐个 url 调用 scrapy ,直到所有 url 都处理完毕。
在我的代码中,如果用户没有指定他/她想要按顺序解析 URL,我们可以以正常方式启动蜘蛛。
process = CrawlerProcess({'USER_AGENT': 'Mozilla/4.0 (compatible; \n 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='\n + url + ' -o ' + outputfile)
process.wait()
请注意:此实现不处理错误。
解决方案 13:
大多数答案建议逐个传递 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]
扫码咨询,免费领取项目管理大礼包!