当Scrapy蜘蛛遇到指定的URL时停止它

时间:2016-09-10 11:13:11

标签: python scrapy

这个问题与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”技巧似乎不起作用。

正确完成此任务的方法应该是什么?

2 个答案:

答案 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时立即停止蜘蛛,您将需要清除三个队列

- 在蜘蛛中间件级别---

  • spider.crawler.engine.slot.scheduler.mqs - &gt;内存队列未来请求
  • spider.crawler.engine.slot.inprogress - &gt;任何正在进行的请求

- 下载中间件级别---

  • spider.requests_queue - &gt;待处理请求队列中的请求

通过覆盖正确的中间件来清除所有这些队列,以防止scrapy访问任何其他页面