diff --git a/docs/topics/stats.rst b/docs/topics/stats.rst index 3b3026654..971a33364 100644 --- a/docs/topics/stats.rst +++ b/docs/topics/stats.rst @@ -8,9 +8,9 @@ Overview ======== Scrapy provides a convenient service for collecting stats in the form of -key/values, both globally and per spider/domain. It's called the Stats -Collector, and it's a singleton which can be imported and used quickly, as -illustrated by the examples in the :ref:`topics-stats-usecases` section below. +key/values, both globally and per spider. It's called the Stats Collector, and +it's a singleton which can be imported and used quickly, as illustrated by the +examples in the :ref:`topics-stats-usecases` section below. The stats collection is enabled by default but can be disabled through the :setting:`STATS_ENABLED` setting. @@ -26,10 +26,10 @@ using the Stats Collector from. Another feature of the Stats Collector is that it's very efficient (when enabled) and extremely efficient (almost unnoticeable) when disabled. -The Stats Collector keeps one stats table per open spider/domain and one global -stats table. You can't set or get stats from a closed domain, but the -domain-specific stats table is automatically opened when the spider is opened, -and closed when the spider is closed. +The Stats Collector keeps one stats table per open spider and one global stats +table. You can't set or get stats from a closed spider, but the spider-specific +stats table is automatically opened when the spider is opened, and closed when +the spider is closed. .. _topics-stats-usecases: @@ -61,36 +61,38 @@ Get global stat value:: >>> stats.get_value('spiders_crawled') 8 -Get all global stats from a given domain:: +Get all global stats (ie. not particular to any spider):: >>> stats.get_stats() {'hostname': 'localhost', 'spiders_crawled': 8} -Set domain/spider specific stat value (domains must be opened first, but this +Set spider specific stat value (spider stats must be opened first, but this task is handled automatically by the Scrapy engine):: - stats.set_value('start_time', datetime.now(), domain='example.com') + stats.set_value('start_time', datetime.now(), spider=some_spider) -Increment domain-specific stat value:: +Where ``some_spider`` is a :class:`~scrapy.spider.BaseSpider` object. - stats.inc_value('pages_crawled', domain='example.com') +Increment spider-specific stat value:: -Set domain-specific stat value only if greater than previous:: + stats.inc_value('pages_crawled', spider=some_spider) - stats.max_value('max_items_scraped', value, domain='example.com') +Set spider-specific stat value only if greater than previous:: -Set domain-specific stat value only if lower than previous:: + stats.max_value('max_items_scraped', value, spider=some_spider) - stats.min_value('min_free_memory_percent', value, domain='example.com') +Set spider-specific stat value only if lower than previous:: -Get domain-specific stat value:: + stats.min_value('min_free_memory_percent', value, spider=some_spider) - >>> stats.get_value('pages_crawled', domain='example.com') +Get spider-specific stat value:: + + >>> stats.get_value('pages_crawled', spider=some_spider) 1238 -Get all stats from a given domain:: +Get all stats from a given spider:: - >>> stats.get_stats('pages_crawled', domain='example.com') + >>> stats.get_stats('pages_crawled', spider=some_spider) {'pages_crawled': 1238, 'start_time': datetime.datetime(2009, 7, 14, 21, 47, 28, 977139)} .. _topics-stats-ref: @@ -108,74 +110,79 @@ class (which they all inherit from). .. class:: StatsCollector - .. method:: get_value(key, default=None, domain=None) + .. method:: get_value(key, default=None, spider=None) Return the value for the given stats key or default if it doesn't exist. - If domain is ``None`` the global stats table is consulted, other the - domain specific one is. If the domain is not yet opened a ``KeyError`` + If spider is ``None`` the global stats table is consulted, otherwise the + spider specific one is. If the spider is not yet opened a ``KeyError`` exception is raised. - .. method:: get_stats(domain=None) + .. method:: get_stats(spider=None) - Get all stats from the given domain/spider (if domain is given) or all - global stats otherwise, as a dict. If domain is not opened ``KeyError`` - is raied. + Get all stats from the given spider (if spider is given) or all global + stats otherwise, as a dict. If spider is not opened ``KeyError`` is + raied. - .. method:: set_value(key, value, domain=None) + .. method:: set_value(key, value, spider=None) Set the given value for the given stats key on the global stats (if - domain is not given) or the domain-specific stats (if domain is given), + spider is not given) or the spider-specific stats (if spider is given), which must be opened or a ``KeyError`` will be raised. - .. method:: set_stats(stats, domain=None) + .. method:: set_stats(stats, spider=None) - Set the given stats (as a dict) for the given domain. If the domain is + Set the given stats (as a dict) for the given spider. If the spider is not opened a ``KeyError`` will be raised. - .. method:: inc_value(key, count=1, start=0, domain=None) + .. method:: inc_value(key, count=1, start=0, spider=None) Increment the value of the given stats key, by the given count, - assuming the start value given (when it's not set). If domain is not - given the global stats table is used, otherwise the domain-specific + assuming the start value given (when it's not set). If spider is not + given the global stats table is used, otherwise the spider-specific stats table is used, which must be opened or a ``KeyError`` will be raised. - .. method:: max_value(key, value, domain=None) + .. method:: max_value(key, value, spider=None) Set the given value for the given key only if current value for the same key is lower than value. If there is no current value for the - given key, the value is always set. If domain is not given the global - stats table is used, otherwise the domain-specific stats table is used, + given key, the value is always set. If spider is not given the global + stats table is used, otherwise the spider-specific stats table is used, which must be opened or a KeyError will be raised. - .. method:: min_value(key, value, domain=None) + .. method:: min_value(key, value, spider=None) Set the given value for the given key only if current value for the same key is greater than value. If there is no current value for the - given key, the value is always set. If domain is not given the global - stats table is used, otherwise the domain-specific stats table is used, + given key, the value is always set. If spider is not given the global + stats table is used, otherwise the spider-specific stats table is used, which must be opened or a KeyError will be raised. - .. method:: clear_stats(domain=None) + .. method:: clear_stats(spider=None) - Clear all global stats (if domain is not given) or all domain-specific - stats if domain is given, in which case it must be opened or a + Clear all global stats (if spider is not given) or all spider-specific + stats if spider is given, in which case it must be opened or a ``KeyError`` will be raised. - .. method:: list_domains() + .. method:: iter_spider_stats() - Return a list of all opened domains. + Return a iterator over ``(spider, spider_stats)`` for each open spider + currently tracked by the stats collector, where ``spider_stats`` is the + dict containing all spider-specific stats. - .. method:: open_domain(domain) + Global stats are not included in the iterator. If you want to get + those, use :meth:`get_stats` method. - Open the given domain for stats collection. This method must be called - prior to working with any stats specific to that domain, but this task + .. method:: open_spider(spider) + + Open the given spider for stats collection. This method must be called + prior to working with any stats specific to that spider, but this task is handled automatically by the Scrapy engine. - .. method:: close_domain(domain) + .. method:: close_spider(spider) - Close the given domain. After this is called, no more specific stats - for this domain can be accessed. This method is called automatically on + Close the given spider. After this is called, no more specific stats + for this spider can be accessed. This method is called automatically on the :signal:`spider_closed` signal. Available Stats Collectors @@ -196,15 +203,16 @@ MemoryStatsCollector .. class:: MemoryStatsCollector A simple stats collector that keeps the stats of the last scraping run (for - each domain) in memory, which can be accessed through the ``domain_stats`` - attribute + each spider) in memory, after they're closed. The stats can be accessed + through the :attr:`domain_stats` attribute, which is a dict keyed by spider + domain name. This is the default Stats Collector used in Scrapy. .. attribute:: domain_stats - A dict of dicts (keyed by domain) containing the stats of the last - scraping run for each domain. + A dict of dicts (keyed by spider domain name) containing the stats of + the last scraping run for each domain. DummyStatsCollector ------------------- @@ -283,41 +291,41 @@ functionality: .. module:: scrapy.stats.signals :synopsis: Stats Collector signals -.. signal:: stats_domain_opened -.. function:: stats_domain_opened(domain) +.. signal:: stats_spider_opened +.. function:: stats_spider_opened(spider) - Sent right after the stats domain is opened. You can use this signal to add - startup stats for domain (example: start time). + Sent right after the stats spider is opened. You can use this signal to add + startup stats for spider (example: start time). - :param domain: the stats domain just opened - :type domain: str + :param spider: the stats spider just opened + :type spider: str -.. signal:: stats_domain_closing -.. function:: stats_domain_closing(domain, reason) +.. signal:: stats_spider_closing +.. function:: stats_spider_closing(spider, reason) - Sent just before the stats domain is closed. You can use this signal to add + Sent just before the stats spider is closed. You can use this signal to add some closing stats (example: finish time). - :param domain: the stats domain about to be closed - :type domain: str + :param spider: the stats spider about to be closed + :type spider: str - :param reason: the reason why the domain is being closed. See + :param reason: the reason why the spider is being closed. See :signal:`spider_closed` signal for more info. :type reason: str -.. signal:: stats_domain_closed -.. function:: stats_domain_closed(domain, reason, domain_stats) +.. signal:: stats_spider_closed +.. function:: stats_spider_closed(spider, reason, spider_stats) - Sent right after the stats domain is closed. You can use this signal to - collect resources, but not to add any more stats as the stats domain has - already been close (use :signal:`stats_domain_closing` for that instead). + Sent right after the stats spider is closed. You can use this signal to + collect resources, but not to add any more stats as the stats spider has + already been close (use :signal:`stats_spider_closing` for that instead). - :param domain: the stats domain just closed - :type domain: str + :param spider: the stats spider just closed + :type spider: str - :param reason: the reason why the domain was closed. See + :param reason: the reason why the spider was closed. See :signal:`spider_closed` signal for more info. :type reason: str - :param domain_stats: the stats of the domain just closed. + :param spider_stats: the stats of the spider just closed. :type reason: dict diff --git a/scrapy/contrib/corestats.py b/scrapy/contrib/corestats.py index 08819abbb..79e9c73e5 100644 --- a/scrapy/contrib/corestats.py +++ b/scrapy/contrib/corestats.py @@ -10,7 +10,7 @@ from scrapy.xlib.pydispatch import dispatcher from scrapy.core import signals from scrapy.stats import stats -from scrapy.stats.signals import stats_domain_opened, stats_domain_closing +from scrapy.stats.signals import stats_spider_opened, stats_spider_closing from scrapy.conf import settings class CoreStats(object): @@ -22,32 +22,32 @@ class CoreStats(object): stats.set_value('envinfo/logfile', settings['LOG_FILE']) stats.set_value('envinfo/pid', os.getpid()) - dispatcher.connect(self.stats_domain_opened, signal=stats_domain_opened) - dispatcher.connect(self.stats_domain_closing, signal=stats_domain_closing) + dispatcher.connect(self.stats_spider_opened, signal=stats_spider_opened) + dispatcher.connect(self.stats_spider_closing, signal=stats_spider_closing) dispatcher.connect(self.item_scraped, signal=signals.item_scraped) dispatcher.connect(self.item_passed, signal=signals.item_passed) dispatcher.connect(self.item_dropped, signal=signals.item_dropped) - def stats_domain_opened(self, domain): - stats.set_value('start_time', datetime.datetime.utcnow(), domain=domain) - stats.set_value('envinfo/host', stats.get_value('envinfo/host'), domain=domain) - stats.inc_value('domain_count/opened') + def stats_spider_opened(self, spider): + stats.set_value('start_time', datetime.datetime.utcnow(), spider=spider) + stats.set_value('envinfo/host', stats.get_value('envinfo/host'), spider=spider) + stats.inc_value('spider_count/opened') - def stats_domain_closing(self, domain, reason): - stats.set_value('finish_time', datetime.datetime.utcnow(), domain=domain) - stats.set_value('finish_status', 'OK' if reason == 'finished' else reason, domain=domain) - stats.inc_value('domain_count/%s' % reason, domain=domain) + def stats_spider_closing(self, spider, reason): + stats.set_value('finish_time', datetime.datetime.utcnow(), spider=spider) + stats.set_value('finish_status', 'OK' if reason == 'finished' else reason, spider=spider) + stats.inc_value('spider_count/%s' % reason, spider=spider) def item_scraped(self, item, spider): - stats.inc_value('item_scraped_count', domain=spider.domain_name) + stats.inc_value('item_scraped_count', spider=spider) stats.inc_value('item_scraped_count') def item_passed(self, item, spider): - stats.inc_value('item_passed_count', domain=spider.domain_name) + stats.inc_value('item_passed_count', spider=spider) stats.inc_value('item_passed_count') def item_dropped(self, item, spider, exception): reason = exception.__class__.__name__ - stats.inc_value('item_dropped_count', domain=spider.domain_name) - stats.inc_value('item_dropped_reasons_count/%s' % reason, domain=spider.domain_name) + stats.inc_value('item_dropped_count', spider=spider) + stats.inc_value('item_dropped_reasons_count/%s' % reason, spider=spider) stats.inc_value('item_dropped_count') diff --git a/scrapy/contrib/downloadermiddleware/stats.py b/scrapy/contrib/downloadermiddleware/stats.py index 0a4631696..64d9e92fc 100644 --- a/scrapy/contrib/downloadermiddleware/stats.py +++ b/scrapy/contrib/downloadermiddleware/stats.py @@ -5,37 +5,30 @@ from scrapy.stats import stats from scrapy.conf import settings class DownloaderStats(object): - """DownloaderStats store stats of all requests, responses and - exceptions that pass through it. - - To use this middleware you must enable the DOWNLOADER_STATS setting. - """ def __init__(self): if not settings.getbool('DOWNLOADER_STATS'): raise NotConfigured def process_request(self, request, spider): - domain = spider.domain_name stats.inc_value('downloader/request_count') - stats.inc_value('downloader/request_count', domain=domain) - stats.inc_value('downloader/request_method_count/%s' % request.method, domain=domain) + stats.inc_value('downloader/request_count', spider=spider) + stats.inc_value('downloader/request_method_count/%s' % request.method, spider=spider) reqlen = len(request_httprepr(request)) - stats.inc_value('downloader/request_bytes', reqlen, domain=domain) + stats.inc_value('downloader/request_bytes', reqlen, spider=spider) stats.inc_value('downloader/request_bytes', reqlen) def process_response(self, request, response, spider): - domain = spider.domain_name stats.inc_value('downloader/response_count') - stats.inc_value('downloader/response_count', domain=domain) - stats.inc_value('downloader/response_status_count/%s' % response.status, domain=domain) + stats.inc_value('downloader/response_count', spider=spider) + stats.inc_value('downloader/response_status_count/%s' % response.status, spider=spider) reslen = len(response_httprepr(response)) - stats.inc_value('downloader/response_bytes', reslen, domain=domain) + stats.inc_value('downloader/response_bytes', reslen, spider=spider) stats.inc_value('downloader/response_bytes', reslen) return response def process_exception(self, request, exception, spider): ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__) stats.inc_value('downloader/exception_count') - stats.inc_value('downloader/exception_count', domain=spider.domain_name) - stats.inc_value('downloader/exception_type_count/%s' % ex_class, domain=spider.domain_name) + stats.inc_value('downloader/exception_count', spider=spider) + stats.inc_value('downloader/exception_type_count/%s' % ex_class, spider=spider) diff --git a/scrapy/contrib/itemsampler.py b/scrapy/contrib/itemsampler.py index 68bbd0680..6021bbf5a 100644 --- a/scrapy/contrib/itemsampler.py +++ b/scrapy/contrib/itemsampler.py @@ -53,11 +53,11 @@ class ItemSamplerPipeline(object): dispatcher.connect(self.engine_stopped, signal=signals.engine_stopped) def process_item(self, spider, item): - sampled = stats.get_value("items_sampled", 0, domain=spider.domain_name) + sampled = stats.get_value("items_sampled", 0, spider=spider) if sampled < items_per_spider: self.items[item.guid] = item sampled += 1 - stats.set_value("items_sampled", sampled, domain=spider.domain_name) + stats.set_value("items_sampled", sampled, spider=spider) log.msg("Sampled %s" % item, spider=spider, level=log.INFO) if close_spider and sampled == items_per_spider: scrapyengine.close_spider(spider) @@ -71,7 +71,7 @@ class ItemSamplerPipeline(object): level=log.WARNING) def spider_closed(self, spider, reason): - if reason == 'finished' and not stats.get_value("items_sampled", domain=spider.domain_name): + if reason == 'finished' and not stats.get_value("items_sampled", spider=spider): self.empty_domains.add(spider.domain_name) self.spiders_count += 1 log.msg("Sampled %d domains so far (%d empty)" % (self.spiders_count, \ @@ -87,7 +87,7 @@ class ItemSamplerMiddleware(object): raise NotConfigured def process_spider_input(self, response, spider): - if stats.get_value("items_sampled", domain=spider.domain_name) >= items_per_spider: + if stats.get_value("items_sampled", spider=spider) >= items_per_spider: return [] elif max_response_size and max_response_size > len(response_httprepr(response)): return [] @@ -100,7 +100,7 @@ class ItemSamplerMiddleware(object): else: items.append(r) - if stats.get_value("items_sampled", domain=spider.domain_name) >= items_per_spider: + if stats.get_value("items_sampled", spider=spider) >= items_per_spider: return [] else: # TODO: this needs some revision, as keeping only the first item diff --git a/scrapy/contrib/pipeline/images.py b/scrapy/contrib/pipeline/images.py index 4fc137c03..3a93553dd 100644 --- a/scrapy/contrib/pipeline/images.py +++ b/scrapy/contrib/pipeline/images.py @@ -219,7 +219,7 @@ class ImagesPipeline(MediaPipeline): msg = 'Image (%s): Downloaded image from %s referred in <%s>' % \ (status, request, referer) log.msg(msg, level=log.DEBUG, spider=info.spider) - self.inc_stats(info.spider.domain_name, status) + self.inc_stats(info.spider, status) try: key = self.image_key(request.url) @@ -258,7 +258,7 @@ class ImagesPipeline(MediaPipeline): referer = request.headers.get('Referer') log.msg('Image (uptodate): Downloaded %s from <%s> referred in <%s>' % \ (self.MEDIA_NAME, request.url, referer), level=log.DEBUG, spider=info.spider) - self.inc_stats(info.spider.domain_name, 'uptodate') + self.inc_stats(info.spider, 'uptodate') checksum = result.get('checksum', None) return {'url': request.url, 'path': key, 'checksum': checksum} @@ -295,9 +295,9 @@ class ImagesPipeline(MediaPipeline): thumb_image, thumb_buf = self.convert_image(image, size) yield thumb_key, thumb_image, thumb_buf - def inc_stats(self, domain, status): - stats.inc_value('image_count', domain=domain) - stats.inc_value('image_status_count/%s' % status, domain=domain) + def inc_stats(self, spider, status): + stats.inc_value('image_count', spider=spider) + stats.inc_value('image_status_count/%s' % status, spider=spider) def convert_image(self, image, size=None): if image.mode != 'RGB': diff --git a/scrapy/contrib/spidermiddleware/depth.py b/scrapy/contrib/spidermiddleware/depth.py index 9e89d2f39..4b15285d7 100644 --- a/scrapy/contrib/spidermiddleware/depth.py +++ b/scrapy/contrib/spidermiddleware/depth.py @@ -18,7 +18,6 @@ class DepthMiddleware(object): stats.set_value('envinfo/request_depth_limit', self.maxdepth) def process_spider_output(self, response, result, spider): - domain = spider.domain_name def _filter(request): if isinstance(request, Request): depth = response.request.meta['depth'] + 1 @@ -28,14 +27,14 @@ class DepthMiddleware(object): level=log.DEBUG, spider=spider) return False elif self.stats: - stats.inc_value('request_depth_count/%s' % depth, domain=domain) - if depth > stats.get_value('request_depth_max', 0, domain=domain): - stats.set_value('request_depth_max', depth, domain=domain) + stats.inc_value('request_depth_count/%s' % depth, spider=spider) + if depth > stats.get_value('request_depth_max', 0, spider=spider): + stats.set_value('request_depth_max', depth, spider=spider) return True # base case (depth=0) if self.stats and 'depth' not in response.request.meta: response.request.meta['depth'] = 0 - stats.inc_value('request_depth_count/0', domain=domain) + stats.inc_value('request_depth_count/0', spider=spider) return (r for r in result or () if _filter(r)) diff --git a/scrapy/contrib/statsmailer.py b/scrapy/contrib/statsmailer.py index 4a580bf5c..c2185e0a7 100644 --- a/scrapy/contrib/statsmailer.py +++ b/scrapy/contrib/statsmailer.py @@ -1,5 +1,5 @@ """ -StatsMailer extension sends an email when a domain finishes scraping. +StatsMailer extension sends an email when a spider finishes scraping. Use STATSMAILER_RCPTS setting to enable and give the recipient mail address """ @@ -17,12 +17,12 @@ class StatsMailer(object): self.recipients = settings.getlist("STATSMAILER_RCPTS") if not self.recipients: raise NotConfigured - dispatcher.connect(self.stats_domain_closed, signal=signals.stats_domain_closed) + dispatcher.connect(self.stats_spider_closed, signal=signals.stats_spider_closed) - def stats_domain_closed(self, domain, domain_stats): + def stats_spider_closed(self, spider, spider_stats): mail = MailSender() body = "Global stats\n\n" body += "\n".join("%-50s : %s" % i for i in stats.get_stats().items()) - body += "\n\n%s stats\n\n" % domain - body += "\n".join("%-50s : %s" % i for i in domain_stats.items()) - mail.send(self.recipients, "Scrapy stats for: %s" % domain, body) + body += "\n\n%s stats\n\n" % spider.domain_name + body += "\n".join("%-50s : %s" % i for i in spider_stats.items()) + mail.send(self.recipients, "Scrapy stats for: %s" % spider.domain_name, body) diff --git a/scrapy/contrib/webconsole/stats.py b/scrapy/contrib/webconsole/stats.py index 8f3ea9b82..837cbcf81 100644 --- a/scrapy/contrib/webconsole/stats.py +++ b/scrapy/contrib/webconsole/stats.py @@ -22,9 +22,9 @@ class StatsDump(object): s = banner(self) s += "