Scrapy + Mongo 构建一个网页爬虫

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 能够应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。javascript

Scrapy官方架构图 Scrapy架构图css

各部件职能

  • 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),同时支持用户挂载自定义逻辑

数据流转

  1. 核心引擎从爬虫获取初始url,并生成一个Request任务投入Scheduler调度计划里
  2. 引擎向调度器请求一个新的Request爬取任务并转发给downloader下载器
  3. 下载器载入页面并返回一个Response响应给引擎
  4. 引擎将Response转发给Spider爬虫作 数据提取 和 搜索新的跟进地址
  5. 处理结果由引擎作分发:提取的数据 -> ItemPipeline管道,新的跟进地址Request -> 调度器
  6. 流程返回第二步循环执行,直至调度器中的任务被处理完毕

这里咱们以爬取马蜂窝问答页面(www.mafengwo.cn/wenda)的文章为例,说明如何构建一个Scrapy爬虫java

搭建环境,安装依赖

#沙箱环境
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

将默认建立以下结构层次的项目node

mafengwo
├── mafengwo
│   ├── __init__.py
│   ├── items.py #定义抽象数据模型
│   ├── pipelines.py #定义数据处理管道
│   ├── settings.py #配置文件
│   └── spiders #存放项目全部爬虫
│       └── __init__.py
└── scrapy.cfg

Item建模

为告终构化数据,咱们须要定义爬取数据结构的抽象模型(严格的说,Item不是必须的,你也能够直接在spider中返回dict数据,可是使用Item能得到额外的数据验证机制)
在 mafengwo/mafengwo/items.py 文件中定义咱们须要爬取的标题、做者、时间和内容属性:python

# -*- 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 )从网页提取数据shell

Selector有四个基本方法数据库

  1. xpath() 根据xpath规则返回全部节点的selector list
  2. css() 根据css规则返回全部节点的selector list
  3. extract() 序列化该节点为unicode字符串并返回list
  4. re() 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表

xpath简要浏览器

  • xpath分为 绝对路径相对路径 两种,并由 路径表达式 组成:
    • /根节点
    • //匹配节点
    • .当前节点
    • ..父节点
  • 路径表达式步进表达式 组成(轴::节点测试[谓语]):
    • 轴(节点层级的相对关系):precedingpreceding-siblingselffollowing-siblingfollowingancestorparentchildattribute...
    • 节点测试(节点匹配):节点名*text()node()...
    • 谓语(过滤):[索引数字][last()][@class="hot"] ...

咱们能够经过如下指令进入ScrapyShell来测试xpath规则bash

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引擎:

  1. 它支持海量采集数据的录入
  2. 有很好的伸缩性拓展性,在后期数据变更调整字段的时候能最小化缩减开发成本。

下面咱们经过一个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()

数据库中能够看到,问答页面数据已经成功抓取并录入了

相关文章
相关标签/搜索