Scrapy 源码剖析(二)Scrapy 是如何运行起来的?

本文深入剖析 Scrapy 的运行原理,从 scrapy 命令的来源开始,逐步讲解运行入口 execute.py、初始化项目配置、检查运行环境、组装命令实例集合、解析命令和参数、初始化 CrawlerProcess,以及如何运行爬虫。文章解释了配置文件、命令加载、爬虫类加载的过程,并提供了整个流程的思维导图。
摘要由CSDN通过智能技术生成

微信搜索关注「水滴与银弹」公众号,第一时间获取优质技术干货。7年资深后端研发,用简单的方式把技术讲清楚。

在上篇文章:Scrapy源码分析(一)架构概览,我们主要从整体上了解了 Scrapy 的架构和数据流转,并没有深入分析每个模块。从这篇文章开始,我将带你详细剖析 Scrapy 的运行原理。

这篇文章,我们先从最基础的运行入口来讲,来看一下 Scrapy 究竟是如何运行起来的。

scrapy 命令从哪来?

当我们基于 Scrapy 写好一个爬虫后,想要把我们的爬虫运行起来,怎么做?非常简单,只需要执行以下命令就可以了。

 scrapy crawl <spider_name>

通过这个命令,我们的爬虫就真正开始工作了。那么从命令行到执行爬虫逻辑,这个过程中到底发生了什么?

在开始之前,不知道你有没有和我一样的疑惑,我们执行的 scrapy 命令从何而来?

实际上,当你成功安装好 Scrapy 后,使用如下命令,就能找到这个命令文件,这个文件就是 Scrapy 的运行入口:

$ which scrapy
/usr/local/bin/scrapy

使用编辑打开这个文件,你会发现,它其实它就是一个 Python 脚本,而且代码非常少。

import re
import sys

from scrapy.cmdline import execute

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
    sys.exit(execute())

安装好 Scrapy 后,为什么入口点是这里呢?

答案就在于 Scrapy 的安装文件 setup.py 中,我们找到这个文件,就会发现在这个文件里,已经声明好了程序的运行入口处:

from os.path import dirname, join
from setuptools import setup, find_packages

setup(
    name='Scrapy',
    version=version,
    url='http://scrapy.org',
    ...
    entry_points={
         # 运行入口在这里:scrapy.cmdline:execute
        'console_scripts': ['scrapy = scrapy.cmdline:execute']
    },
    classifiers=[
        ...
    ],
    install_requires=[
        ...
    ],
)

我们需要关注的是 entry_points 配置,它就是调用 Scrapy 开始的地方,也就是cmdline.pyexecute 方法。

也就是说,我们在安装 Scrapy 的过程中,setuptools 这个包管理工具,就会把上述代码生成好并放在可执行路径下,这样当我们调用 scrapy 命令时,就会调用 Scrapy 模块下的 cmdline.pyexecute 方法。

而且在这这里,我们可以学到一个小技巧——如何用 Python 编写一个可执行文件?其实非常简单,模仿上面的思路,只需要以下几步即可完成:

  1. 编写一个带有 main 方法的 Python 模块(首行必须注明 Python 执行路径)
  2. 去掉.py后缀名
  3. 修改权限为可执行(chmod +x 文件名)
  4. 直接用文件名就可以执行这个 Python 文件

例如,我们创建一个文件 mycmd,在这个文件中编写一个 main 方法,这个方法编写我们想要的执行的逻辑,之后执行 chmod +x mycmd 把这个文件权限变成可执行,最后通过 ./mycmd 就可以执行这段代码了,而不再需要通过 python <file.py> 方式就可以执行了,是不是很简单?

运行入口(execute.py)

现在,我们已经知道了 Scrapy 的运行入口是 scrapy/cmdline.pyexecute 方法,那我们就看一下这个方法。

def execute(argv=None, settings=None):
    if argv is None:
        argv = sys.argv

    # --- 兼容低版本scrapy.conf.settings的配置 ---
    if settings is None and 'scrapy.conf' in sys.modules:
        from scrapy import conf
        if hasattr(conf, 'settings'):
            settings = conf.settings
    # -----------------------------------------

	# 初始化环境、获取项目配置参数 返回settings对象
    if settings is None:
        settings = get_project_settings()
    # 校验弃用的配置项
    check_deprecated_settings(settings)

    # --- 兼容低版本scrapy.conf.settings的配置 ---
    import warnings
    from scrapy.exceptions import ScrapyDeprecationWarning
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", ScrapyDeprecationWarning)
        from scrapy import conf
        conf.settings = settings
    # ---------------------------------------

    # 执行环境是否在项目中 主要检查scrapy.cfg配置文件是否存在
    inproject = inside_project()

    # 读取commands文件夹 把所有的命令类转换为{cmd_name: cmd_instance}的字典
    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()
    # 设置项目配置和级别为command
    settings.setdict(cmd.default_settings, priority='command')
    cmd.settings = settings
    # 添加解析规则
    cmd.add_options(parser)
    # 解析命令参数,并交由Scrapy命令实例处理
    opts, args = parser.parse_args(args=arg
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值