提前声明:该专栏涉及的所有案例均为学习使用,如有侵权,请联系本人删帖!
文章目录
一、scrapy框架的介绍和安装
1.python安装scrapy模块
pip install scrapy
2.scrapy爬虫架构
- Scrapy 是一个快速、高层次的基于 python 的 web 爬虫构架,它用于抓取web站点并从页面中提取结构化的数据。可以更容易构建大规模的抓取项目;
- Scrapy 使用了 Twisted异步网络库来处理网络通讯。 异步处理请求,速度非常快。
- Scrapy 常应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
- Scrapy 可以使用自动调节机制自动调整爬行速度
二、第一个scrapy案例
第1步:安装好Scrapy模块后,cmd命令创建一个项目
scrapy startproject 项目名
第2步:创建好项目后,用pycharm打开项目
项目目录
第3步:在pycharm中的控制台创建一个爬虫spider
这里每个spider就相当于一个独立的爬虫脚本,域名可以随便输入一个,后期代码中可以进行更改
scrapy genspider spider_name 域名
第4步:在settings.py中,修改robots协议
第5步:在新建好的spider中,初始化start_urls列表
start_urls列表是该爬虫启动的时候,最先执行的爬虫,默认返回的爬虫response在parse函数中接收。
第6步:settings.py中添加请求头
如果要添加cookies的设置
第7步:在spider文件中的parse方法里测试是否能够获取到页面数据。
scrapy crawl 爬虫名 # 有日志启动
scrapy crawl 爬虫名 --nolog # 无日志启动
第8步:在items.py中,定义我们要爬取的字段是那些。
第9步:在spider中导item包,并在parse方法中实例化一个item
第10步:运行项目
取到xpath的列表后需要获取数据要使用的方法:
response.xpath返回一个selector对象,此对象可继续调用xpath元素及其方法
可以通过以下两个方法获取selector对象的字符串内容:
Extract_first(),相当于text[0]
Extract(),取出返回整个的list的么一个字符串内容
第11步:将数据保存到mongoDB中的操作步骤
将提取完全的item去yeild出来传入pipeline中间件
要使用popelines.py中的item,必须需要配置
在items.py中重新插入一个字段,加此字段是因为hash查询速度快且唯一
在popelines.py加入以下代码
import pymongo, hashlib
class MaoyanPipeline(object):
def __init__(self):
self.client = pymongo.MongoClient()
self.db = self.client['maoyan']
def get_md5(self, value): # hash查找更快
md5 = hashlib.md5()
md5.update(value.encode('utf-8'))
return md5.hexdigest()
def process_item(self, item, spider):
item['detail_hash'] = self.get_md5(item['detail_url'])
self.db['movie'].update({'detail_hash': item['detail_hash']}, {'$set': dict(item)}, True)
return item
查询数据库是否有数据
三、start_requests方法
使用start_requests方法,也可以替换默认的启动
优点:可以手动的设置初始url的一些request信息,比如可以自带meta参数,比如可以给他收到设置一些请求头。
四、配置文件settings.py的详细介绍
#==>第一部分:基本配置<===
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
BOT_NAME = 'Amazon'
#2、爬虫应用路径
SPIDER_MODULES = ['Amazon.spiders']
NEWSPIDER_MODULE = 'Amazon.spiders'
#3、客户端User-Agent请求头
#USER_AGENT = 'Amazon (+http://www.yourdomain.com)'
#4、是否遵循爬虫协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
#5、是否支持cookie,cookiejar进行操作cookie,默认开启,一般请求页面会携带上一次的cookie
#COOKIES_ENABLED = False
#6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
#TELNETCONSOLE_ENABLED = False
#TELNETCONSOLE_HOST = '127.0.0.1'
#TELNETCONSOLE_PORT = [6023,]
#7、Scrapy发送HTTP请求默认使用的请求头
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
#===>第二部分:并发与延迟<===
#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
#===>第四部分:爬取深度与爬取方式<===
#1、爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
# DEPTH_LIMIT = 3
#2、爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo
# 后进先出,深度优先
# DEPTH_PRIORITY = 0
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先进先出,广度优先
# DEPTH_PRIORITY = 1
# SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
# SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'
#3、调度器队列
# SCHEDULER = 'scrapy.core.scheduler.Scheduler'
# from scrapy.core.scheduler import Scheduler
#4、访问URL去重
# DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'
#===>第五部分:中间件、Pipelines、扩展<===
# 爬虫中间件,前边为地址,后变为优先级,越小越先启动
SPIDER_MIDDLEWARES = {
'guba_scrapy.middlewares.GubaScrapySpiderMiddleware': 543,
}
# 下载中间件,前边为地址,后变为优先级,越小越先启动
DOWNLOADER_MIDDLEWARES = {
'guba_scrapy.middlewares.GubaScrapyDownloaderMiddleware': 543,
}
# 插件
EXTENSIONS = {
'scrapy.extensions.telnet.TelnetConsole': None,
}
# 管道中间件,前边为地址,后变为优先级,越小越先启动
ITEM_PIPELINES = {
'guba_scrapy.pipelines.GubaScrapyPipeline': 300,
}
#===>第六部分:缓存<===
"""
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'
五、Scrapy的组件及其运行流程
运行流程:
- 引擎:Hi!Spider, 你要处理哪一个网站?
- Spider:老大要我处理xxxx.com。
- 引擎:你把第一个需要处理的URL给我吧。
- Spider:给你,第一个URL是xxxxxxx.com。
- 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
- 调度器:好的,正在处理你等一下。
- 引擎:Hi!调度器,把你处理好的request请求给我。
- 调度器:给你,这是我处理好的request
- 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
- 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
- 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
- Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
- 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
- 管道调度器:好的,现在就做!
注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)
六、管道文件pipeline
1.pipeline的作用
- 清理html数据
- 验证爬取的数据
- 去重并丢弃
- 将爬取的结果保存到数据库中或文件中
2.简单使用一下管道pipeline
例如我们有个baidu_spider爬虫
import scrapy
class BaiduSpiderSpider(scrapy.Spider):
name = 'baidu_spider'
allowed_domains = []
start_urls = ['http://www.baidu.com']
def parse(self, response):
item = {}
item['url'] = response.url
return item
然后我们在pipelines.py 文件中输出一下这个item
from itemadapter import ItemAdapter
class GubaScrapyPipeline:
def process_item(self, item, spider):
print(item)
return item
想要管道文件生效,需要配置一下:
运行一下:
scrapy crawl baidu_spider --nolog
成功输出结果,说明我们的爬虫文件的item成功传入了管道文件!
3.管道文件的多个类
问题:一个管道文件有多个管道类,如何能让item在不同类中传递呢?
添加一个管道类,修改管道文件。每个管道类中return item的意思是将处理过的item传给下一个管道类,如果不写return,那么下一个管道文件会处理空值。
from itemadapter import ItemAdapter
class GubaScrapyPipeline:
def process_item(self, item, spider):
print('我进入了管道类GubaScrapyPipeline')
return item
class GubaScrapyPipeline11111:
def process_item(self, item, spider):
print('我进入了管道类GubaScrapyPipeline111')
return item
修改配置文件,每个类后边的数字代表优先级,越小越先执行
运行一下
4.多个爬虫文件如何使用管道文件
问题:我们scrapy项目中一般会有多个爬虫项目,但是一般管道文件只有一个,那么当不同爬虫文件的item进入管道中,那么我们如何处理呢?
方法一:
假设我们有两个爬虫文件
为了区分管道item,我们在各自的爬虫里加入一个特殊的字段
管道文件修改为
from itemadapter import ItemAdapter
class GubaScrapyPipeline:
def process_item(self, item, spider):
if item['from_spider']=='baidu_spider':
print('我是处理百度爬虫的管道类')
return item
class GubaScrapyPipeline111:
def process_item(self, item, spider):
if item['from_spider'] == 'tencent_spider':
print('我是处理腾讯爬虫的管道类')
return item
方法二:
我们管道文件也可以对每个来源的爬虫进行判断
from itemadapter import ItemAdapter
class GubaScrapyPipeline:
def process_item(self, item, spider):
if spider.name == 'baidu_spider':
print('我是处理百度爬虫的管道类')
return item
class GubaScrapyPipeline111:
def process_item(self, item, spider):
if spider.name == 'tencent_spider':
print('我是处理腾讯爬虫的管道类')
return item
5.管道中的其他方法
open_spider(self,spider)
表示当spider被开启的时候调用这个方法
close_spider(self,spider)
当spider挂去年比时候这个方法被调用
from_crawler(cls,crawler)
这个可以用于获取settings配置文件中的信息,需要注意的这个是一个类方法,用法例子如下:
6.一些item pipeline的使用例子
例子1
这个例子是将item写入到json文件中
import json
class JsonWriterPipeline(object):
def __init__(self):
self.file = open('items.jl', 'wb')
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item</pre>
例子2
将item写入到MongoDB,同时这里演示了from_crawler的用法
import pymongo
class MongoPipeline(object):
collection_name = 'scrapy_items'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.collection_name].insert(dict(item))
return item</pre>
例子3:去重
一个用于去重的过滤器,丢弃那些已经被处理过的item,假设item有一个唯一的id,但是我们spider返回的多个item中包含了相同的id,去重方法如下:这里初始化了一个集合,每次判断id是否在集合中已经存在,从而做到去重的功能
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item</pre>
7.注意事项
- 使用管道,必须要在settings.py文件中进行配置
- 配置文件中的管道配置时候的数字代表优先级,越小越先执行
- 管道类中的item传递必须通过return
- 管道类中的process_item(self, item, spider)才可以接收传过来的item,其他方法不可以
- 管道的作用一般就是处理接收过来的item,或者将接收的item存入数据库
- spider将值传入管道文件通过return,但是return的时候不可以retrun列表,一般就是字典
七、下载中间件middleware
1.常用的下载中间件方法
class RenrenDownloaderMiddleware:
def process_request(self, request, spider):
# 当每个request请求通过下载中间件时候调用的,可以不返回值,比如我们就单单的使用随机useragent和proxy时候
return None
def process_response(self, request, response, spider):
# 当我们下载器从网页上下载完http请求,调用的方法,必须要返回一个响应传给spider
return response
def process_exception(self, request, exception, spider):
#处理在使用下载器中间时候遇到的异常
pass
2.使用下载中间件写一个随机user-agent
问题:有时候我们爬取一个网站爬取太频繁,可能网站会通过判断user-agent判断是不是一个爬虫,我们因此可以通过随机user-agent来避免这种情况
1.在setting.py 加入我们的user-agent列表
USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1',
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0',
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
'Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)'
]
2.在下载中间件middlewares.py文件中随机选择一个user-agent
import random
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
ua=random.choice(self.settings.USER_AGENT_LIST)
request.headers["User-Agent"]=ua
3.在settings中启用下载中间件
DOWNLOADER_MIDDLEWARES = {
'renren.middlewares.RandomUserAgentMiddleware': 543,
}
4.在spider.py中输出看一下
结果:
3.使用下载中间件写代理IP
步骤:
- 首先准备一些代理IP
- 一般我们对代理IP会有一个验证的过程,假设有一个代理IP的列表,定义一个函数,如果代理ip可以通过则没事,当代理IP不通过删除该代理IP,然后再进行正常的爬虫
- 然后写下载中间件,此处知识假设,真实不可能只有一个代理IP,一般是从代理ip列表随机选择
- 配置文件中激活下载中间件
八、scrapy-redis分布式
1.scrapy-redis分布式
首先了解什么是分布式?
将一个任务分割为多份,每一份由一个计算机完成,最后所有的计算机能够成为一个整体,成为这个任务的结果
在了解下分布式爬虫
即多台电脑爬取同一个项目。原来的项目都是部署在一台电脑上的,爬取速度虽然快,但是我们还能提升,联想到分布式,我们可以通过多台电脑进行配合爬取,这样我们的爬取速度就可以大幅度提升。
那Scrapy-redis的原理
- Master端:核心服务器,搭建Redis数据库,不负责爬取,只负责url指纹判断是否重复、request的分配、以及数据的存储
- Slaver端::爬虫程序执行端,负责执行爬虫程序,运行过程中提交新的request给Master
首先Slaver端从Master端拿任务(request, url)进行数据爬取,Slaver抓取数据的同时,产生新的request就提交给Master进行处理。
Master端只有一个Redis数据库,负责将未处理的request去重和任务分配,将处理后的request加入待爬取的队列,并且存储爬取的数据
scrapy和scrapy-redis的区别
- scrapy是一个爬虫框架,它不支持分布式
- scrapy-redis通过scrapy增加一个redis组件,这个组件里设置了待爬取url的列表和每个url
2.scrapy-redis拓展组件
上边刚说 scrapy-redis比Scrapy增加了组件,那么介绍下组件
组件 | 作用 |
---|---|
Scheduler | Scrapy改造了python本来的双向队列形成了自己的Scrapy队列,但是Scrapy多个spider不能共享待爬取队列, 即Scrapy本身不支持爬虫分布式。scrapy-redis 的解决是把这个待爬取队列换成redis数据库,从同一个redis队列存放要爬取的request,便能让多个spider去同一个数据库里读取。 |
Duplication Filter | Scrapy中用集合实现这个request去重功能,Scrapy中把已经发送的request指纹放入到一个集合中,把下一个request的指纹拿到集合中比对,如果该指纹存在于集合中,说明这个request发送过了,如果没有则继续操作。 在scrapy-redis中去重是由Duplication Filter 组件来实现的,它通过redis的set 不重复的特性,巧妙的实现了Duplication Filter去重。scrapy-redis调度器从引擎接受request,将request的指纹存⼊redis的set检查是否重复,并将不重复的请求push到redis的 请求队列。 |
Item Pipeline | scrapy-redis 的Item Pipeline将爬取到的 Item 存⼊redis的 items queue。修改过Item Pipeline 可以很方便的根据 key 从 items queue 提取item,从⽽实现items processes 集群。 |
Base Spider | 不再使用scrapy原有的Spider类,重写的RedisSpider 继承了Spider和RedisMixin这两个类,RedisMixin是用来从redis读取url的类。 该类可以去连接数据库,其他中的两个函数一个是可以使爬虫一直处于活跃状态不关闭,另一个函数可以再处理一个请求后再去寻找下一个请求 |
3.scrapy-redis架构
- master端将待爬取的请求放入redis后,其他爬虫端开始执行
- 首先调度器从redis队列中拿到请求返回给引擎,再通过下载器中间件到下载器
- 下载器下载后将数据返回给引擎,再到spiders
- spiders一方面继续让引擎去调度器拿请求重复该过程,另一方面将获取的数据交给管道
- 管道将数据处理后存入redis的指定item队列
4.使用Scrapy-redis分布式的步骤
第一步:继承新类
第二步:注释start_urls,新建py文件,将url存储到redis,设置任务
第三步:在spider中增加一个类变量redis_key
第四步:在settings.py进行配置
主机:
# 配置scrapy-redis调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置url去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
# 优先级队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# 主机名
REDIS_HOST = 'localhost'
# 端口号
REDIS_PORT = 6379
从机:
1.将start_urls初始化的代码全部注释
2.从机的redis可以关闭
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 优先级队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# ITEM_PIPELINES = {
# 'scrapy_redis.pipelines.RedisPipeline': 300
# }
REDIS_HOST = '主机ip'
REDIS_PORT = 6379