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文件中csv\json

  • 基于管道:>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身份认证

      • 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://blog.51cto.com/u_15023263/2558748

使用 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  #-------'\xd6\xd0\xb9\xfa'
5  urllib.quote(country.encode('gb2312'))
6  #--------'%D6%D0%B9%FA'