如何自己实现一个scrapy框架——框架完善(四)

这篇主要讲解如何实现多爬虫文件和多管道,多中间件
#一、duo多爬虫文件实现
##1、首先,将每一个站点的爬虫分离为单独的文件
我们项目中的站点一共两个,百度和豆瓣,分离为两个文件
baidu.py

# project_dir/spiders/baidu.py
from scrapy_plus.core.spider import Spider

# 继承框架的爬虫基类
class BaiduSpider(Spider):

    start_urls = ['http://www.baidu.com']    # 设置初始请求url
    
    # 不实现具体的爬虫解析,所以只有一个url,,先用这个代替生成
    def start_requests(self):
    yield Request(self.start_urls[0])

douban.py

# project_dir/spiders/douban.py
from scrapy_plus.core.spider import Spider
from scrapy_plus.http.request import Request
from scrapy_plus.item import Item


class DoubanSpider(Spider):

    start_urls = []  # 重写start_requests方法后,这个属性就没有设置的必要了

    def start_requests(self):
        # 重写start_requests方法,返回多个请求
        base_url = 'http://movie.douban.com/top250?start='
        for i in range(0, 250, 25):    # 逐个返回第1-10页的请求属相
            url = base_url + str(i)
            yield Request(url)
            ···

创建一个spiders文件夹,把爬虫文件都放到里面去

##2、同时执行多个不同的爬虫
将各个站点的爬虫以字典的形式传入

# project_dir/main.py
from scrapy_plus.core.engine import Engine    # 导入引擎

from spiders.baidu import BaiduSpider
from spiders.douban import DoubanSpider

if __name__ == '__main__':
    baidu_spider = BaiduSpider()    # 实例化爬虫对象
    douban_spider = DoubanSpider()    # 实例化爬虫对象
    spiders = {'baidu':baidu_spider, 'douban':douban_spider}
    engine = Engine(spiders)    # 传入爬虫对象
    engine.start()    # 启动引擎

在引擎中用到爬虫对象的地方都要做相应的修改

'''引擎
a. 对外提供整个的程序的入口
b. 依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)
'''
......
class Engine(object):

    # 此处修改
    def __init__(self, spiders):    # 接收外部传入的多个爬虫对象
        self.spiders = spiders    # 爬虫对象

        ......

    ......

    def _start_requests(self):
        '''向调度器添加初始请求'''
        # 1. 爬虫模块发出初始请求
        for spider_name, spider in self.spiders.items(): # 此处新增
            for start_request in spider.start_requests(): # 此处修改
                # 2. 把初始请求添加给调度器
                # 利用爬虫中间件预处理请求对象
                start_request = self.spider_mid.process_request(start_request)

                # 此处新增
                # 为请求对象绑定它所属的爬虫的名称
                start_request.spider_name = spider_name   

                self.scheduler.add_request(start_request)
                # 请求总数+1
                self.total_request_nums += 1

    def _execute_request_response_item(self):
        '''根据请求、发起请求获取响应、解析响应、处理响应结果'''

        ......
        # 此处新增
        # 根据request的spider_name属性,获取对应的爬虫对象
        spider = self.spiders[request.spider_name]

        # 此处修改
        # 获取响应的parse方法
        parse = getattr(spider, request.parse) # getattr(类, 类中方法名的字符串) = 类方法对象

        # 5. 指定的解析函数返回可迭代对象
        for result in parse(response):
            # 6.1 判断:如果是request对象
            if isinstance(result, Request):
                # request经过spider中间件
                result = self.spider_mid.process_request(result)

                # 此处新增
                # 给request对象增加一个spider_name属性
                result.spider_name = request.spider_name

                # 就通过add_request()给调度器的队列
                self.scheduler.add_request(result)
                # 请求数+1
                self.total_request_nums += 1
    ......

##3、再次改进,将每个爬虫的名称直接设置为爬虫类的一个属性
项目中爬虫代码参考

class BaiduSpider(Spider):
    name = 'baidu'    # 为爬虫命名
    start_urls = ['http://www.baidu.com']    # 设置初始请求url

豆瓣的也要
最后main.py改为

'''按照这样的方式设定key值'''
# spiders = {'baidu': baidu_spider, 'douban': douban_spider}
spiders = {BaiduSpider.name: baidu_spider, DoubanSpider.name: douban_spider}

4.1 相应的去修改scrapy_plus中的spider.py

scrapy_plus/core/spider.py
......

class Spider(object):
    '''
    1. 构建请求信息(初始的),也就是生成请求对象(Request)
    2. 解析响应对象,返回数据对象(Item)或者新的请求对象(Request)
    '''

    name = '' # 此处新增
    start_urls = []
......

安装代码,并运行main.py,直到调试成功

#二、多管道实现
##1 为什么需要多个管道
同爬虫文件一样,不同的爬虫可能需要不同的管道文件,因此管道文件需要在项目中进行实现
这里写图片描述
##2 项目文件夹中实现管道文件
在项目文件夹下建立pipelines.py文件,不同在于:
这里的process_item必须把item对象最后再返回回来,因为是多个管道文件的设置了
需要增加一个参数,也就是传入爬虫对象,以此来判断当前item是属于那个爬虫对象的

# project_dir/pipelines.py
from spiders.baidu import BaiduSpider
from spiders.douban import DoubanSpider


class BaiduPipeline(object):

    # 这里有所不同的是,需要增加一个参数,也就是传入爬虫对象
    # 以此来判断当前item是属于那个爬虫对象的
    def process_item(self, item, spider):
        '''处理item'''
        if isinstance(spider, BaiduSpider):
            print("百度爬虫的数据:", item)
        return item    # 最后必须返回item


class DoubanPipeline(object):

    # 这里有所不同的是,需要增加一个参数,也就是传入爬虫对象
    # 以此来判断当前item是属于那个爬虫对象的
    def process_item(self, item, spider):
        '''处理item'''
        if isinstance(spider, DoubanSpider):
            print("豆瓣爬虫的数据:", item)
        return item    # 最后必须返回item

##3 修改引擎的代码
管道对象将从外部传入
调用管道的process_item方法时,需要遍历出管道
并且需要传递第二个参数,爬虫对象:修改框架pipelines.py中的process_item()函数,增加一个spider参数

# scrapy_plus/core/engine.py
......

class Engine:
    '''完成对引擎模块的封装'''

    # 此处修改
    def __init__(self,spiders,pipelines=[]):
        '''
        实例化其他的组件,在引起中能够通过调用组件的方法实现功能
        '''
        ......
        self.pipelines = pipelines # 此处修改
        ......

    def _execute_request_response_item(self):
        '''根据请求、发起请求获取响应、解析响应、处理响应结果'''
        ......
            else:
                # 此处修改
                # 就通过process_item()传递数据给管道
                for pipeline in self.pipelines:
                    pipeline.process_item(result, spider)

        self.total_response_nums += 1
......

##4 修改main.py
为引擎传入项目中的管道对象:

# project_dir/main.py
from scrapy_plus.core.engine import Engine    # 导入引擎

from spiders.baidu import BaiduSpider
from spiders.douban import DoubanSpider
from pipelines import BaiduPipeline, DoubanPipeline

if __name__ == '__main__':
    baidu_spider = BaiduSpider()    # 实例化爬虫对象
    douban_spider = DoubanSpider()    # 实例化爬虫对象

    spiders = {BaiduSpider.name: baidu_spider, DoubanSpider.name: douban_spider}    # 爬虫们
    pipelines = [BaiduPipeline(), DoubanPipeline()]    # 管道们
    engine = Engine(spiders, pipelines=pipelines)    # 传入爬虫对象
    engine.start()    # 启动引擎

安装后运行结果:

·····
这是爬虫中间件:process_response方法
百度爬虫的数据: <scrapy_plus.item.Item object at 0x000002732B1F1BA8>
这是下载器中间件:process_request方法
这是下载器中间件:process_response方法

#三、实现项目中传入多个中间件
##1 为什么需要多个中间件
不同的中间件可以实现对请求或者是响应对象进行不同的处理,通过不同的中间件实现不同的功能,让逻辑更加清晰
##2 在项目文件夹中创建middlewares文件
项目文件夹中的spider_middlewares.py

class TestSpiderMiddleware1(object):

    def process_request(self, request):
        '''处理请求头,添加默认的user-agent'''
        print("TestSpiderMiddleware1: process_request")
        return request

    def process_response(self, response):
        '''处理数据对象'''
        print("TestSpiderMiddleware1: process_response")
        return response


class TestSpiderMiddleware2(object):

    def process_request(self, request):
        '''处理请求头,添加默认的user-agent'''
        print("TestSpiderMiddleware2: process_request")
        return request

    def process_response(self, response):
        '''处理数据对象'''
        print("TestSpiderMiddleware2: process_response")
        return response

项目文件夹中的downloader_middlewares.py

class TestDownloaderMiddleware1(object):

    def process_request(self, request):
        '''处理请求头,添加默认的user-agent'''
        print("TestDownloaderMiddleware1: process_request")
        return request

    def process_response(self, response):
        '''处理数据对象'''
        print("TestSDownloaderMiddleware1: process_response")
        return response


class TestDownloaderMiddleware2(object):

    def process_request(self, request):
        '''处理请求头,添加默认的user-agent'''
        print("TestDownloaderMiddleware2: process_request")
        return request

    def process_response(self, response):
        '''处理数据对象'''
        print("TestDownloaderMiddleware2: process_response")
        return response

##2 修改项目文件夹中的main.py
为引擎传入多个中间件

......
# 此处新增
from spider_middlewares import TestSpiderMiddleware1, TestSpiderMiddleware2
from downloader_middlewares import TestDownloaderMiddleware1, TestDownloaderMiddleware2

if __name__ == '__main__':

    ......
    # 此处新增
    spider_mids = [TestSpiderMiddleware1(), TestSpiderMiddleware2()]    # 多个爬虫中间件
    downloader_mids = [TestDownloaderMiddleware1(), TestDownloaderMiddleware2()]    # 多个下载中间件

    # 此处修改
    engine = Engine(spiders, pipelines=pipelines, spider_mids=spider_mids, downloader_mids=downloader_mids)    # 传入爬虫对象
......

##3 、相应的的修改engine.py
改为使用多个中间件

# scrapy_plus/core/engine.py
.....

class Engine:
    '''完成对引擎模块的封装'''

    # 此处修改
    def __init__(self,spiders,pipelines=[],spider_mids=[],downloader_mids=[]):
        '''
        实例化其他的组件,在引起中能够通过调用组件的方法实现功能
        '''
        ......
        # 此处修改
        self.spider_mids = spider_mids
        self.downloader_mids = downloader_mids

    ......

    def _start_request(self):
        for spider_name,spider in self.spiders.items():
            for start_request in spider.start_requests():
                #1. 对start_request进过爬虫中间件进行处理
                # 此处修改
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(start_request)

                # 为请求对象绑定它所属的爬虫的名称
                start_request.spider_name = spider_name

                ......

    def _execute_request_response_item(self):
        #3. 调用调度器的get_request方法,获取request对象
        request = self.scheduler.get_request()
        if request is None: #如果没有获取到请求对象,直接返回
            return

        # 此处修改
        #request对象经过下载器中间件的process_request进行处理
        for downloader_mid in self.downloader_mids:
            request = downloader_mid.process_request(request)

        #4. 调用下载器的get_response方法,获取响应
        response = self.downloader.get_response(request)

        response.meta = request.meta

        # 此处修改
        #response对象经过下载器中间件的process_response进行处理
        for downloader_mid in self.downloader_mids:
            response = downloader_mid.process_response(response)

        # 此处修改
        #response对象经过下爬虫中间件的process_response进行处理
        for spider_mid in self.spider_mids:
            response = spider_mid.process_response(response)

        #parse方法
        spider = self.spiders[request.spider_name]
        parse = getattr(spider,request.parse)

        #5. 调用爬虫的parse方法,处理响应
        for result in parse(response):
            #6.判断结果的类型,如果是request,重新调用调度器的add_request方法
            if isinstance(result,Request):

                # 此处修改
                #在解析函数得到request对象之后,使用process_request进行处理
                for spider_mid in self.spider_mids:
                    result = spider_mid.process_request(result)
                result.spider_name = request.spider_name
                self.scheduler.add_request(result)
                self.total_request_nums += 1
            #7如果不是,调用pipeline的process_item方法处理结果
            else:
                # 就通过process_item()传递数据给管道
                for pipeline in self.pipelines:
                    result = pipeline.process_item(result,spider)

        self.total_response_nums += 1

    ......

重新安装后运行结果:

TestSpiderMiddleware2: process_request
TestSpiderMiddleware1: process_request
TestSpiderMiddleware2: process_request
TestDownloaderMiddleware1: process_request
TestDownloaderMiddleware2: process_request
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值