3

本课只针对python3环境下的Scrapy版本(即scrapy1.3+)

选取什么网站来爬取呢?

对于歪果人,上手练scrapy爬虫的网站一般是官方练手网站 http://quotes.toscrape.com

我们中国人,当然是用豆瓣Top250啦!https://movie.douban.com/top250

clipboard.png

第一步,搭建准备

  1. 为了创造一个足够干净的环境来运行scrapy,使用virtualenv是不错的选择。
>>> mkdir douban250 && cd douban250
>>> virtualenv -p python3.5 doubanenv

首先要保证已经安装有virtualenv和python3.x版本,上面命令为创建python3.5环境的virtualenv

virtualenv教程:廖雪峰python教程-virtualenv

  1. 开启virtualenv,并安装scrapy
>>> source doubanenv/bin/activate
>>> pip install scrapy
  1. 使用scrapy初始化项目一个项目,比如我们命名为douban_crawler
>>> scrapy startproject douban_crawler

这时生成了一个目录结构

douban_crawler/
    douban.cfg
    douban_crawler/
        __init__.py
        items.py
        middlewares.py
        piplines.py
        setting.py

并且命令行会给你一个提示:

You can start your first spider with:
    cd douban_crawler
    scrapy genspider example example.com
  1. 按提示执行这两步吧!
>>> cd douban_crawler
>>> scrapy genspider douban movie.douban.com/top250

genspider后目录结构中增加了spider目录

douban_crawler/
    douban.cfg
    douban_crawler/
        spiders/
            __init__.py
            douban.py
        __init__.py
        items.py
        middlewares.py
        piplines.py
        setting.py

在pycharm中设置好项目

  1. 首先,在pycharm中打开douban_crawler/
  2. 然后设置pycharm的虚拟环境:

Perference > Project:douban_crawler > Project Interpreter
点击设置图标> Add Local > existing environment;把预置的python解析器,切换到刚刚创立的virtualenv下的doubanenv > bin > python3.5

开始爬取

准备工作完成,可以开始爬取了!

打开spiders/目录下的douban.py文件

# douban_crawler/ > spiders/ > douban.py

import scrapy

class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['movie.douban.com/top250']
    start_urls = ['http://movie.douban.com/top250/']

    def parse(self, response):
        pass

start_urls就是我们需要爬取的网址啦!

把start_urls中的http://movie.douban.com/top250/

改成https://movie.douban.com/top250/

接下来我们将改写parse()函数,进行解析。

解析豆瓣250条目

使用chrome或firefox浏览器打开https://movie.douban.com/top250/

使用右键菜单中的检查(inspect)分析元素,可以看出:

'.item'包裹了一个个词条

每一个词条下面

'.pic a img'的src属性包含了封面图片地址
'.info .hd a'的src属性包含了豆瓣链接
'.info .hd a .title'中的文字包含了标题,因为每个电影会有多个别名,我们只用取第一个标题就行了。
'.info .bd .star .rating_num'包含了分数
'.info .bd .quote span.inq'包含了短评

另外导演、年代、主演、简介等信息需要点击进入条目的才能爬取,我们先爬取以上五条信息吧!

按照刚刚的解析,填写parse()函数

# douban_crawler/ > spiders/ > douban.py

# -*- coding: utf-8 -*-
import scrapy

class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['movie.douban.com/top250']
    start_urls = ['https://movie.douban.com/top250/']

    def parse(self, response):
        items = response.css('.item')
        for item in items:
            yield {
                'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
                'link': item.css('.info .hd a::attr(href)').extract_first(),
                'title': item.css('.info .hd a .title::text').extract_first(),
                'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
                'quote': item.css('.info .bd .quote span.inq::text').extract_first()
            }

.css()运行类似于jquery或pyquery的解析器,但它可以用::text或::attr(href)来直接获取属性或文字,应当说比jquery解析器还要更方便。

当然,.css只能返回一个对象,而需要具体的文字或属性,则需要.extract()或.extract_first()

其中.extract()返回所有符合条件的文字或属性数组,而.extract_first()只返回查询到的第一条文字或属性。

在shell里验证解析语句是否正确

这里我们需要给大家一个窍门,就是先用shell验证刚刚写的css对不对

在pycharm窗口左下方打开terminal
命令行输入:

>>> scrapy shell https://movie.douban.com/top250

会出来一堆的返回信息,最后会出来一堆提示

[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x10d276f28>
[s]   item       {}
[s]   request    <GET https://movie.douban.com/top250>
[s]   response   <403 https://movie.douban.com/top250>
[s]   settings   <scrapy.settings.Settings object at 0x10e543128>
[s]   spider     <DefaultSpider 'default' at 0x10fa99080>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects 
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser

我们看到response一项

——什么?返回的403错误?

原来我们爬取豆瓣没有设置User-Agent请求头,而豆瓣不接受无请求头的Get请求,最终返回了403错误。

这时赶紧在settings.py里面加入一行

#  spider_crawler/ > settings.py

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36' 

保存文件后,重新运行

>>> scrapy shell https://movie.douban.com/top250

提示:

[s]   response   <200 https://movie.douban.com/top250>

这时就可以开始检验刚刚的解析语句能不能获得想要的结果了:

response.css('.item')

返回了一个<Selector>对象数组,我们取其中的第一个selector

>>> items = response.css('.item')
>>> item = item[0]

#把cover_pic对应的解析语句拷贝过来
>>> item.css('.pic a img::attr(src)').extract_first()

返回

'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg'

即证明解析语句正确,其它的四项可以一一验证

>>> item.css('.pic a img::attr(src)').extract_first()
'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg'
>>> item.css('.info .hd a::attr(href)').extract_first()
'https://movie.douban.com/subject/1292052/'
>>> item.css('.info .hd a .title::text').extract_first()
'肖申克的救赎'
>>> item.css('.info .bd .star .rating_num::text').extract_first()
'9.6'
>>> item.css('.info .bd .quote span.inq::text').extract_first()
'希望让人自由。'

这时候用exit()退出shell
再运行爬虫

>>> scrapy crawl douban

就可以看到解析后的数据输出了!

翻页爬取全部250条数据

刚刚我们初步爬取了一下,但这一页只显示25条,如何通过翻页爬到全部250条呢?

通过chrome浏览器的“检查”功能,我们找到豆瓣页面上的“下页”所对应的链接:

response.css('.paginator .next a::attr(href)')

现在我们改写一个douban.py

# douban_crawler/ > spiders/ > douban.py
class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['movie.douban.com/top250']
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        items = response.css('.item')
        for item in items:
            yield {
                'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
                'link': item.css('.info .hd a::attr(href)').extract_first(),
                'title': item.css('.info .hd a .title::text').extract_first(),
                'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
                'quote': item.css('.info .bd .quote span.inq::text').extract_first()
            }
        next_page = response.css('.paginator .next a::attr(href)').extract_first()
        if next_page:
            next_page_real = response.urljoin(next_page)
            yield scrapy.Request(next_page_real, callback=self.parse,dont_filter=True)

以上通过response.urljoin()返回了拼接后的真实url
然后通过一个递归返回Request对象,遍历了所有的页面。

注意!爬豆爬一定要加入dont_filter=True选项,因为scrapy只要解析到网站的Url有'filter=',就会自动进行过滤处理,把处理结果分配到相应的类别,但偏偏豆瓣url里面的filter为空不需要分配,所以一定要关掉这个选项。

这时再运行一次爬虫

>>> scrapy crawl douban

250条数据已经爬取出来啦!

存储数据到文件

很简单,运行爬虫时加个-o就输出啦!

>>> scrapy crawl douban -o douban.csv

于是你看到当前目录下多了一个.csv文件,存储的正是我们想要的结果!

你也可以用 .json .xml .pickle .jl 等数据类型存储,scrapy也是支持的!

利用Items把爬到的数据结构化

Scrapy的Item功能很类似于Django或其它mvc框架中的model作用,即把数据转化成固定结构,这样才能便出保存和展示。

我们打开 items.py文件,如下定义一个名为DoubanItem的数据类型。

# douban_clawler > items.py
import scrapy

class DoubanItem(scrapy.Item):
    title = scrapy.Field()
    link = scrapy.Field()
    rating = scrapy.Field()
    cover_pic = scrapy.Field()
    quote = scrapy.Field()

有了DoubanItem后,就可以改造douban.py里面的parse函数,使爬取的信息全部转化为Item形式啦

# -*- coding: utf-8 -*-
import scrapy
from douban_crawler.items import DoubanItem

class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['movie.douban.com/top250']
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        items = response.css('.item')
        for item in items:
            yield DoubanItem({
                'cover_pic': item.css('.pic a img::attr(src)').extract_first(),
                'link': item.css('.info .hd a::attr(href)').extract_first(),
                'title': item.css('.info .hd a .title::text').extract_first(),
                'rating': item.css('.info .bd .star .rating_num::text').extract_first(),
                'quote': item.css('.info .bd .quote span.inq::text').extract_first()
            })

        next_page = response.css('.paginator .next a::attr(href)').extract_first()
        if next_page:
            next_page_real = response.urljoin(next_page)
            yield scrapy.Request(next_page_real, callback=self.parse,dont_filter=True)

非常简单,只修改了两行:

  1. 引入DoubanItem
  2. 原来yield的一个dict格式,现在直接在DoubanItem中传入dict就可以把dict转化成DoubanItem对象了!

现在你可以scrapy crawl douban再试一次爬取,看是不是已经转换成了DoubanItem形式了?

存储数据到MongoDB

有了DoubanItem数据结构,我们就可以保存进MongoDB啦!

保存到MongoDB,我们需要用到pipline组件,没错,就是上面那一堆文件中的piplines.py

之前我们确保两件事:

  1. mongodb服务已经开启。如果没有开启请sudo mongod开启本地。
  2. pymongo包已安装。如果没有安装请pip install pymongo

使用pipline,请记住四个字!

启!

(开启爬虫)对应于open_spider,在spider开启时调用

在这里面启动mongodb

承!

(承接爬取任务)对应于from_clawler,它有几个特点:

  1. 它是类对象,所以必须加上@classmethod。
  2. 只要有这个函数,它就一定会被调用。
  3. 它必须返回我们Pipline本身对象的实例。
  4. 它有进入Scrapy所有核心组件的能力,也就是说可以通过它访问到settings对象。

转!

(转换对象)对应于process_item,它有几个特点:

  1. 必须返回Item对象或raise DropItem()错误
  2. 在这个函数内将传入爬取到的item并进行其它操作。

合!

(闭合爬虫)于应于close_spider,在spider关闭是调用

我们可以在这个函数内关闭MongoDB

有了以上知道,我们上代码!

# douban_crawler > piplines.py
import pymongo


class DoubanCrawlerPipeline(object):
    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri = crawler.settings.get('MONGO_URI'),
            mongo_db = crawler.settings.get('MONGO_DB')
        )

    def process_item(self, item, spider):
        self.db['douban250'].insert_one(dict(item))
        return item

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

同时在setting中添加MONGODB的配置

MONGO_URI = "localhost"
MONGO_DB = "douban"

还有非常重要的一步!
在setting中打开pipline的注释!!

ITEM_PIPELINES = {
   'douban_crawler.pipelines.DoubanCrawlerPipeline': 300,
}

现在打开crawler

scrapy crawl douban

爬到的信息已经保存到数据库了!

clipboard.png


刘羽冲
4.1k 声望931 粉丝

破庙中神易,破心中神难。 当人类第一次仰望天空的时候,就已经有罪!