zoukankan      html  css  js  c++  java
  • Python11 Scrapy框架基础

    Python11 ---- Scrapy框架基础

    Scrapy框架基础

    路径管理

    路径

    • 绝对路径
      总是从根目录开始

      H:\PyCharmProjects\tutorials_2\jd_crawler\main.py
    • 相对路径

      jd_crawler\main.py
      • ...

        .代表当前目录, ..代表父目录

    • 工作目录

      当前执行命令所在的目录

      # 将工作目录添加进当前的路径列表
      sys.path.append(os.getcwd())

    路径列表

    • 查看当前路径列表
      只有在路径列表当中的包和模块才可以导入和调用

      import sys
      print(sys.path)
    • 路径搜索顺序

      • 当前脚本路径, 也就是执行文件的目录
      • PYTHONPATH路径
      • 虚拟环境路径
      • site-packages
        • 安装的第三方库所在路径
    • 可以向路径列表添加路径

      sys.path.append(r"H:\PyCharmProjects\tutorials_2")

    常见报错

    • ModuleNotFoundError: No module named 'xxxx'

      • 为什么在pycharm中不报错, 在命令行当中报错

        Pycharm会自动将当前项目的根目录添加到路径列表当中
    • ModuleNotFoundError: No module named 'parser.search'; 'parser' is not a pac kage

      • 自定义包和内置包名有冲突

        修改包名即可

      • 导入的不是一个包

    • ModuleNotFoundError: No module named '__main__.jd_parser'; '__main__' is no t a package

      • 入口程序不可以使用相对路径

      • __main__

        主程序模块名会被修改为__main__

    • ValueError: attempted relative import beyond top-level package

      当前访问路径已经超过了python已知的最大路径

      from tutorial_2.jd_crawler.jd_parser.search import parse_jd_item
      
      top-level package 指的是上述from导入命令中的首路径tutorial_2, 而不是根据目录结构
      • 把工作目录加入到路径列表当中
      • 进入到项目根目录下执行命令
      • 上述两个操作相当于将项目根目录加入到路径列表当中

    注意事项

    • 确定入口程序, 没有一个锚定的路径就没有办法做相对路径的管理
    • 将项目根目录加入到入口程序当中
    • 进入到项目根目录下执行命令
    • 项目目录结构不要嵌套的太深
    • 脚本文件或者临时运行单个模块中的方法, 可以将根目录临时添加到路径列表当中

    课后作业

    • 用命令行启动jd_crawler
    • /test目录中增加parser_test.py模块做解析测试.
    简单来说就是获取当前的工作目录,在执行程序
    import sys
    # print(sys.path)
    import os
    # 添加工作路径至环境变量中
    sys.path.append(os.getcwd())
    # print(sys.path)
    from jd_parser.search import parse_jd_item
    
    with open(r"test/search.html", "r", encoding="utf-8") as f:
        html = f.read()
        result = parse_jd_item(html)
        print(result)
    	
    D:\python> python .\jd_crawler\main.py

    Scrapy爬虫框架介绍

    • 文档

    • 什么是scrapy
      基于twisted搭建的异步爬虫框架.
      scrapy爬虫框架根据组件化设计理念和丰富的中间件, 使其成为了一个兼具高性能和高扩展的框架

    • scrapy提供的主要功能

      • 具有优先级功能的调度器
      • 去重功能
      • 失败后的重试机制
      • 并发限制
      • ip使用次数限制
      • ....
    • scrapy的使用场景

      • 不适合scrapy项目的场景
        • 业务非常简单, 对性能要求也没有那么高, 那么我们写多进程, 多线程, 异步脚本即可.
        • 业务非常复杂, 请求之间有顺序和失效时间的限制.
        • 如果你不遵守框架的主要设计理念, 那就不要使用框架
      • 适合使用scrapy项目
        • 数据量大, 对性能有一定要求, 又需要用到去重功能优先级功能的调度器
    • scrapy组件

      • ENGINESPIDERS中获取初始请求任务Requests
      • ENGINE得到Requests之后发送给SCHEDULER, SCHEDULER对请求进行调度后产出任务.
      • Scheduler返回下一个请求任务给ENGINE
      • ENGINE将请求任务交给DOWNLOADER去完成下载任务, 途径下载器中间件.
      • 一旦下载器完成请求任务, 将产生一个Response对象给ENGINE, 途径下载器中间件
      • ENGINE收到Response对象后, 将该对象发送给SPIDERS去解析和处理, 途径爬虫中间件
      • SPIDER解析返回结果
        • 将解析结果ITEMS发送给ENGINE
        • 生成一个新的REQUESTS任务发送给ENGINE
      • 如果ENGINE拿到的是ITEMS, 那么就会发送给ITEM PIPELINES做数据处理, 如果是REQUESTS则发送给SCHEDULER
      • 周而复始, 直到没有任务产出

    Scrapy教程

    • 安装

      pip install scrapy
    • 创建项目 建议创建一个独立的项目

      scrapy startproject jd_crawler_scrapy
    • 目录结构

      • spiders(目录) 建议:初学者前期可分开,后期有需求在合并
        存放SPIDERS项目文件, 一个scrapy项目下可以有多个爬虫实例

      • items
        解析后的结构化结果.一种约束,必要值

      • middlewares
        下载器中间件和爬虫中间件的地方

      • piplines
        处理items的组件, 一般都在pipelines中完成items插入数据表的操作

      • settings
        统一化的全局爬虫配置文件

      • scrapy.cfg
        项目配置文件

    • scrapy爬虫demo

      import scrapy
      
      
      class JdSearch(scrapy.Spider):
          name = "jd_search"
      
          def start_requests(self):
              for keyword in ["鼠标", "键盘", "显卡", "耳机"]:
                  for page_num in range(1, 11):
                      url = f"https://search.jd.com/Search?keyword={keyword}&page={page_num}"
      
                      # 选用FormRequest是因为它既可以发送GET请求, 又可以发送POST请求
                      yield scrapy.FormRequest(
                          url=url,
                          method='GET',
                          # formdata=data,             # 如果是post请求, 携带数据使用formdata参数
                          callback=self.parse_search   # 指定回调函数处理response对象
                      )
      
      
          def parse_search(self, response):
              print(response)
    • 启动爬虫
      需要到Scrapy根目录下去执行

    D:\python\jd_crawler_scrapy> scrapy crawl jd_search

    
    ## 课后作业
    - 背诵`scrapy`组件流程(必考)
    - 完成scrapy项目的demo
    
    ## Scrapy的启动和debug
    
    - 命令行
    

    scrapy crawl jd_search

    
    - 启动脚本
    

    新建run.py

    from scrapy import cmdline

    command = "scrapy crawl jd_search".split()
    cmdline.execute(command)

    
    ## Scrapy Item
    
    只是对解析的结构化结果进行一个约束, 在到达pipeline前就可以检查出数据错误.
    
    ## Scrapy的设置
    
    - ***ROBOTTEXT_OBEY**
    

    ROBOTTEXT_OBEY=False

    
    获取对方网站是否允许爬虫获取数据的信息.
    
    - **设置中间件**
    
    数字越小, 离`ENGINE`越近
    

    DOWNLOADER_MIDDLEWARES = {
    # 'jd_crawler_scrapy.middlewares.JdCrawlerScrapyDownloaderMiddleware': 543,
    'jd_crawler_scrapy.middlewares.UAMiddleware': 100,
    }

    
    - **设置PIPELINE**
    

    ITEM_PIPELINES = {
    'jd_crawler_scrapy.pipelines.JdCrawlerScrapyPipeline': 300,
    }

    
    - **请求限制**
    
    - ***CONCURRENT_REQUESTS**
      请求并发数, 通过控制请求并发数达到避免或者延缓IP被封禁
      假设值为32,1秒浏览32个页面,这一般是不可能的
    
      ```
      CONCURRENT_REQUESTS = 1
      ```
      
      - CONCURRENT_REQUESTS_PER_DOMAIN
      控制每个`域名`请求的并发数
      若队列是混合队列可使用此值,可以控制每一个域名的并发数,一般不会这样做,场景较少
    
    - CONCURRENT_REQUESTS_IP
      控制每个`IP`请求的次数. 通过这样的方式可以过掉一些对IP封禁严格的网站
      假设有一个IP地址池,可以对IP进行并发次数的控制,简单来说就是IP请求次数上限控制
    
    - CONCURRENT_ITEMS
      默认为100, 控制处理`item`的并发数. 如果我存入的数据库性能比较差, 通过这样的方式解决防止数据库崩溃的情况(控制存入数据库并发数)
    
    - ***DOWNLOAD_DELAY**
      默认为0, 控制请求的频率. **在调度完一个请求后, 休息若干秒**.   timesleep延迟
    
      > Scrapy会自动帮我们进行随机休息    (DOWNLOAD_DELAY - 0.5, DOWNLOAD_DELAY + 0.5)
    
      ```
      DOWNLOAD_DELAY = 2
      ```
    
    - ***DOWNLOAD_TIMEOUT**
      **控制每个请求的超时时间**. 通过这样的方式解决IP代理池质量差的问题.
    
      ```
      # 根据自己的IP代理池质量自定决定
      DOWNLOAD_TIMEOUT = 6   
      ```
    
    - ***REDIRECT_ENABLE**
      默认为`True`, **建议修改为`False`**, 因为大部分情况下, 重定向都是识别出你当前身份有问题, 重定向到`log in`页面
        
    - 重试机制
    
    - ***RETRY_ENABLE**
      ```
        RETRY_ENABLE = False
      ```
    
      默认为`True`, 建议改成`False`, 然后自己重写重试中间件
    
    - RETRY_TIMES
      控制重新次数,  RETRY_TIMES其实是当前项目的兜底配置
    
      > **如果当前请求失败后永远会重试**, 正好你请求的接口是收费的, 万一有一天报错, 那么产生的费用是巨大的.
    
      ```
      RETRY_TIMES = 3
      ```
    
    - RETRY_HTTP_CODES
     408 请求超时
     429 太多请求
     500 无处处理该请求
     502 后端服务器问题
     503 服务器过载,拒绝客户端连接或在队列中
     504 后端服务器问题
      ```
      RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
      ```
    
    - 过滤器
    
    - **设置中指定过滤器**
    
      ```
      DUPEFILTER_CLASS = "jd_crawler_scrapy.middlewares.MyRFPDupeFilter"
      ```
    
    - **Spider中打开过滤器**
    
      ```
                    yield scrapy.FormRequest(
                          dont_filter=False,
                          url=url,
                          method='GET',
                          # formdata=data,           
                          callback=self.parse_search  
                      )
      ```
    
    - 过滤器
    
      ```
      from scrapy.dupefilters import RFPDupeFilter
      from w3lib.url import canonicalize_url
      from scrapy.utils.python import to_bytes
      import hashlib
      import weakref
      
      class MyRFPDupeFilter(RFPDupeFilter):
          """
          过滤器是在到达下载器之前就生成了过滤指纹, 如果我们的下载器中间件报错了, 那么过滤指纹仍然生效, 但是没有实际请求.
          所以我们可以通过一些特殊参数来进行自定义过滤规则
          """
          def request_fingerprint(self, request, include_headers=None, keep_fragments=False):
              cache = _fingerprint_cache.setdefault(request, {})
              cache_key = (include_headers, keep_fragments)
              if cache_key not in cache:
                  fp = hashlib.sha1()
                  fp.update(to_bytes(request.method))
                  fp.update(to_bytes(canonicalize_url(request.url, keep_fragments=keep_fragments)))
                  fp.update(request.body or b'')
                  fp.update(request.meta.get("batch_no", "").encode("utf-8"))
                  cache[cache_key] = fp.hexdigest()
              return cache[cache_key]
      ```
    
    
    
    - LOG
    
    - LOG_ENABLE
    
      默认为`True`, 是否使用log
    
    - LOG_FILE 
    
      设置保存的log文件目录
    
    - LOG_LEVEL(按严重程序排序)
    
      - CRITICAL
      - ERROR
      - WARNING
      - INFO
      - DEBUG
    
    ## Scrapy的中间件
    
    - 请求头中间件
    

    class UAMiddleware:
    def process_request(self, request, spider):
    request.headers["user-agent"] = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36"

    
    - 重试中间件
    

    from scrapy.downloadermiddlewares.retry import RetryMiddleware
    from scrapy.utils.response import response_status_message

    class MyRetryMiddleware(RetryMiddleware):
    """
    解决对方服务器返回正常状态码200, 但是根据IP需要进行验证码验证的情况.
    我们可以通过换IP可以解决验证码, 那么就应该重试.
    """
    def process_response(self, request, response, spider):
    if request.meta.get('dont_retry', False):
    return response
    if "验证码" in response.text:
    reason = response_status_message(response.status)
    return self._retry(request, reason, spider) or response
    return response

    
    ## 课后作业
    
    - 将jd_crawler_scrapy完善.
    - 完成代理中间件的编写(查阅文档).
    - 理解并重写重试中间件
    - 理解并重写过滤器
  • 相关阅读:
    工单系统的设计与实现(4)
    java_tcp_简单示例
    java_udp编程
    mysql 锁问题 (相同索引键值或同一行或间隙锁的冲突)
    行锁与表锁详解
    BTree和B+Tree详解
    深入浅出java常量池
    MySQL三大范式和反范式
    java多线程 栅栏CyclicBarrier
    SpringBoot初始教程之Servlet、Filter、Listener配置
  • 原文地址:https://www.cnblogs.com/final233/p/15751888.html
Copyright © 2011-2022 走看看