前言
scrapy有很多的內置命令,但是有時候我們會想要自定義一些命令,因為寫腳本不如敲個命令來的有逼格,也更方便,
不過scrapy官網并沒有對自定義命令的檔案,有的只是一句話:您也可以使用該COMMANDS_MODULE設定添加自定義專案命令 ,有關如何實作命令的示例,請參見scrapy / commands中的 Scrapy命令,說白了就是讓我們自己看原始碼,
腳本方式啟動爬蟲
原始碼等下看,先看一下如何使用在python檔案中啟動爬蟲,而不是使用scrapy crawl XXX,看示例:
import scrapy
from scrapy.crawler import CrawlerProcess
class MySpider(scrapy.Spider):
# Your spider definition
...
process = CrawlerProcess(settings={
'FEED_FORMAT': 'json',
'FEED_URI': 'items.json'
})
process.crawl(MySpider)
#process.crawl(MySpider1) 可以運行多個,并且是同時運行的
process.start()
這里 CrawlerProcess的引數是爬蟲啟動時的配置,應該類似于scrapy crawl XXX -o 后面的引數,
也可以使用CrawlerRunner來實作
from twisted.internet import reactor
import scrapy
from scrapy.crawler import CrawlerRunner
from scrapy.utils.log import configure_logging
class MySpider(scrapy.Spider):
...
configure_logging({'LOG_FORMAT': '%(levelname)s: %(message)s'})
runner = CrawlerRunner()
d = runner.crawl(MySpider)
d.addBoth(lambda _: reactor.stop()) # 關閉twisted的reactor
# d1 = runner.crawl(MySpider1)
# d1.addBoth(lambda _: reactor.stop())
# 當然也可以這樣寫:
# runner.crawl(MySpider)
# runner.crawl(MySpider1)
# d = runner.join()
# d.addBoth(lambda _: reactor.stop())
reactor.run()
如果不想同時運行,就像一個一個運行(另外代碼中出現的...效果類似于pass):
from twisted.internet import reactor, defer
from scrapy.crawler import CrawlerRunner
from scrapy.utils.log import configure_logging
class MySpider1(scrapy.Spider):
# Your first spider definition
...
class MySpider2(scrapy.Spider):
# Your second spider definition
...
configure_logging()
runner = CrawlerRunner()
@defer.inlineCallbacks
def crawl():
yield runner.crawl(MySpider1)
yield runner.crawl(MySpider2)
reactor.stop()
crawl()
reactor.run()
自定義命令
crawlall
先看一個用的最多的命令,運行所有爬蟲檔案crawlall.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 'Runs all of the spiders'
def run(self, args, opts):
spider_list = self.crawler_process.spiders.list()
for name in spider_list:
self.crawler_process.crawl(name, **opts.__dict__)
self.crawler_process.start()
這在百度隨便搜一下就出來了,而且基本上代碼都不會變,
在settings.py同級目錄下創建個commands的檔案夾,把crawlall.py放在檔案夾下,接著在settings.py中添加COMMANDS_MODULE = "newspider.commands"其中newspider為scrapy的專案名稱,而commands就是我們創建的目錄了
接著就可以在命令列使用scrapy crawlall來運行所有爬蟲了
假設需求
假設現在有個需求:寫一個通用爬蟲來抓取一些靜態網頁,資料決議部分可以由其他人來做,但是這里的其他人不懂scrapy是啥,他們只是負責寫xpath和正則的,
這樣的爬蟲使用scrapy應該很簡單,最開始想到的是給他們一個模板檔案叫他們把一些需要修改的內容修改一下,但是實際操作時可能并不順利,因為即使只需要修改部分內容,‘其他人’ 看到這么多代碼也會說我不會啥啥啥,這就導致任務無法進行,而且他們如果不小心動了相關代碼也不知道,也不好管理,
怎么辦呢?可以將模板檔案精簡,去掉代碼部分只留下需要修改的內容字典(當然不一樣是字典,某種約定的格式就行,字典只是方便管理),比如:{'標題':['//title/text()',]},這樣就看起來很簡單,只需要讓他們注意一下括號成對和逗號就行,接著我們只要根據這些來創建爬蟲檔案就行了,但是新的問題又出現了,他們怎么測驗自己的xpath寫對了沒有?總不能讓我們來測驗在給他們來重寫吧,這效率也太低了,
終于引出正題了,有兩種辦法,其一就是上面的自定義腳本,其二就是自定義命令,雖然自定義腳本更簡單,但這里為了說明自定義命令怎么使用還是使用自定義命令吧,
命令效果:根據字典檔案來抓取相關內容,可以根據模板檔案和字典檔案來創建爬蟲檔案,然后在運行這個爬蟲就達到效果了,
這種效果就像scrapy genspider(根據模板創建爬蟲)和scrapy runspider(運行爬蟲)的結合,所以我們直接看這兩個命令的原始碼,代碼很長就不放上來的,可以自己去本地看檔案(如果是anaconda, Anaconda\Lib\site-packages\scrapy\commands
里面)
看完后我發現genspider命令使用的是string.Template這個方法來創建爬蟲檔案,使用也很簡單,這其實就相當于format
import string
a = '$a dadafsfas $b'
d = {'a':1, 'b': 'dsada'}
new_a = string.Template(a).substitute(d)
接著看完runspider的代碼,我們的命令就可以這么寫:
import sys
import os
import json
import string
import logging
from importlib import import_module
from scrapy.utils.spider import iter_spider_classes
from scrapy.commands import ScrapyCommand
from scrapy.exceptions import UsageError
logger = logging.getLogger(__name__)
def create_spider(setting_rule, fname):
d = {
'spidername': fname, '標題': setting_rule.get('標題')
}
with open('../tempspider.py', 'r', encoding='utf-8') as f:
tempstr = f.read()
with open(f'../spiders/{fname}_spider.py', 'w', encoding='utf-8') as fw:
fw.write(string.Template(tempstr).substitute(d).replace('true', 'True').replace('false', 'False').replace('null', 'None'))
def _import_file(filepath):
abspath = os.path.abspath(filepath)
dirname, file = os.path.split(abspath)
logging.info(dirname)
fname, fext = os.path.splitext(file)
if fext != '.py':
raise ValueError("Not a Python source file: %s" % abspath)
if dirname:
sys.path = [dirname] + sys.path
try:
module = import_module(fname)
except Exception as e:
logger.error('模板檔案可能有語法錯誤,請檢查后重試!(%s)' % str(e))
else:
create_spider(module.setting_rule, fname)
sys.path = [dirname+'/../spiders'] + sys.path
spider_module = import_module(f'{fname}_spider')
return spider_module
finally:
if dirname:
sys.path.pop(0)
sys.path.pop(0)
class Command(ScrapyCommand):
requires_project = True
def syntax(self):
return "<spider_file>"
def short_desc(self):
return "Run a self-contained spider (without creating a project)"
def run(self, args, opts):
if len(args) != 1:
raise UsageError()
filename = args[0]
if not os.path.exists(filename):
raise UsageError("File not found: %s\n" % filename)
try:
spider_module = _import_file(filename)
except (ImportError, ValueError) as e:
raise UsageError("Unable to load %r: %s\n" % (filename, e))
spclasses = list(iter_spider_classes(spider_module))
if not spclasses:
raise UsageError("No spider found in file: %s\n" % filename)
spidercls = spclasses.pop()
self.crawler_process.crawl(spidercls, **opts.__dict__)
self.crawler_process.start()
if self.crawler_process.bootstrap_failed:
self.exitcode = 1
怎么看起來代碼這么復雜呢?因為我直接復制的runspider.py的代碼,其中包含了太多的例外處理,實際上runspider運行爬蟲的核心代碼就只有幾句:
from importlib import import_module
from scrapy.utils.spider import iter_spider_classes
spider_module = import_module(模塊名稱) # 匯入爬蟲模塊
# 回傳模塊中的爬蟲類的迭代器,也就是只要爬蟲類,去掉一些多余的函式和變數
spclasses = list(iter_spider_classes(spider_module))
spidercls = spclasses.pop() # 因為知道只有一個爬蟲類
self.crawler_process.crawl(spidercls, **opts.__dict__)
self.crawler_process.start() # 運行
我們將最上面的命令代碼寫入到test.py并放在commands目錄下,接著scrapy test 模板字典.py就可以測驗寫的字典能不能決議出資料了,為了和假設的需求更貼切,我們還可以改變scrapy的日志系統,讓日志輸出看起來更人性化,而不是更程式員化,
溫馨提示:上面的代碼只做參考,可能運行會報錯,很大可能是因為目錄處理的原因,我暫時還不知道怎么更合理的處理目錄,上級目錄直接+ '../'好像不太優雅,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/145102.html
標籤:Python
下一篇:splash官方檔案解讀(翻譯)
