zoukankan      html  css  js  c++  java
  • Scrapy爬虫实战| 手把手教你使用CrawlSpider框架爬取数码宝贝全图鉴

    大家好,之前给大家分享过Scrapy框架各组件的详细配置,今天就来更新一篇实战干货:CrawlSpider框架爬取数码宝贝全图鉴。可能本文爬的这个网站你不感兴趣,但我希望你能通过下面的爬取分析、操作中学会点什么,写的很详细,如果你对Scrapy感兴趣或者正在学习Scrapy那么本文将是一个极好的练习案例。

    需求分析

    主页面分析

    首先点击http://digimons.net/digimon/chn.html进入中文检索页面

    查看页面源码

    有两点发现:

    • 数据不是通过Ajax加载

    • 获得全部数据不需要什么翻页逻辑,所有数码兽的后半url都在当前源码里。href中的格式是数码兽英文名/index.html

    接下来分析几个数码兽详情页的url:

    http://digimons.net/digimon/agumon_yuki_kizuna/index.html
    http://digimons.net/digimon/herakle_kabuterimon/index.html
    http://digimons.net/digimon/mugendramon/index.html
    http://digimons.net/digimon/king_etemon/index.html

    根据这个情况有一种思路:利用正则或者其他(超)文本解析工具获取源码中的所有href,然后利用urllib.parse.urljoin和父目录路径http://digimons.net/digimon/ 拼起来构成完整url,再访问详情页。

    但本文换了一种思路。许多爬虫的数据采集工作都是类似的,因此Scrapy提供了若干个更高程度封装的通用爬虫类,可用以下命令查看:

    # 查看scrapy提供的通用爬虫(Generic Spiders)
    scrapy genspider -l

    CrawlSpider 是通用爬虫里最常用的一个,通过一套规则引擎,它自动实现了页面链接的搜索跟进,解决了包含但不限于自动采集详情页、跟进分类/分页地址等问题。主要运行逻辑是深度优先

    这个网站的设计非常简单,因此可以考虑用便捷的全网爬取框架。这个框架的前提是:无关url和需要url有明显差别,可以利用正则获取其他方式区别开。

    简而言之,可以想象给定爬虫一个一只url以后,爬虫会继续访问从这个url出发能访问到的新url,然后爬虫需要根据预设的语法判断这个url是不是所需的,如果是则先解析后延伸访问新url,如果不是则继续访问新url,假如没有新url该叉结束

    文章中的外链比较多是wikipedia,url差别较大。通过对比和数码兽详情页的url,可以总结出所需url的格式:

    http://digimons.net/digimon/.*/index.html

    利用正则替换中间的英文名即可

    详情页分析

    爬取的需求如下

    基本一只数码兽所有的资料都会爬取下来,但需要注意不同的数码兽资料不一定完整,故写代码需要留意(举两个例子)

    需求分析差不多了,现在可以着手写代码!

    代码实现

    创建项目

    直接看代码

    # scrapy startproject <Project_name>
    scrapy startproject Digimons
    
    # scrapy genspider <spider_name> <domains>
    scrapy genspider –t crawl digimons http://digimons.net/digimon/chn.html
    

    spiders.py

    依次打开项目文件夹Digimons - Digimons - spiders,创建digimons.py,内容如下

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from ..items import DigimonsItem
    
    
    class DigimonsSpider(CrawlSpider):
        name = 'digimons'
        allowed_domains = ['digimons.net']
        start_urls = ['http://digimons.net/digimon/chn.html']
    
        # 爬虫的规则是重点,把先前分析的结果url放进allow
        # callback='parse_item',符合规则的url回调该函数
        # follow = False则爬到该页面后不继续拓宽深度
        rules = (
            Rule(LinkExtractor(allow=r'http://digimons.net/digimon/.*/index.html'), callback='parse_item', follow=False),
        )
    
        # 按需求逐个解析,有的不存在需要判断
        def parse_item(self, response):
            # 名字列表
            name_lst = response.xpath('//*[@id="main"]/article/h2[1]//text()').extract()
            name_lstn = [i.replace('/', '').strip() for i in name_lst if i.strip() != '']
            # 中文名
            Cname = name_lstn[0].replace(' ', '-')
            # 日文名
            Jname = name_lstn[1]
            # 英文名
            Ename = name_lstn[2]
            # 等级
            digit_grade = response.xpath("//article/div[@class='data'][1]/table/tr[1]/td/text()").extract()
            digit_grade = '-' if digit_grade == [] else ''.join(digit_grade)
            # 类型
            digit_type = response.xpath("//article/div[@class='data'][1]/table/tr[2]/td/text()").extract()
            digit_type = '-' if digit_type == [] else ''.join(digit_type)
            # 属性
            digit_attribute = response.xpath("//article/div[@class='data'][1]/table/tr[3]/td/text()").extract()
            digit_attribute = '-' if digit_attribute == [] else ''.join(digit_attribute)
            # 所属
            belongs = response.xpath("//article/div[@class='data'][1]/table/tr[4]/td/text()").extract()
            belongs = '-' if belongs == [] else ''.join(belongs)
            # 适应领域
            adaptation_field = response.xpath("//article/div[@class='data'][1]/table/tr[5]/td/text()").extract()
            adaptation_field = '-' if adaptation_field == [] else ''.join(adaptation_field)
            # 首次登场
            debut = response.xpath("//article/div[@class='data'][1]/table/tr[6]/td/text()").extract()
            debut = '-' if debut == [] else ''.join(debut)
            # 名字来源
            name_source = response.xpath("//article/div[@class='data'][1]/table/tr[7]/td/text()").extract()
            name_source = '-' if name_source == [] else '/'.join(name_source).strip('/')
            # 必杀技
            nirvana = response.xpath("//article/div[@class='data'][2]/table/tr/td[1]/text()").extract()
            nirvana = '-' if nirvana == [] else '/'.join(nirvana).strip('/')
            # 介绍资料
            info_lst = response.xpath("//*[@id='cn']/p/text()").extract()
            info = ''.join([i.replace('/', '').strip() for i in info_lst if i.strip() != ''])
            # 图片url
            img_url = response.xpath('//*[@id="main"]/article/div[1]/a/img/@src').extract()
            img_url = response.url[:-10] + img_url[0] if img_url != [] else '-'
            
            # 个人习惯简单输出
            print(Cname, Jname, Ename)
    
            # 如果要持久化存储转向items
            item = DigimonsItem()
            item['Cname'] = Cname
            item['Jname'] = Jname
            item['Ename'] = Ename
            item['digit_grade'] = digit_grade
            item['digit_type'] = digit_type
            item['digit_attribute'] = digit_attribute
            item['belongs'] = belongs
            item['adaptation_field'] = adaptation_field
            item['debut'] = debut
            item['name_source'] = name_source
            item['nirvana'] = nirvana
            item['info'] = info
            item['img_url'] = img_url
            yield item

    items.py

    关于MySQL存储的细节可以参考收藏|我的Mysql学习笔记

    # -*- coding: utf-8 -*-
    
    # Define here the models for your scraped items
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/items.html
    
    import scrapy
    
    # 和spiders.py中的传入对应
    class DigimonsItem(scrapy.Item):
        Cname = scrapy.Field()
        Jname = scrapy.Field()
        Ename = scrapy.Field()
        digit_grade = scrapy.Field()
        digit_type = scrapy.Field()
        digit_attribute = scrapy.Field()
        belongs = scrapy.Field()
        adaptation_field = scrapy.Field()
        debut = scrapy.Field()
        name_source = scrapy.Field()
        nirvana = scrapy.Field()
        info = scrapy.Field()
        img_url = scrapy.Field()
    
        # 注释部分是sql语法,需要在命令行运行
        def get_insert_sql_and_data(self):
        # CREATE TABLE digimons(
        # id int not null auto_increment primary key,
        # Chinese_name text, Japanese_name text, English_name text,
        # digit_grade text, digit_type text, digit_attribute text,
        # belongs text, adaptation_field text, debut text, name_source text,
        # narvana text, info text)ENGINE=INNODB DEFAULT CHARSET=UTF8mb4;
            insert_sql = 'insert into digimons(Chinese_name,Japanese_name,English_name,digit_grade,digit_type,' 
                         'digit_attribute,belongs,adaptation_field,debut,name_source,nirvana,info,img_url)' 
                         'values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
            data = (self['Cname'],self['Jname'],self['Ename'],self['digit_grade'],self['digit_type'],
                    self['digit_attribute'],self['belongs'],self['adaptation_field'],self['debut'],
                    self['name_source'],self['nirvana'],self['info'],self['img_url'])
            return (insert_sql, data)

    pipelines.py

    借助items.py和Mysqlhelper完成存储

    # -*- coding: utf-8 -*-
    from mysqlhelper import Mysqlhelper
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    
    class DigimonsPipeline(object):
        def __init__(self):
            self.mysqlhelper = Mysqlhelper()
    
        def process_item(self, item, spider):
            if 'get_insert_sql_and_data' in dir(item):
                (insert_sql, data) = item.get_insert_sql_and_data()
                self.mysqlhelper.execute_sql(insert_sql, data)
            return item

    settings.py

    没有特别修改,下面的代码默认是注释状态,需要打开

    ITEM_PIPELINES = {
       'Digimons.pipelines.DigimonsPipeline': 300,
    }

    extends.py

    自定义拓展这部分内容不看也可以,我实现的功能就是在爬虫运行结束的时候自动给微信推送消息(借助喵提醒),喵提醒需要申请账号获得自己的id,官方已经给了一个API可以包装在本地

    from urllib import request, parse
    import json
    
    class Message(object):
        def __init__(self,text):
            self.text = text
        def push(self):
            # 重要,在id中填写自己绑定的id
            page = request.urlopen("http://miaotixing.com/trigger?" + parse.urlencode({"id": "xxxxxx", "text": self.text, "type": "json"}))
            result = page.read()
            jsonObj = json.loads(result)
            if (jsonObj["code"] == 0):
                print("
    Reminder message was sent successfully")
            else:
                print("
    Reminder message failed to be sent,wrong code:" + str(jsonObj["code"]) + ",describe:" + jsonObj["msg"])

    写在另外的py文件里命名为message.py,接下来写extends.py,需要自己新建,位置和pipelines.py,items.py同级

    from scrapy import signals
    from message import Message
    
    
    class MyExtension(object):
        def __init__(self, value):
            self.value = value
    
        @classmethod
        def from_crawler(cls, crawler):
            val = crawler.settings.getint('MMMM')
            ext = cls(val)
    
            crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
            crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
    
            return ext
    
        def spider_opened(self, spider):
            print('spider running')
    
        def spider_closed(self, spider):
            # 推送的消息可以自定义,可以再给出获取了多少条数据
            text = 'DigimonsSpider运行结束'
            message = Message(text)
            message.push()
            print('spider closed')

    重要的是如果添加了自定义拓展,需要在settings中也打开,默认是:

    # Enable or disable extensions
    # See https://docs.scrapy.org/en/latest/topics/extensions.html
    #EXTENSIONS = {
    #    'scrapy.extensions.telnet.TelnetConsole': None,
    #}

    需要修改并打开:

    # Enable or disable extensions
    # See https://docs.scrapy.org/en/latest/topics/extensions.html
    EXTENSIONS = {
       'Digimons.extends.MyExtension': 500,
    }

    running.py

    最后就是运行这个项目了,也可以直接在命令行中cd到项目位置后运行

    scrapy crawl digimons

    个人比较喜欢在py文件中运行,新建一个running.py(和items.py同级目录)

    from scrapy.cmdline import execute
    
    execute('scrapy crawl digimons'.split())

    最后运行runnings.py这个项目就启动了

    项目运行

    下面就是本项目的运行过程

    运行完成共获得1179条数据,进入Navicat查看

    数据非常好的储存了,可以看到不是所有的数码兽都有所属阵营,现在我哦们可以做一些简单的查询,比如七大魔王阵营里都有谁:

    查询结果对比确实只有7只,重复出现的都是形态变化或者变体:

    对其中重复的一只数码兽再次查询,的确简介都不同,如果觉得用MySQL查看不方便,可以在Navicat中转出成EXCEL查看

    到这里,本项目就结束了,我们下个案例见~

  • 相关阅读:
    漂亮的表格样式;jQuery清楚表格所有行;js解析后台传过来的JSON数据;动态生成表格数据
    js下载文件;下载方式<a> 和href方式;已经中文乱码问题
    oracle数据库优化与LNNVL函数使用
    HTML页面中使用JQuery获取表格的所有单元的的数据
    oracle中 SQL语句查询后;拼接列;拼接行
    页面制作验证码
    jqGrid,样式
    html 页面弹窗
    Java float保留两位小数或多位小数
    VB操作EXCEL文件大全
  • 原文地址:https://www.cnblogs.com/liuzaoqi/p/13041398.html
Copyright © 2011-2022 走看看