前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python中命令行的应用实践

python中命令行的应用实践

原创
作者头像
MicLon
发布2023-02-25 15:34:31
5440
发布2023-02-25 15:34:31
举报
文章被收录于专栏:python-进阶python-进阶

起源

小k是一家互联网公司的爬虫(cv)工程师,他在这家公司写过大大小小无数个爬虫脚本。有一天他打开自己写过的一个爬虫项目,看到密密麻麻几十个网站的spider文件,内心暗喜,”我真是个人才,居然能写出这么多优秀且稳定的代码“。忍不住得将项目截图发给小m,等待着即将回复的:”卧槽牛逼啊“,但随即等来的却是一句:”你这么多爬虫文件,你怎么运行的?“,小k一时语塞,陷入了沉思:

我每天面对运行几十个爬虫,每次都是一个个文件右击运行,能不能通过命令行来运行爬虫呢?能不能通过类似scrapy crawl xxx的方式来直接运行我的爬虫呢?

Scrapy中的命令行

通过scrapy -h可以查看到scrapy所有的命令行:

bench         Run quick benchmark test
check         Check spider contracts
commands      
crawl         Run a spider
edit          Edit spider
fetch         Fetch a URL using the Scrapy downloader
genspider     Generate new spider using pre-defined templates
list          List available spiders
parse         Parse URL (using its spider) and print the results
runspider     Run a self-contained spider (without creating a project)
settings      Get settings values
shell         Interactive scraping console
startproject  Create new project
version       Print Scrapy version
view          Open URL in browser, as seen by Scrapy

Use "scrapy <command> -h" to see more info about a command

命令行入口源码比较好找,一般在库的__main__.py下即可看到,scrapy的入口源码如下:

# __main__.py
from scrapy.cmdline import execute

if __name__ == '__main__':
    execute()

进入execute方法中可以看到,其实scrapy中所有的命令行都是动态生成的,不仅如此,它还支持用户自定义命令行:

内置命令行

根据源码可以看到,scrapy内置了commands模块,该模块下包含了所有的命令行,比如crawllistshell等等,这些命令行都是通过scrapy.commands模块下的ScrapyCommand类来实现的,通过继承该类,可以实现自定义命令行。

例如:scrapy list 对应的命令行源码如下:

from scrapy.commands import ScrapyCommand


class Command(ScrapyCommand):

    requires_project = True
    default_settings = {'LOG_ENABLED': False}

    def short_desc(self):
        return "List available spiders"

    def run(self, args, opts):
        for s in sorted(self.crawler_process.spider_loader.list()):
            print(s)

简单介绍下ScrapyCommand类中的属性:

  • requires_project:是否需要项目,如果为True,则在运行命令行时需要在项目目录下运行,否则会报错。
  • crawler_processscrapy中的核心对象,可以通过该对象来获取spider_loadersettings等等。
  • run:命令行的主要逻辑,可以在该方法中实现命令行的主要逻辑。也是我们自定义命令行时需要重写的方法。
  • short_desc:命令行的描述,可以通过scrapy -h查看到。
  • long_desc:命令行的详细描述,可以通过scrapy <command> -h查看到。

自定义命令行

有了对scrapy内置命令行的了解,我们就可以自定义命令行了,比如我们想要实现一个scrapy runall命令行,通过此命令行,我可以运行项目下所有的爬虫。

可以通过如下方式实现:

# runall.py
# -*- coding: utf-8 -*-
from scrapy.commands import ScrapyCommand


class Command(ScrapyCommand):
    requires_project = True

    def syntax(self):
        return '[options]'

    def short_desc(self):
        return 'Run all spiders'

    def run(self, args, opts):
        spider_loader = self.crawler_process.spider_loader
        for spider_name in spider_loader.list():
            self.crawler_process.crawl(spider_name)
        self.crawler_process.start()

接下来我们需要将该命令行注册到scrapy中,我们首先新建commands包,然后将上面编写的runall.py放到该包下。

然后在项目的setting.py文件中进行修改。

# settings.py
COMMANDS_MODULE = 'PROJECT_NAME.commands'

先来看看是否注册上了:

scrapy -h

可以看到,我们的命令行已经注册上了,接下来我们就可以运行了:

scrapy runall

其他项目中的命令行

还有一个场景小k也考虑到了,就是当自己不是用scrapy搭建爬虫框架时,比如纯requests的项目中如何也可以通过命令行的方式启动爬虫呢?

我们先捋一下思路:

  1. 通过命令行启动:python run.py -n spider_name
  2. 通过run.py文件中的main方法来启动爬虫
  3. 通过给定的spider_name来获取对应的爬虫类(动态导入)
  4. 运行爬虫

沿着这个思路,我们可以应用argparse来实现命令行的解析,然后通过__import__来动态导入爬虫类,最后运行爬虫。

::: tabs

@tab run.py

import sys

from cli import main

if __name__ == '__main__':
    sys.exit(main())

@tab cli.py

# -*- coding: utf-8 -*-
import argparse
import sys

from spiders import BaseSpider


def import_object(name: str):
    """字符串导入模块方法"""
    if name.count(".") == 0:
        return __import__(name)
    parts = name.split(".")
    obj = __import__(".".join(parts[:-1]), fromlist=[parts[-1]])
    try:
        return getattr(obj, parts[-1])
    except AttributeError:
        raise ImportError("No module named %s" % parts[-1])


def make_argument():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--name", "-n", help="name of the spider",
    )
    return parser


def main():
    # 解析命令行参数
    args = make_argument().parse_args()
    try:
        # 动态导入爬虫类
        object_path = f'spiders.{args.name}'
        spider = import_object(object_path)
        for name, obj in spider.__dict__.items():
            # 判断是否是爬虫类
            if (
                    isinstance(obj, type)
                    and issubclass(obj, BaseSpider)
                    and obj is not BaseSpider
            ):
                spider = obj()
                spider.run()

    except ImportError:
        return sys.exit(1)

@tab spiders/base.py

# -*- coding: utf-8 -*-
from abc import abstractmethod


class BaseSpider:

    @abstractmethod
    def run(self):
        raise NotImplemented

@tab spiders/baidu.py

from spiders.base import BaseSpider


class BaiduSpider(BaseSpider):
    def run(self):
        print('running baidu spider')

@tab spiders/souhu.py

from spiders.base import BaseSpider


class SouhuSpider(BaseSpider):
    def run(self):
        print('running souhu spider')

:::

此时我们可以通过命令行来启动爬虫了:

python run.py -n baidu

命令行的小升级

上面我们为了启动BaiduSpider,需要在命令行中输入python run.py -n baidu,这样的话,我觉得有点麻烦,能不能像scrapy一样,直接点运行。

这种command-script的方式,在pip package的模式下只需要setup.py中配置一下就可以了,但是我们这里是纯python项目,所以我们需要手动配置一下。

这里我巧妙的运用了alias来实现,当然我为了测试只是临时使用。

alias runspider='python run.py'

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起源
  • Scrapy中的命令行
    • 内置命令行
      • 自定义命令行
      • 其他项目中的命令行
        • 命令行的小升级
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档