学会运用爬虫框架 Scrapy (二)

图片来自 unsplash

上篇文章介绍了爬虫框架 Scrapy 如何安装,以及其特性、架构、数据流程。相信大家已经对 Scrapy 有人了初步的认识。本文是 Scrapy 系列文章的第二篇,主要通过一个实例讲解 scrapy 的用法。

1 选取目标

网络爬虫,顾名思义是对某个网站或者系列网站,按照一定规则进行爬取信息。爬取程序的首要工作当然是选定爬取目标。本次爬取目标选择是V电影,网址是http://www.vmovier.com/。爬取内容是[最新推荐]栏目的前15条短视频数据信息。具体信息包括封面、标题、详细说明以及视频播放地址。

点击查看大图

2 定义 Item

为什么将爬取信息定义清楚呢?因为接下来 Item 需要用到。在 Item.py 文件中,我们以类的形式以及 Field 对象来声明。其中 Field 对象其实是一个字典类型,用于保存爬取到的数据。而定义出来的字段,可以简单理解为数据库表中的字段,但是它没有数据类型。Item 则复制了标准的 dict API,存放以及读取跟字典没有差别。

V电影的 Item,我们可以这样定义:

import scrapy

class ScrapyDemoItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 封面
    cover = scrapy.Field()
    # 标题
    title = scrapy.Field()
    # 简述
    dec = scrapy.Field()
    # 播放地址
    playUrl = scrapy.Field()

3 编写 Spider

Spider 目录是我们爬虫程序爬取网站以及提取信息的模块。我们首先在目录下新建一个名为 VmoiveSpider 的文件。同时,该类继承scrapy.Spider

这里我们用到的scrapy.spider.Spider 是 Scrapy 中最简单的内置 spider。继承 spider 的类需要定义父类中的属性以及实现重要的方法。

  • name

这个属性是非常重要的,所以必须定义它。定义 name 目的是为爬虫程序命名。因此,还要保持 name 属性是唯一的。它是 String 类型,我们在 VmoiveSpider 可以定义:

name = 'vmoive'
  • start_urls

start_urls 是 Url 列表,也是必须被定义。可以把它理解为存放爬虫程序的主入口 url 地址的容器。

  • allowed_domains

可选字段。包含了spider允许爬取的域名(domain)列表(list)。 当 OffsiteMiddleware 启用时, 域名不在列表中的URL不会被跟进。根据 V 电影的 url 地址,我们可以这样定义:

allowed_domains = ['vmovier.com']
  • parse(response)

parser 方法是Scrapy处理下载的response的默认方法。它同样必须被实现。parse 主要负责处理 response 并返回处理的数据以及跟进的URL。该方法及其他的Request回调函数必须返回一个包含 Request 及(或) Item 的可迭代的对象。

在 scrapy_demo/sipders/VmoiveSpider 的完整代码如下:

#!/usr/bin/env python
# coding=utf-8

import scrapy
from scrapy_demo.items import ScrapyDemoItem

class VmoiveSpider(scrapy.Spider):
    # name是spider最重要的属性, 它必须被定义。同时它也必须保持唯一
    name = 'vmoive'

    # 可选定义。包含了spider允许爬取的域名(domain)列表(list)
    allowed_domains = ['vmovier.com']

    start_urls = [
        'http://www.vmovier.com/'
    ]

    def parse(self, response):
        self.log('item page url is ==== ' + response.url)

        moivelist = response.xpath("//li[@class='clearfix']")
        for m in moivelist:
            item = ScrapyDemoItem()
            item['cover'] = m.xpath('./a/img/@src')[0].extract()
            item['title'] = m.xpath('./a/@title')[0].extract()
            item['dec'] = m.xpath("./div/div[@class='index-intro']/a/text()")[0].extract()
            print(item)
            yield item

4 运行程序

在项目目录下打开终端,并执行以下命令。我们没有pipelines.py中将爬取结果进行存储,所以我们使用 scrapy 提供的导出数据命令,将 15 条电影信息导出到名为 items.json 文件中。其中 vmoive 为刚才在 VmoiveSpider 中定义的 name 属性的值。

scrapy crawl vmoive -o items.json

运行的部分结果如下:

{
'cover': 'http://cs.vmoiver.com/Uploads/cover/2017-09-08/59b25a504e4e0_cut.jpeg@600w_400h_1e_1c.jpg',
 'dec': '15年过去了,但我依然有话说',
 'title': '灾难反思纪录短片《“911”之殇》'
 }

5 深究

在阅读上述代码过程中,大家可能会有两个疑问。第一,为什么要在 xpath 方法后面添加[0]? 第二,为什么要在 [0] 后面添加 extract()方法 ? 请听我慢慢道来。

  1. 添加个[0], 因为 xpath() 返回的结果是列表类型。我以获取标题内容为例子讲解不添加[0]会出现什么问题。那么代码则变为
m.xpath('./a/@title').extract()

运行结果会返回一个列表,而不是文本信息。

['灾难反思纪录短片《“911”之殇》']

2)这里涉及到内建选择器 Selecter 的知识。extract()方法的作用是串行化并将匹配到的节点返回一个unicode字符串列表。看了定义,是不是更加懵逼了。那就看下运行结果来压压惊。
不加上 extract() 的运行结果如下:

<Selector xpath='./a/@title' data='灾难反思纪录短片《“911”之殇》'>

6 进阶

上述代码只是在 V电影主页中提取信息,而进入电影详情页面中匹配搜索信息。因此,我们是获取不到电影的播放地址的。如何搞定这难题?我们可以在 parse 方法中做文章。parse() 前文提到它必须返回一个 Reuqest 对象或者 Item。再者, Request 中就包含 url。换句话说,我们只有获取到电影详情页的 url 地址,并在传递给返回的 Request 对象中。

因此,代码可以这么改进:

#!/usr/bin/env python
# coding=utf-8

import scrapy
from scrapy_demo.items import ScrapyDemoItem

class VmoiveSpider(scrapy.Spider):
    # name是spider最重要的属性, 它必须被定义。同时它也必须保持唯一
    name = 'vmoive'

    # 可选定义。包含了spider允许爬取的域名(domain)列表(list)
    allowed_domains = ['vmovier.com']

    start_urls = [
        'http://www.vmovier.com/'
    ]

    def parse(self, response):
        self.log('item page url is ==== ' + response.url)

        moivelist = response.xpath("//li[@class='clearfix']")

        for m in moivelist:
            item = ScrapyDemoItem()
            item['cover'] = m.xpath('./a/img/@src')[0].extract()
            item['title'] = m.xpath('./a/@title')[0].extract()
            item['dec'] = m.xpath("./div/div[@class='index-intro']/a/text()")[0].extract()
            # print(item)

            # 提取电影详细页面 url 地址 
            urlitem = m.xpath('./a/@href')[0].extract()
            url = response.urljoin(urlitem)
            # 如果你想将上面的 item 字段传递给 parse_moive, 使用 meta 参数
            yield scrapy.Request(url, callback=self.parse_moive, meta={
                'cover':item['cover'],
                'title': item['title'],
                'dec': item['dec'],
            })

    def parse_moive(self, response):
        item = ScrapyDemoItem()
        item['cover'] = response.meta['cover']
        item['title'] = response.meta['title']
        item['dec'] = response.meta['dec']
        item['playUrl'] = response.xpath("//div[@class='p00b204e980']/p/iframe/@src")[0].extract()
        yield item

再次运行程序,查看运行结果。


点击查看大图

7 数据持久化

在实际生产中,我们很少把数据导出到 json 文件中。因为后期维护、数据查询、数据修改都是一件麻烦的事情。我们通常是将数据保存到数据库中。

我们先定义并创建数据库表

DROP TABLE IF EXISTS vmoive
CREATE TABLE vmoive(
  id INT(6) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  cover VARCHAR(255),
  title VARCHAR(255),
  mdec VARCHAR(255),
  playUrl VARCHAR(255)
) DEFAULT  CHARSET=utf8;

在 settings 文件中增加数据库的配置

# Configure item pipelines  这里默认是注释的
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'scrapy_demo.pipelines.ScrapyDemoPipeline': 300,     # 保存到 mysql 数据库中
}

# Mysql 配置信息
# 根据你的环境修改
MYSQL_HOST = '127.0.0.1'
MYSQL_DBNAME = 'vmoive'     # 数据库名
MYSQL_USER = 'root'         # 数据库用户
MYSQL_PASSWORD = '123456'   # 数据库密码

在 scrapy 中,我们要在 pipeline 文件中编写处理数据存储的代码。

# -*- coding: utf-8 -*-
import pymysql
from scrapy_demo import settings

class ScrapyDemoPipeline(object):

    def __init__(self,):
        self.conn = pymysql.connect(
            host        = settings.MYSQL_HOST,
            db          = settings.MYSQL_DBNAME,
            user        = settings.MYSQL_USER,
            passwd      = settings.MYSQL_PASSWORD,
            charset     = 'utf8',  # 编码要加上,否则可能出现中文乱码问题
            use_unicode = False )
        self.cursor = self.conn.cursor()

    # pipeline 默认调用
    def process_item(self, item, spider):
        # 调用插入数据的方法
        self.insertData(item)
        return item

    # 插入数据方法
    def insertData(self, item):
        sql = "insert into vmoive(cover, title, mdec, playUrl) VALUES(%s, %s, %s, %s);"
        params = (item['cover'], item['title'], item['dec'], item['playUrl'])
        self.cursor.execute(sql, params)
        self.conn.commit()

附:源代码地址


系列文章:
学会运用爬虫框架 Scrapy (一)
学会运用爬虫框架 Scrapy (三)
学会运用爬虫框架 Scrapy (四) —— 高效下载图片
学会运用爬虫框架 Scrapy (五) —— 部署爬虫

推荐阅读:
爬虫实战二:爬取电影天堂的最新电影
爬虫与反爬虫的博弈
爬虫系列的总结


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容