Scrapy框架总结(1)

Scrapy简介

较为流行的python爬虫框架。
本文着重将记录本人入门Scrapy时的所有精炼总结(除了一些书、官方文档,同时也会借鉴一些比较好的blog的内容,因为书写的太生涩,而官方文档又搞得和过家家一样,乱的不行,根本没法看)。希望能给大家带来帮助,抛砖引玉。
如果爬下来的数据还不会分析,建议先看本人上一篇博文《BeautifulSoup总结及contents内容分析》

Scrapy架构

架构如下图所示:
Scrapy框架图
图中绿色线条代表了数据流向。其他几个则是其组件。
//以下是我个人对这些组件的理解,并非官方文档解释

  • 引擎(Scrapy Engine):负责整个数据流走向
  • 调度器(scheduler):负责将Request入队,并在需要时提供给引擎
  • 下载器(DownLoader): 负责提交Request,并获得对应网站的Response,将其提交给spider下一步处理。(可以根据用户定义的下载中间件中的配置进行自定义下载)
  • 蜘蛛(Spider):负责处理网站返回的Response,提取Item 或者是 需要继续跟进的URL
  • 数据管道(Item pipeline): 去重、过滤、加工和存储Item
  • 下载中间件(Downloader middlewares):自定义扩展下载功能的组件
  • Spider 中间件 (Spider middlewares):自定义扩展Engine和Spider中间通信的功能组件

Scrapy运作流程

1 引擎:Hi!Spider, 你要处理哪一个网站?
2 Spider:老大要我处理xxxx.com。
3 引擎:你把第一个需要处理的URL给我吧。
4 Spider:给你,第一个URL是xxxxxxx.com。
5 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
6 调度器:好的,正在处理你等一下。
7 引擎:Hi!调度器,把你处理好的request请求给我。
8 调度器:给你,这是我处理好的request
9 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
10 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
11 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
12 Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
13 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
14 管道``调度器:好的,现在就做!
参考: https://segmentfault.com/a/1190000013178839

项目文件目录结构

在命令行中,执行 scrapy startproject <项目名称>即会在所在当前目录下创建项目目录以及相关文件。

项目名
|—— 项目名
|	|—— _init_.py	#包定义
|	|—— items.py	#模型定义
|	|—— middlewares.py	#中间件定义
|	|—— pipelines.py	#管道定义
|	|—— settings.py 	#配置文件。编程方式控制的配置文件
|	|—— spider
|		|—— _init_.py	#默认蜘蛛代码文件
|——— scrapy.cfg	#运行配置文件。该文件存放的目录为根目录。模块名的字段定义了项目的设置

最基本的Scrapy爬虫制作流程

1、新建项目
2、明确目标:主要是编写Item.py
3、制作爬虫:主要是编写spider.py
4、存储内容:主要是编写pipelines.py

实战

环境安装

//建议直接安装,不要用conda创建一个环境再安装。
//因为scrapy命令需要在全局使用,这样才能在任何文件夹轻松调用。
pip install Scrapy
//验证是否安装成功。运行该命令出现图中内容即为成功。
scrapy

在这里插入图片描述

1、新建项目

项目介绍:从中新网爬取新闻供稿的标题、链接、内容和日期,并以json形式保存到本地。

// 需要先cd到你想要存放该项目的路径下
scrapy startproject chinanews_crawler

在这里插入图片描述

2、明确目标

先查看以下目标网站内容:
中新网:http://www.chinanews.com/rss/
然后用chrome的开发者工具,查看以下我们需要的链接的位置。
在这里插入图片描述
我们发现,那些链接是被放在一个<iframe>元素内,也就是当前展示的其实是两个页面组成的,直接爬取该网站是拿不到链接的。于是,查看<ifreame>元素,发现src="http://www.chinanews.com/rss/rss_2.html"。于是,打开该网页,找到真正的链接所在。
我们继续F12查看其元素。
在这里插入图片描述
发现所有的新闻频道链接都在一个<a>标签中。并且打开这些频道后,就是即时新闻的内容了,也就是到达了我们的目标。如下图:
在这里插入图片描述
然后,我们要的是title,link,description,pubDate。
最后,我们可以开始编写代码了。

文件:items.py
from scrapy.item import Item, Field


class ChinaNewsItem(Item):
    title = Field()  # 标题
    link = Field()  # 详情链接
    desc = Field()  # 新闻综述
    pub_date = Field()  # 发布日期

3、制作爬虫

可以直接在spider/文件夹下,编辑那个 init 文件,或者新建一个py文件,再者在命令行中,scrapy genspider 爬虫名 目标网站。
无论那种方式,其实都是生成一个爬虫的类。

执行:scrapy genspider newsCrawler chinanews.com
打开新生成的spider/newsCrawler.py文件,内容如下:

# -*- coding: utf-8 -*-
from scrapy.spider import Spider

class EasyNewsCrawlerItem(Spider):
    name = 'newsCrawler'  # 爬虫名,启动时需要用到,scrapy crawl newsCrawler,就是启动该爬虫(对照运行流程中第一步中,引擎:Hi,spider,你要处理哪个网站。
    allowed_domains = ['chinanews.com']  # 允许爬虫搜索的域名范围
    start_urls = ['http://chinanews.com/']  # 起始爬取位置

    def parse(self, response):
        """解析函数。response就是下载器下载好的页面内容,爬虫就在该网站中,提取所需的Item"""
        pass

根据2、明确目标中,Item的内容,我们发现,直接爬取并不能实现。因为,http://www.chinanews.com/rss/rss_2.html 这个页面只有链接,要打开这些链接,才能看到正真的内容。于是,我们需要再写一个解析的函数,处理真正网站的内容。

def parse_feed(self, response):
    """二次解析,本次目标:解析出最后结果,包装成Item"""
    rss_page = BeautifulSoup(response.body, "lxml")
    items = rss_page.find_all('item')
    for item in items:
        newsItem = EasyNewsCrawlerItem()
        newsItem['title'] = item.title.text
        # link是个自闭和标签,不能用item.link.text,原因可以看我上一篇博文
        newsItem['link'] = item.contents[2] 
        newsItem['description'] = item.title.text
        newsItem['pubDate'] = item.pubdate.text
        yield newsItem

最后,我们稍微处理一下第一个解析函数,将二次解析定位转一下就行了。下面给出spider的完整代码。

# -*- coding: utf-8 -*-
from scrapy.spider import Spider
from scrapy.http import Request
from bs4 import BeautifulSoup
from ..items import EasyNewsCrawlerItem


class NewscrawlerSpider(Spider):
    name = 'newsCrawler'  # 爬虫名,启动时需要用到,scrapy crawl newsCrawler,就是启动该爬虫(对照运行流程中第一步中,引擎:Hi,spider,你要处理哪个网站。
    allowed_domains = ['chinanews.com']  # 允许爬虫搜索的域名范围
    start_urls = ['http://www.chinanews.com/rss/rss_2.html']  # 起始爬取位置

    def parse(self, response):
        """解析函数。response就是下载器下载好的页面内容,爬虫就在该网站中,提取所需的Item"""
        """本次目标:解析出href中的链接,然后留给下一个解析函数继续解析"""
        # 不熟悉BeautifulSoup的可以看我上一个博文
        rss_page = BeautifulSoup(response.body, "lxml")
        """拿到该网站后,先找到所有<a>标签,然后把其中的href的内容保存起来。"""
        rss_links = set([item['href'] for item in rss_page.find_all("a")])  # 用set是为了滤掉重复链接
        for link in rss_links:
            yield Request(url=link, callback=self.parse_feed)

    def parse_feed(self, response):
        """二次解析,本次目标:解析出最后结果,包装成Item"""
        rss_page = BeautifulSoup(response.body, "lxml")
        items = rss_page.find_all('item')
        for item in items:
            newsItem = EasyNewsCrawlerItem()
            newsItem['title'] = item.title.text
            # link是个自闭和标签,不能用item.link.text,原因可以看我上一篇博文
            newsItem['link'] = item.contents[2]
            newsItem['description'] = item.title.text
            newsItem['pubDate'] = item.pubdate.text
            yield newsItem

            """如果是item.pubDate.text会失败并报错,然后我查了一下文档,发现:"""
            """
            如果同样的代码在不同环境下结果不同,可能是因为两个环境下使用不同的解析器造成的.
            例如这个环境中安装了lxml,而另一个环境中只有html5lib, 解析器之间的区别中说明了原因.
            修复方法是在 BeautifulSoup 的构造方法中中指定解析器 
            因为HTML标签是 大小写敏感 的,所以3种解析器再出来文档时都将tag和属性转换成小写.
            """
            """结论:beautifulSoup会将tag统一变成小写"""

4、 存储内容

存储数据的方法有以下几种:

  1. 通过pipeline(管道)存储
  2. 全局性指定。setting.py文件中配置存储选项
  3. 动态指定。命令行启动时添加-o参数

方法1:管道存储
为了加深对管道的理解,体现其功能,这里写了三个管道,分别对应过滤功能,加工功能,存储功能。每个管道都是一个拥有process_item方法的类。
同时,管道写好了后,要在setting.py文件中将管道配置一下,主要是控制 Item经过管道的顺序,可以取值为0-1000,值越小优先级越高。

文件:pipelines.py

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

from scrapy.exceptions import DropItem
import time
from bs4 import BeautifulSoup
import json


class PreservationPipeline(object):
    """过滤性管道。只通过最近一个小时以内的新闻"""

    def process_item(self, item, spider):
        # <pubDate>2018-12-06 16:09:03<pubDate>
        # 先将字符串转为时间戳
        newsTime = time.mktime(time.strptime(item['pub_date'], '%Y-%m-%d %H:%M:%S'))
        # 获取当前时间戳
        nowTime = time.time()
        if (nowTime - newsTime) / (60 * 60) > 1:
            raise DropItem("%s ,Not Fresh!" % item)  # 超过一个小时,丢弃
        return item


class CleanPipeline(object):
    """加工性管道。删除掉所有的\r\n符号"""

    def process_item(self, item, spider):
        def clear_html(text):
            html = BeautifulSoup(text)
            return html.get_text().replace('\n', '')

        item['desc'] = clear_html(item['desc'])
        return item


class JsonFeedPipeline(object):
    """存储管道。存储到指定的Json文件中去"""

    def __init__(self):
        self.json_file = open('pipResult.json', 'w')
        self.json_file.write("[\n")

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + ",\n"
        # BeautifulSoup会统一为Unicode编码,需要重新编码一下
        self.json_file.write(line.encode('utf-8').decode("unicode_escape"))
        return item

    def close_spider(self, spider):
        self.json_file.write("\n]")
        self.json_file.close()

文件: setting.py

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'chinanews_crawler.pipelines.PreservationPipeline': 300,
    'chinanews_crawler.pipelines.CleanPipeline': 301,
    'chinanews_crawler.pipelines.JsonFeedPipeline': 302,
}

这样,整个爬虫系统就写好了,直接在项目根目录下,运行命令 scrapy crawl newsCrawler,即可看到pipResult.json文件。

方法2:全局性指定
在setting.py文件中,直接加上以下几项。

FEED_URI = "result.json"    # 保存文件名
FEED_FORMAT = "json"    # 保存文件格式
FEED_EXPORT_ENCODING = 'utf-8'  # 保存文件的编码

这样,整个爬虫系统就写好了,直接在项目根目录下,运行命令 scrapy crawl newsCrawler,即可看到result.json文件。

方法3:动态指定
在Scrapy命令中加入-o的输出参数即可。(本人觉得还不如方法2,每次命令都得加,然后还不利于后人查看代码。因此此方法本人并未尝试)

本项目仅供学习参考,所有步骤与本人遇到的坑,本人给予了解释注释 。因此,请不要都测试同一网站,以免引起不必要的麻烦,谢谢。
@copyright Dawn
编辑于:2018/12/6

  • 22
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值