总结scrapy框架
一. 核心组件和中间件
引擎 engine 自动运行,无需关祖,会自动组织所有的请求对象,分发给下载器
下载器 downloader 从引擎处获取到请求对象后,请求数据
爬虫 spiders
scrapy.Spider 普通的爬虫
scrapy.CrawSpider 可设置规则的爬虫类
Rule规则类
start_requests()开始函数
调度器 scheduler
管道 Item pipeline
清理HTML数据
验证爬去的数据(检查item包含某些字段)
查重
将爬去的结果保存到数据库中
对图片数据进行下载这个可以用图片管道
流程:
1、爬虫引擎获得出使请求开始爬取
2、爬虫引擎开始请求调度程序,并准备对下一次的请求进行抓取
3、爬虫调度其返回下一个请求给爬虫引擎
4、引擎请求发送给下载器,通过下载中间件下载网络数据
5、一旦下载器完成页面下载,将下载结果返回给爬虫引擎
6、引擎将下载器的响应通过中间件返回给爬虫进行处理
7、爬虫处理响应,并通过中间件返回处理后的items,以及新的请求给引擎
8、引擎发送处理后的items到项目管道,然后把处理结果返回给调度器,调度器计划处理下一个请求抓取
9、重复该过程,知道爬取所有的url请求
二. 核心的模块和类
scrapy.Spider 普通爬虫类的父类
- name 爬虫名, 在scrapy crawl 命令中使用
- start_urls 起始的请求URL资源列表
- allowed_domains 允许访问的服务器域名列表
- start_requests() 方法,爬虫启动后执行的第一个方法(流程中的第一步:发起请求)
- logger 当前爬虫的日志记录器
- parse() 默认请求成功后,对响应的数据默认解析的方法
scrapy.Reqeust
- 初始化时参数: url, method, body, encoding, callback, headers, cookies, dont_filter, priority, meta
- meta dict格式, 可以设置proxy 代理
scrapy.http.Response/TextResponse/HtmlResponse
- status 响应状态码
- meta 响应的元信息, 包含request中的meta信息
- url 请求的URL
- request 请求对象
- headers 响应头
- body 字节数据
- text 文本数据
- css()/xpath() 提取HTML元素信息(基于lxml/bs4)
scrapy.Item 类 ,类似于dict, 作用: 解析出不同结构的数据时,使用不同的Item类,便于数据管道处理.
scrapy.Field 类, 用于Item子类中声明字段属性(数据属性)
scrapy.signals 信号
- spider_opened 打开爬虫
- spider_closed 关闭爬虫
- spider_error 爬虫出现异常
优先级:
- 请求优先级: 值高 == 优先级大, 值低 == 优先级小
配置settings中的优先级
- 管道优先级: 值高 == 优先级小, 值低 == 优先级高
- 中间件优先级: 值高 == 优先级小, 值低 == 优先级高
作业1:
最新电影信息: https://www.dygod.net/html/gndy/dyzz/index.html
按分类: 创建分类表和电影信息.
作业2:
读书网的出版的图书信息: https://www.dushu.com/book/
三、目录结构
spiders
__init__.py
自定义的爬虫文件.py 自定义的爬虫和回调函数
__init__.py
items.py 定义数据结构的地方,是一个继承scrapy.item类,属性字段的类型是scrapy.Field()
注意:scrapy.Item类,世纪是一个dict字典,所以在spider的parse()函数返回的的迭代元素应该是dict字典对象,且 字典的key和item的Field()相对应
setting.py
1、项目名称,默认的USER_AGENT由它来构成也作为日志记录的日志名
2、爬虫应用路径
SPIDER_MODULES=['Amazon.spiders']
NEWSPIDER_MODULE='Amazon.spiders'
3、客户端User-Agent请求头
USER_AGENT='自己写'
4、是否遵循爬虫协议
ROBOTSTXT=False
5、是否支持cookie,cookiejar进行操作cookie,默认开启
COOKIE_ENABLED=False
6、Telnet用于查看当前爬虫的信息,操作爬虫等....使用telnet ip port ,然后通过命令操作
TELNETCONSOLE_ENABLED=False
TELNETCONSOLE_HOST='127.0.0.1'
TELNETCONSOLE_PORT=[6023,]
7、Scrapy发送HTTP请求默认使用的请求头
DEFAUT_REQUEST_HEADERS={
.....
}
#===>第二部分:并发与延迟<===
#1、下载器总共最大处理的并发请求数,默认值16
#CONCURRENT_REQUESTS = 32
#2、每个域名能够被执行的最大并发请求数目,默认值8
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
#I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
#II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
#CONCURRENT_REQUESTS_PER_IP = 16
#4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
#DOWNLOAD_DELAY = 3
#===>第三部分:智能限速/自动节流:AutoThrottle extension<===
#一:介绍
from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
设置目标:
1、比使用默认的下载延迟对站点更好
2、自动调整scrapy到最佳的爬取速度,所以用户无需自己调整下载延迟到最佳状态。用户只需要定义允许最大并发的请求,剩下的事情由该扩展组件自动完成
#二:如何实现?
在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。
#三:限速算法
自动限速算法基于以下规则调整下载延迟
#1、spiders开始时的下载延迟是基于AUTOTHROTTLE_START_DELAY的值
#2、当收到一个response,对目标站点的下载延迟=收到响应的延迟时间/AUTOTHROTTLE_TARGET_CONCURRENCY
#3、下一次请求的下载延迟就被设置成:对目标站点下载延迟时间和过去的下载延迟时间的平均值
#4、没有达到200个response则不允许降低延迟
#5、下载延迟不能变的比DOWNLOAD_DELAY更低或者比AUTOTHROTTLE_MAX_DELAY更高
#四:配置使用
#开启True,默认False
AUTOTHROTTLE_ENABLED = True
#起始的延迟
AUTOTHROTTLE_START_DELAY = 5
#最小延迟
DOWNLOAD_DELAY = 3
#最大延迟
AUTOTHROTTLE_MAX_DELAY = 10
#每秒并发请求数的平均值,不能高于 CONCURRENT_REQUESTS_PER_DOMAIN或CONCURRENT_REQUESTS_PER_IP,调高了则吞吐量增大强奸目标站点,调低了则对目标站点更加”礼貌“
#每个特定的时间点,scrapy并发请求的数目都可能高于或低于该值,这是爬虫视图达到的建议值而不是硬限制
AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0
#调试
AUTOTHROTTLE_DEBUG = True
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 16
#===>第五部分:中间件、Pipelines、扩展<===
#1、Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'Amazon.middlewares.AmazonSpiderMiddleware': 543,
#}
#2、Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
# 'Amazon.middlewares.DownMiddleware1': 543,
}
#3、Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
#4、Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
# 'Amazon.pipelines.CustomPipeline': 200,
}
#===>第六部分:缓存<===
"""
1. 启用缓存
目的用于将已经发送的请求或相应缓存下来,以便以后使用
from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
from scrapy.extensions.httpcache import DummyPolicy
from scrapy.extensions.httpcache import FilesystemCacheStorage
"""
# 是否启用缓存策略
# HTTPCACHE_ENABLED = True
# 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
# HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"
# 缓存超时时间
# HTTPCACHE_EXPIRATION_SECS = 0
# 缓存保存路径
# HTTPCACHE_DIR = 'httpcache'
# 缓存忽略的Http状态码
# HTTPCACHE_IGNORE_HTTP_CODES = []
# 缓存存储的插件
# HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
middleware.py
中间件
process_request(self, request, spider): None|Request|Response|raise IgnoreRequest
process_response(self, request, response, spider): Request|Response|raise IgnoreRequest
process_exception(self, request, exception, spider): None
pipelines 管道类
用来处理下载数据的后续处理
四、scrapy shell
1、response 对象
response.body 二进制
response.text 文本
response.url 请求路由
response.status 状态码
2、response解析
response.xapth() xpath进行解析,返回ItemList
response.css() css进行解析,返回ItemList
3、selector对象
extract() 使用xpath请求到的对象是一个selector对象,需要进一步使用extract()方法拆包,转换为unicode字符串
extract() 返回第一个解析到的值,如果列表为空,此种方法也不会报错,会返回一个空值
xapth() xpath进行解析
css() css进行解析
select() 可以继续使用select方法对Selector对象进一步提取信息
4、item对象
dict(itemobj)可以使用dict直接将item对象转换成字典对象
item(dicobj) 也可以使用字典对象创建一个Item对象
五、爬虫停止条件
1、Scrapy的CloseSpider扩展会满足条件时自动终止爬虫程序
2、可选项
CLOSESPIDER_TIMEOUT(秒)指定时间过后
CLOSESPIDER_ITEMCOUNT 抓取了指定数目的Item之后
CLOSESPIDER_PAGECOUNT 收到了指定数目的响应之后
CLOSESPIDER_ERRORCOUNT 发生了指定数目的错误之后
例如
$ scrapy crawl fast -s CLOSESPIDER_ITEMCOUNT=10
$ scrapy crawl fast -s CLOSESPIDER_PAGECOUNT=10
$ scrapy crawl fast -s CLOSESPIDER_TIMEOUT=10
六、规则爬虫
1、导入
from scrapy.linkextractors import LinkExtractor
2、创建对象
extractor = LinkExtractor(提取规则)
3、提取规则
直接表达式 extractor = LinkExtractor(r'list_23_\d+\.html')
allow 提取符合这则的链接
deny 不提取符合正则的链接
restrict_xpaths xpath条件
restrict_css css条件
4、提取链接
links = extractor.extract_links(response)
for link in links:
print(link.url, link.text)
5、创建爬虫文件
scrapy genspider -t crawl 爬虫名 域名
使用模板 crawlspider 创建爬虫类
七、日志LOG
级别
CRITICAL 严重错误
ERROR 一般错误
WARNNING 警告信息
INFO 一般信息
DEBUG 调试信息
设置级别
LOG_LEVEL ="日志等级"
LOG_FILE ="name.log"
扩展
loggin 的日志模块
spider.logger.info('Spider opened: %s' % spider.name)
scrapy框架的中间件中添加日志处理
handler = StreamHandler()
handler.setLevel(logging.INFO)
handler.setFormatter(logging.Formatter(fmt="%(levelname)s: %(message)s"))
# 添加日志的处理器, spider.logger -> logging.LoggerAdapter类实例对象
spider.logger.logger.addHandler(handler)
# 向scrapy.core.engine日志记录器添加StreamHandler
# scrapy框架中存在哪些日志记录器???
logger_engine = logging.getLogger('scrapy.core.engine')
logger_engine.addHandler(handler)
sys.execpthook
import sys
def download(url):
print('开始下载', url)
# 程序在运行的过程中可能会出现的异常
# 针对此异常,我们的程序并没有处理
raise Exception('--下载超时-')
def global_except(except_type, msg, trackback):
print('-------上报程序中断/异常信息------')
print(except_type, msg)
print(dir(trackback))
# 显示当前异常帧frame
print(trackback.tb_frame.f_lineno,
trackback.tb_frame.f_code.co_name,
trackback.tb_frame.f_locals)
# 显示下一个常帧的跟踪位置信息
print(trackback.tb_next.tb_frame.f_lineno,
trackback.tb_frame.f_code.co_name,
trackback.tb_next.tb_frame.f_locals)
print(trackback.tb_next.tb_next)
print('-----------------------')
if __name__ == '__main__':
sys.excepthook = global_except
download('http://www.baidu.com/s?kw=123')
八、处理POST请求
Request和Response
Request
url
callback
meta
Response
text
meta
status
url
headers
start_requests(self)
spiders 文集爱你中删除start_urls和parse()函数
yield scrapy.FormRequest()
url POST 请求
formdata dict格式
headers dict
cookies dict
callback 回调函数
yield scrapy.Request()
yield 方法调用一般加yield表示返回这个对西那个给引擎
八、代理
settings文件配置 开启DOWNLOADER_MIDDLEWARES
middlewares文件修改 实现下载中间件制定的类中的process_request方法
添加代码:request.meta[‘proxy’] = ‘http://101.236.60.8:8866’
九、写入MYSQL
首先在__init__中用pymysql创建一个链接
conn = pymysql.Connect(
。。。。。
db='gcdm',
charset='utf8')
然后创建一个db模块,相当于一个小型的dao
from dm530 import conn
def exists_author(name): #这个函数封装的时有没有这个动漫
sql="""
select id from dm
where name=%s
"""
with conn.cursor() as c:
try:
c.execute(sql)
ret=c.fetchall()
return ret
except:
return None
def query(sql,args=None):
with conn.cursor() as c: #这个函数封装的时查找动漫的原生sql
try:
c.execute(sql,args=args)
ret=c.fetchall()
return ret
except Exception as e:
print('--**********->',e)
return False
def raw(sql,args=None):
with conn.cursor() as c:
try: #这个函数封装的是提交的原生sql
c.execute(sql,args)
conn.commit()
except Exception as e:
print('--///->',e,sql,args)
conn.rollback()
自定义一个管道
class MysqlPipeline:
def __init__(self):
sql1 = 'select table_name from information_schema where tb_dm'
sql2="create table tb_dm(id varchar(32),alex varchar(100),area varchar(15),category varchar(128),cover varchar(128), path varchar(128))"
if not db.query(sql1):
db.raw(sql2)
def process_item(self, item, spider):
sql="insert into %s(%s) values(%s)"
item['id']=item.pop('_id')
item.pop('name')
field=','.join([i for i in item])
values=','.join('%%(%s)s'%i for i in item)
if isinstance(item,Dm530Item):
tb_name='tb_dm'
else:
tb_name=None
print("普通的item")
if tb_name:
db.raw(sql%(tb_name,field,values),dict(item))
return item
十、使用分布式爬虫
在爬虫文件中,首先让爬虫类继承RedisCrawlSpider'
from scrapy_redis.spiders import RedisCrawlSpider
安装 pip install scrapy-redis
在setting里面设置
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" #用来去重,自带的 ,去重过滤器
SCHEDULER = 'scrapy_redis.scheduler.Scheduler' #使用自带的调度器
SCHEDULER_PERSIST= True #是否保留驱虫的记录,当等于True时,当重启时会继续调用原来的
REDIS_URL='redis://@127.0.0.1:6379/2'
REDIS_ENCODING ='utf-8'
REDIS_HOST = '127.0.0.1'
REDIS_PORT =6379
然后在爬虫类里面定义 redis_key = 'gcdm:start_urls'
将服务启动起来之后就需要往Redis中去推任务
from redis import Redis
_rd=Redis('127.0.0.1',port=6379,db=2)
if __name__ == '__main__':
_rd.lpush('gcdm:start_urls','http://www.dm530.cc/')
然后分布式爬虫就启动起来了
十一、使用规则爬虫
首先创建项目之后,初始化项目,使用scrapy crawl -t crawl 爬虫名 域名
进入爬虫类在rulers里面定义规则
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
LinkExtractor 常用的从参数可以设置 allow 选取的正则
deny 不选取的正则
restrict_xpaths 可以用xpath提取到链接的父类上
restrict_css 可以用css提取到链接的父类上
在源码中有 _requests_to_follow这个函数,首先会判断副response是不HtmlResponse格式的,然后定义一个集合,
通过rule.link_extractor.extract_links(response)提取到规则下面的链接,然后循环遍历,rule.process_links,他会用Rlue里面的_get_method 方法,这个函数最后回调用getattr(spider,process_linkes),把里面的href提取出来,然后放到定义的集合下面,然后创建Request对象,然后调用process_request进行下载
callback表示回调函数,也就是说在LinkExtractor中会根据你的规则创建一个请求去访问,当返回response时就会交给你的callback函数去解析
follow表示,的那个访问返回一个新页面时,要不要用LinkExtractor规则里的规则去继续使用在返回的页面中
十二、使用数据管道
class Dm530Pipeline:
def process_item(self, item, spider):
item.pop('images')
item.pop('image_urls')
item['category'] = ','.join(item['category'])
return item
对数据的格式进行改变
十三 、使用selinum
在scrapy中可以在process_request用selinum实现抓取js渲染的页面
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
if not request.url.endswith('.jpg'):
spider.browser.get(request.url)
for i in range(1,3):
js="var d = document.documentElement.scrollTop=%s;"%(i*1000)
spider.browser.execute_script(js)
time.sleep(2)
html=spider.browser.page_source
return HtmlResponse(request.url,body=html.encode('utf-8'))
十四、写入到Mongodb中
将数据写入Mongo中,首先在scrapy框架中添加Mongo
先在Mongo中创建一个库
db.createCollection('gcdm1')
然后在你的环境下pip install pymongo
在中间件from_crawler中添加一个关闭信号,然后在爬虫开启和爬虫关闭的时候分别打开和关闭Mongo链接
def spider_opened(self, spider):
self.Client=MongoClient('127.0.0.1',port=27017)
spider.db=self.Client.gcdm1
spider.logger.info('Spider opened: %s' % spider.name)
def spider_closed(self, spider):
self.Client.close()
spider.logger.info('Spider closed: %s' % spider.name)
在管道中开一个管道用来存放
class MongoPipeline:
def process_item(self, item, spider):
item['_id']=uuid.uuid4().hex #需要在items中添加_id字段
spider.db.gcdm1.insert_one(item)
return item
十五、使用图片管道
首先在setting里面设置图片存储路径
IMAGES_STORE='images'
首先在items里面定制一个字段类
import scrapy
class Dm530Item(scrapy.Item):
name=scrapy.Field()
alex=scrapy.Field()
area=scrapy.Field()
category=scrapy.Field()
cover=scrapy.Field()
image_urls=scrapy.Field()
images=scrapy.Field()
然后把在爬虫类里面,给这个类的实例,并用它来配置
def parse_item(self, response):
。。。。。。。
item['image_urls']=[item['category']]
item['images']=[]
return item
在settings里面把ITEM_PIPELINES中的
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1} 这里和官网不太一样,scrapy没有contirb,pipelines改为pipelines
图片存储:文件系统存储
首先,文件系统会对你的url进行sha1 hash,然后会在你的url前面添加 setting中的IMAGES_STORE/full/.....jpg
这就是你的文件存储路径
在settings里面可以添加
IMAGES_EXPIRES=90
则记为90天后失效
缩略图生成设置土坯那字典
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
这时所旅途的格式为
<IMAGES_STORE>/full/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg 完整图片
<IMAGES_STORE>/thumbs/small/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg 小图
<IMAGES_STORE>/thumbs/big/63bbfea82b8880ed33cdb762aa11fab722a90a24.jpg 大图
过滤出小尺寸
IMAGES_MIN_HEIGHT=110
IMAGES_MIN_WIDTH=110
十六、自定义图片管道
class MyImagesPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
# 发起下载图片的请求
yield Request(item['image_urls'][0],
meta=dict(name=item['name']))
def item_completed(self, results, item, info):
# 将下载的保存的地址更新到item中,并返回item
print('item_completed-->>', results) #item_completed-->> [(True, {'url': 'http://img8.dm530.net/pic/uploadimg/2020- 4/20204423475664192.jpg', 'path': '游戏王SEVENS/62e00b1ecfeb4ab0bfaa57cc2757ca53.jpg', 'cheum': '47683e74a0e1d7e8b55466cc4ad69ab7'})]
paths = [x['path'] for ok, x in results if ok]
item['path'] = paths[0] #给items中的字段体加path
return item # 最终的item
def file_path(self, request, response=None, info=None):
# 返回相于settings中配置的IMAGES_STORE目录的文件路径
# eg. 上海师范大学天华学院校花何丹丹/xx.jpg
dir_name = request.meta['name']
dir_path = os.path.join(IMAGES_STORE, dir_name)
if not os.path.exists(dir_path):
os.mkdir(dir_path) # 用户的第一次图片下载
file_name = uuid.uuid4().hex+'.jpg'
return '%s/%s' % (dir_name, file_name)