Scrapy框架学习(六)—-Downloader Middleware及使用MongoDB储存数据
Downloader Middleware(下载中间件)
Downloader Middleware
(下载中间件)是一个介于Scrapy
的Request/Response
处理的钩子框架。是用于全局修改Scrapy
request
和response
的一个轻量级、底层的系统。
要激活下载器中间件组件,需要将其加入settings.py
中的DOWNLOADER_MIDDLEWARES
设置中。该设置是一个字典(dict),key为中间件的路径,value为中间件的执行顺序(0-1000),value值越小优先级越高。
DownloaderMiddleware类:
class scrapy.contrib.downloadermiddleware.DownloaderMiddleware
DownloaderMiddleware的方法:
process_request(request,spider)
每当request通过下载中间件时,该方法被调用。
process_request
:可以返回None
、Response
、Request
、raise
、IgnoreRequest
对象,必须返回其中之一。
如果返回
None
,scrapy
将继续处理该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()
方法会被调用。如果没有任何一个方法处理该异常, 则request
的errback(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