scrapy 源码解析 (一):启动流程源码分析(一)命令行启动
前言
虽然爬虫的入门级编写并不难,但要让爬虫真正稳定可靠的运行起来,真不是一件容易的事。
首先,要用到scrapy,就必须要读懂scrapy这个爬虫框架,如果连这个框架的执行逻辑都搞不懂,那么爬虫也很难写好。
1.命令行启动
这里先不谈使用了各种框架的复杂情况,比如scrapyd服务、redis分布式队列等。只看最简单的情况,假设只写了几个简单爬虫spider(中间件和管道写不写无影响)。
输入命令
通过命令行运行其中的某一个spider:
scrapy crawl myspider
首先,命令里的scrapy是一个可执行文件,后面的crawl myspider是scrapy的参数。
可执行文件scrapy在/usr/local/python/bin目录里,是一个python脚本,有效代码为:
from scrapy.cmdline import execute if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(execute())
这个文件的作用就是从命令行里读取命令,然后传递给scrapy.cmdline包的execute()方法进行下一步操作。
execute()方法
从python的第三方库目录里找到scrapy/cmdline.py文件,可以看到代码中有execute()方法(为了压缩篇幅,这里删掉了无关紧要的代码):
def execute(argv=None, settings=None): if argv is None: argv = sys.argv if settings is None: settings = get_project_settings() # set EDITOR from environment if available try: editor = os.environ['EDITOR'] except KeyError: pass else: settings['EDITOR'] = editor check_deprecated_settings(settings) inproject = inside_project() cmds = _get_commands_dict(settings, inproject) cmdname = _pop_command_name(argv) parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), \ conflict_handler='resolve') if not cmdname: _print_commands(settings, inproject) sys.exit(0) elif cmdname not in cmds: _print_unknown_command(settings, cmdname, inproject) sys.exit(2) cmd = cmds[cmdname] parser.usage = "scrapy %s %s" % (cmdname, cmd.syntax()) parser.description = cmd.long_desc() settings.setdict(cmd.default_settings, priority='command') cmd.settings = settings cmd.add_options(parser) opts, args = parser.parse_args(args=argv[1:]) _run_print_help(parser, cmd.process_options, args, opts) cmd.crawler_process = CrawlerProcess(settings) _run_print_help(parser, _run_command, cmd, args, opts) sys.exit(cmd.exitcode) def _run_command(cmd, args, opts): if opts.profile: _run_command_profiled(cmd, args, opts) else: cmd.run(args, opts)
此方法中的重要操作有:
1.执行get_project_settings()方法,导入全局配置文件scrapy.cfg,进而导入项目的settings .py。这里用到了scrapy/utils/project.py中的get_project_settings()方法,这里先不展开。
2.解析命令行scrapy之后的命令,并把命令中的配置项导入到settings中,中间的一大段代码就是执行此功能,每个有效的命令均对应scrapy/commands中以命令命名的文件中的Command类。
3.cmd.crawler_process = CrawlerProcess(settings)这句代码建立CrawlerProcess对象,并把所有的设置settings传递给此对象。
4._run_print_help(parser, _run_command, cmd, args, opts)调用_run_command执行命令。如果指定了profile命令行参数,则用cProfile运行命令,cProfile是一个标准模块,这里不必考虑。无论如何,最后都会执行命令的run()方法,也就是scrapy/commands/crawl.py中Command类的run()方法。
执行crawl命令
scrapy/commands/crawl.py#Command:
def run(self, args, opts): if len(args) < 1: raise UsageError() elif len(args) > 1: raise UsageError("running 'scrapy crawl' with more than one spider is no longer supported") spname = args[0] self.crawler_process.crawl(spname, **opts.spargs) self.crawler_process.start()
这里有2个重要操作:
1.调用CrawlerProcess的crawl方法,执行初始化(创建新爬虫对象)。
2.调用CrawlerProcess的start方法,正式运行。
CrawlerProcess便是scrapy运行过程中最根本的进程,是所有爬虫运行的基础。
附:get_project_settings()导入配置文件的过程
scrapy/utils/project.py:
... ENVVAR = 'SCRAPY_SETTINGS_MODULE' ... def get_project_settings(): if ENVVAR not in os.environ: project = os.environ.get('SCRAPY_PROJECT', 'default') init_env(project) settings = Settings() settings_module_path = os.environ.get(ENVVAR) if settings_module_path: settings.setmodule(settings_module_path, priority='project') # XXX: remove this hack pickled_settings = os.environ.get("SCRAPY_PICKLED_SETTINGS_TO_OVERRIDE") if pickled_settings: settings.setdict(pickle.loads(pickled_settings), priority='project') # XXX: deprecate and remove this functionality env_overrides = {k[7:]: v for k, v in os.environ.items() if k.startswith('SCRAPY_')} if env_overrides: settings.setdict(env_overrides, priority='project') return settings
get_project_settings会首先判断是否设置了SCRAPY_SETTINGS_MODULE环境变量,这个环境变量用来指定工程的配置模块。如果没有这个环境变量,则会调用init_env来初始化环境变量,由于我们没有设置SCRAPY_PROJECT,所以会用default默认值来执行init_env(在scrapy/utils/conf.py)。
scrapy/utils/conf.py:
def init_env(project='default', set_syspath=True): """Initialize environment to use command-line tool from inside a project dir. This sets the Scrapy settings module and modifies the Python path to be able to locate the project module. """ cfg = get_config() if cfg.has_option('settings', project): os.environ['SCRAPY_SETTINGS_MODULE'] = cfg.get('settings', project) closest = closest_scrapy_cfg() if closest: projdir = os.path.dirname(closest) if set_syspath and projdir not in sys.path: sys.path.append(projdir) def get_config(use_closest=True): """Get Scrapy config file as a SafeConfigParser""" sources = get_sources(use_closest) cfg = SafeConfigParser() cfg.read(sources) return cfg def get_sources(use_closest=True): xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or \ os.path.expanduser('~/.config') sources = ['/etc/scrapy.cfg', r'c:\scrapy\scrapy.cfg', xdg_config_home + '/scrapy.cfg', os.path.expanduser('~/.scrapy.cfg')] if use_closest: sources.append(closest_scrapy_cfg()) return sources
init_env首先调用get_config()获取cfg配置文件,这个配置文件获取的优先级是:
1./etc/scrapy.cfg,c:\scrapy\scrapy.cfg
2.XDG_CONFIG_HOME环境变量指定的目录下的scrapy.cfg
3.~/.scrapy.cfg
4.当前执行目录下的scrapy.cfg或者父目录中的scrapy.cfg
由于1,2,3默认都不设置,所以就使用当前执行命令下的scrapy.cfg,也就是工程目录下的scrapy.cfg。这个文件的内容很简单:
[settings] default = myspider.settings [deploy] #url = http://localhost:6800/ project = myspider
根据default = myspider.settings找到对应的配置模块,后面会执行一系列导入settings.py配置项的操作,过程其实很复杂,这里不再详细说了。
————————————————
版权声明:本文为CSDN博主「csdn_yym」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdn_yym/java/article/details/85420528
本文来自博客园,作者:秋华,转载请注明原文链接:https://www.cnblogs.com/qiu-hua/p/12930422.html