框架的雏形实现

目标

  1. 完成request模块的基础封装
  2. 完成respons模块的基础封装
  3. 完成item模块的基础封装
  4. 完成spider模块的封装
  5. 完成调度器模块的封装
  6. 完成下载器模块的封装
  7. 完成管道模块的封装
  8. 完成引擎模块的封装
  9. 了解set.py的编写
  10. 掌握制作模块的方法
  11. 熟悉框架的执行过程
  12. 掌握框架的启动方法
  13. 理解中间件的用途
  14. 熟悉中间件在框架逻辑中的位置
  15. 能够编写中间件模块

1 实现http模块和item模块

1.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    # 请求体

1.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    # 响应体

1.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 能够让这个属性的值是只读的,即不能够对其进行重新赋值,达到一定的保护的目的

2 实现核心模块

2.1 spider模块的封装

2.1.1 爬虫组件功能:

  • 构建请求信息(初始的),也就是生成请求对象(Request)
  • 解析响应对象,返回数据对象(Item)或者新的请求对象(Request)

2.1.2 实现方案:

  • 实现start_requests方法,返回请求对象
  • 实现parse方法,返回Item对象或者新的请求对象
# 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对象

2.2 调度器模块的封装

2.2.1 调度器功能:

  • 缓存请求对象(Request),并为下载器提供请求对象,实现请求的调度:
  • 对请求对象进行去重判断:实现去重方法_filter_request,该方法对内提供,因此设置为私有方法

2.2.2 实现方案:

  • 利用队列FIFO存储请求;
  • 实现add_request方法添加请求,接收请求对象作为参数;
  • 实现get_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

2.3 下载器模块的封装

2.3.1 下载器功能:

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

2.3.2 实现方案:

  • 利用requests、urllib2等模块发请求,这里使用requests模块
  • 实现get_response方法,接收request请求对象作为参数,发起请求,获取响应
# 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)

2.4 管道模块的封装

2.4.1 管道组件功能:

  • 负责处理数据对象

2.4.2 实现方案:

  • 实现process_item方法,接收数据对象作为参数
# scrapy_plus/core/pipeline.py
'''管道组件封装'''


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

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

2.5 引擎模块的封装

2.5.1 引擎组件功能:

  • 对外提供整个的程序的入口
  • 依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)

2.5.2 实现方案:

  • 利用init方法初始化其他组件对象,在内部使用
  • 实现start方法,由外部调用,启动引擎
  • 实现_start_engine方法,完成整个框架的运行逻辑
  • 具体参考上一小节中雏形结构引擎的逻辑
# 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)

3 框架安装

3.1 安装框架的目的

利用setup.py将框架安装到python环境中,在编写爬虫时候,作为第三方模块来调用,

3.2 框架安装第一步:完成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("#")]

3.3 框架安装第二步:完成requirements.txt的编写

功能:

  • 写明依赖环境所支持的模块及其版本

使用:

  • 在setup.py中使用
  • 放置在setup.py同级目录下
requests>=2.18.4
six>=1.11.0

3.4 框架安装第三步:完成VERSION.txt的编写

功能:

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

使用:

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

3.5 框架安装第四步:执行安装命令

步骤:

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

4 框架运行 -- main.py

编写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>

5 实现中间件模块

5.1 为什么需要中间件

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

5.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

5.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

5.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.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. 把初始请求添加给调度器
        # 利用爬虫中间件预处理请求对象
        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)

5.6 观察结果

运行main.py文件,查看结果

运行结果:

这是爬虫中间件:process_request方法
这是下载器中间件:process_request方法
这是下载器中间件:process_request方法
这是爬虫中间件:process_response方法
item对象: <scrapy_plus.item.Item object at 0x10759eef0>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值