Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
Scrapy官方架构图
各部件职能
- Scrapy Engine 控制数据流在系统组件中的流动,并回调相关动作事件
- Scheduler 从引擎接受request并入队,当引擎请求他们时返回request
- Downloader 获取页面数据并提供给引擎,而后提供给spider
- Spiders 用户定义的爬虫模块
- Item Pipeline 处理被spider提取出来的item,包括丢弃、验证和持久化等等
- Downloader middlewares 引擎及下载器之间的hook,处理Downloader传递给引擎的response,同时支持用户挂载自定义逻辑
- Spider middlewares 引擎及Spider之间的hook,处理spider的输入(response)和输出(items及requests),同时支持用户挂载自定义逻辑
数据流转
- 核心引擎从爬虫获取初始url,并生成一个Request任务投入Scheduler调度计划里
- 引擎向调度器请求一个新的Request爬取任务并转发给downloader下载器
- 下载器载入页面并返回一个Response响应给引擎
- 引擎将Response转发给Spider爬虫做 数据提取 和 搜索新的跟进地址
- 处理结果由引擎做分发:提取的数据 -> ItemPipeline管道,新的跟进地址Request -> 调度器
- 流程返回第二步循环执行,直至调度器中的任务被处理完毕
这里我们以爬取马蜂窝问答页面(www.mafengwo.cn/wenda)的文章为例,说明如何构建一个Scrapy爬虫
搭建环境,安装依赖
#沙箱环境
virtualenv mafengwoenv
source mafengwoenv/bin/activate #进入沙箱环境
#安装两个兼容包
pip install cryptography ndg-httpsclient
#pip安装本次爬虫项目依赖的包
pip install scrapy #爬虫框架
pip install pymongo #mongo引擎的python驱动
创建Scrapy项目
#创建一个名为 mafengwo 的项目
scrapy startproject mafengwo
将默认创建如下结构层次的项目
mafengwo
├── mafengwo
│ ├── __init__.py
│ ├── items.py #定义抽象数据模型
│ ├── pipelines.py #定义数据处理管道
│ ├── settings.py #配置文件
│ └── spiders #存放项目所有爬虫
│ └── __init__.py
└── scrapy.cfg
Item建模
为了结构化数据,我们需要定义爬取数据结构的抽象模型(严格的说,Item不是必须的,你也可以直接在spider中返回dict数据,但是使用Item能获得额外的数据验证机制)
在 mafengwo/mafengwo/items.py 文件中定义我们需要爬取的标题、作者、时间和内容属性:
# -*- coding: utf-8 -*-
import scrapy
class WendaItem(scrapy.Item):
title = scrapy.Field()
author = scrapy.Field()
time = scrapy.Field()
content = scrapy.Field()
编写问答页爬虫主程序解析页面
#从基础爬虫模板创建我们的爬虫
scrapy genspider --template basic wenda www.mafengwo.cn/wenda
编辑生成的爬虫程序 mafengwo/mafengwo/spiders/wenda.py,完善我们的数据爬取逻辑:
# -*- coding: utf-8 -*-
import scrapy
from mafengwo import items
class WendaSpider(scrapy.Spider):
name = "wenda"
allowed_domains = ["www.mafengwo.cn"] #必须是和start_urls一致的域名,且不能跟上目录
start_urls = [
'http://www.mafengwo.cn/wenda/',
]
#框架默认的页面解析器入口,start_urls页面将被传入
def parse(self, response):
#遍历文章列表
for link in response.xpath("//ul[@class='_j_pager_box']/li"):
url = link.xpath("div[@class='wen']/div[@class='title']/a/@href").extract_first()
url = response.url + url[7:] #详情页地址
yield scrapy.Request(url, callback=self.parse_detail) #跟进详情页
#当前页条目抓取完毕后,跟进下一页
# next = response.xpath('xxx').extract_first()
# if next:
# yield scrapy.Request(next, self.parse)
#我们自定义的详情页解析器
def parse_detail(self, response):
item = items.WendaItem()
#抽取页面信息存入模型
item['author'] = response.xpath("//div[@class='pub-bar fr']/a[@class='name']/text()").extract_first()
item['title'] = response.xpath("//div[@class='q-title']/h1/text()").extract_first()
item['time'] = response.xpath("//div[@class='pub-bar fr']/span[@class='time']/span/text()").extract_first()
item['content'] = response.xpath("//div[@class='q-desc']/text()").extract_first()
yield item
DOM调试
scrapy使用Scrapy Selectors(基于XPath 和 CSS )从网页提取数据
Selector有四个基本方法:
- xpath() 根据xpath规则返回所有节点的selector list
- css() 根据css规则返回所有节点的selector list
- extract() 序列化该节点为unicode字符串并返回list
- re() 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表
xpath简要:
- xpath分为
绝对路径
、相对路径
两种,并由路径表达式
组成:/
根节点//
匹配节点.
当前节点..
父节点
路径表达式
由步进表达式
组成(轴::节点测试[谓语]):- 轴(节点层级的相对关系):
preceding
、preceding-sibling
、self
、following-sibling
、following
、ancestor
、parent
、child
、attribute
... - 节点测试(节点匹配):
节点名
、*
、text()
、node()
... - 谓语(过滤):
[索引数字]
、[last()]
、[@class="hot"]
...
- 轴(节点层级的相对关系):
我们可以通过以下指令进入ScrapyShell来测试xpath规则
scrapy shell --nolog 'http://www.mafengwo.cn/wenda/' #进入交互式工具
>>>sel.xpath('/div/span') #shell中测试xpath
>>>fetch("http://www.mafengwo.cn") #切换页面,将会刷新response等对象
我们也可以直接在爬虫解析器中嵌入一个钩子 scrapy.shell.inspect_response(response, self)
,从而在爬取过程中回调到ScrapyShell,并在当时特定场景下进行调试
特别的,对于xpath,我们也可以在浏览器console中通过如下方法来测试xpath
$x('规则')
parse解析器调试
我们可以通过以下命令来调试解析器对页面数据的分析情况
scrapy parse --spider=爬虫名 -c 解析器名 -d 跟进深度 -v 调试地址
数据处理管道Item Pipeline
当Item数据在Spider中被收集之后,它将会被传递到Item Pipeline,并按序执行所有管道 管道接收到Item后可以执行自定义逻辑,同时也决定此Item是否继续通过pipeline,或是被丢弃 管道典型的运用:
- 爬取结果持久化
- 清理HTML数据
- 验证爬取的数据
- 查重(并丢弃)
爬虫数据常用的持久化策略是mongo引擎:
- 它支持海量采集数据的录入
- 有很好的伸缩性拓展性,在后期数据变动调整字段的时候能最小化缩减开发成本。
下面我们通过一个mongo管道做爬虫数据的持久化
当然,你也可以直接将持久化逻辑写入爬虫主程序,但是ItemPipline中的持久化逻辑能避免低配IO对爬虫的阻塞
# -*- coding: utf-8 -*-
import pymongo
from scrapy import exceptions
class MongoPipeline(object):
#不用事先创建mongo数据库、集合 和 定义文档,即插即用
mongo_uri = 'localhost'
mongo_database = 'mafengwo'
collection_name = 'wenda_pages'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
#下面这个类方法定义了如何由Crawler对象创建这个管道实例
#在这里我们可以通过crawler参数类似于 `crawler.settings.get()` 形式访问到诸如settings、signals等所有scrapy框架内核组件
@classmethod
def from_crawler(cls, crawler):
return cls(cls.mongo_uri, cls.mongo_database)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.collection = self.client[self.mongo_db][self.collection_name]
def close_spider(self, spider):
self.client.close()
# 管道必须实现的一个方法,在此实现具体的持久化逻辑
def process_item(self, item, spider):
if (not item['title']):
raise exceptions.DropItem('丢弃一个标题不存在页面')
else:
self.collection.insert(dict(item))
return item
启用mongo持久化管道
我们可以在 mafengwo/mafengwo/settings.py 文件中写入配置
ITEM_PIPELINES = {
'mafengwo.pipelines.MongoPipeline': 1, #数字确定了不同管道运行的先后顺序,从低到高
}
但是为了不污染全局管道配置,我们把setting写入爬虫自配置中,即爬虫主程序的 custom_settings 属性中:
class WendaSpider(scrapy.Spider):
custom_settings = {
'ITEM_PIPELINES': {'mafengwo.pipelines.MongoPipeline': 1}
}
运行我们的问答页爬虫
scrapy crawl wenda
当运行 scrapy爬虫模块时,scrapy尝试从中查找Spider的定义,并且在爬取引擎中运行它。 爬取启动后,scrapy首先依据模块的 start_urls 属性创建请求,并将请求的response作为参数传给默认回调函数 parse 。 在回调函数 parse中,我们可以产生(yield)更多的请求,并将响应传递给下一层次的回调函数。
mongo中查看数据
mongo
>show dbs
>use mafengwo #切换数据库
>show collections
>db.wenda_pages.findOne()
>db.wenda_pages.find().limit(3).pretty()
数据库中可以看到,问答页面数据已经成功抓取并录入了
来源:oschina
链接:https://my.oschina.net/u/2400083/blog/724361