这个问题与Force my scrapy spider to stop crawling非常相似,而其他一些人几年前曾问过这个问题。但是,建议的解决方案要么是Scrapy 1.1.1的日期,要么是不完全相关的。 任务是在蜘蛛到达某个URL时关闭蜘蛛。例如,在抓取媒体项目的新闻网站时,您肯定需要这样做。
在CLOSESPIDER_TIMEOUT
CLOSESPIDER_ITEMCOUNT
CLOSESPIDER_PAGECOUNT
CLOSESPIDER_ERRORCOUNT
设置中,项目数和页数选项很接近但不够,因为您永远不知道页数或项目数。< / p>
raise CloseSpider(reason='some reason')
异常似乎完成了这项工作,但到目前为止,它有点奇怪。我遵循“Learning Scrapy”教科书,我的代码结构与书中的结构类似。
在items.py
中,我列出了一些项目:
class MyProjectItem(scrapy.Item):
Headline = scrapy.Field()
URL = scrapy.Field()
PublishDate = scrapy.Field()
Author = scrapy.Field()
pass
在myspider.py
中,我使用蜘蛛抓取页面进行处理的def start_requests()
方法,解析def parse()
中的每个索引页,并为def parse_item()
中的每个项指定XPath }:
class MyProjectSpider(scrapy.Spider):
name = 'spidername'
allowed_domains = ['domain.name.com']
def start_requests(self):
for i in range(1,3000):
yield scrapy.Request('http://domain.name.com/news/index.page'+str(i)+'.html', self.parse)
def parse(self, response):
urls = response.xpath('XPath for the URLs on index page').extract()
for url in urls:
# The urls are absolute in this case. There’s no need to use urllib.parse.urljoin()
yield scrapy.Request(url, callback=self.parse_item)
def parse_item(self, response):
l = ItemLoader(item=MyProjectItem(), response=response)
l.add_xpath('Headline', 'XPath for Headline')
l.add_value('URL', response.url)
l.add_xpath ('PublishDate', 'XPath for PublishDate')
l.add_xpath('Author', 'XPath for Author')
return l.load_item()
如果在raise CloseSpider(reason='some reason')
中放置了def parse_item()
例外,它仍会在最终停止之前删除多个项目:
if l.get_output_value('URL') == 'http://domain.name.com/news/1234567.html':
raise CloseSpider('No more news items.')
如果它被放置在def parse()
方法中以便在到达特定URL时停止,则在仅从索引页面中获取包含该特定URL的第一个项目后停止:
def parse(self, response):
most_recent_url_in_db = 'http://domain.name.com/news/1234567.html '
urls = response.xpath('XPath for the URLs on index page').extract()
if most_recent_url_in_db not in urls:
for url in urls:
yield scrapy.Request(url, callback=self.parse_item)
else:
for url in urls[:urls.index(most_recent_url_in_db)]:
yield scrapy.Request(url, callback=self.parse_item)
raise CloseSpider('No more news items.')
例如,如果您有5个索引页面(每个页面都有25个项目URL),而most_recent_url_in_db
位于第4页,则表示您将拥有第1-3页中的所有项目,并且只有第一个第4页的项目。然后蜘蛛停止。如果列表中的most_recent_url_in_db
为10,则索引页4中的项目2-9将不会出现在您的数据库中。
在许多情况下建议使用crawler.engine.close_spider()
或在How do I stop all spiders and the engine immediately after a condition in a pipeline is met?中分享的“hacky”技巧似乎不起作用。
正确完成此任务的方法应该是什么?
答案 0 :(得分:1)
我建议改变你的做法。 Scrapy在没有线性顺序的情况下同时抓取许多请求,这就是为什么在找到你正在寻找的东西时关闭蜘蛛的原因,因为之后的请求已经可以处理了。
要解决此问题,您可以按顺序对Scrapy进行爬网,这意味着按固定顺序一次请求。这可以通过不同的方式实现,这里有一个关于我如何去做的例子。
首先,您应该一次抓取一个页面。这可以这样做:
class MyProjectSpider(scrapy.Spider):
pagination_url = 'http://domain.name.com/news/index.page{}.html'
def start_requests(self):
yield scrapy.Request(
self.pagination_url.format(1),
meta={'page_number': 1},
)
def parse(self, response):
# code handling item links
...
page_number = response.meta['page_number']
next_page_number = page_number + 1
if next_page_number <= 3000:
yield scrapy.Request(
self.pagination_url.format(next_page_number),
meta={'page_number': next_page_number},
)
实施后,您可以使用每个页面中的链接执行类似操作。但是,由于您可以在不下载内容的情况下过滤它们,因此可以执行以下操作:
class MyProjectSpider(scrapy.Spider):
most_recent_url_in_db = 'http://domain.name.com/news/1234567.html '
def parse(self, response):
url_found = False
urls = response.xpath('XPath for the URLs on index page').extract()
for url in urls:
if url == self.most_recent_url_in_db:
url_found = True
break
yield scrapy.Request(url, callback=self.parse_item)
page_number = response.meta['page_number']
next_page_number = page_number + 1
if not url_found:
yield scrapy.Request(
self.pagination_url.format(next_page_number),
meta={'page_number': next_page_number},
)
把所有人放在一起你会有:
class MyProjectSpider(scrapy.Spider):
name = 'spidername'
allowed_domains = ['domain.name.com']
pagination_url = 'http://domain.name.com/news/index.page{}.html'
most_recent_url_in_db = 'http://domain.name.com/news/1234567.html '
def start_requests(self):
yield scrapy.Request(
self.pagination_url.format(1),
meta={'page_number': 1}
)
def parse(self, response):
url_found = False
urls = response.xpath('XPath for the URLs on index page').extract()
for url in urls:
if url == self.most_recent_url_in_db:
url_found = True
break
yield scrapy.Request(url, callback=self.parse_item)
page_number = response.meta['page_number']
next_page_number = page_number + 1
if next_page_number <= 3000 and not url_found:
yield scrapy.Request(
self.pagination_url.format(next_page_number),
meta={'page_number': next_page_number},
)
def parse_item(self, response):
l = ItemLoader(item=MyProjectItem(), response=response)
l.add_xpath('Headline', 'XPath for Headline')
l.add_value('URL', response.url)
l.add_xpath ('PublishDate', 'XPath for PublishDate')
l.add_xpath('Author', 'XPath for Author')
return l.load_item()
希望能让您了解如何完成您所寻找的目标,祝您好运!
答案 1 :(得分:0)
当你提出close_spider() exception
时,理想的假设是scrapy应该立即停止,放弃所有其他活动(任何未来的页面请求,管道中的任何处理..等等)
但事实并非如此,当你举起close_spider() exception
时,scrapy会尝试关闭当前操作的优雅,这意味着它会停止当前请求,但会等待其他任何请求请求在任何队列中挂起(有多个队列!)
(即,如果您没有覆盖默认设置并且有超过16个启动网址,则scrapy一次会发出16个请求)
现在,如果您想在提升close_spider() exception
时立即停止蜘蛛,您将需要清除三个队列
- 在蜘蛛中间件级别---
- 下载中间件级别---
通过覆盖正确的中间件来清除所有这些队列,以防止scrapy访问任何其他页面