zoukankan      html  css  js  c++  java
  • Python爬虫之Scrapy框架爬取XXXFM音频文件

    本文介绍使用Scrapy爬虫框架爬取某FM音频文件。

    框架介绍

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

    官方文档

    安装Scrapy

    使用pip安装

    pip install Scrapy
    
    

    创建项目

    打开系统终端,cd到项目安装文件夹,输入命令:

    scrapy startproject FmFiles
    
    

    其中*FmFiles**为项目名称。

    创建Scrapy项目后,用Pycharm打开项目,在编译器下输入代码,也可以直接在终端下输入代码。

    本文介绍使用Pycharm编写项目代码。

    项目设置

    Scrapy设定(settings)提供了定制Scrapy组件的方法。您可以控制包括核心(core),插件(extension),pipelinespider组件。

    设定为代码提供了提取以key-value映射的配置值的的全局命名空间(namespace)。 设定可以通过下面介绍的多种机制进行设置。

    设定(settings)同时也是选择当前激活的Scrapy项目的方法(如果您有多个的话)。

    进入FmFiles子文件夹下名为settings的Python文件,本项目下需要覆盖以下几个默认设置:

    • 不遵守robots.txt文件,该文件定义了爬虫相关协议,包括不允许爬虫的代理、IP等信息。
    ROBOTSTXT_OBEY = False
    
    
    • 自定义pipeline文件所在路径
    ITEM_PIPELINES = {
       'FmFiles.pipelines.FmfilesPipeline': 300,
    }
    
    
    • 设置爬取的文件保存路径
    FILES_STORE = '文件保存根路径'
    
    
    • 开启媒体重定向。默认情况下,媒体文件pipeline会忽略重定向,即向媒体文件URL请求的HTTP重定向将意味着媒体下载被认为是失败的。
    MEDIA_ALLOW_REDIRECTS = True
    
    
    • 调整文件保留延迟天数。媒体文件pipeline会避免下载最近下载过的文件,默认延迟90天。
    FILES_EXPIRES = 120
    
    

    定义Item

    Item是保存爬取到的数据的容器;其使用方法和Python字典类似, 并且提供了额外保护机制来避免拼写错误导致的未定义字段错误。

    下载的图像和文件默认保存在指定根目录下的full子文件夹下,且文件名默认为URI(资源的唯一标识符),本项目需修改每个文件的文件名和所在文件夹名,且爬虫关闭后需删除该full子文件夹。

    进入settingsPython文件,代码如下:

    import scrapy
    
    
    class FmfilesItem(scrapy.Item):
        # define the fields for your item here like:
        # 专辑名称
        file_album = scrapy.Field()
        # 专辑中文件名
        file_name = scrapy.Field()
        # 专辑中文件url
        file_url = scrapy.Field()
        pass
    
    

    编写爬虫文件

    首先创建爬虫文件,进入终端输入命令:

    scrapy genspider fmfiles ximalaya.com
    
    

    其中genspider为创建爬虫的scrapy命令,fmfiles是建立的爬虫名称(爬虫的唯一识别字符串),ximalaya.com的爬取网站的限制域名。

    爬虫文件建立后,进入spiders文件夹下的fmfiles文件夹。

    导入模块

    import scrapy
    import os
    import json
    from scrapy.selector import Selector
    from FmFiles.items import FmfilesItem
    from FmFiles.settings import FILES_STORE
    
    

    定义爬虫类相关属性:

    name = 'fmfiles'
        allowed_domains = ['']
        # PC端起始url
        pc_url = 'http://www.ximalaya.com/'
        # 移动端起始url
        mobile_url = 'http://m.ximalaya.com/'
    
    

    allowed_domains是爬虫限制域名,所有进入爬取队列的url必须符合这个域名,否则不爬取,该项目不限制。

    该项目通过输入手机端或电脑端音频专辑所在url爬取该专辑下所有音频文件,故需要PC端和移动端的起始url以识别。

    pc_url为PC端音频专辑的起始url。

    mobile_url为移动端音频专辑的起始url。

    获取文件外输入的专辑url列表

    该项目从主执行文件中输入PC端或移动端的多个专辑url,建立main Python文件,输入执行scrapy爬虫命令的代码:

    from scrapy.cmdline import execute
    
    if __name__ == '__main__':
        # 在此添加专辑url列表或在命令行执行scrapy crawl fmfiles -a urls={多个专辑url,以逗号隔开}
        album_urls = ['http://www.ximalaya.com/1000202/album/2667276/']
        urls = ','.join(album_urls)
        execute_str = 'scrapy crawl fmfiles -a urls=' + urls
        execute(execute_str.split())
    
    

    其中execute()执行来自系统终端命令行语句,参数为单个命令的列表。

    urls为爬虫所需外部参数的键值,与爬虫初始化器中属性名一致。

    在爬虫文件获取输入参数值:

        def __init__(self, urls=None):
            super(FmfilesSpider, self).__init__()
            self.urls = urls.split(',')
    
    

    解析输入参数

    判断urls参数是来自PC端还是移动端的音频专辑url,在请求爬取url方法中输入代码:

        def start_requests(self):
            for url in self.urls:
                if url.startswith(self.mobile_url):
                    yield self.request_album_url(url)
                elif url.startswith(self.pc_url):
                    yield scrapy.Request(url=url, callback=self.parse_pc)
    
    

    若为移动端专辑url,直接请求该url获取各个资源url;若为PC端专辑url,还需在请求html中解析出相应的移动端专辑url。原因是移动端url的反爬虫措施较PC端少,更易爬取。

    其中request_album_url(url)函数解析专辑url并交给scrapy请求该url,定义如下:

        def request_album_url(self, album_url=''):
            if len(album_url) == 0:
                return None
            album_url = album_url.strip().strip('/')
            album_id = album_url.split('/')[-1]
            return scrapy.Request(album_url,
                                  meta={'aid': album_id},
                                  callback=self.parse,
                                  dont_filter=True)
    
    

    其中parse_pc函数为请求PC端专辑url后的回调函数,在下面讲解。

    解析PC端专辑url

    使用XPath语法解析html结构中的元素和内容,scrapy官方关于XPath语法部分

        def parse_pc(self, response):
            # 从PC端专辑html中解析出移动端专辑url
            mobile_url = response.xpath('//head/link[contains(@rel, "alternate")]/@href').extract_first()
            yield self.request_album_url(mobile_url)
    
    

    解析移动端专辑主页url

        def parse(self, response):
            # 专辑名称
            album_name = response.xpath('//article/div/div/h2/text()').extract_first().strip()
            # self.album_name = album_name
            filepath = FILES_STORE + album_name
            if not os.path.exists(filepath):
                os.mkdir(filepath)
            meta = response.meta
            yield self.json_formrequest(aname=album_name,
                                        aid=meta['aid'])
    
    

    其中json_formrequest函数提交资源文件json表单,表单参数为json所在url、专辑id、资源页数(一页20个文件)。

        def json_formrequest(self, aname='', aid=0, page=1):
            moreurl = '/album/more_tracks'
            # album_id = album_url.split('/')[-1]
            page = str(page)
            formrequest = scrapy.FormRequest(url='http://m.ximalaya.com' + moreurl,
                                             formdata={'url': moreurl,
                                                       'aid': str(aid),
                                                       'page': str(page)},
                                             meta={'aname': str(aname),
                                                   'aid': str(aid),
                                                   'page': str(page)},
                                             method='GET',
                                             callback=self.parse_json,
                                             dont_filter=True)
            return formrequest
    
    

    解析资源json文件并保存到item

        def parse_json(self, response):
            jsondata = json.loads(response.text)
            if jsondata['res'] is False:
                return None
            next_page = jsondata['next_page']
            selector = Selector(text=jsondata['html'])
            file_nodes = selector.xpath('//li[@class="item-block"]')
            if file_nodes is None:
                return None
            meta = response.meta
            for file_node in file_nodes:
                file_name = file_node.xpath('a[1]/h4/text()').extract_first().strip()
                file_url = file_node.xpath('a[2]/@sound_url').extract_first().strip()
                item = FmfilesItem()
                item['file_album'] = meta['aname']
                item['file_name'] = file_name + '.' + file_url.split('.')[-1]
                item['file_url'] = file_url
                yield item
            if int(next_page) == 0:
                return None
            if int(next_page) == (int(meta['page']) + 1):
                yield self.json_formrequest(aname=meta['aname'],
                                            aid=meta['aid'],
                                            page=next_page)
    
    

    编写管道文件

    导入模块

    import scrapy
    import os
    from scrapy.pipelines.files import FilesPipeline
    from FmFiles.settings import FILES_STORE
    from scrapy.exceptions import DropItem
    
    

    定义管道类属性

    动态获取资源文件默认父目录“full”,并保存为属性:

        __file_dir = None
    
    

    该属性在爬虫关闭后会执行删除文件夹操作。

    编写管道执行函数

    通过上述爬虫文件中获取到资源文件所在url后,交给scrapy的文件管道类下载,重写scrapy媒体文件下载函数:

        def get_media_requests(self, item, info):
            file_url = item['file_url']
            yield scrapy.Request(file_url)
    
    

    重写该函数后scrapy会自动处理url并下载。

    资源下载完成后修改文件名:

        def item_completed(self, results, item, info):
            file_paths = [x['path'] for ok, x in results if ok]
            if not self.__file_dir:
                self.__file_dir = file_paths[0].split('/')[0]
            if not file_paths:
                raise DropItem("Item contains no files")
            os.rename(FILES_STORE + file_paths[0],
                      FILES_STORE + item['file_album'] + '/' + item['file_name'])
            return item
    
    

    爬虫结束后删除默认父文件夹:

        def close_spider(self, spider):
            if self.__file_dir is None:
                return None
            ori_filepath = FILES_STORE + self.__file_dir
            if os.path.exists(ori_filepath):
                os.rmdir(ori_filepath)
    
    

    执行爬虫

    代码书写完毕后,执行main文件开始爬取。

  • 相关阅读:
    软件工程个人作业1
    构建之法问题以及阅读计划
    软件工程概论课后作业1
    动手动脑7补
    学习进度条06
    软件工程课堂测试07(结对开发)
    团队介绍
    软件工程结对作业02(借鉴版)
    软件工程结对作业02(原创版)
    构建之法阅读笔记06
  • 原文地址:https://www.cnblogs.com/keqipu/p/7656238.html
Copyright © 2011-2022 走看看