摘要:學習點的命令行工具實現了低耦合,需要刪減增加哪個命令行只需要在模塊中修改增刪就可以實現。下一篇將會記錄根據借鑒命令行工具實現方法來實現自己的命令行
其實這篇文章是scrapy源碼學習的(一),加載器那篇才是(二)scrapy的命令行工具
本文環境:
wind7 64bits
python 3.7
scrapy 1.5.1
scrapy擁有非常靈活的低耦合的命令行工具,如果自己想要重新實現覆蓋掉scrapy自帶的命令也是可以的。
使用它的命令行工具可以大致分為兩種情況:
在創建的project路徑下
不在project路徑下
先看下不在scrapy項目路徑下的命令行有哪些:
Scrapy 1.5.1 - no active project Usage: scrapy[options] [args] Available commands: bench Run quick benchmark test fetch Fetch a URL using the Scrapy downloader genspider Generate new spider using pre-defined templates 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 [ more ] More commands available when run from project directory Use "scrapy -h" to see more info about a command
在項目路徑下的命令行新增了check、crawl、edit、list、parse這些命令,具體:
Scrapy 1.5.1 - project: myspider01 Usage: scrapy[options] [args] Available commands: bench Run quick benchmark test check Check spider contracts 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 -h" to see more info about a command
也即是說scrapy可以根據當前路徑是否是scrapy項目路徑來判斷提供可用的命令給用戶。
創建一個scrapy項目在當前路徑下創建一個scrapy項目,DOS下輸入:
scrapy startproject myproject
可以查看剛剛創建的項目myproject的目錄結構:
├── scrapy.cfg //scrapy項目配置文件 ├── myproject ├── spiders // 爬蟲腳本目錄 ├── __init__.py ├── __init__.py ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py // 項目設置
可以斷定,在我們使用"startproject"這個scrapy命令時,scrapy會把一些項目默認模板拷貝到我們創建項目的路徑下,從而生成我們看到的類似上面的目錄結構。我們可以打開scrapy的包,看看這些模板在哪個地方。切換至scrapy的安裝路徑(比如:..Python37Libsite-packagesscrapy),可以看到路徑下有templates文件夾,而此文件夾下的project文件夾便是創建項目時拷貝的默認模板存放目錄。
那么scrapy是怎么實現類似“startproject”這樣的命令的呢?
scrapy是使用命令行來啟動腳本的(當然也可以調用入口函數來啟動),查看其命令行實現流程必須先找到命令行實行的入口點,這個從其安裝文件setup.py中找到。
打開setup.py 找到entry_points:
... entry_points={ "console_scripts": ["scrapy = scrapy.cmdline:execute"] }, ...
可以看到scrapy開頭的命令皆由模塊scrapy.cmdline的execute函數作為入口函數。
分析入口函數先瀏覽一下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)
閱讀cmdline.py的execute函數,大概了解了命令行實現的基本流程:
命令參數的獲取可以通過兩種方式傳遞:
第一種是調用execute,比如:
from scrapy.cmdline import execute execute(argv=["scrapy","startproject","myproject","-a","xxxx"])
這樣就相當于第二種方式:命令控制臺執行
scrapy startproject myproject -a xxxx
傳遞的參數都是
["scrapy","startproject","myproject","-a","xxxx"]
如果當前不是調用的方式傳遞settings給execute入口,而是一般的命令控制臺啟動scrapy,那么scrapy會在當前路徑下搜索加載可能存在的項目配置文件。主要是通過函數get_project_settings執行。
ENVVAR = "SCRAPY_SETTINGS_MODULE" def get_project_settings(): #獲取配置 if ENVVAR not in os.environ: #初始化獲取項目的default級配置,即是scrapy生成的默認配置 project = os.environ.get("SCRAPY_PROJECT", "default") #初始化項目環境,設置系統環境變量SCRAPY_SETTINGS_MODULE的值為配置模塊路徑 init_env(project) settings = Settings() settings_module_path = os.environ.get(ENVVAR) if settings_module_path: settings.setmodule(settings_module_path, priority="project") ... return settings
獲取的配置文件主要是scrapy.cfg,我們可以看下他的內容:
[settings] default = myproject.settings [deploy] #url = http://localhost:6800/ project = myproject
在生成項目myproject的時候,這個配置文件就已經指定了項目設置模塊的路徑"myproject.settings",所以上面的get_project_settings函數獲取便是配置文件settings字段中的default鍵值,然后導入該設置模塊來生成配置。具體實現在init_env函數中。
def init_env(project="default", set_syspath=True): """在當前項目路徑下初始化項目環境. 并且通過配置系統環境來讓python能夠定位配置模塊 """ #在項目路徑下進入命令行,才能準確獲取配置 #獲取可能存在scrapy.cfg配置文件的模塊路徑 cfg = get_config() #獲取到配置文件后設置系統環境變量SCRAPY_SETTINGS_MODULE為配置模塊路徑, #如: myproject.settings,默認項目級別均為default,即是配置文件字段settings中的鍵 if cfg.has_option("settings", project): os.environ["SCRAPY_SETTINGS_MODULE"] = cfg.get("settings", project) #將最近的scrapy.cfg模塊路徑放入系統路徑使Python能夠找到該模塊導入 closest = closest_scrapy_cfg() if closest: projdir = os.path.dirname(closest) if set_syspath and projdir not in sys.path: #加入項目設置模塊路徑到系統路徑讓Python能夠定位到 sys.path.append(projdir) def get_config(use_closest=True): """ SafeConfigParser.read(filenames) 嘗試解析文件列表,如果解析成功返回文件列表。如果filenames是string或Unicode string, 將會按單個文件來解析。如果在filenames中的文件不能打開,該文件將被忽略。這樣設計的目的是, 讓你能指定本地有可能是配置文件的列表(例如,當前文件夾,用戶的根目錄,及一些全系統目錄), 所以在列表中存在的配置文件都會被讀取。""" sources = get_sources(use_closest) cfg = SafeConfigParser() cfg.read(sources) return cfg def get_sources(use_closest=True): """先獲取用戶的根目錄,及一些全系統目錄下的有scrapy.cfg的路徑加入sources 最后如果使用最靠近當前路徑的scrapy.cfg的標志use_closest為True時加入該scrapy.cfg路徑""" xdg_config_home = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config") sources = ["/etc/scrapy.cfg", r"c:scrapyscrapy.cfg", xdg_config_home + "/scrapy.cfg", os.path.expanduser("~/.scrapy.cfg")] if use_closest: sources.append(closest_scrapy_cfg()) return sources def closest_scrapy_cfg(path=".", prevpath=None): """ 搜索最靠近當前當前路徑的scrapy.cfg配置文件并返回其路徑。 搜索會按照當前路徑-->父路徑的遞歸方式進行,到達頂層沒有結果則返回‘’ """ if path == prevpath: return "" path = os.path.abspath(path) cfgfile = os.path.join(path, "scrapy.cfg") if os.path.exists(cfgfile): return cfgfile return closest_scrapy_cfg(os.path.dirname(path), path)
通過init_env來設置os.environ["SCRAPY_SETTINGS_MODULE"]的值,這樣的話
#將項目配置模塊路徑設置進系統環境變量 os.environ["SCRAPY_SETTINGS_MODULE"] = "myproject.settings"
初始化后返回到原先的get_project_settings,生成一個設置類Settings實例,然后再將設置模塊加載進實例中完成項目配置的獲取這一動作。
判斷當前路徑是否是scrapy項目路徑,其實很簡單,因為前面已經初始化過settings,如果在項目路徑下,那么
os.environ["SCRAPY_SETTINGS_MODULE"]的值就已經被設置了,現在只需要判斷這個值是否存在便可以判斷是否在項目路徑下。具體實現在inside_project函數中實現:
def inside_project(): scrapy_module = os.environ.get("SCRAPY_SETTINGS_MODULE") if scrapy_module is not None: try: import_module(scrapy_module) except ImportError as exc: warnings.warn("Cannot import scrapy settings module %s: %s" % (scrapy_module, exc)) else: return True return bool(closest_scrapy_cfg())
知道了當前是否在項目路徑下,還有初始化了項目配置,這個時候就可以獲取到在當前路徑下能夠使用的命令行有哪些了。
獲取當前可用命令集合比較簡單,直接加載模塊scrapy.commands下的所有命令行類,判斷是否需要在項目路徑下才能使用該命令,是的話直接實例化加入一個字典(格式:<命令名稱>:<命令實例>)返回,具體實現通過_get_commands_dict:
def _get_commands_dict(settings, inproject): cmds = _get_commands_from_module("scrapy.commands", inproject) cmds.update(_get_commands_from_entry_points(inproject)) #如果有新的命令行模塊在配置中設置,會自動載入 cmds_module = settings["COMMANDS_MODULE"] if cmds_module: cmds.update(_get_commands_from_module(cmds_module, inproject)) return cmds def _get_commands_from_module(module, inproject): d = {} for cmd in _iter_command_classes(module): #判斷是否需要先創建一個項目才能使用該命令, #即目前是否位于項目路徑下(inproject)的可用命令有哪些,不是的有哪些 if inproject or not cmd.requires_project: cmdname = cmd.__module__.split(".")[-1] #獲取該命令名稱并實例化 加入返回字典 #返回{<命令名稱>:<命令實例>} d[cmdname] = cmd() return d def _iter_command_classes(module_name): #獲取scrapy.commands下所有模塊文件中屬于ScrapyCommand子類的命令行類 for module in walk_modules(module_name): for obj in vars(module).values(): if inspect.isclass(obj) and issubclass(obj, ScrapyCommand) and obj.__module__ == module.__name__ and not obj == ScrapyCommand: yield obj
其中判斷是否是命令類的關鍵在于該命令模塊中的命令類是否繼承了命令基類ScrapyCommand,只要繼承了該基類就可以被檢測到。這有點類似接口的作用,ScrapyCommand基類其實就是一個標識類(該類比較簡單,可以查看基類代碼)。而該基類中有一個requires_project標識,標識是否需要在scrapy項目路徑下才能使用該命令,判斷該值就可以獲得當前可用命令。
獲取到了可用命令集合,接下來會加載Python自帶的命令行解析模塊optparser.OptionParser的命令行參數解析器,通過實例化獲取該parser,傳入當前命令實例的add_options屬性方法中來加載當前命令實例附加的解析命令,如:-a xxx, -p xxx, --dir xxx 之類的類似Unix命令行的命令。這些都是通過parser來實現解析。
其實在加載解析器之前,會去判斷當前的用戶輸入命令是否是合法的,是不是可用的,如果可用會接下去解析執行該命令,不可用便打印出相關的幫助提示。比如:
Usage ===== scrapy startproject[project_dir] Create new project Options ======= --help, -h show this help message and exit Global Options -------------- --logfile=FILE log file. if omitted stderr will be used --loglevel=LEVEL, -L LEVEL log level (default: DEBUG) --nolog disable logging completely --profile=FILE write python cProfile stats to FILE --pidfile=FILE write process ID to FILE --set=NAME=VALUE, -s NAME=VALUE set/override setting (may be repeated) --pdb enable pdb on failure
至此,scrapy命令行工具的實現流程基本結束。
學習點scrapy的命令行工具實現了低耦合,需要刪減增加哪個命令行只需要在scrapy.commands模塊中修改增刪就可以實現。但是實現的關鍵在于該模塊下的每一個命令行類都得繼承ScrapyCommand這個基類,這樣在導入的時候才能有所判斷,所以我說ScrapyCommand是個標識類。基于標識類來實現模塊的低耦合。
下一篇將會記錄根據借鑒scrapy命令行工具實現方法來實現自己的命令行
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/42650.html
摘要:一基礎環境由于不是職業的開發者,因此環境是基于的。二安裝打開命令行工具創建虛擬環境,默認情況下會創建目錄,所有的虛擬環境都會產生一個子目錄保存在此,里面包含基本程序文件以及庫文件。 目錄 基于 Python 的 Scrapy 爬蟲入門:環境搭建 基于 Python 的 Scrapy 爬蟲入門:頁面提取 基于 Python 的 Scrapy 爬蟲入門:圖片處理 作為一個全棧工程師(...
摘要:提升篇之配置增加并發并發是指同時處理的的數量。其有全局限制和局部每個網站的限制。使用級別來報告這些信息。在進行通用爬取時并不需要,搜索引擎則忽略。禁止能減少使用率及爬蟲在內存中記錄的蹤跡,提高性能。 scrapy提升篇之配置 增加并發 并發是指同時處理的request的數量。其有全局限制和局部(每個網站)的限制。Scrapy默認的全局并發限制對同時爬取大量網站的情況并不適用,因此您需要...
閱讀 1867·2023-04-25 19:51
閱讀 1168·2021-11-15 11:43
閱讀 4529·2021-11-02 14:40
閱讀 1999·2021-10-11 10:59
閱讀 1338·2021-09-22 15:05
閱讀 1027·2021-09-09 09:32
閱讀 648·2019-08-30 15:56
閱讀 549·2019-08-30 15:52