zoukankan      html  css  js  c++  java
  • Scrapy框架2

    一、进程、线程、协成

      1、进程、线程、协成之间的关系
               1、  线程是计算机中最小的工作单元。
                  2、 进程是提供资源供n个线程使用,即进程是最小的管理单元。

          3、协程是人为控制的线程。

          4、总结:1、python中由于有 GIL锁的存在,所以一个进程中同一时刻只有一个线程被CPU调度,所以在计算密集型,使用多进程而在io密集型使用多线程。

             2、使用协成可以实现单线程下的并发,线程中cpu在遇到io操作时就会切到下一个线程中,这样大大的影响的效率于是人为的控制cpu在线程中的切换,当cpu在线程中遇到io会切换到该线程的下一个程序中这就是协成

      2、异步非阻塞模块

       1、gevent模块,基于协程的异步非阻塞模块。
               2、Twisted,基于事件驱动(while循环检测)异步非阻塞模块(框架)。
               3、非阻塞是指不等待 ,异步是指回调,执行某个任务完成之后,自动执行的函数。回调函数

    二、scarpy框架

      1、重写scrapy请求头方法

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.http import Request
    from scrapy.selector import HtmlXPathSelector             #response= HtmlXPathSelector(response)主要是在parse函数中使用,
    from urllib.parse import urlencode
    from ..items import MyscrapyItem class BaiduSpider(scrapy.Spider): name = 'baidu' #爬虫应用的名称,通过此名称启动爬虫命令 allowed_domains = ['www.amazon.cn'] # 允许爬取数据的域名 def __init__(self,keywords=None,*args,**kwargs): super(BaiduSpider,self).__init__(*args,**kwargs) self.keywords=keywords #主要是接受一些外部传进来的参数 def start_requests(self): #重写scrapy请求头url必须写这个名字的函数 url='https://www.amazon.cn/s/ref=nb_sb_noss_1?' #新url parmas={ 'field-keywords':self.keywords #主要是定义一些外部传进来的参数 } url=url+urlencode(parmas,encoding='utf-8') #路径拼接,形成一个完整的url路径 yield Request(url,callback=self.parse_index) #yield返回数据给回调函数,也可以是return+[]的方式返回数据给回调函数,yield返回的是生成器,return+[]返回的可迭代对象 # return [Request(url='www.baidu.com',callback=self.parse),] def parse_index(self, response): urls=response.xpath('//*[contains(@id,"result_"]/div/div[3]/div[1]/a/@href').extract() for url in urls: print(url) yield Request(url,callback=self.parse) #返回详情页的信息 next_url=response.urljoin(response.xpath('//*[@id="pagnNextLink"]/@href').extract_first()) yield Request(next_url,callback=self.parse_index) #返回下一页的url路径,返回的是个Request对象 def parse(self, response): price=response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first().strip() colour=response.xpath('//*[@id="variation_color_name"]/div/span/text()').extract_first().strip() item=MyscrapyItem() item['price']=price item['colour']=colour return item #返回的是个item对象
         或者是这种格式,只要返回的是个item对象及格
         obj = XiaoHuarItem(price=price, colour=colour, )
         yield obj

       2、items文件写法

    import scrapy
    class MyscrapyItem(scrapy.Item):
        price=scrapy.Field() 
        colour=scrapy.Field()
    

       3、自定义pipelines文件

    from scrapy.exceptions import DropItem    #主动触发异常用的
    class CustomPipeline(object):
        def __init__(self,val):
            self.vale = val              #构造一个对象特有的方法
        def process_item(self, item, spider):       #用于存储爬取的数据,可以存储在文件也可以存储在数据库。
          
           f=opne('item.log','a+')
         f.write(item.price,item.colour)
         f.close() return item # return表示会被后续的pipeline继续处理,因为有可以一个爬虫项目会有多个爬虫程序一起执行,但是都有写入同一个文件中,那就让第一个文件写入,其余后面的文件不用重复写入。 # raise DropItem() # 表示将item丢弃,不会被后续pipeline处理     # if spider.name='baidu': #这就表示该文件只是存储这个名字的爬虫爬取的数据
          #f=opne('item.log','a+')
          #f.write(item.price,item.colour)
          #f.close()
        #return item
    @classmethod def from_crawler(cls, crawler):#初始化时候,用于创建pipeline对象,crawler这个类可以得到settings配置文件内的数据,如果需要给pipeline对象创建某个额外的参数就可以用这中方法 val = crawler.settings.getint('MMMM') #val = crawler.settings.get('MMMM') 读取配置文件的某个信息 return cls(val) def open_spider(self,spider): #在爬虫程序开始执行之前调用该方法 print('000000') def close_spider(self,spider): #在爬虫程序关闭后调用该方法 print('111111')

    总结:这五个方法的执行顺序: 先执行from_crawler方法,用于初始化数据的时候读取配置文件,然后再执行爬虫程序的__init__方法,然后程序启动前执行open_spider方法,程序结束前保存数据时执行process_item方法,程序结果后
    执行close_spider方法。

       4、自定义url去重功能

    scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,settings相关配置有: 
    DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #去重文件,可以自定义 DUPEFILTER_DEBUG = False #默认去重功能是关闭的可以在settings中打开去重功能也可以在爬虫程序中手动指定去重 yield Request(url,callback=self.parse,dont_filter=True) JOBDIR = "保存范文记录的日志路径,如:/root/" # 最终路径为 /root/requests.seen 默认url是存放在内存中的,只要添加上jobdir就可以吧url存放在指定文件中

       自己写一个去重规则

    class RepeatUrl:
        def __init__(self):
            self.visited_url = set() #默认是一个集合,也可以是自定义链接一个数据库,文件等
    
        @classmethod
        def from_settings(cls, settings):
            """
            初始化时,调用
            """
            return cls()
    
        def request_seen(self, request):
            """
            检测当前请求是否已经被访问过
            :param request: 
            :return: True表示已经访问过;False表示未访问过
            """
            if request.url in self.visited_url:
                return True
            self.visited_url.add(request.url)
            return False
    
        def open(self):
            """
            开始爬去请求时,调用
            :return: 
            """
            print('open replication')
    
        def close(self, reason):
            """
            结束爬虫爬取时,调用
            :param reason: 
            :return: 
            """
            print('close replication')
    
        def log(self, request, spider):
            """
            记录日志
            :param request: 
            :param spider: 
            :return: 
            """
            print('repeat', request.url)
    注意:写好自定义的去重规则后要将该文件设置到settings中该规程才能生效 比如:DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'

       5、自定义命令

       #自定制一键启动所有爬虫命令 
      from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings 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() #开始爬取任务
    注意:1、在spiders同级创建任意目录,如:commands
       2、在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令)
       3、在settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称'
       4、在项目目录执行命令:scrapy crawlall
      
      #自定制在py文件中启动某个爬虫命令  
      from scrapy.cmdline import execute  
      execute(['scrapy', '命令', '爬虫程序名称','-a','传入的参数'])
      eg:execute(['scrapy', 'crawl', 'baidu','-a','keywords=iphone7'])
    注意:自定制的py文件名称必须是entrypoint.py

         6、自定义信号

    from scrapy import signals
    class MyExtension(object):
        def __init__(self, value):
            self.value = value    #定义自己想要的方法
    
        @classmethod
        def from_crawler(cls, crawler): #扩展功能,在扩展功能里注册信号
            val = crawler.settings.getint('MMMM')#读取配置文件信息
            ext = cls(val)
            crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) #注册信号
            crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) #注册信号,可以注册很多信号,看源码
            return ext   #如果不需要扩展功能那么就直接注册信号即可
    
        def spider_opened(self, spider): #爬虫程序启动时触发的信号
            print('open')
    
        def spider_closed(self, spider): #爬虫程序关闭时触发的信号
            print('close')
    注释:1、在项目目录下创建一个py文件,将自定义的信号写入该文件,建议py文件名为extensions
       2、在settings中注册自定义信号的文件
        eg:EXTENSIONS = {'scrapy.extensions.telnet.TelnetConsole': 300,}

        7、中间件

    #下载中间件
    class DownMiddleware1(object): def process_request(self, request, spider): #请求过去的中间件带的时request """ 请求需要被下载时,经过所有下载器中间件的process_request调用 return: None,继续后续中间件去下载,默认就是什么都不返回; Response对象,停止process_request的执行,开始执行process_response,先导入Response(from scrapy.http import Response)
           eg:return Response(url='http://www.baidu.com',request=request) #只要返回Response对象就不会执行后续的Request,而是直接执行Response Request对象,停止中间件的执行,将Request重新调度器 #如果返回的是Request对象那就不会执行后续的Request和Reques而是直接到引擎,重新调度 raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception """ pass def process_response(self, request, response, spider): #返回数据的中间件带的时response """ spider处理完成,返回时调用,默认返回的就是Response return: Response 对象:转交给其他中间件process_response Request 对象:停止中间件,request会被重新调度下载 raise IgnoreRequest 异常:调用Request.errback """ return response def process_exception(self, request, exception, spider): """ 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常的时候调用 :return: None:继续交给后续中间件处理异常; Response对象:停止后续process_exception方法,正确执行后续方法 Request对象:停止中间件后续方法,request将会被重新调用下载 """ return None

    注释:1、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为DownMiddleware
       2、在settings中注册中间件,DOWNLOADER_MIDDLEWARES = {'chouti.middlewares.MyCustomDownloaderMiddleware': 600,},写了几个中间件就注册几个中间件
       3、可以在中间件的process_request中将所有的Request请求添加上请求头,添加cookies等操作,也可以添加代理,判断那个Request需要代理。

    #爬虫中间件
    class SpiderMiddleware(object):
        def process_spider_input(self,response, spider):
            """下载完成,执行,然后交给parse处理,默认返回none""" 
            pass
        def process_spider_output(self,response, result, spider):
            """spider处理完成,返回时调用,return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)"""
            return result
    def process_spider_exception(self,response, exception, spider): """异常调用,return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline""" return None
    def process_start_requests(self,start_requests, spider): """爬虫启动时调用,return: 包含 Request 对象的可迭代对象,由于爬虫只是启动一次所以process_start_requests也只是调用一次 """ return start_requests
    注释:1、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为SpiderMiddleware
       2、在settings中注册中间件,
    SPIDER_MIDDLEWARES = { 'chouti.middlewares.ChoutiSpiderMiddleware': 600,},写了几个中间件就注册几个中间件

       8、自定义代理

    代理,需要在环境变量中设置
        from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
        
        方式一:使用默认
         import os os.environ {
          http_proxy:http://root:woshiniba@192.168.11.11:9999/ #有的代理链接是需要用户名和密码的,这个就是root为用户名,woshiniba为密码 https_proxy:http://192.168.11.11:9999/
          } #该方法只需要在爬虫程序文件的类的__init__中添加上就可以了,程序会判断只要是_prxoy结尾的就会吧这个代理拿出来
    方式二:使用自定义下载中间件 def to_bytes(text, encoding=None, errors='strict'): if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError('to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type(text).__name__) if encoding is None: encoding = 'utf-8' return text.encode(encoding, errors) class ProxyMiddleware(object):#代理中间件 def process_request(self, request, spider): PROXIES = [ {'ip_port': '111.11.228.75:80', 'user_pass': ''}, {'ip_port': '120.198.243.22:80', 'user_pass': ''}, {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, {'ip_port': '122.96.59.104:80', 'user_pass': ''}, {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ] proxy = random.choice(PROXIES)#随机选择一个代理,高明就高明在这个地方,默认代理是不能随机生成的,所有也会有被封的风险。 if proxy['user_pass'] is not None: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加上代理 encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass']))#代理加密,固定加密方法 request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) #代理授权 #print "**************ProxyMiddleware have pass************" + proxy['ip_port'] else: #print "**************ProxyMiddleware no pass************" + proxy['ip_port'] request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port'])#添加代理 注释:1、在爬虫项目路径中创建代理文件
         2、在settings中注册上代理 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware': 500,}

       9、settings配置文件详解

    1. 爬虫名称
    BOT_NAME = 'step8_king'
    
    2. 爬虫应用路径
    SPIDER_MODULES = ['step8_king.spiders'] #其实就是创建的爬虫程序名称+spiders
    NEWSPIDER_MODULE = 'step8_king.spiders'
    3. 客户端 user-agent请求头 # USER_AGENT = 'step8_king (+http://www.yourdomain.com)' 4. 禁止爬虫配置(机器人协议) # ROBOTSTXT_OBEY = True
    5. 并发请求数 # CONCURRENT_REQUESTS = 4 #一个线程最大能并发多少个请求,力度粗 6. 延迟下载秒数 # DOWNLOAD_DELAY = 2 默认是秒,每多少秒一次下载 7. 单域名访问并发数,并且延迟下次秒数也应用在每个域名 力度细 # CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 单IP访问并发数,如果有值则忽略:CONCURRENT_REQUESTS_PER_DOMAIN,并且延迟下次秒数也应用在每个IP 力度细 # CONCURRENT_REQUESTS_PER_IP = 3 8. 是否支持cookie,cookiejar进行操作cookie # COOKIES_ENABLED = True # COOKIES_DEBUG = True
    9. Telnet用于查看当前爬虫的信息,操作爬虫等... # 使用telnet ip port ,然后通过命令操作 # TELNETCONSOLE_ENABLED = True # TELNETCONSOLE_HOST = '127.0.0.1' # TELNETCONSOLE_PORT = [6023,] 10. 默认请求头,死板 # Override the default request headers: # DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', # } 11. 定义pipeline处理请求 # ITEM_PIPELINES = { # 'step8_king.pipelines.JsonPipeline': 700, # 'step8_king.pipelines.FilePipeline': 500, # } 12. 自定义扩展,基于信号进行调用 # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html # EXTENSIONS = { # # 'step8_king.extensions.MyExtension': 500, # } 13. 爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度 # DEPTH_LIMIT = 3 14. 爬取时,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' 15. 调度器队列 # SCHEDULER = 'scrapy.core.scheduler.Scheduler' # from scrapy.core.scheduler import Scheduler 16. 访问URL去重 # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl' """ 17. 自动限速算法 from scrapy.contrib.throttle import AutoThrottle 自动限速设置 1. 获取最小延迟 DOWNLOAD_DELAY 2. 获取最大延迟 AUTOTHROTTLE_MAX_DELAY 3. 设置初始下载延迟 AUTOTHROTTLE_START_DELAY 4. 当请求下载完成后,获取其"连接"时间 latency,即:请求连接到接受到响应头之间的时间 5. 用于计算的... AUTOTHROTTLE_TARGET_CONCURRENCY target_delay = latency / self.target_concurrency new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延迟时间 new_delay = max(target_delay, new_delay) new_delay = min(max(self.mindelay, new_delay), self.maxdelay) slot.delay = new_delay """

       10、https证书设置

        Https访问时有两种情况:
        1. 要爬取网站使用的可信任证书(默认支持)
            DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
            DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory"
            
        2. 要爬取网站使用的自定义证书
            DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory"
            DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory"
            
            # https.py
            from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory
            from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate)
            
            class MySSLFactory(ScrapyClientContextFactory):
                def getCertificateOptions(self):
                    from OpenSSL import crypto
                    v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) #打开本地保存的证书文件
                    v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read())
                    return CertificateOptions(
                        privateKey=v1,  # pKey对象
                        certificate=v2,  # X509对象
                        verify=False,
                        method=getattr(self, 'method', getattr(self, '_ssl_method', None))
                    )
        其他:
            相关类
                scrapy.core.downloader.handlers.http.HttpDownloadHandler
                scrapy.core.downloader.webclient.ScrapyHTTPClientFactory
                scrapy.core.downloader.contextfactory.ScrapyClientContextFactory
            相关配置
                DOWNLOADER_HTTPCLIENTFACTORY
                DOWNLOADER_CLIENTCONTEXTFACTORY
    
  • 相关阅读:
    NOIP2016-2020 复盘
    「笔记」线段树合并/分裂
    「笔记」线性基
    20210628模拟赛解题报告
    「笔记」左偏树
    题解 CF718C Sasha and Array
    一些杂碎的知识点
    20210614 模拟赛
    洛谷 P4249 [WC2007]剪刀石头布
    CF132E Bits of merry old England
  • 原文地址:https://www.cnblogs.com/xuanan/p/7827966.html
Copyright © 2011-2022 走看看