Scrapy基础

在Scrapy的数据流是由执行引擎控制,具体流程如下:

1、spiders产生request请求,将请求交给引擎
2、引擎(EGINE)吧刚刚处理好的请求交给了调度器,以一个队列或者堆栈的形式吧这些请求保存起来,调度一个出来再传给引擎
3、调度器(SCHEDULER)返回给引擎一个要爬取的url
4、引擎把调度好的请求发送给download,通过中间件发送(这个中间件至少有 两个方法,一个请求的,一个返回的),
5、一旦完成下载就返回一个response,通过下载器中间件,返回给引擎,引擎把response 对象传给下载器中间件,最后到达引擎
6、引擎从下载器中收到response对象,从下载器中间件传给了spiders(spiders里面做两件事,1、产生request请求,2、为request请求绑定一个回调函数),spiders只负责解析爬取的任务。不做存储,
7、解析完成之后返回一个解析之后的结果items对象及(跟进的)新的Request给引擎
就被ITEM PIPELUMES处理了
8、引擎将(Spider返回的)爬取到的Item给Item Pipeline,存入数据库,持久化,如果数据不对,可重新封装成一个request请求,传给调度器
9、(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站

spider属性、方法

#1、name = 'amazon' 
定义爬虫名,scrapy会根据该值定位爬虫程序
所以它必须要有且必须唯一(In Python 2 this must be ASCII only.)

#2、allowed_domains = ['www.amazon.cn'] 
定义允许爬取的域名,如果OffsiteMiddleware启动(默认就启动),
那么不属于该列表的域名及其子域名都不允许爬取
如果爬取的网址为:https://www.example.com/1.html,那就添加'example.com'到列表.

#3、start_urls = ['http://www.amazon.cn/']
如果没有指定url,就从该列表中读取url来生成第一个请求

#4、custom_settings
值为一个字典,定义一些配置信息,在运行爬虫程序时,这些配置会覆盖项目级别的配置
所以custom_settings必须被定义成一个类属性,由于settings会在类实例化前被加载

#5、settings
通过self.settings['配置项的名字']可以访问settings.py中的配置,如果自己定义了custom_settings还是以自己的为准

#6、logger
日志名默认为spider的名字
self.logger.debug('=============>%s' %self.settings['BOT_NAME'])

#5、crawler:了解
该属性必须被定义到类方法from_crawler中

#6、from_crawler(crawler, *args, **kwargs):了解
You probably won’t need to override this directly  because the default implementation acts as a proxy to the __init__() method, calling it with the given arguments args and named arguments kwargs.

#7、start_requests()
该方法用来发起第一个Requests请求,且必须返回一个可迭代的对象。它在爬虫程序打开时就被Scrapy调用,Scrapy只调用它一次。
默认从start_urls里取出每个url来生成Request(url, dont_filter=True)
#针对参数dont_filter,请看自定义去重规则

如果你想要改变起始爬取的Requests,你就需要覆盖这个方法
class ImagezzSpider(scrapy.Spider):
	name = 'Imagezz'
	allowed_domains = ['image.so.com']

	def start_requests(self):
		base_url = 'http://image.so.com/zj?'
		data = {'ch':'photography','listtype':'new'}
		for page in range(1,2):
			data['sn'] = page * 30
			parmas = parse.urlencode(data)
			url = base_url + parmas
			yield scrapy.Request(url,callback=self.parse)

	def parse(self, response):
		pass
		
#8、parse(response)
这是默认的回调函数,所有的回调函数必须返回an iterable of Request and/or dicts or Item objects.

#9、log(message[, level, component]):了解
Wrapper that sends a log message through the Spider’s logger, kept for backwards compatibility. For more information see Logging from Spiders.

#10、closed(reason)
爬虫程序结束时自动触发

去重

#方法一:
1、新增类属性
visited=set() #类属性

2、回调函数parse方法内:
def parse(self, response):
	if response.url in self.visited:
		return None
	.......
	self.visited.add(response.url) 

#方法一改进:针对url可能过长,所以我们存放url的hash值
def parse(self, response):
		url=md5(response.request.url)
	if url in self.visited:
		return None
	.......

	self.visited.add(url) 

#方法二:Scrapy自带去重功能
配置文件:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #默认的去重规则帮我们去重,去重规则在内存中
DUPEFILTER_DEBUG = False
JOBDIR = "保存范文记录的日志路径,如:/root/"  # 最终路径为 /root/requests.seen,去重规则放文件中

scrapy自带去重规则默认为RFPDupeFilter,只需要我们指定
Request(...,dont_filter=False) ,如果dont_filter=True则告诉Scrapy这个URL不参与去重。

#方法三:
我们也可以仿照RFPDupeFilter自定义去重规则,

from scrapy.dupefilter import RFPDupeFilter,看源码,仿照BaseDupeFilter

#步骤一:在项目目录下自定义去重文件cumstomdupefilter.py
'''
if hasattr("MyDupeFilter",from_settings):
	 func = getattr("MyDupeFilter",from_settings)
		obj = func()
	 else:
		return MyDupeFilter()
'''
class MyDupeFilter(object):
	def __init__(self):
		self.visited = set()

	@classmethod
	def from_settings(cls, settings):
		'''读取配置文件'''
		return cls()

	def request_seen(self, request):
		'''请求看过没有,这个才是去重规则该调用的方法'''
		if request.url in  self.visited:
			return True
		self.visited.add(request.url)

	def open(self):  # can return deferred
		'''打开的时候执行'''
		pass

	def close(self, reason):  # can return a deferred
		pass

	def log(self, request, spider):  # log that a request has been filtered
		'''日志记录'''
		pass

#步骤二:配置文件settings.py
# DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'  #默认会去找这个类实现去重
#自定义去重规则
DUPEFILTER_CLASS = 'AMAZON.cumstomdupefilter.MyDupeFilter'


# 源码分析:
from scrapy.core.scheduler import Scheduler
见Scheduler下的enqueue_request方法:self.df.request_seen(request)


## 传递参数
我们可能需要在命令行为爬虫程序传递参数,比如传递初始的url,像这样
#命令行执行
scrapy crawl myspider -a category=electronics
#在__init__方法中可以接收外部传进来的参数
import scrapy
class MySpider(scrapy.Spider):
	name = 'myspider'

	def __init__(self, category=None, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.start_urls = ['http://www.example.com/categories/%s' % category]
		#...
#注意接收的参数全都是字符串,如果想要结构化的数据,你需要用类似json.loads的方法

响应解析

response.selector.css()
response.selector.xpath()
可简写为
response.css()
response.xpath()

#1 //与/
response.xpath('//body/a/')#
response.css('div a::text')

>>> response.xpath('//body/a') #开头的//代表从整篇文档中寻找,body之后的/代表body的儿子
[]
>>> response.xpath('//body//a') #开头的//代表从整篇文档中寻找,body之后的//代表body的子子孙孙
[<Selector xpath='//body//a' data='<a href="image1.html">Name: My image 1 <'>, <Selector xpath='//body//a' data='<a href="image2.html">Name: My image 2 <'>, <Selector xpath='//body//a' data='<a href="image3.html">Name: My image 3 <'>, ]

#2 文本text
>>> response.xpath('//body//a/text()')
>>> response.css('body a::text')

#3、extract与extract_first:从selector对象中解出内容
>>> response.xpath('//div/a/text()').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']
>>> response.css('div a::text').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']

>>> response.xpath('//div/a/text()').extract_first()
'Name: My image 1 '
>>> response.css('div a::text').extract_first()
'Name: My image 1 '

#4、属性:xpath的属性加前缀@
>>> response.xpath('//div/a/@href').extract_first()
'image1.html'
>>> response.css('div a::attr(href)').extract_first()
'image1.html'

#4、嵌套查找
>>> response.xpath('//div').css('a').xpath('@href').extract_first()
'image1.html'

#5、设置默认值
>>> response.xpath('//div[@id="xxx"]').extract_first(default="not found")
'not found'

#4、按照属性查找
response.xpath('//div[@id="images"]/a[@href="image3.html"]/text()').extract()
response.css('#images a[@href="image3.html"]/text()').extract()

#5、按照属性模糊查找
response.xpath('//a[contains(@href,"image")]/@href').extract()
response.css('a[href*="image"]::attr(href)').extract()

response.xpath('//a[contains(@href,"image")]/img/@src').extract()
response.css('a[href*="imag"] img::attr(src)').extract()

response.xpath('//*[@href="image1.html"]')
response.css('*[href="image1.html"]')

#6、正则表达式
response.xpath('//a/text()').re(r'Name: (.*)')
response.xpath('//a/text()').re_first(r'Name: (.*)')

#7、xpath相对路径
>>> res=response.xpath('//a[contains(@href,"3")]')[0]
>>> res.xpath('img')
[<Selector xpath='img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('./img')
[<Selector xpath='./img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('.//img')
[<Selector xpath='.//img' data='<img src="image3_thumb.jpg">'>]
>>> res.xpath('//img') #这就是从头开始扫描
[<Selector xpath='//img' data='<img src="image1_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image2_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image3_thumb.jpg">'>, <Selector xpa
th='//img' data='<img src="image4_thumb.jpg">'>, <Selector xpath='//img' data='<img src="image5_thumb.jpg">'>]

#8、带变量的xpath
>>> response.xpath('//div[@id=$xxx]/a/text()',xxx='images').extract_first()
'Name: My image 1 '
>>> response.xpath('//div[count(a)=$yyy]/@id',yyy=5).extract_first() #求有5个a标签的div的id
'images'

piplines

'''
#1、settings.py
IMAGES_STORE = r'E:\Scrapy'
MONGO_URI = 'localhost'
MONGO_DB = 'images360'
'''
import pymongo

class MongoPipeline(object):
	'''把解析好的item对象做一个持久化,保存到数据库中'''
	def __init__(self,mongo_uri,mongo_db):
		#MongoDB的ip和数据库名
		self.mongo_uri = mongo_uri
		self.mongo_db = mongo_db

	#借助from_crawler实现在初始化之前对settings参数调用
	@classmethod
	def from_crawler(cls,crawler):
		return cls(
			mongo_uri=crawler.settings.get('MONGO_URI'),
			mongo_db=crawler.settings.get('MONGO_DB')
		)

	def open_spider(self,spider):
		self.client = pymongo.MongoClient(self.mongo_uri)
		self.db = self.client[self.mongo_db]

	def process_item(self,item,spider):			
		#插入数据
		self.db[item.collection].insert(dict(item))
		return item

	def close_spider(self,spider):
		#关闭连接
		self.client.close()

class FilePipeline(object):
	def __init__(self, file_path):
		self.file_path = file_path

	@classmethod
	def from_crawler(cls, crawler):
		"""
		Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完
		成实例化
		"""
		file_path = crawler.settings.get('FILE_PATH')
		return cls(file_path)

	def open_spider(self, spider):
		"""
		爬虫刚启动时执行一次
		"""
		print('==============>爬虫程序刚刚启动')
		self.fileobj=open(self.file_path,'w',encoding='utf-8')

	def close_spider(self, spider):
		"""
		爬虫关闭时执行一次
		"""
		print('==============>爬虫程序运行完毕')
		self.fileobj.close()

	def process_item(self, item, spider):
		# 操作并进行持久化

		# return表示会被后续的pipeline继续处理
		d = dict(item)
		if all(d.values()):
			self.fileobj.write(r"%s\n" %str(d))
		return item
		
		# raise DropItem()
		# 表示将item丢弃,不会被后续pipeline处理

Dowloader Middeware

下载中间件的用途
    1、在process——request内,自定义下载,不用scrapy的下载
    2、对请求进行二次加工,比如
        设置请求头
        设置cookie
        添加代理
### 方法介绍
class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request: 
        :param spider: 
        :return:  
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        pass

def process_response(self, request, response, spider):
    """
    spider处理完成,返回时调用
    :param response:
    :param result:
    :param spider:
    :return: 
        Response 对象:转交给其他中间件process_response
        Request 对象:停止中间件,request会被重新调度下载
        raise IgnoreRequest 异常:调用Request.errback
    """
    print('response1')
    return response

def process_exception(self, request, exception, spider):
    """
    当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
    :param response:
    :param exception:
    :param spider:
    :return: 
        None:继续交给后续中间件处理异常;
        Response对象:停止后续process_exception方法
        Request对象:停止中间件,request将会被重新调用下载
    """
    return None

请求头、代理设置

def process_request(self, request, spider):
    # Called for each request that goes through the downloader
    USER_AGENT = [
        'Mozilla/4.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/5.0)',   
        'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)'
    ]
    user_agent = random.choice(USER_AGENT)
    request.headers['User-Agent'] = user_agent
	
def process_request(self, request, spider):
    # Called for each request that goes through the downloader
    PROXIES = ["124.152.32.140:53281","119.48.179.46:9999","121.233.207.142:9999",
          "111.177.187.119:9999","219.145.170.23:8888"]
    proxy = random.choice(PROXIES)
	request.meta['download_timeout']=10  #设置超时时间
    request.meta['proxy'] = 'http://' + proxy
	#request.meta['proxy']='http://user:pwd@ip:port'

爬虫中间件SpiderMiddleware

class SpiderMiddleware(object):
    def process_spider_input(self,response, spider):
        """
        下载完成,执行,然后交给parse处理
        :param response: 
        :param spider: 
        :return: 
        """
        pass

def process_spider_output(self,response, result, spider):
    """
    spider处理完成,返回时调用
    :param response:
    :param result:
    :param spider:
    :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)
    """
    return result

def process_spider_exception(self,response, exception, spider):
    """
    异常调用
    :param response:
    :param exception:
    :param spider:
    :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline
    """
    return None

def process_start_requests(self,start_requests, spider):
    """
    爬虫启动时调用
    :param start_requests:
    :param spider:
    :return: 包含 Request 对象的可迭代对象
    """
    return start_requests

配置settings

#==>第一部分:基本配置<===
#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,默认开启
#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、扩展<===
#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'
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值