• scrapy框架使用:分布式、增量式


    scrapy框架的使用

    前记:爬虫框架部分整理完成,后续慢慢完善,

    声明:

       1)仅作为个人学习,如有冒犯,告知速删!

       2)不想误导,如有错误,不吝指教!

    创建工程:

    • scrapy startproject name

      • cd proName

        • scrapy genspider spiderName urlName(限制爬虫的爬取范围)

        • 执行:scrapy crawl spiderName

    start_urls = []=====>列表中的元素会被scrapy自动进行发送--几个url,请求

     

    setting必备设置:

    • robotstxt_obey = False

    • USER_AGENT = ' '

    • LOG_LEVEL = 'ERROR'

    • LOG_FILE = 'log.txt'----一般不用

     

    scrapy框架中:

    • xpath返回的列表中的列表元素是selector对象,我们需要解析获取的字符串的数据

    • 必须经过一个extract() 操作才可以将该对象汇总存储的字符串的数据获取

    .extract()----列表

    .extract_first()-----字符串

    ---问题:xpath返回的列表中的列表元素有多个(selector对象),想要将每个列表元素对应的selector中的字符串取出--------使用 extract()

     

    scrapy数据解析

    • xpath语法

      • 通过返回的response数据可以直接直接进行Xpath解析。

     

    scrapy持久化存储

    • 基于终端指令:

      • 只可以将parse方法的返回值存储到电脑磁盘中

        • scrapy crwal first -o file.csv-------->将当前返回值存储到file文件中csvjson

    • 基于管道:>pipelines.py

      • 编码流程

          1. 数据解析(在爬虫类中)

          2. 在item的类中定义相关的属性(爬虫类)

          3. 将解析的数据存储封装到item类型的对象中;

          4. 将item对象提交给管道 yield

          5. 在管道类中process_item方法负责接收item对象,然后对item进行任意形式的格式持久化存储

          6. 在settings中设置MySQL的配置信息

             # 如下所示
             # mysql 配置  
             MYSQL_DB_NAME = 'scrapy_db'
             MYSQL_HOST = '127.0.0.1'
             MYSQL_USER = 'root'
             MYSQL_PASSWORD = '123456'

     

    • 细节补充:

      • 管道文件中的一个管道类表示将数据存储到某一个形式的平台中。

      • 如果管道文件中定义多个管道类,爬虫类提交的item的操作会给到优先级最高的管道类,只有优先级最高的管道类才可以接受到item,剩下的管道类是需要从优先级最高的管道中接受item;

      • process_item 方法的实现中的return item的操作表示item传递给下一个即将被执行的管道类

     

    手动请求发送:

    yield scrapy.Request(url,callback)-----发送的get请求

    对yield总结:

      1). 向管道提交item的时候:yield item

      2). 手动请求发送:yeld scrapy.Request(url,callback)

    手动发送post请求:

      yield scrapy.FormRequest(url,formdata,callback):formdata是一个字典表示请求参数

    scrapy五大核心组件:

      - 略(网上都有)

    scrapy的请求传参:

         -  作用:实现深度爬取
      - 使用场景:使用scrapy爬取的数据没有存在同一张页面的数据
      - 传递item:使用meta的技巧----yield scrapy.Request(url,callback,meta)
      - 接受item:response.meta

     

    提升scrapy爬取数据的效率(配置文件中):

    - 增加并发:

      CONCURRENT_REQUESTS = 100

    - 降低日志级别:

      LOG_LEVEL='ERROR'

    - 禁止cookies:

      COOKIES_ENABLE = True

    -禁止重试:

      retry_ebable = False

    - 减少下载超时:

      DOWNLOAD_TIMEOUT = 3----请求超过3,丢弃,

     

    scrapy的中间件(middlewares):

    --概念:下载中间件

      --->处于引擎和下载器

    --封装了两个类:

      NameDownloaderMiddleware;NameSpiderMiddleware

    --一次请求经过两次中间件:

      ---必须经过引擎:

    • 首先由调度器(scheduler)到下载器(downloader)之间可以进行request的修改与设置;

    • 再由下载器(downloader)到主爬虫(spider)之间可以进行response的修改与设置;

    -- 批量拦截所有的请求响应

    -- 拦截请求

      -- 篡改请求的头信息(UA伪装)

      -- 篡改请求对应的ip(代理)

    -- 拦截响应:

      -- 篡改响应数据,篡改响应对象

    下载器中间件中的核心方法(NameDownloaderMiddleware):

    • 位置:处于scrapy的Request和Response之间的处理模块;

       

    1. process_request---拦截正常请求,并修改与设置

      • 进行ua伪装:resquest.headers["User-Agent"] = "xxx "  

        • 随机更换UA

          •  1  #middlewares中间件重写,记得开启该中间件
             2  from scrapy import signals
             3  import random
             4  from xbhog.settings import USER_AGENTS_LIST
             5  6  class UserAgentMiddleware(object):
             7  8      def process_request(self,request,spider):
             9          #设置随机请求头
            10          ua = random.choice(USER_AGENTS_LIST)
            11          #设置初始URL中的UA
            12          request.headers['User-Agent'] = ua
          •  1 #settings设置
             2  USER_AGENTS_LIST = [
             3      "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
             4      "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
             5      "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
             6      "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
             7      "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
             8      "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
             9      "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
            10      "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
            11  ]
        • 有一个fake-useragent,自行百度下用法;

          • 见仁见智

      • 进行代理设置:resquest.meta["proxy"] = proxy

        • 代理池中随机选择代理ip

        • 代理ip的webapi发送请求获取一个代理ip

        • 添加代理:基本认证与摘要认证

        • source:一文读懂HTTP Basic身份认证https://juejin.im/entry/5ac175baf265da239e4e3999

        • 1 #settings设置 PROXY_LIST = {"ip_port":"ip:port","user_pass":"user:pass"}
        •  1 #付费代理
           2 import base64
           3 
           4 # 代理隧道验证信息  这个是在那个网站上申请的
           5 proxyServer = 'http://proxy.abuyun.com:9010' # 收费的代理ip服务器地址,这里是abuyun
           6 proxyUser = 用户名
           7 proxyPass = 密码
           8 #Basic后面有一个空格
           9 proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass)
          10 
          11 class ProxyMiddleware(object):
          12     def process_request(self, request, spider):
          13         # 设置代理
          14         request.meta["proxy"] = proxyServer
          15         # 设置认证
          16         request.headers["Proxy-Authorization"] = proxyAuth
        • 返回三种方式:

          • 返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法

          • 返回Response对象:不再请求,把response返回给引擎j交给spider解析

          • 返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法。

      • scrapy与selenium中间件的使用:

        • 可以选择性的使用selenium,因为selenium比较慢,我们尽可能的避免使用它,在下载器中间件里面的process_request函数,可以理解所有的请求都要经过该函数,所以我们在修改请求时,可以判断所过的url里面有没有我们需要用selenium的url,一般可以使用url里面的关键字进行判断;

        •  1 #判断关键字是否在url中
           2 if "xxxxx" in resquest.url:
           3     driver = webdriver.Chorme()
           4     driver.get(url)
           5     time.sleep(3)
           6     
           7     data = driver.page_source
           8     
           9     driver.close()
          10     #创建相应对象
          11     res = HtmlResponse(url=url,body=data,encoding='utf-8',request=request)
        •  1 from scrapy.http import HtmlResponse
           2 import time
           3 
           4 
           5 #设置selenium的中间件
           6 class SelemiumSpiderMiddleware(object):
           7 
           8     def process_request(self, request, spider):
           9         spider.driver.get(request.url)
          10         time.sleep(1)
          11         #获得渲染后的网页源代码
          12         page_text = spider.driver.page_source
          13         spider.driver.close()
          14         #创建响应对象
          15         return HtmlResponse(url=request.url, body=page_text, request=request, encoding='utf-8')
    1. process_response--拦截所有的响应

      • 在downloader执行Request下载后,会得到对应的response,在发送到spider之前,可以使用process_response来对数据进行处理。

         

    1. process_exception--拦截发生异常的请求对象

      • 当downloader或者process_request()方法抛出异常是,该方法会被调用;

    spider中间件:(NameSpiderMiddleware):

    • spider Middleware是接入到scrapy的Spider处理机制的钩子框架;

    • 在Downloader生成Response之后,Response会被发送到Spider之前,首先经过SpiderMiddleware处理,当spider处理生成item和Request之后,还会经过SpiderMiddleware处理。

    • 暂时没有用到,只大体学习记录;

     

    --四大核心方法(百度自行了解):

      -process_spider_input
      -process_spider_output
      -process_spider_exception
      -process_start_requests

     

     

    CrawlSpider:

     - 基于scrapy进行全站数据爬取的一种新的手段
    • CrawlSpider就是spider的一个子类

      • 链接提取器(LinkExtractor):

        • 规则解析器(Rule):

    • 使用流程

      • 新建一个工程

      • cd 工程中

      • 新建一个爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com

    • 样例:

       1  #demo
       2  # -*- coding: utf-8 -*-
       3  import scrapy
       4  from scrapy.linkextractors import LinkExtractor
       5  from scrapy.spiders import CrawlSpider, Rule
       6  7  8  class CrawlproSpider(CrawlSpider):
       9      name = 'Crawlpro'
      10      allowed_domains = ['www.xxx.com']
      11      start_urls = ['http://www.xxx.com/']
      12 13      rules = (
      14          Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
      15      )
      16 17      def parse_item(self, response):
      18          item = {}
      19          #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
      20          #item['name'] = response.xpath('//div[@id="name"]').get()
      21          #item['description'] = response.xpath('//div[@id="description"]').get()
      22          return item
      23

       

    分布式:

    -- 目的:

      - 让多台爬虫机器同时运行爬虫任务并协同爬取,协同爬取的前提是共享爬取队列;

      - 统一爬取队列,多个调度器、多个下载器----结果是爬取效率翻倍。

     

    -- 维护爬虫队列:

    - 性能考虑:基于内存存储的redis

    1. 列表有lpush、lpop、rpush、rpop方法,实现先进先出爬取队列,也可以实现先进后出栈式爬取队列

    2. 集合元素无需并且不重复

    3. 可实现带优先级的调度队列

    -- URL地址去重:

    • 使用md5生成的数据指纹来筛选数据,将转换的md5值与之前的传入redis中的数据进行比对;

    • 使用哈希算法生成数据指纹筛选数据,将转换的md5值与之前的传入redis中的数据进行比对;

    • 布隆过滤器

    -- 文本内容去重:

    1. 编辑距离算法

    2. simhash算法

    -- 防止中断:

    1. 为什么要防止中断:

      1. 在scrapy中,爬虫运行时的Request队列放在内存中,在爬虫运行中中断后,这个空间就被释放,此队列就被销毁,所以一旦爬虫被中断,爬虫再次运行就相当于全新的爬取过程。

    2. 解决方法:

      1. 将队列中的Request保存起来,再次爬取直接读取保存的数据即可

      2. 实现命令:

         scrapy crawl spider -s JOB_DIP=crawls/spider(路径使用JOB_DIP标识)

         

    增量式:

    • 概念:用于检测网站数据更新的情况。

    • 应用的网站类型:

      • 增量式深度爬取

      • 增量式非深度爬取

    • 核心机制:去重;redis-set去重方式(可做持久化存储),python中的set是基于缓存中的

    • 增量式流程:

      •  与基本的爬虫Scrapy流程相似,只是再spider中加了数据指纹的认证
         再pipelines中增加了redis的存储过程
      • rules配置规则

        • 数据库的连接

        1. item数据类型创建完成

        2. pipelines

          • 数据库的调用:spider.方法

          • redis的存储

         

    • 框架中的item传输问题(parse传到其他函数中):

      • 传输:

         
        1 yield scrapy.Resquest(url,callback=self.parse_detail,meta={'item':item})
      • 下个函数接收:

        1  item = response.meta['item']    
        2  item[""] = ...
        3  yield item

       

     

    scrapy中的post请求:

    首先创建好scrapy文件,注释掉allowed_domains以及start_urls;重写初始化请求(start_requests),最后yield返回给解析函数。

     1  class xxx(scrapy.Spider):
     2      name = 'xxxx'
     3      #allowed_domains = ['']
     4      #start_url = ['']
     5      
     6      def start_requests(self):
     7          headers = {}
     8          base_url = ''
     9          formdata = {}
    10          yield scrapy.FormRequest(url=base_url,headers=headers,formdata=formdata,callback=self.parse)
    11          #如果使用FormRequest报错,备选方案
    12  scrapy.Request(url=base_url,headers=headers,body=json.dumps(formdata),method= 'POST',callback=self.parse)
    13                 
    14                 
    15       def parse(self,response):
    16                 pass

    扩展:

    反爬机制整理:

    • robots

    • UA伪装

    • 验证码

    • 代理

    • cookie

    • 动态变化的请求参数

    • JS加密

    • JS混淆

    • 图片懒加载

    • 动态数据的获取

    • selenium:规避检测

     

    图片懒加载:

    • 网站优化手段

    • 应用到标签的伪属性,数据捕获的时候一定基于伪属性进行

    •     是一种反爬机制,图片懒加载是一种网页优化技术。图片作为一种网络资源,在被请求时也与普通静态资源一样,将占用网络资源,而一次性将整个页面的所有图片加载完,将大大增加页面的首屏加载时间。为了解决这种问题,通过前后端配合,使图片仅在浏览器当前视窗内出现时才加载该图片,达到减少首屏图片请求数的技术就被称为“图片懒加载”。
          在网页源码中,在img标签中首先会使用一个“伪属性”(通常使用src2,original…)去存放真正的图片链接而并非是直接存放在src属性中。当图片出现到页面的可视化区域中,会动态将伪属性替换成src属性,完成图片的加载。
    • imagePileline:专门用于二进制数据下载和持久化存储的管道类(图片下载)

     

    scrapy中技巧:

    1. 当调用item中的类时,没有显示且标红

    2. 解决方法:找到项目根文件--右击--找到Mark Directory as----Source-root后点击,生成源文件

    3.  1 #翻页设置   
       2 url = response.xpath('//span[@class="next"]/a/@href').extract_first()
       3     if url !=None:
       4             ''''
       5             在提取数据后,parse()方法查找到下一页的链接,使用urljoin()方法构建完整的绝对URL(因为链接可以是相对的),
       6             并产生一个新的请求到下一个页面,将自己作为回调函数来处理下一页的数据提取,并保持遍历所有页面的抓取。
       7             '''
       8             url = response.urljoin(url)
       9             yield scrapy.Request(
      10                 url=url,callback=self.parse
      11             )

    selenium-绕过网站监测:

    sourcehttps://www.cnblogs.com/presleyren/p/12936553.html

    使用 Google 的Chrome Devtools-Protocol(Chrome 开发工具协议)简称CDP。

    通过这个命令,我们可以给定一段 JavaScript 代码,让 Chrome 刚刚打开每一个页面,还没有运行网站自带的 JavaScript代码时,就先执行我们给定的这段代码。

    那么如何在 Selenium中调用 CDP的命令呢?实际上非常简单,我们使用driver.execute_cdp_cmd。根据 Selenium 的官方文档[2],传入需要调用的 CDP 命令和参数即可;

    只需要执行一次,之后只要你不关闭这个driver开启的窗口,无论你打开多少个网址,他都会自动提前在网站自带的所有 js 之前执行这个语句,隐藏window.navigator.webdriver

    完美隐藏window.navigator.webdriver。并且,关键语句:

    1  driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    2    "source": """
    3      Object.defineProperty(navigator, 'webdriver', {
    4        get: () => undefined
    5      })
    6    """
    7  })

    虽然使用以上代码就可以达到目的了,不过为了实现更好的隐藏效果,大家也可以继续加入两个实验选项:

     1  from selenium import webdriver
     2  options = webdriver.ChromeOptions()
     3  options.add_experimental_option("excludeSwitches", ["enable-automation"])
     4  options.add_experimental_option('useAutomationExtension', False)
     5  driver = webdriver.Chrome(options=options, executable_path='./chromedriver')
     6  driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
     7    "source": """
     8      Object.defineProperty(navigator, 'webdriver', {
     9        get: () => undefined
    10      })
    11    """
    12  })
    13  driver.get('http://exercise.kingname.info')

    这是close()的说明:

    Closes the current window. 关闭当前窗口。

    这是quit()的说明:

    Quits the driver and closes every associated window. 退出驱动并关闭所有关联的窗口。

     

    • gb2312与gb2312 urlencode区别

    1  import urllib
    2  country = u'中国'
    3  country.encode('gb2312')
    4  #-------'xd6xd0xb9xfa'
    5  urllib.quote(country.encode('gb2312'))
    6  #--------'%D6%D0%B9%FA'

     

  • 相关阅读:
    解释器模式
    java-->Hashtable简单使用
    HashTable和HashMap区别
    享元模式
    Beanutils.copyProperties( )用法
    删除List集合中的元素方法
    Date中before和after方法的使用
    Spring定时任务@Scheduled注解使用方式
    Oracle中INSTR、SUBSTR和NVL的用法
    StringBuffer的delete方法与deleteCharAt方法的区别。
  • 原文地址:https://www.cnblogs.com/xbhog/p/13335979.html
Copyright © 2020-2023  润新知