Scrapy框架学习(六)----Downloader Middleware及使用MongoDB储存数据

Scrapy框架学习(六)—-Downloader Middleware及使用MongoDB储存数据

Downloader Middleware(下载中间件)

Downloader Middleware(下载中间件)是一个介于ScrapyRequest/Response处理的钩子框架。是用于全局修改Scrapy requestresponse的一个轻量级、底层的系统。

要激活下载器中间件组件,需要将其加入settings.py中的DOWNLOADER_MIDDLEWARES设置中。该设置是一个字典(dict),key为中间件的路径,value为中间件的执行顺序(0-1000),value值越小优先级越高。

DownloaderMiddleware类:

class scrapy.contrib.downloadermiddleware.DownloaderMiddleware

DownloaderMiddleware的方法:

process_request(request,spider)

每当request通过下载中间件时,该方法被调用。

process_request:可以返回NoneResponseRequestraiseIgnoreRequest对象,必须返回其中之一。

  • 如果返回Nonescrapy将继续处理该request,执行其他的中间件的响应方法。直到合适的下载器处理函数(downloader handler)被调用,该request被执行,其response被下载。

  • 如果其返回Response对象,Scrapy将不会调用 任何 其他的process_request()process_exception()方法,或相应地下载函数; 其将返回该response。已安装的中间件的process_response()方法则会在每个response返回时被调用

  • 如果其返回Request对象,Scrapy则停止调用process_request方法并重新调度返回的request。当新返回的request被执行后,相应地中间件链将会根据下载的response被调用。

  • 如果其raise一个IgnoreRequest异常,则安装的下载中间件的process_exception()方法会被调用。如果没有任何一个方法处理该异常, 则requesterrback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。

process_response(request, response, spider)

当下载器完成http请求,传递响应给引擎的时候调用

process_exception(request, exception, spider)

当下载处理器(download handler)或process_request()(下载中间件)抛出异常(包括 IgnoreRequest 异常)时, Scrapy调用 process_exception()

Scrapy内置的中间件

在Scrapy框架中,内置了很多的下载中间件,他们都在scrapy.contrib.downloadermiddleware模块下面,根据功能细分,如:

# 下载超时中间件
from scrapy.contrib.downloadermiddleware.downloadtimeout import DownloadTimeoutMiddleware
# Cookies相关的中间件
from scrapy.contrib.downloadermiddleware.cookies import CookiesMiddleware
# HTTP代理相关的中间件
from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware
# User-Agent的中间件
from scrapy.contrib.downloadermiddleware.useragent import UserAgentMiddleware
# HTTP缓存的中间件
from scrapy.contrib.downloadermiddleware.httpcache import HttpCacheMiddleware
# 重定向相关的中间件
from scrapy.contrib.downloadermiddleware.redirect import RedirectMiddleware

如何使用内置的中间件

我们在settings.py文件中,定义好了注释的一些字段,如果我们需要使用,可以打开注释,配置我们的参数。如:

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36'

...

COOKIES_ENABLED = True

如果没有,我们可以直接在settings.py中声明。不过字段需要和中间件的字段相匹配(从源码中找)。

下面我们看下UserAgentMiddleware源码:其实是通过获取settings.py中的USER_AGENT字段。

class UserAgentMiddleware(object):
    """This middleware allows spiders to override the user_agent"""

    def __init__(self, user_agent='Scrapy'):
        self.user_agent = user_agent

    @classmethod
    def from_crawler(cls, crawler):
        o = cls(crawler.settings['USER_AGENT'])
        crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
        return o

    def spider_opened(self, spider):
        self.user_agent = getattr(spider, 'user_agent', self.user_agent)

    def process_request(self, request, spider):
        if self.user_agent:
            request.headers.setdefault(b'User-Agent', self.user_agent)

自定义中间件

我们看到UserAgentMiddleware中间件只是获取一个Uset-Agent来做替换,有时候,我们可能需要随机获取
User-Agent。那么我们来自定义一个中间件吧。

middlewares.py中定义一个RandomUserAgentMiddlerware

from test.settings import USER_AGENTS
import random

class RandomUserAgentMiddleware(object):

    def process_request(self, request, spider):
        # 从settings中定义好的USER_AGENTS中随机获取一个
        user_agent = random.choice(USER_AGENTS)
        print(user_agent)
        request.headers.setdefault(b'User-Agent', user_agent)

在settings.py中定义USER_AGENTS字段

USER_AGENTS = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
    'Mozilla/5.0(Macintosh;U;IntelMacOSX10_6_8;en-us)AppleWebKit/534.50(KHTML,likeGecko)Version/5.1Safari/534.50',
    'Mozilla/5.0(Macintosh;IntelMacOSX10.6;rv:2.0.1)Gecko/20100101Firefox/4.0.1',
    'Agent:Mozilla/5.0(Macintosh;IntelMacOSX10_7_0)AppleWebKit/535.11(KHTML,likeGecko)Chrome/17.0.963.56Safari/535.11',
    'Mozilla/4.0(compatible;MSIE7.0;WindowsNT5.1;Trident/4.0;SE2.XMetaSr1.0;SE2.XMetaSr1.0;.NETCLR2.0.50727;SE2.XMetaSr1.0)'
]

这里需要注意一下,网址对User-Agent的支持,有的网址是,如果User-Agent使用手机的,会加载手机的网页,导致解析页面出问题。

使用自定义的中间件

在settings.py中设置:

DOWNLOADER_MIDDLEWARES = {
   'test.middlewares.RandomUserAgentMiddleware': 300,
}

MongoDB的使用

之前我们写的博客,都是将数据存储到文件中,那么我们怎么将文件存储到数据库中呢?

下面我们以MongoDB数据库为例:

scrapy数据存储是通过管道(pipeline)来处理的。所以我们的MongoDB储存数据是写在pipeline.py文件中。

1. 安装pymongo库

pip install pymongo

2. 定义MongoDB数据库的主机、数据库名称和文档名称

为了我们使用方便,我们将MongoDB的信息定义在settings.py文件中,如:

# 主机名
MONGODB_HOST='127.0.0.1'
# 端口
MONGODB_PORT=27017
# 数据库名称
MONGODB_DB_NAME='your_db_name'
# 集合名称
MONGODB_COLLECTION_NAME='your_collection_name'

3. 配置MongoDB数据

pipelines.py文件中定义一个TestPipeline类。

class LoginScrapyPipeline(object):
    def __init__(self):
        host = settings["MONGODB_HOST"]
        port = settings["MONGODB_PORT"]
        db_name = settings["MONGODB_DB_NAME"]
        collection_name = settings["MONGODB_COLLECTION_NAME"]

        # 建立连接
        client = pymongo.MongoClient(host=host, port=port)
        # 创建数据库或使用数据库
        mydb = client[db_name]
        # 创建集合或使用集合
        self.collection = mydb[collection_name]

4. 将数据写入MongoDB

  def process_item(self, item, spider):
        # 插入item数据到mongodb数据库
        self.collection.insert(dict(item))

        return item
案例

案例以链家网二手房为例:

1. 创建项目

scrapy startproject lianjia

2. 编写item,定义字段
在items.py文件中定义一个LianjiaItem类,定义需要的字段,如:

import scrapy

class LianjiaItem(scrapy.Item):
    # 标题
    title = scrapy.Field()
    # 房子编号
    code = scrapy.Field()
    # 房间    几房几厅
    room = scrapy.Field()
    # 房子    面积
    size = scrapy.Field()
    # 房子    位置
    position = scrapy.Field()
    # 房子    朝向
    orientation = scrapy.Field()
    # 地址    小区地址
    address = scrapy.Field()
    # 区域    闵行
    zone = scrapy.Field()
    # 子区域  浦江
    area = scrapy.Field()
    # 建造时间
    buildTime = scrapy.Field()
    # 总价
    money = scrapy.Field()
    # 单价
    priceUnit = scrapy.Field()
    # 标签
    label = scrapy.Field()

3. 编写spider爬虫

在spiders目录创建一个lianjie_spider.py文件,文件中定义一个LianjiaSpider类。LianliaSpider继承CrawlSpider。具体实现如下:

from scrapy.spider import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor
from lianjia.items import LianjiaItem


class LianJiaSpider(CrawlSpider):
    name = 'lianjia'
    roominfoow_domains = ['sh.lianjia.com']
    start_urls = ['http://sh.lianjia.com/ershoufang']

    pageLink = LinkExtractor(allow='/ershoufang/d\d+')

    rules = [Rule(link_extractor=pageLink, callback='page_parse', follow=True)]

    def page_parse(self, response):
        # 根节点     //ul[@class="js_fang_list"]/li
        # title     .//div[@class="prop-title"]/a/text()
        # code      .//div[@class="prop-title"]/a/@key
        # room  size position orientation    .//span[@class="info-col row1-text"]/text()     [room,size,position,or] 可能没有数据
        # address                            .//span[@class="info-col row2-text"]/a/span/text()
        # zone  area                         .//span[@class="info-col row2-text"]/a/text()   [zone,area]
        # buildTime                          .//span[@class="info-col row2-text"]/text()         需要清除数据(\d+)
        # money     .//div[@class="info-col price-item main"]/span/text()    [390,万] 需要拼接
        # priceUnit                         .//span[@class="info-col price-item minor"]/text()
        # label     .//div[@class="property-tag-container"]/span/text()  [标签1,标签2,标签3]

        for element in response.xpath('//ul[@class="js_fang_list"]/li'):
            title = element.xpath('.//div[@class="prop-title"]/a/text()').extract_first()
            code = element.xpath('.//div[@class="prop-title"]/a/@key').extract_first()

            roominfo = element.xpath('.//span[@class="info-col row1-text"]/text()').extract()[-1].split('|')
            length = len(roominfo)
            room = ''
            size = ''
            position = ''
            orientation = ''
            if length > 0:
                room = roominfo[0].strip()
            if length > 1:
                size = roominfo[1].strip()
            if length > 2:
                position = roominfo[2].strip()
            if length > 3:
                orientation = roominfo[3].strip()

            address = element.xpath('.//span[@class="info-col row2-text"]/a/span/text()').extract_first()

            zones = element.xpath('.//span[@class="info-col row2-text"]/a/text()').extract()
            zone = ''
            area = ''
            if len(zones) > 0:
                zone = zones[0]
            if len(zones) > 1:
                area = zones[1]

            buildTime = element.xpath('.//span[@class="info-col row2-text"]/text()').extract()[-1].replace('|', '').strip()

            money = ''
            for item in element.xpath('.//div[@class="info-col price-item main"]/span/text()').extract():
                money = money + item

            priceUnit = element.xpath('.//span[@class="info-col price-item minor"]/text()').extract_first().strip()

            label = ''
            for item in element.xpath('.//div[@class="property-tag-container"]/span/text()').extract():
                label = label + item + ","

            item = LianjiaItem()
            item['title'] = title
            item['code'] = code
            item['room'] = room
            item['size'] = size
            item['position'] = position
            item['orientation'] = orientation
            item['address'] = address
            item['zone'] = zone
            item['area'] = area
            item['buildTime'] = buildTime
            item['money'] = money
            item['priceUnit'] = priceUnit
            item['label'] = label

            yield item

4. 编写Pipeline

pipelines.py文件中,定义LianjiaPipeline类。我们使用MongoDB保存数据。代码如下:


import pymongo

from scrapy.conf import settings

class LianjiaPipeline(object):
    def __init__(self):
        host = settings['MONGODN_HOST']
        port = settings['MONGODB_PORT']
        db_name = settings['MONGODB_DB_NAME']
        collection_name = settings['MONGODB_COLLECTION_NAME']

        self.client = pymongo.MongoClient(host=host, port=port)
        mydb = self.client[db_name]
        self.collection = mydb[collection_name]

    def process_item(self, item, spider):
        self.collection.insert(dict(item))
        return item

    def close_spider(self, spider):
        self.client.close()
        pass

5. 编写自定义的UserAgent中间件

在middlewares.py文件中,定义RandomUserAgentMiddleware。如:


from lianjia import settings
import random


class RandomUserAgentMiddleware(object):

    def process_request(self, request, spider):
        user_agent = random.choice(settings.USER_AGENTS)
        request.headers.setdefault(b'User-Agent', user_agent)

6. 配置settings

在settings.py文件中,配置好Mongodb,user agent,log等信息。如:

# -*- coding: utf-8 -*-

BOT_NAME = 'lianjia'

SPIDER_MODULES = ['lianjia.spiders']
NEWSPIDER_MODULE = 'lianjia.spiders'

# log输出文件
# LOG_FILE='lianjia.log'
# log级别
# LOG_LEVEL='INFO'

# mongodb 数据库的配置信息
# 主机ip
MONGODN_HOST = '127.0.0.1'
# 端口
MONGODB_PORT = 27017
# 数据库名称
MONGODB_DB_NAME = 'lianjia'
# 集合名称
MONGODB_COLLECTION_NAME = 'tb_ershoufang'

# User-Agent信息,可随机获取其中之一 
USER_AGENTS = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
    'Mozilla/5.0(Macintosh;U;IntelMacOSX10_6_8;en-us)AppleWebKit/534.50(KHTML,likeGecko)Version/5.1Safari/534.50',
    'Mozilla/5.0(Macintosh;IntelMacOSX10.6;rv:2.0.1)Gecko/20100101Firefox/4.0.1',
    'Mozilla/5.0(Macintosh;IntelMacOSX10_7_0)AppleWebKit/535.11(KHTML,likeGecko)Chrome/17.0.963.56Safari/535.11',
]

# 下载延迟时间
DOWNLOAD_DELAY = 2

# 默认请求头
DEFAULT_REQUEST_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}

# 下载中间件,使用自定义的RandomUserAgentMiddleware中间件
DOWNLOADER_MIDDLEWARES = {
   'lianjia.middlewares.RandomUserAgentMiddleware': 543,
}

# 使用LianjiaPipeline管道
ITEM_PIPELINES = {
   'lianjia.pipelines.LianjiaPipeline': 300,
}

7. 执行爬虫程序

scrapy crawl lianjia

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值