diff --git a/scrapy/cmdline.py b/scrapy/cmdline.py index a2b0edc6b..c7e859515 100644 --- a/scrapy/cmdline.py +++ b/scrapy/cmdline.py @@ -7,6 +7,7 @@ import cProfile import scrapy from scrapy import log +from scrapy.crawler import CrawlerProcess from scrapy.xlib import lsprofcalltree from scrapy.conf import settings from scrapy.command import ScrapyCommand @@ -79,6 +80,8 @@ def check_deprecated_scrapy_ctl(argv): def execute(argv=None): if argv is None: argv = sys.argv + crawler = CrawlerProcess(settings) + crawler.install() check_deprecated_scrapy_ctl(argv) # TODO: remove for Scrapy 0.11 cmds = _get_commands_dict() cmdname = _get_command_name(argv) @@ -113,9 +116,8 @@ def execute(argv=None): settings.defaults.update(cmd.default_settings) del args[0] # remove command name from args - from scrapy.project import crawler log.start() - crawler.configure() + cmd.set_crawler(crawler) ret = _run_command(cmd, args, opts) if ret is False: parser.print_help() diff --git a/scrapy/command.py b/scrapy/command.py index ab357c08f..fecbeab01 100644 --- a/scrapy/command.py +++ b/scrapy/command.py @@ -18,6 +18,14 @@ class ScrapyCommand(object): # default settings to be used for this command instead of global defaults default_settings = {} + def set_crawler(self, crawler): + self._crawler = crawler + + @property + def crawler(self): + self._crawler.configure() + return self._crawler + def syntax(self): """ Command syntax (preferably one-line). Do not include command name. diff --git a/scrapy/commands/crawl.py b/scrapy/commands/crawl.py index 7a7d76edc..db6009206 100644 --- a/scrapy/commands/crawl.py +++ b/scrapy/commands/crawl.py @@ -1,10 +1,7 @@ from scrapy import log from scrapy.command import ScrapyCommand -from scrapy.core.queue import ExecutionQueue -from scrapy.project import crawler from scrapy.conf import settings from scrapy.http import Request -from scrapy.project import crawler from scrapy.utils.url import is_url from collections import defaultdict @@ -32,31 +29,29 @@ class Command(ScrapyCommand): settings.overrides['CRAWLSPIDER_FOLLOW_LINKS'] = False def run(self, args, opts): - q = ExecutionQueue() + q = self.crawler.queue urls, names = self._split_urls_and_names(args) for name in names: q.append_spider_name(name) if opts.spider: try: - spider = crawler.spiders.create(opts.spider) + spider = self.crawler.spiders.create(opts.spider) for url in urls: q.append_url(url, spider) except KeyError: log.msg('Unable to find spider: %s' % opts.spider, log.ERROR) else: for name, urls in self._group_urls_by_spider(urls): - spider = crawler.spiders.create(name) + spider = self.crawler.spiders.create(name) for url in urls: q.append_url(url, spider) - - crawler.queue = q - crawler.start() + self.crawler.start() def _group_urls_by_spider(self, urls): spider_urls = defaultdict(list) for url in urls: - spider_names = crawler.spiders.find_by_request(Request(url)) + spider_names = self.crawler.spiders.find_by_request(Request(url)) if not spider_names: log.msg('Could not find spider that handles url: %s' % url, log.ERROR) diff --git a/scrapy/commands/fetch.py b/scrapy/commands/fetch.py index eefbd71eb..980348b12 100644 --- a/scrapy/commands/fetch.py +++ b/scrapy/commands/fetch.py @@ -2,10 +2,8 @@ import pprint from scrapy import log from scrapy.command import ScrapyCommand -from scrapy.project import crawler from scrapy.http import Request from scrapy.spider import BaseSpider -from scrapy.project import crawler from scrapy.utils.url import is_url class Command(ScrapyCommand): @@ -44,11 +42,11 @@ class Command(ScrapyCommand): spider = None if opts.spider: try: - spider = crawler.spiders.create(opts.spider) + spider = self.crawler.spiders.create(opts.spider) except KeyError: log.msg("Could not find spider: %s" % opts.spider, log.ERROR) - crawler.queue.append_request(request, spider, \ + self.crawler.queue.append_request(request, spider, \ default_spider=BaseSpider('default')) - crawler.start() + self.crawler.start() diff --git a/scrapy/commands/genspider.py b/scrapy/commands/genspider.py index f451afbc4..35bb0f23e 100644 --- a/scrapy/commands/genspider.py +++ b/scrapy/commands/genspider.py @@ -4,7 +4,6 @@ from os import listdir from os.path import join, dirname, abspath, exists, splitext import scrapy -from scrapy.project import crawler from scrapy.command import ScrapyCommand from scrapy.conf import settings from scrapy.utils.template import render_templatefile, string_camelcase @@ -60,7 +59,7 @@ class Command(ScrapyCommand): name, domain = args[0:2] module = sanitize_module_name(name) try: - spider = crawler.spiders.create(name) + spider = self.crawler.spiders.create(name) except KeyError: pass else: diff --git a/scrapy/commands/list.py b/scrapy/commands/list.py index a3fe8a3fa..29b57edfd 100644 --- a/scrapy/commands/list.py +++ b/scrapy/commands/list.py @@ -1,5 +1,4 @@ from scrapy.command import ScrapyCommand -from scrapy.project import crawler class Command(ScrapyCommand): @@ -10,4 +9,4 @@ class Command(ScrapyCommand): return "List available spiders" def run(self, args, opts): - print "\n".join(crawler.spiders.list()) + print "\n".join(self.crawler.spiders.list()) diff --git a/scrapy/commands/parse.py b/scrapy/commands/parse.py index a0fd193dd..839b226ab 100644 --- a/scrapy/commands/parse.py +++ b/scrapy/commands/parse.py @@ -1,5 +1,4 @@ from scrapy.command import ScrapyCommand -from scrapy.project import crawler from scrapy.http import Request from scrapy.item import BaseItem from scrapy.utils import display @@ -69,11 +68,11 @@ class Command(ScrapyCommand): def get_spider(self, request, opts): if opts.spider: try: - return crawler.spiders.create(opts.spider) + return self.crawler.spiders.create(opts.spider) except KeyError: log.msg('Unable to find spider: %s' % opts.spider, log.ERROR) else: - spider = crawler.spiders.create_for_request(request) + spider = self.crawler.spiders.create_for_request(request) if spider: return spider log.msg('Unable to find spider for: %s' % request, log.ERROR) @@ -84,8 +83,8 @@ class Command(ScrapyCommand): spider = self.get_spider(request, opts) if not spider: return None, None - crawler.queue.append_request(request, spider) - crawler.start() + self.crawler.queue.append_request(request, spider) + self.crawler.start() if not responses: log.msg('No response downloaded for: %s' % request, log.ERROR, \ spider=spider) diff --git a/scrapy/commands/queue.py b/scrapy/commands/queue.py index f1d43b0a5..03379d8f6 100644 --- a/scrapy/commands/queue.py +++ b/scrapy/commands/queue.py @@ -1,8 +1,9 @@ from scrapy.command import ScrapyCommand +from scrapy.commands import runserver from scrapy.utils.misc import load_object from scrapy.conf import settings -class Command(ScrapyCommand): +class Command(runserver.Command): requires_project = True default_settings = {'LOG_ENABLED': False} @@ -26,7 +27,7 @@ class Command(ScrapyCommand): cmd = args[0] botname = settings['BOT_NAME'] - queue = load_object(settings['SERVICE_QUEUE'])().queue + queue = self.crawler.queue.queue if cmd == 'add': if len(args) < 2: diff --git a/scrapy/commands/runserver.py b/scrapy/commands/runserver.py index 9cd6778b1..21a36fe59 100644 --- a/scrapy/commands/runserver.py +++ b/scrapy/commands/runserver.py @@ -1,6 +1,4 @@ from scrapy.command import ScrapyCommand -from scrapy.project import crawler -from scrapy.utils.misc import load_object from scrapy.conf import settings class Command(ScrapyCommand): @@ -10,7 +8,9 @@ class Command(ScrapyCommand): def short_desc(self): return "Start Scrapy in server mode" + def process_options(self, args, opts): + super(Command, self).process_options(args, opts) + settings.overrides['QUEUE_CLASS'] = settings['SERVER_QUEUE_CLASS'] + def run(self, args, opts): - queue_class = load_object(settings['SERVICE_QUEUE']) - crawler.queue = queue_class() - crawler.start() + self.crawler.start() diff --git a/scrapy/commands/runspider.py b/scrapy/commands/runspider.py index 98d80ddbb..84253c66d 100644 --- a/scrapy/commands/runspider.py +++ b/scrapy/commands/runspider.py @@ -4,7 +4,6 @@ import os from scrapy import log from scrapy.utils.spider import iter_spider_classes from scrapy.command import ScrapyCommand -from scrapy.project import crawler def _import_file(filepath): abspath = os.path.abspath(filepath) @@ -52,5 +51,5 @@ class Command(ScrapyCommand): return spider = spclasses.pop()() # schedule spider and start engine - crawler.queue.append_spider(spider) - crawler.start() + self.crawler.queue.append_spider(spider) + self.crawler.start() diff --git a/scrapy/commands/settings.py b/scrapy/commands/settings.py index 22406227f..dab5d2709 100644 --- a/scrapy/commands/settings.py +++ b/scrapy/commands/settings.py @@ -1,5 +1,4 @@ from scrapy.command import ScrapyCommand -from scrapy.conf import settings as settings_ class Command(ScrapyCommand): @@ -24,26 +23,16 @@ class Command(ScrapyCommand): help="print setting value, intepreted as an float") parser.add_option("--getlist", dest="getlist", metavar="SETTING", \ help="print setting value, intepreted as an float") - parser.add_option("--init", dest="init", action="store_true", \ - help="print initial setting value (before loading extensions and spiders)") - - def process_options(self, args, opts): - super(Command, self).process_options(args, opts) - if opts.init: - self._print_setting(opts) def run(self, args, opts): - if not opts.init: - self._print_setting(opts) - - def _print_setting(self, opts): + settings = self.crawler.settings if opts.get: - print settings_.get(opts.get) + print settings.get(opts.get) elif opts.getbool: - print settings_.getbool(opts.getbool) + print settings.getbool(opts.getbool) elif opts.getint: - print settings_.getint(opts.getint) + print settings.getint(opts.getint) elif opts.getfloat: - print settings_.getfloat(opts.getfloat) + print settings.getfloat(opts.getfloat) elif opts.getlist: - print settings_.getlist(opts.getlist) + print settings.getlist(opts.getlist) diff --git a/scrapy/commands/shell.py b/scrapy/commands/shell.py index 2d0e04d45..87119718c 100644 --- a/scrapy/commands/shell.py +++ b/scrapy/commands/shell.py @@ -4,14 +4,13 @@ Scrapy Shell See documentation in docs/topics/shell.rst """ -from scrapy.project import crawler -from scrapy.core.queue import KeepAliveExecutionQueue from scrapy.command import ScrapyCommand from scrapy.shell import Shell class Command(ScrapyCommand): requires_project = False + default_settings = {'QUEUE_CLASS': 'scrapy.core.queue.KeepAliveExecutionQueue'} def syntax(self): return "[url|file]" @@ -30,7 +29,6 @@ class Command(ScrapyCommand): def run(self, args, opts): url = args[0] if args else None - shell = Shell(crawler, update_vars=self.update_vars, inthread=True) - shell.start(url=url).addBoth(lambda _: crawler.stop()) - crawler.queue = KeepAliveExecutionQueue() - crawler.start() + shell = Shell(self.crawler, update_vars=self.update_vars, inthread=True) + shell.start(url=url).addBoth(lambda _: self.crawler.stop()) + self.crawler.start() diff --git a/scrapy/conf/default_settings.py b/scrapy/conf/default_settings.py index 70222f268..6b03ca43f 100644 --- a/scrapy/conf/default_settings.py +++ b/scrapy/conf/default_settings.py @@ -175,6 +175,8 @@ MEMUSAGE_WARNING_MB = 0 NEWSPIDER_MODULE = '' +QUEUE_CLASS = 'scrapy.core.queue.ExecutionQueue' + RANDOMIZE_DOWNLOAD_DELAY = True REDIRECT_MAX_METAREFRESH_DELAY = 100 @@ -207,8 +209,7 @@ SCHEDULER_MIDDLEWARES_BASE = { SCHEDULER_ORDER = 'DFO' -SERVICE_QUEUE = 'scrapy.contrib.queue.SqliteExecutionQueue' -SERVICE_QUEUE_FILE = 'scrapy.db' +SERVER_QUEUE_CLASS = 'scrapy.contrib.queue.SqliteExecutionQueue' SPIDER_MANAGER_CLASS = 'scrapy.spidermanager.SpiderManager' @@ -227,6 +228,8 @@ SPIDER_MIDDLEWARES_BASE = { SPIDER_MODULES = [] +SQLITE_DB = 'scrapy.db' + SQS_QUEUE = 'scrapy' SQS_VISIBILITY_TIMEOUT = 7200 SQS_POLLING_DELAY = 30 diff --git a/scrapy/contrib/pipeline/media.py b/scrapy/contrib/pipeline/media.py index 752a3c144..0c229376b 100644 --- a/scrapy/contrib/pipeline/media.py +++ b/scrapy/contrib/pipeline/media.py @@ -2,7 +2,6 @@ from twisted.internet.defer import Deferred, DeferredList from scrapy.utils.defer import mustbe_deferred, defer_result from scrapy import log -from scrapy.project import crawler from scrapy.utils.request import request_fingerprint from scrapy.utils.misc import arg_to_iter @@ -21,6 +20,8 @@ class MediaPipeline(object): def __init__(self): self.spiderinfo = {} + from scrapy.project import crawler + self.crawler = crawler def open_spider(self, spider): self.spiderinfo[spider] = self.SpiderInfo(spider) diff --git a/scrapy/contrib/queue/__init__.py b/scrapy/contrib/queue/__init__.py index fe39f07ce..dc3d46db8 100644 --- a/scrapy/contrib/queue/__init__.py +++ b/scrapy/contrib/queue/__init__.py @@ -8,7 +8,7 @@ class SqliteExecutionQueue(ExecutionQueue): def __init__(self, *a, **kw): super(SqliteExecutionQueue, self).__init__(*a, **kw) - self.queue = JsonSqlitePriorityQueue(settings['SERVICE_QUEUE_FILE']) + self.queue = JsonSqlitePriorityQueue(settings['SQLITE_DB']) def _append_next(self): msg = self.queue.pop() diff --git a/scrapy/core/engine.py b/scrapy/core/engine.py index f7fe32254..6516fed89 100644 --- a/scrapy/core/engine.py +++ b/scrapy/core/engine.py @@ -40,7 +40,7 @@ class ExecutionEngine(object): """ self.scheduler = load_object(settings['SCHEDULER'])() self.downloader = Downloader() - self.scraper = Scraper(self) + self.scraper = Scraper(self, self.crawler.settings) self.configured = True self._spider_closed_callback = spider_closed_callback diff --git a/scrapy/core/queue.py b/scrapy/core/queue.py index 428e7a5ab..17ba27272 100644 --- a/scrapy/core/queue.py +++ b/scrapy/core/queue.py @@ -9,10 +9,7 @@ class ExecutionQueue(object): polling_delay = 5 - def __init__(self, _spiders=None): - if _spiders is None: - from scrapy.project import crawler - _spiders = crawler.spiders + def __init__(self, _spiders): self.spider_requests = [] self._spiders = _spiders diff --git a/scrapy/core/scraper.py b/scrapy/core/scraper.py index 1fd5fc05a..50be2ed5c 100644 --- a/scrapy/core/scraper.py +++ b/scrapy/core/scraper.py @@ -15,7 +15,6 @@ from scrapy.item import BaseItem from scrapy.core.spidermw import SpiderMiddlewareManager from scrapy import log from scrapy.stats import stats -from scrapy.conf import settings class SpiderInfo(object): @@ -60,7 +59,7 @@ class SpiderInfo(object): class Scraper(object): - def __init__(self, engine): + def __init__(self, engine, settings): self.sites = {} self.spidermw = SpiderMiddlewareManager() itemproc_cls = load_object(settings['ITEM_PROCESSOR']) diff --git a/scrapy/crawler.py b/scrapy/crawler.py index 5e715b37f..e6793d369 100644 --- a/scrapy/crawler.py +++ b/scrapy/crawler.py @@ -4,7 +4,6 @@ from twisted.internet import reactor, defer from scrapy.xlib.pydispatch import dispatcher from scrapy.core.engine import ExecutionEngine -from scrapy.core.queue import ExecutionQueue from scrapy.extension import ExtensionManager from scrapy.utils.ossignal import install_shutdown_handlers, signal_names from scrapy.utils.misc import load_object @@ -16,17 +15,28 @@ class Crawler(object): def __init__(self, settings): self.configured = False self.settings = settings - self.spiders = load_object(settings['SPIDER_MANAGER_CLASS'])() - self.engine = ExecutionEngine(self) - def configure(self, queue=None): - self.extensions = ExtensionManager.from_settings(self.settings) - if not self.spiders.loaded: - self.spiders.load() + def install(self): + import scrapy.project + assert not hasattr(scrapy.project, 'crawler'), "crawler already installed" + scrapy.project.crawler = self - self.queue = queue or ExecutionQueue() - self.engine.configure(self._spider_closed) + def uninstall(self): + import scrapy.project + assert hasattr(scrapy.project, 'crawler'), "crawler not installed" + del scrapy.project.crawler + + def configure(self): + if self.configured: + return self.configured = True + self.engine = ExecutionEngine(self) + self.extensions = ExtensionManager.from_settings(self.settings) + spman_cls = load_object(self.settings['SPIDER_MANAGER_CLASS']) + self.spiders = spman_cls.from_settings(self.settings) + queue_cls = load_object(self.settings['QUEUE_CLASS']) + self.queue = queue_cls(self.spiders) + self.engine.configure(self._spider_closed) @defer.inlineCallbacks def _start_next_spider(self): @@ -56,6 +66,7 @@ class Crawler(object): @defer.inlineCallbacks def start(self): + yield defer.maybeDeferred(self.configure) yield defer.maybeDeferred(self.engine.start) self._nextcall = reactor.callLater(0, self._start_next_spider) diff --git a/scrapy/project.py b/scrapy/project.py index 5cb52b9b2..bbcb0fd27 100644 --- a/scrapy/project.py +++ b/scrapy/project.py @@ -1,4 +1,11 @@ -from scrapy.crawler import CrawlerProcess -from scrapy.conf import settings +""" +This module contains the Scrapy Crawler once installed by calling the crawler +``install`` method, like this:: -crawler = CrawlerProcess(settings) + crawler.install() + +After that, you can import the (singleton) crawler like this:: + + from scrapy.project import crawler + +""" diff --git a/scrapy/spidermanager.py b/scrapy/spidermanager.py index edc17e40e..0b0a3c673 100644 --- a/scrapy/spidermanager.py +++ b/scrapy/spidermanager.py @@ -4,16 +4,26 @@ spiders """ from scrapy import log -from scrapy.conf import settings from scrapy.utils.misc import walk_modules from scrapy.utils.spider import iter_spider_classes class SpiderManager(object): - def __init__(self): - self.loaded = False + def __init__(self, spider_modules): + self.spider_modules = spider_modules self._spiders = {} + for name in self.spider_modules: + for module in walk_modules(name): + self._load_spiders(module) + + def _load_spiders(self, module): + for spcls in iter_spider_classes(module): + self._spiders[spcls.name] = spcls + + @classmethod + def from_settings(cls, settings): + return cls(settings.getlist('SPIDER_MODULES')) def create(self, spider_name, **spider_kwargs): """Returns a Spider instance for the given spider name, using the given @@ -53,21 +63,5 @@ class SpiderManager(object): """Returns list of spiders available.""" return self._spiders.keys() - def load(self, spider_modules=None): - """Load spiders from spider_modules or SPIDER_MODULES setting.""" - if spider_modules is None: - spider_modules = settings.getlist('SPIDER_MODULES') - self.spider_modules = spider_modules - - self._spiders = {} - for name in self.spider_modules: - for module in walk_modules(name): - self._load_spiders(module) - self.loaded = True - - def _load_spiders(self, module): - for spcls in iter_spider_classes(module): - self._spiders[spcls.name] = spcls - def close_spider(self, spider): pass diff --git a/scrapy/telnet.py b/scrapy/telnet.py index f4ae9673d..41b17ac74 100644 --- a/scrapy/telnet.py +++ b/scrapy/telnet.py @@ -10,9 +10,11 @@ from twisted.conch import manhole, telnet from twisted.conch.insults import insults from twisted.internet import reactor, protocol +from scrapy.xlib.pydispatch import dispatcher from scrapy.exceptions import NotConfigured from scrapy.project import crawler from scrapy.stats import stats +from scrapy import signals from scrapy.utils.signal import send_catch_log from scrapy.utils.trackref import print_live_refs from scrapy.utils.engine import print_engine_status @@ -35,8 +37,15 @@ class TelnetConsole(protocol.ServerFactory): if not settings.getbool('TELNETCONSOLE_ENABLED'): raise NotConfigured self.noisy = False - port = settings.getint('TELNETCONSOLE_PORT') - reactor.callWhenRunning(reactor.listenTCP, port, self) + self.portnum = settings.getint('TELNETCONSOLE_PORT') + dispatcher.connect(self.start_listening, signals.engine_started) + dispatcher.connect(self.stop_listening, signals.engine_stopped) + + def start_listening(self): + self.port = reactor.listenTCP(self.portnum, self) + + def stop_listening(self): + self.port.stopListening() def protocol(self): telnet_vars = self._get_telnet_vars() diff --git a/scrapy/tests/test_cmdline/__init__.py b/scrapy/tests/test_cmdline/__init__.py index 7c88870c3..697ebb512 100644 --- a/scrapy/tests/test_cmdline/__init__.py +++ b/scrapy/tests/test_cmdline/__init__.py @@ -20,29 +20,20 @@ class CmdlineTest(unittest.TestCase): return comm[0].strip() def test_default_settings(self): - self.assertEqual(self._execute('settings', '--get', 'TEST1', '--init'), \ - 'default') self.assertEqual(self._execute('settings', '--get', 'TEST1'), \ 'default + loaded + started') def test_override_settings_using_settings_arg(self): - self.assertEqual(self._execute('settings', '--get', 'TEST1', '--init', \ - '--settings', 'scrapy.tests.test_cmdline.settings2'), \ - 'override') self.assertEqual(self._execute('settings', '--get', 'TEST1', \ '--settings', 'scrapy.tests.test_cmdline.settings2'), \ 'override + loaded + started') def test_override_settings_using_set_arg(self): - self.assertEqual(self._execute('settings', '--get', 'TEST1', '--init', '--set', 'TEST1=override'), \ - 'override') self.assertEqual(self._execute('settings', '--get', 'TEST1', '--set', 'TEST1=override'), \ 'override + loaded + started') def test_override_settings_using_envvar(self): self.env['SCRAPY_TEST1'] = 'override' - self.assertEqual(self._execute('settings', '--get', 'TEST1', '--init'), \ - 'override') self.assertEqual(self._execute('settings', '--get', 'TEST1'), \ 'override + loaded + started') diff --git a/scrapy/tests/test_engine.py b/scrapy/tests/test_engine.py index 79de27fe3..160193d3f 100644 --- a/scrapy/tests/test_engine.py +++ b/scrapy/tests/test_engine.py @@ -1,20 +1,19 @@ -""" -Scrapy engine tests -""" +import sys, os, re, urlparse -import sys, os, re, urlparse, unittest - -from twisted.internet import reactor -from twisted.web import server, resource, static, util +from twisted.internet import reactor, defer +from twisted.web import server, static, util +from twisted.trial import unittest from scrapy import signals -from scrapy.project import crawler +from scrapy.conf import Settings +from scrapy.crawler import Crawler from scrapy.xlib.pydispatch import dispatcher from scrapy.tests import tests_datadir from scrapy.spider import BaseSpider from scrapy.item import Item, Field from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor from scrapy.http import Request +from scrapy.utils.signal import disconnect_all class TestItem(Item): name = Field() @@ -24,7 +23,6 @@ class TestItem(Item): class TestSpider(BaseSpider): name = "scrapytest.org" allowed_domains = ["scrapytest.org", "localhost"] - start_urls = ['http://localhost'] itemurl_re = re.compile("item\d+.html") name_re = re.compile("