Python爬蟲之Scrapy框架爬取XXXFM音頻文件

本文介紹使用Scrapy爬蟲框架爬取某FM音頻文件。

框架介紹

Scrapy是一個(gè)為了爬取網(wǎng)站數(shù)據(jù),提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架。 可以應(yīng)用在包括數(shù)據(jù)挖掘,信息處理或存儲(chǔ)歷史數(shù)據(jù)等一系列的程序中。

官方文檔

安裝Scrapy

使用pip安裝

pip install Scrapy

創(chuàng)建項(xiàng)目

打開系統(tǒng)終端,cd到項(xiàng)目安裝文件夾,輸入命令:

scrapy startproject FmFiles

其中FmFiles*為項(xiàng)目名稱。

創(chuàng)建Scrapy項(xiàng)目后,用Pycharm打開項(xiàng)目,在編譯器下輸入代碼,也可以直接在終端下輸入代碼。

本文介紹使用Pycharm編寫項(xiàng)目代碼。

項(xiàng)目設(shè)置

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

設(shè)定為代碼提供了提取以key-value映射的配置值的的全局命名空間(namespace)。 設(shè)定可以通過(guò)下面介紹的多種機(jī)制進(jìn)行設(shè)置。

設(shè)定(settings)同時(shí)也是選擇當(dāng)前激活的Scrapy項(xiàng)目的方法(如果您有多個(gè)的話)。

進(jìn)入FmFiles子文件夾下名為settings的Python文件,本項(xiàng)目下需要覆蓋以下幾個(gè)默認(rèn)設(shè)置:

  • 不遵守robots.txt文件,該文件定義了爬蟲相關(guān)協(xié)議,包括不允許爬蟲的代理、IP等信息。
ROBOTSTXT_OBEY = False

  • 自定義pipeline文件所在路徑
ITEM_PIPELINES = {
   'FmFiles.pipelines.FmfilesPipeline': 300,
}

  • 設(shè)置爬取的文件保存路徑
FILES_STORE = '文件保存根路徑'

  • 開啟媒體重定向。默認(rèn)情況下,媒體文件pipeline會(huì)忽略重定向,即向媒體文件URL請(qǐng)求的HTTP重定向?qū)⒁馕吨襟w下載被認(rèn)為是失敗的。
MEDIA_ALLOW_REDIRECTS = True

  • 調(diào)整文件保留延遲天數(shù)。媒體文件pipeline會(huì)避免下載最近下載過(guò)的文件,默認(rèn)延遲90天。
FILES_EXPIRES = 120

定義Item

Item是保存爬取到的數(shù)據(jù)的容器;其使用方法和Python字典類似, 并且提供了額外保護(hù)機(jī)制來(lái)避免拼寫錯(cuò)誤導(dǎo)致的未定義字段錯(cuò)誤。

下載的圖像和文件默認(rèn)保存在指定根目錄下的full子文件夾下,且文件名默認(rèn)為URI(資源的唯一標(biāo)識(shí)符),本項(xiàng)目需修改每個(gè)文件的文件名和所在文件夾名,且爬蟲關(guān)閉后需刪除該full子文件夾。

進(jìn)入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

編寫爬蟲文件

首先創(chuàng)建爬蟲文件,進(jìn)入終端輸入命令:

scrapy genspider fmfiles ximalaya.com

其中genspider為創(chuàng)建爬蟲的scrapy命令,fmfiles是建立的爬蟲名稱(爬蟲的唯一識(shí)別字符串),ximalaya.com的爬取網(wǎng)站的限制域名。

爬蟲文件建立后,進(jìn)入spiders文件夾下的fmfiles文件夾。

Snip20171012_4.png

導(dǎo)入模塊

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

定義爬蟲類相關(guān)屬性:

name = 'fmfiles'
    allowed_domains = ['']
    # PC端起始url
    pc_url = 'http://www.ximalaya.com/'
    # 移動(dòng)端起始url
    mobile_url = 'http://m.ximalaya.com/'

allowed_domains是爬蟲限制域名,所有進(jìn)入爬取隊(duì)列的url必須符合這個(gè)域名,否則不爬取,該項(xiàng)目不限制。

該項(xiàng)目通過(guò)輸入手機(jī)端或電腦端音頻專輯所在url爬取該專輯下所有音頻文件,故需要PC端和移動(dòng)端的起始url以識(shí)別。

pc_url為PC端音頻專輯的起始url。

mobile_url為移動(dòng)端音頻專輯的起始url。

獲取文件外輸入的專輯url列表

該項(xiàng)目從主執(zhí)行文件中輸入PC端或移動(dòng)端的多個(gè)專輯url,建立main Python文件,輸入執(zhí)行scrapy爬蟲命令的代碼:

from scrapy.cmdline import execute

if __name__ == '__main__':
    # 在此添加專輯url列表或在命令行執(zhí)行scrapy crawl fmfiles -a urls={多個(gè)專輯url,以逗號(hào)隔開}
    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()執(zhí)行來(lái)自系統(tǒng)終端命令行語(yǔ)句,參數(shù)為單個(gè)命令的列表。

urls為爬蟲所需外部參數(shù)的鍵值,與爬蟲初始化器中屬性名一致。

在爬蟲文件獲取輸入?yún)?shù)值:

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

解析輸入?yún)?shù)

判斷urls參數(shù)是來(lái)自PC端還是移動(dòng)端的音頻專輯url,在請(qǐng)求爬取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)

若為移動(dòng)端專輯url,直接請(qǐng)求該url獲取各個(gè)資源url;若為PC端專輯url,還需在請(qǐng)求html中解析出相應(yīng)的移動(dòng)端專輯url。原因是移動(dòng)端url的反爬蟲措施較PC端少,更易爬取。

其中request_album_url(url)函數(shù)解析專輯url并交給scrapy請(qǐng)求該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函數(shù)為請(qǐng)求PC端專輯url后的回調(diào)函數(shù),在下面講解。

解析PC端專輯url

使用XPath語(yǔ)法解析html結(jié)構(gòu)中的元素和內(nèi)容,scrapy官方關(guān)于XPath語(yǔ)法部分

    def parse_pc(self, response):
        # 從PC端專輯html中解析出移動(dòng)端專輯url
        mobile_url = response.xpath('//head/link[contains(@rel, "alternate")]/@href').extract_first()
        yield self.request_album_url(mobile_url)

解析移動(dòng)端專輯主頁(yè)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函數(shù)提交資源文件json表單,表單參數(shù)為json所在url、專輯id、資源頁(yè)數(shù)(一頁(yè)20個(gè)文件)。

    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)

編寫管道文件

導(dǎo)入模塊

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

定義管道類屬性

動(dòng)態(tài)獲取資源文件默認(rèn)父目錄“full”,并保存為屬性:

    __file_dir = None

該屬性在爬蟲關(guān)閉后會(huì)執(zhí)行刪除文件夾操作。

編寫管道執(zhí)行函數(shù)

通過(guò)上述爬蟲文件中獲取到資源文件所在url后,交給scrapy的文件管道類下載,重寫scrapy媒體文件下載函數(shù):

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

重寫該函數(shù)后scrapy會(huì)自動(dòng)處理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

爬蟲結(jié)束后刪除默認(rèn)父文件夾:

    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)

執(zhí)行爬蟲

代碼書寫完畢后,執(zhí)行main文件開始爬取。

Snip20171012_3.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 前言 作為一名合格的數(shù)據(jù)分析師,其完整的技術(shù)知識(shí)體系必須貫穿數(shù)據(jù)獲取、數(shù)據(jù)存儲(chǔ)、數(shù)據(jù)提取、數(shù)據(jù)分析、數(shù)據(jù)挖掘、...
    whenif閱讀 18,313評(píng)論 45 523
  • 序言第1章 Scrapy介紹第2章 理解HTML和XPath第3章 爬蟲基礎(chǔ)第4章 從Scrapy到移動(dòng)應(yīng)用第5章...
    SeanCheney閱讀 15,256評(píng)論 13 61
  • 在之前一篇抓取漫畫圖片的文章里,通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Python程序,遍歷所有漫畫的url,對(duì)請(qǐng)求所返回的html源...
    msq3閱讀 13,023評(píng)論 14 88
  • 今天早晨到公司沒(méi)多久,倆領(lǐng)導(dǎo)找我,一個(gè)應(yīng)用服務(wù)器的一些接口速度極慢,馬上要給客戶演示,另一個(gè)應(yīng)用接口無(wú)返回結(jié)果.....
    pu_debug閱讀 1,367評(píng)論 1 51
  • 小柳 距第一次見面 過(guò)去已經(jīng)半年 你問(wèn)我 是否 那時(shí)已經(jīng)喜歡 這是開什么玩笑 本人哪里有那么賤 何況當(dāng)時(shí) 不過(guò)圍觀...
    小柳姐姐閱讀 248評(píng)論 0 0

友情鏈接更多精彩內(nèi)容