环境: Python 3.9 版本: Scrapy 2.5.1 IDE: vsCode
调试配置 在项目模块下创建run.py
文件, 使用Scrapy
提供的命令来运行调试.
from scrapy.cmdline import executeexecute('scrapy crawl quotes' .split())
把调试程序设置为run.py
的路径并设置启动目录, 之后点击调试就可以直接运行了. 因为vsCode
默认会跳过第三方代码的调试, 还需要在配置里添加"justMyCode": false
, 才能调试非用户代码.
{ "name" : "Start Scrapy" , "type" : "python" , "request" : "launch" , "program" : "run.py" , "justMyCode" : false , "cwd" : "${workspaceFolder}/scrapy/tutorial" , "console" : "integratedTerminal" }
启动逻辑 查看Scrapy
路径.
$ which scrapy /root/Application/miniconda3/envs/py-3.9/bin/scrapy
先打开Scrapy
文件看看. 可以看到最后是执行了execute()
方法, 这个方法不正是我们用Python
运行Scrapy
的来进行调试的方法吗?
接下来让我们进入内部看看它做了些什么, 这里只注重主逻辑, 一些细节处就不去刨析了.
def execute (argv=None , settings=None ): if argv is None : argv = sys.argv if settings is None : settings = get_project_settings() try : editor = os.environ['EDITOR' ] except KeyError: pass else : settings['EDITOR' ] = editor 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 = f"scrapy {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)
先看看get_project_settings()
里面重要的地方是初始化Settings
类的过程
Settings
类进行了模块的动态加载, self.setmodule
最终会调用import_module()
方法
通过调试看看settings
下,最后加载了attributes
里一大堆配置.
接下来看看_get_commands_dict()
这里主要加载了scrapy.commands
下所有模块
最后返回模块名和其映射,仔细看看这不就是运行Scrapy
的命令吗
继续往下看,加载Scrapy
自带的解析器后,开始准备命令的执行工作了。
准备工作做完了,开始初始化CrawlerProcess()
看看继承的类CrawlerRunner
, 这个才是重点
CrawlerRunner
的__init__
调用了_get_spider_loader(settings)
,它调用了load_object()
方法
load_object()
使用import_module()
动态加载了scrapy.spiderloader
模块
SpiderLoader
加载时调用了_load_all_spiders()
方法
它通过walk_modules
方法加载了spiders目录下所有的爬虫
接下来看看_run_command
方法。
它最后都会执行scrapy.commands.crawl
里的run
方法,启动爬虫引擎,这也是极其重要的一步. 其中crawl
方法会实例化scrapy.crawler.Crawler
类, 之后通过start()
来启动爬虫引擎.
至此, 大致的执行逻辑就捋清了ヾ(•ω•
)o`
关键函数 import_module()
这是Python
自带importlib
库的方法, 也是进行动态加载模块的关键, Scrapy
的很多方法都使用到它.
def import_module (name, package=None ): """Import a module. The 'package' argument is required when performing a relative import. It specifies the package to use as the anchor point from which to resolve the relative import to an absolute import. """ level = 0 if name.startswith('.' ): if not package: msg = ("the 'package' argument is required to perform a relative " "import for {!r}" ) raise TypeError(msg.format (name)) for character in name: if character != '.' : break level += 1 return _bootstrap._gcd_import(name[level:], package, level)