初步实现爬虫框架

1.爬虫的流程(掌握)

爬虫框架解决的问题是爬虫问题,先来看看爬虫的基本流程:

  1. 构建请求信息(url、method、headers、params、data)
  2. 发起HTTP/HTTPS请求,获取HTTP/HTTPS响应
  3. 解析响应,分析响应数据的数据结构或者页面结构
    • 提取数据
    • 提取请求的地址
  4. 对数据进行存储/对新的请求地址重复前面的步骤

流程图:
在这里插入图片描述

2.分析scrapy爬虫流程(掌握)

精准流程图:
在这里插入图片描述

  1. 三个内置对象:

     请求对象(Request)
     响应对象(Response)
     数据对象(Item)
    
  2. 五个核心组件:

     爬虫组件
         构建请求信息(初始的),也就是生成请求对象(Request)
         解析响应对象,返回数据对象(Item)或者新的请求对象(Request)
     调度器组件
         缓存请求对象(Request),并为下载器提供请求对象,实现请求的调度
         对请求对象进行去重判断
     下载器组件
         根据请求对象(Request),发起HTTP、HTTPS网络请求,拿到HTTP、HTTPS响应,构建响应对象(Response)并返回
     管道组件
         负责处理数据对象(Item)
     引擎组件
         负责驱动各大组件,通过调用各自对外提供的API接口,实现它们之间的交互和协作
         提供整个框架的启动入口
    
  3. 两个中间件:

     爬虫中间件
         对请求对象和数据对象进行预处理
    
     下载器中间件
         对请求对象和响应对象进行预处理
    

3.设计结构目录

模块之间的关系图:
在这里插入图片描述

  1. 首先给框架起一个名称,如:

     scrapy_plus
    
  2. 继续分类以及解耦的设计思想:

    • 把核心模块放置在一起

    • 请求对象模块和响应对象模块统一作为http模块

    • 数据对象单独作为一个分类

      代码结构如下:

      -- scrapy_plus
        -- __init__.py
        -- core
          -- __init__.py
          -- spider.py
          -- scheduler.py
          -- downloader.py
          -- pipeline.py
          -- engine.py
        -- http
          -- __init__.py
          -- request.py
          -- response.py
        -- item.py
      

4.框架初步实现

1.request对象的封装

对HTTP基本的请求属性进行简单封装,实现一个Request对象

# scrapy/http/request.py
'''封装Request对象'''

class Request(object):
    '''框架内置请求对象,设置请求信息'''

    def __init__(self, url, method='GET',\
              headers=None, params=None, data=None):
        self.url = url    # 请求地址
        self.method = method    # 请求方法
        self.headers = headers    # 请求头
        self.params = params    # 请求参数
        self.data = data    # 请求体

2.response对象的封装

对HTTP基本的响应属性进行简单封装,实现一个Response对象

# scrapy/http/response.py
'''封装Response对象'''

class Response(object):
    '''框架内置Response对象'''
    def __init__(self, url, status_code, headers, body):
        self.url = url    # 响应url
        self.status_code = status_code    # 响应状态码
        self.headers = headers    # 响应头
        self.body = body    # 响应体

3.item对象的封装

对数据进行简单封装,实现Item对象:

# scrapy/item.py
'''item对象'''

class Item(object):
    '''框架内置Item对象'''
    def __init__(self, data):
        # data表示传入的数据
        self._data = data    # 设置为简单的私有属性

    @property
    def data(self):
      '''对外提供data进行访问,一定程度达到保护的作用'''
      return self._data

其中property的理解:

  • property 能够让调用一个方法和调用一个属性一样容易,即不用打括号
  • property 能够让这个属性的值是只读的,即不能够对其进行重新赋值,达到一定的保护的目的

4.spider.py

# scrapy_plus/core/spider.py
'''爬虫组件封装'''
from scrapy_plus.item import Item    # 导入Item对象
from scrapy_plus.http.request import Request    # 导入Request对象

class Spider(object):
    '''
    1. 构建请求信息(初始的),也就是生成请求对象(Request)
    2. 解析响应对象,返回数据对象(Item)或者新的请求对象(Request)
    '''
    start_url = 'http://www.baidu.com'    # 默认初始请求地址   这里以请求百度首页为例
    def start_requests(self):
        '''构建初始请求对象并返回'''
        return Request(self.start_url)

    def parse(self, response):
        '''解析请求
        并返回新的请求对象、或者数据对象
        '''
        return Item(response.body)   # 返回item对象

5.调度器

  • 缓存请求对象(Request),并为下载器提供请求对象,实现请求的调度:
  • 对请求对象进行去重判断:实现去重方法_filter_request,该方法对内提供,因此设置为私有方法
# scrapy_plus/core/scheduler.py
'''调度器模块封住'''
# 利用six模块实现py2和py3兼容
from six.moves.queue import Queue

class Scheduler(object):
    '''
    1. 缓存请求对象(Request),并为下载器提供请求对象,实现请求的调度
    2. 对请求对象进行去重判断
    '''
    def __init__(self):
        self.queue = Queue()

    def add_request(self, request):
        '''添加请求对象'''
        self.queue.put(request)

    def get_request(self):
        '''获取一个请求对象并返回'''
        request = self.queue.get()
        return request

    def _filter_request(self):
        '''请求去重'''
        # 暂时不实现
        pass

6.下载器

  • 根据请求对象(Request),发起HTTP、HTTPS网络请求,拿到HTTP、HTTPS响应,构建响应对象(Response)并返回
# scrapy_plus/core/downloader.py
'''下载器组件'''
import requests
from scrapy_plus.http.response import Response

class Downloader(object):
'''根据请求对象(Request),发起HTTP、HTTPS网络请求,拿到HTTP、HTTPS响应,构建响应对象(Response)并返回'''

    def get_response(self, request):
        '''发起请求获取响应的方法'''
        # 1. 根据请求对象,发起请求,获取响应
        #    判断请求方法:
        if request.method.upper() == 'GET':
            resp = requests.get(request.url, headers=request.headers,\
                          params=request.params)
        elif request.method.upper() == 'POST':
            resp = requests.post(request.url,headers=request.headers,\
                      params=request.params,data=request.data)
        else:
            # 如果方法不是get或者post,抛出一个异常
            raise Exception("不支持的请求方法")
        # 2. 构建响应对象,并返回
        return Response(resp.url, resp.status_code, resp.headers, resp.content)

7.管道

  • 负责处理数据对象
# scrapy_plus/core/pipeline.py
'''管道组件封装'''

class Pipeline(object):
    '''负责处理数据对象(Item)'''

    def process_item(self, item):
        '''处理item对象'''
        print("item: ", item)

8.引擎

# scrapy_plus/core/engine.py
'''引擎组件'''
from scrapy_plus.http.request import Request    # 导入Request对象
from .scheduler import Scheduler
from .downloader import Downloader
from .pipeline import Pipeline
from .spider import Spider

class Engine(object):
    '''
    a. 对外提供整个的程序的入口
    b. 依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)
    '''
    def __init__(self):
        self.spider = Spider()    # 接收爬虫对象
        self.scheduler = Scheduler()    # 初始化调度器对象
        self.downloader = Downloader()    # 初始化下载器对象
        self.pipeline = Pipeline()    # 初始化管道对象

    def start(self):
        '''启动整个引擎'''
        self._start_engine()

    def _start_engine(self):
        '''依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)'''
        # 1. 爬虫模块发出初始请求
        start_request = self.spider.start_requests()
        # 2. 把初始请求添加给调度器
        self.scheduler.add_request(start_request)
        # 3. 从调度器获取请求对象,交给下载器发起请求,获取一个响应对象
        request = self.scheduler.get_request()
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        result = self.spider.parse(response)
        # 6. 判断结果对象
        # 6.1 如果是请求对象,那么就再交给调度器
        if isinstance(result, Request):
            self.scheduler.add_request(result)
        # 6.2 否则,就交给管道处理
        else:
            self.pipeline.process_item(result)

5.框架安装

1.setup.py的编写

  • 以下代码相当于一个模板,只用更改name字段,改为对应的需要安装的模块名称就可以,比如这里是:scrapy_plus
  • 将setup.py文件放到scrapy_plus的同级目录下
from os.path import dirname, join
# from pip.req import parse_requirements

from setuptools import (
    find_packages,
    setup,
)

def parse_requirements(filename):
    """ load requirements from a pip requirements file """
    lineiter = (line.strip() for line in open(filename))
    return [line for line in lineiter if line and not line.startswith("#")]

with open(join(dirname(__file__), './VERSION.txt'), 'rb') as f:
    version = f.read().decode('ascii').strip()

setup(
    name='scrapy-plus',  # 模块名称
    version=version,
    description='A mini spider framework, like Scrapy',  # 描述
    packages=find_packages(exclude=[]),
    author='itcast',
    author_email='your@email.com',
    license='Apache License v2',
    package_data={'': ['*.*']},
    url='#',
    install_requires=parse_requirements("requirements.txt"),  # 所需的运行环境
    zip_safe=False,
    classifiers=[
        'Programming Language :: Python',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: Unix',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
    ],
)

注意: 上面代码中可能会报错需要额外安装packaging模块,更新setuptools

  • pip install packaging
  • pip install --upgrade setuptools

pip.req可能不存在,对应的可以:

def parse_requirements(filename):
    """ load requirements from a pip requirements file """
    lineiter = (line.strip() for line in open(filename))
    return [line for line in lineiter if line and not line.startswith("#")]

2.requirements.txt的编写

  • 放置在setup.py同级目录下,用来写明依赖环境所支持的模块及其版本
requests>=2.18.4
six>=1.11.0

3.VERSION.txt的编写

使用及作用:

  • 标明当前版本,一个合格的模块,应当具备相应的版本号

  • 在setup.py中使用 , 放置在setup.py同级目录下

1.0

4. 执行安装命令

  • 切换到setup.py所在目录
  • 切换到对应需要python虚拟环境下
  • 在终端执行python setup.py install

6.框架运行

编写main.py

新在其他路径下创建一个项目文件夹 project_dir

# project_dir/main.py

from scrapy_plus.core.engine import Engine    # 导入引擎

if __name__ == '__main__':
    engine = Engine()    # 创建引擎对象
    engine.start()    # 启动引擎

运行结果:管道中打印的item对象

item对象:<scrapy_plus.item.Item object at 0x10759eef0>

7.中间件

中间件相当于一个钩子,能够在其中对request对象和response响应根据特定的需求进行一些特定的处理 例如:对于所有的request对象,我们需要在其中对他添加代理或者是随机的User-Agent都可以在中间件中完成

1.中间件实现的逻辑

逻辑图:
在这里插入图片描述

2.内置中间件的代码结构:

- scrapy_plus
  -- __init__.py
  -- core
    -- __init__.py
    -- spider.py
    -- scheduler.py
    -- downloader.py
    -- pipeline.py
    -- engine.py
  -- http
    -- __init__.py
    -- request.py
    -- response.py
  -- middlewares
    -- __init__.py
    -- spider_middlewares.py
    -- downloader_middlewares.py
  -- item.py

3.爬虫中间件spider_middlewares

# scrapy_plus/middlewares/spider_middlewares.py
class SpiderMiddleware(object):
    '''爬虫中间件基类'''

    def process_request(self, request):
        '''预处理请求对象'''
        print("这是爬虫中间件:process_request方法")
        return request

    def process_response(self, response):
        '''预处理数据对象'''
        print("这是爬虫中间件:process_response方法")
        return response

4.完成下载downloader_middlewares

# scrapy_plus/middlewares/downloader_middlewares.py
class DownloaderMiddleware(object):
    '''下载器中间件基类'''

    def process_request(self, request):
        '''预处理请求对象'''
        print("这是下载器中间件:process_request方法")
        return request

    def process_response(self, response):
        '''预处理响应对象'''
        print("这是下载器中间件:process_response方法")
        return response

5.修改engine.py

# scrapy_plus/core/engine.py
'''引擎
a. 对外提供整个的程序的入口
b. 依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)
'''
from scrapy_plus.http.request import Request    # 导入Request对象
from scrapy_plus.middlewares.spider_middlewares import SpiderMiddleware
from scrapy_plus.middlewares.downloader_middlewares import DownloaderMiddleware

from .spider import Spider
from .scheduler import Scheduler
from .downloader import Downloader
from .pipeline import Pipeline


class Engine(object):

    def __init__(self):

        ......

        self.spider_mid = SpiderMiddleware()    # 初始化爬虫中间件对象
        self.downloader_mid = DownloaderMiddleware()    # 初始化下载器中间件对象

    ......

    def _start_engine(self):
        '''依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)'''
        # 1. 爬虫模块发出初始请求
        start_request = self.spider.start_requests()
        # 2. 把初始请求添加给调度器
        # 利用爬虫中间件预处理请求对象 , 调用调度器的add_request方法,添加request的对象到调度器中
        start_request = self.spider_mid.process_request(start_request)
        self.scheduler.add_request(start_request)
        # 3. 从调度器获取请求对象,交给下载器发起请求,获取一个响应对象
        request = self.scheduler.get_request()
        # 利用下载器中间件预处理请求对象
        request = self.downloader_mid.process_request(request)
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 利用下载器中间件预处理响应对象
        response = self.downloader_mid.process_response(response)
        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        result = self.spider.parse(response)
        # 6. 判断结果对象
        # 6.1 如果是请求对象,那么就再交给调度器
        if isinstance(result, Request):
            # 利用爬虫中间件预处理请求对象
            result = self.spider_mid.process_request(result)
            self.scheduler.add_request(result)
        # 6.2 否则,就交给管道处理
        else:
            self.pipeline.process_item(result)

8.日志的使用

1.日志的参数说明

  • 日志的等级
import logging
# 日志的五个等级,等级依次递增
# 默认是WARNING等级
logging.DEBUG
logging.INFO
logging.WARNING
logging.ERROR
logging.CRITICAL
# 设置日志等级
logging.basicConfig(level=logging.INFO)
# 使用
logging.debug('DEBUG')
logging.info('INFO')
logging.warning('WARNING')
logging.error('ERROR')
logging.critical('CRITICAL')

format格式说明:

      %(name)s  Logger的名字
      %(levelno)s 数字形式的日志级别
      %(levelname)s 文本形式的日志级别
      %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
      %(filename)s 调用日志输出函数的模块的文件名
      %(module)s 调用日志输出函数的模块名
      %(funcName)s 调用日志输出函数的函数名
      %(lineno)d 调用日志输出函数的语句所在的代码行
      %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
      %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
      %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
      %(thread)d 线程ID。可能没有
      %(threadName)s 线程名。可能没有
      %(process)d 进程ID。可能没有
      %(message)s 用户输出的消息

datefmt参数说明:

%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

2.利用logger封装日志模块

在scrapy_plus目录下建立utils包 (utility:工具),专门放置工具类型模块,如日志模块log.py 下面的代码内容是固定的,在任何地方都可以使用下面的代码实习日志内容的输出

# scrapy_plus/utils/log.py
import sys
import logging

# 默认的配置
DEFAULT_LOG_LEVEL = logging.INFO    # 默认等级
DEFAULT_LOG_FMT = '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s'   # 默认日志格式
DEFUALT_LOG_DATEFMT = '%Y-%m-%d %H:%M:%S'  # 默认时间格式
DEFAULT_LOG_FILENAME = 'log.log'    # 默认日志文件名称


class Logger(object):

    def __init__(self):
        # 1. 获取一个logger对象
        self._logger = logging.getLogger()
        # 2. 设置format对象
        self.formatter = logging.Formatter(fmt=DEFAULT_LOG_FMT,datefmt=DEFUALT_LOG_DATEFMT)
        # 3. 设置日志输出
        # 3.1 设置文件日志模式
        self._logger.addHandler(self._get_file_handler(DEFAULT_LOG_FILENAME))
        # 3.2 设置终端日志模式
        self._logger.addHandler(self._get_console_handler())
        # 4. 设置日志等级
        self._logger.setLevel(DEFAULT_LOG_LEVEL)

    def _get_file_handler(self, filename):
        '''返回一个文件日志handler'''
        # 1. 获取一个文件日志handler
        filehandler = logging.FileHandler(filename=filename,encoding="utf-8")
        # 2. 设置日志格式
        filehandler.setFormatter(self.formatter)
        # 3. 返回
        return filehandler

    def _get_console_handler(self):
        '''返回一个输出到终端日志handler'''
        # 1. 获取一个输出到终端日志handler
        console_handler = logging.StreamHandler(sys.stdout)
        # 2. 设置日志格式
        console_handler.setFormatter(self.formatter)
        # 3. 返回handler
        return console_handler

    @property
    def logger(self):
        return self._logger

# 初始化并配一个logger对象,达到单例的
# 使用时,直接导入logger就可以使用
logger = Logger().logger

3.在框架中使用日志模块

使用参考

# scrapy_plus/core/engine.py
from datetime import datetime
from scrapy_plus.http.request import Request    # 导入Request对象
from scrapy_plus.middlewares.spider_middlewares import SpiderMiddleware
from scrapy_plus.middlewares.downloader_middlewares import DownloaderMiddleware
from scrapy_plus.utils.log import logger    # 导入logger
from .spider import Spider
from .scheduler import Scheduler
from .downloader import Downloader
from .pipeline import Pipeline

class Engine(object):
    ......
    def start(self):
        '''启动整个引擎'''
        start = datetime.now()  # 起始时间
        logger.info("开始运行时间:%s" % start)  # 使用日志记录起始运行时间
        self._start_engine()
        stop = datetime.now()  # 结束时间
        logger.info("开始运行时间:%s" % stop)  # 使用日志记录结束运行时间
        logger.info("耗时:%.2f" % (stop - start).total_seconds())  # 使用日志记录运行耗时
	......

9.配置文件的实现

1. 实现框架的默认配置文件

在scrapy_plus下建立conf包文件夹在它下面建立default_settings.py:设置默认配置的配置

import logging

# 默认的日志配置
DEFAULT_LOG_LEVEL = logging.INFO    # 默认等级
DEFAULT_LOG_FMT = '%(asctime)s %(filename)s[line:%(lineno)d] \
                  %(levelname)s: %(message)s'   # 默认日志格式
DEFUALT_LOG_DATEFMT = '%Y-%m-%d %H:%M:%S'  # 默认时间格式
DEFAULT_LOG_FILENAME = 'log.log'    # 默认日志文件名称

再在conf下创建settings.py文件

# scrapy_plus/conf/settings
from .default_settings import *    # 全部导入默认配置文件的属性

2.在框架中使用

利用框架配置文件改写log.py

# scrapy_plus/utils/log.py
import sys
import logging
from scrapy_plus.conf import settings    # 导入框架的settings文件

class Logger(object):
    def __init__(self):
        # 1. 获取一个logger对象
        self._logger = logging.getLogger()
        # 2. 设置format对象
        self.formatter = logging.Formatter(fmt=settings.DEFAULT_LOG_FMT,datefmt=settings.DEFUALT_LOG_DATEFMT)
        # 3. 设置日志输出
        # 3.1 设置文件日志模式
        self._logger.addHandler(self._get_file_handler(settings.DEFAULT_LOG_FILENAME))
        # 3.2 设置终端日志模式
        self._logger.addHandler(self._get_console_handler())
        # 4. 设置日志等级
        self._logger.setLevel(settings.DEFAULT_LOG_LEVEL)

3.创建项目配置文件,并实现修改框架默认配置文件属性

项目文件夹下创建项目配置文件settings.py:

# project_dir/settings.py
# 修改默认日志文件名称
DEFAULT_LOG_FILENAME = '日志.log'    # 默认日志文件名称

修改框架的settings.py文件,实现修改默认配置文件属性的目的

# scrapy_plus/conf/settings
from .default_settings import *    # 全部导入默认配置文件的属性
# 这里导入的settings,是项目文件夹的settings文件,如果与默认设置文件的内容重复会覆盖
from settings import *
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值