Scrapy 启动源码分析

近期准备搭建一个框架, 深感知识欠缺, 遂向优秀的`Scrapy`取取经( •̀ .̫ •́ )✧

0%

环境: Python 3.9
版本: Scrapy 2.5.1
IDE: vsCode

调试配置

在项目模块下创建run.py文件, 使用Scrapy提供的命令来运行调试.

from scrapy.cmdline import execute

execute('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) # 加载了Scrapy命令模块
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目录下所有的爬虫

image-20220105160151008

接下来看看_run_command方法。

它最后都会执行scrapy.commands.crawl里的run方法,启动爬虫引擎,这也是极其重要的一步.
其中crawl方法会实例化scrapy.crawler.Crawler类, 之后通过start()来启动爬虫引擎.

image-20220105163802583

至此, 大致的执行逻辑就捋清了ヾ(•ω•)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)
------------ 已触及底线了 感谢您的阅读 ------------
  • 本文作者: OWQ
  • 本文链接: https://www.owq.world/66180b04/
  • 版权声明: 本站所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处( ̄︶ ̄)↗