日常在使用scrapy爬点自己感兴趣的东西时,总会频繁的进行startproject和genspider,然后重复性的将一些常见功能代码在scrapy库默认的模板上进行人工添加和完善(有时候更尴尬的是,可能长时间没用scrapy,导致一些写法已经忘记),这也不能责怪scrapy,毕竟通过其命令创建的项目和爬虫模板,应该是最简版,对于想深入研究scrapy或者想深度使用scrapy的人来说,就有点过于捡漏,当然,还会有人说,可以通过复制粘贴的方式,从其他已经编写好的项目中拿过来,这也可以走通,不过,如果能让scrapy在使用命令创建项目和爬虫的时候,自动添加进去我们可能常用的代码和功能,应该是最好的。
如果想达到上面的偷懒目的,就需要修改甚至添加scrapy的默认项目和爬虫模板,下面会详细介绍,并贴上笔者整理好的模板,抛砖引玉,同时希望可以帮到读者,甚至举一反三,读者可以自行定义自己想要的项目和爬虫模板。
一、修改默认模板思路
一般创建项目和爬虫时,使用scrapy startproject 和scrapy genspider命令,那么肯定scrapy命令是已经加入path环境变量且可接受后面两个参数,并完成相关创建工作,我们可以找到scrapy库,进入commands文件夹,发现这里就是scrapy终端命令文件,通过查看startproject.py和genspider.py内代码,基本可以确定,创建项目和爬虫的模板是在scrapy库文件夹内的templates文件夹内,我们打开,可以发现里面有project和spiders两个文件夹
1.1 project文件夹
进入后,会感觉非常梳理,里面有scrapy.cfg文件(即项目配置文件),点击进入module文件夹,里面就是items、settings、pipelines等我们看到的项目文件,只不过这些文件的后缀是tmpl
其实scrapy在创建项目的时候,做的主要工作如下:
- 将project文件夹复制到scrapy startproject 所在或所指定的文件夹内
- 将module文件夹名称修改为指定的项目名称
- 将module文件夹内的tmpl文件,变为py文件,即去除tmpl文件后缀
所以,我们只需要修改该文件夹内的对应文件,即可完成对创建项目的默认模板的修改
1.2 spiders文件夹
点击进去,里面就是basic、crawler等我们通过命令 scrapy genspider -l命令看到的可用spider模板列表,所以,我们只需要修改甚至在该文件夹内新增我们的spider模板(本质就是一个py文件),以后就可以通过scrapy genspider命令直接创建了。
1.3 指定模板路径
当然,我们直接修改scrapy库内的模板,影响太大,万一是多人或者多个项目共用一个scrapy库,肯定就直接对其他项目或使用者造成了影响,不过scrapy允许我们可以指定模板路径,这样就可以解决以上问题了
我们查看scrapy库里面的commands文件夹,里面有startproject.py文件,打开,可以看到最后有如下代码(同理,genspider.py内,也有类似下面的代码):
@property
def templates_dir(self):
return join(
self.settings['TEMPLATES_DIR'] or join(scrapy.__path__[0], 'templates'),
'project'
)
该函数是用于获取模板路径的,可以看到可以通过命令行settings内的TEMPLATES_DIR参数获取,或者直接使用scrapy库里的templates,既然如此,我们可以
- 将自己使用的模板(一定要包括项目和spiders模板)放置到某一指定文件路径下
- 然后在终端使用scrapy startproject或者scrapy genspider时,后面多传入一个settings参数,如下:
scrapy startproject -s TEMPLATES_DIR='your templates_path' <projectname>
scrapy genspider -s TEMPLATES_DIR='your templates_path' <spidername> <domain>
二、优化默认项目模板
笔者主要优化的是幕刃项目模板里面的settings和pipelines文件,
- settings文件:因为scrapy默认的settings文件内只是包含一些基本设置,并且UserAgent不太友好,一般都需要自行设置随机UA,还有其他一些常用设置也需要加进去
- pipelines文件:因为在pipelines文件内,笔者一般需要监听spider_opened和spider_closed事件,然后对应的处理一些事务,比如在爬虫关闭时,关闭文件或断开数据库连接等,所以也经常性的需要对scrapy默认pipelines进行修改,此处也要统一优化
- 增加itemloaders.py.tmpl文件,主要默认在项目内添加itemloaders,并在里面写好常规代码结构和参考,便于快速构建自己的itemloader,并用来自动完成较为复杂或常用的清洗item动作
2.1 pipelines优化如下:
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from scrapy.exceptions import DropItem
class ${ProjectName}Pipeline:
#init the pipe instance
def __init__(self,crawler):
#refer settings args like self.args=crawler.settings.args
pass
@classmethod
def from_crawler(cls,crawler):
#called when project start
p=cls(crawler)
#register signals to specific pipe methods
crawler.signals.connect(p.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(p.spider_closed, signal=signals.spider_closed)
return p
def spider_opened(self,spider):
#called when spider is opened
pass
def process_item(self, item, spider):
#called for each item that yield by spider
#must either return item itself or raise DropItem error
if :
return item
else:
raise DropItem()
def spider_closed(self,spider,reason):
#called when spider is closed
pass
其中:
- from_crawler类方法,主要是注册监听spider_opened和spider_closed信号,然后和对应方法进行绑定,如果不注册,则无法监听这两个信号并做相应处理,当然,你也可以注册监听其他信号,具体不再展开,可以点击https://docs.scrapy.org/en/latest/topics/signals.html 查看了解更多其他信号类型
- 增加常用的DropItem结构,因为在pipelines内,一般需要过滤掉无效的item
读者可以直接复制到自己的pipelines.py.tmpl文件内,并按照自己需要再添加常用功能和代码模板,可以大大提升撸码效率
2.2 settings优化如下:
from faker import Faker
ua=Faker()
BOT_NAME = '$project_name'
SPIDER_MODULES = ['$project_name.spiders']
NEWSPIDER_MODULE = '$project_name.spiders'
#auto close spider when countdown default 0
CLOSESPIDER_TIMEOUT = 0
#auto close spider when scrape pagecount
CLOSESPIDER_PAGECOUNT = 0
#auto close spider when scrape itemcount
CLOSESPIDER_ITEMCOUNT = 0
#auto close spider when occur errorcount
CLOSESPIDER_ERRORCOUNT = 0
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = ua.user_agent()
- 主要是添加了随机UA功能
- 另外增加了经常用来在调试时需设置的爬取指定数量网页、items等设置项,这样就不用手动复制粘贴设置等
2.3 增加itemloaders.py.tmpl模板
在project文件夹内,新增以上文件,并在文件内贴上如下代码:
# Define here the itemloaders used to process the item
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/loaders.html
# demo codes here, imported into spider :
# from myproject.items import Product
# def parse(self, response):
# l = ItemLoader(item=Product(), response=response)
# l.add_xpath('name', '//div[@class="product_name"]')
# l.add_xpath('name', '//div[@class="product_title"]')
# l.add_xpath('price', '//p[@id="price"]')
# l.add_css('stock', 'p#stock]')
# l.add_value('last_updated', 'today') # you can also use literal values
# return l.load_item()
import scrapy
from scrapy.loader import ItemLoader
from itemloaders.processors import TakeFirst,Join,MapCompose
class yourItemLoader(ItemLoader):
# define the itemloader that process the item
# define the default processor
default_input_processor=lambda x:x
default_output_processor=lambda y:y
# define the input and output processor fror specific field
fieldname_in=MapCompose(func,str)
fieldname_in=TakeFirst()
- 以上主要写上itemloader用法,避免时间过长遗忘,还需要查询
- 增加定义自己itemloader的代码结构,提升撸码效率
- 最后,别忘了修改下scrapy下commads文件夹内的startproject.py文件内如下代码:
TEMPLATES_TO_RENDER = (
('scrapy.cfg',),
('${project_name}', 'settings.py.tmpl'),
('${project_name}', 'items.py.tmpl'),
('${project_name}', 'pipelines.py.tmpl'),
('${project_name}', 'middlewares.py.tmpl'),
('${project_name}', 'itemloaders.py.tmpl'), #此处注册下自己新增的模板文件
)
三、优化默认爬虫模板
笔者经常使用的就是basic和crawler爬虫模板,所以,只演示对这两个模板的修改优化
3.1 basic
优化代码如下:
import scrapy,requests
from scrapy import signals
class $classname(scrapy.Spider):
name = '$name'
allowed_domains = ['$domain']
start_urls = [
'http://$domain/',
]
#spider level settings,highest priority
custom_settings=dict(
CLOSESPIDER_ITEMCOUNT=10,
)
@classmethod
def from_crawler(cls, crawler):
#called when crawl started
#register signals with spider methods,such as opened and closed,when you want to do somethings following specific signals
s=cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
return s
def spider_opend(self,spider):
#called when spider open,unnecessory
pass
def start_requests(self):
#called automatically when spider start, only called just once
#unnecessory,if you want to reset reqeust header ,you can reload this method
#otherwise,scrapy will generate requests from list start_urls,and call the callback method parse fallback
#must yield request
pass
def parse(self, response):
#must either yield requests or items
pass
def spider_closed(self,spider,reason):
##called when spider close,unnecessory
pass
- 增加了定义spider级别的settings方法,可以针对该spider自定义一些设置项
- 增加了from_crawler类方法,注册了spider_opened和spider_closed信号
- 增加了start_requests方法,如果不需要,可以删除,否则,就可以不用再自行撸代码
3.2 crawler
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy import signals
class $classname(CrawlSpider):
name = '$name'
allowed_domains = ['$domain']
start_urls = ['http://$domain/']
rules = (
#use re to extract urls from start_urls , and then parse by callback methods
#:allow:str or tuple ,specify urls that extract with re
#:allow:str or tuple ,specify urls that won't extract with re
#:restrict_xpaths: str or tuple ,specify area that extract urls with xpath
Rule(LinkExtractor(allow=r'Items/',deny='',restrict_xpaths='',), follow=True),
Rule(LinkExtractor(allow=r'tags/'), callback='parse_item'),
)
@classmethod
def from_crawler(cls, crawler):
#called when crawl started
#register signals with spider methods,such as opened and closed,when you want to do somethings following specific signals
s=cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
return s
def spider_opend(self,spider):
#called when spider open,unnecessory
pass
def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item
def spider_closed(self,spider,reason):
##called when spider close,unnecessory
pass
- 新增了from_crawler类方法,注册了spider_opened和spider_closed信号
- 增加了Rules常用的语法结构,避免自行撸代码或忘记语法
3.3 新增自定义spider模板
可以在spiders文件夹内,自行创建.tmpl文件,写好自己的爬虫蜘蛛模板,以后就可以在命令行内,通过scrapy genspider -t [templatename] <spidername> <domain>直接使用
四、写在最后
其实scrapy允许自己对该库进行更深程度的定制,包括对middlewares文件,甚至在模板内自行添加命令行命令等,或者对scrapy库源代码进行直接修改
或者,如果对scrapy的selector用起来比较爽(笔者觉得的确比lxml和beatifulsoup好用),因为其整合了xpath、css和re,并且支持链式操作,也可以将selector引入自己的模块内直接使用,包括LinkExtracto等等
后续笔者会分享更多scrapy比较好玩的功能。