天天看點

Python_爬蟲_喜馬拉雅音頻

目錄

項目需求:抓取專輯所有音頻檔案。

1. 項目截圖

2. 找資料

3. 項目難點講解

4. 源代碼

聯系我們,一起學Python吧

項目需求:抓取專輯所有音頻檔案。

超詳細講解,按需檢視,文末附源代碼。關注我們,隻漲知識,不掉發。

1. 項目截圖

Python_爬蟲_喜馬拉雅音頻

2. 找資料

搜尋郭德綱相聲,選擇第一個專輯,點選進入。

Python_爬蟲_喜馬拉雅音頻

 找到我們需要下載下傳的音頻檔案,一共49個。

Python_爬蟲_喜馬拉雅音頻

這裡以谷歌浏覽器來示範使用開發者工具(F12)來找資料,找資料小技巧:直接先找xhr分類,如沒有資料,再找all裡第一個html,一般情況下,批量采集的資料是以json資料格式擷取的。

喜馬拉雅的音頻資料藏在點選播放後的url裡。截圖中的url正是音頻清單的資料了。

Python_爬蟲_喜馬拉雅音頻

接着點開右側 Preview預覽的檔案,trackId就是區分每個檔案的id。要找到下載下傳資料的地方,就必須找到唯一的關鍵詞。

Python_爬蟲_喜馬拉雅音頻

順着左側的url檢視,發現下面這個url對應的右側資料,有我們需要的.m4a音頻檔案資料。

Python_爬蟲_喜馬拉雅音頻

找資料就搞定了,49個音頻檔案清單資料和對應的音頻檔案位址資料。

3. 項目難點講解

經過url的觀察,發現請求頭裡,都會有個xm-sign的資料。沒有這個資料,則抓取不到我們要的資料。

Python_爬蟲_喜馬拉雅音頻

那我們就生成xm-sign資料,規則是 md5(himalaya-伺服器時間戳)(100以内随機數)伺服器時間戳(100以内随機數)現在時間戳。(每次抓取資料的時候,都需要調用該方法,現在時間戳和伺服器時間戳相差大到一定值,則擷取不到資料)

def getServerTime(self):
        """
        擷取喜馬拉雅伺服器的時間戳
        :return:
        """
        # 這個位址就是傳回伺服器時間戳的接口
        serverTimeUrl = "https://www.ximalaya.com/revision/time"
        response = requests.get(serverTimeUrl,headers = self.headers)
        return response.text
 
    def getSign(self,serverTime):
        """
        生成 xm-sign
        規則是 md5(himalaya-伺服器時間戳)(100以内随機數)伺服器時間戳(100以内随機數)現在時間戳
        :param serverTime:
        :return:
        """
        nowTime = str(round(time.time()*1000))
 
        sign = str(hashlib.md5("himalaya-{}".format(serverTime).encode()).hexdigest()) + "({})".format(str(round(random.random()*100))) + serverTime + "({})".format(str(round(random.random()*100))) + nowTime
        # 将xm-sign添加到請求頭中
        self.headers["xm-sign"] = sign
        return sign
           

這裡提供一個擷取音頻檔案分頁頁碼的方法。原理是擷取分頁數字所在的html結構,循環該結構,依次加1得到頁碼。(方法有很多,可以參考這個思路)

"""
    擷取課程下分頁的頁碼(名稱,圖檔,簡介,)
    """
    def getPageCount(self,url):
        page_count = 0
        # 分頁資料測試
        res = requests.get(url,headers=self.headers)
        try:
            # print(res.text)
            soup = bs4.BeautifulSoup(res.text,'html.parser')
            # print(soup)
            data_list = soup.find('ul',class_='pagination-page _Xo').find_all('li')     # 頁碼所在的html結構
            
            # lesson_title = soup.find('h1',class_='title lO_').text    # 名稱
            # lesson_img = soup.find('img',class_='img lO_')['src']     # 圖檔
            # lesson_abstract = soup.find('article',class_='intro aB_')     # 簡介
            for data in data_list:
                if(data.text.isdigit()):    # 如果是數字,代表是分頁的
                    page_count+=1
        except:
            print('='*30 + '不存在分頁' + '='*30)
        if(page_count == 0):
            page_count = 1  # 分頁從1開始,為第一頁

        return page_count
        # return lesson_title,lesson_img,lesson_abstract,page_count
           

這裡再引申一下:插入資料到資料庫(sql server),其他資料庫大同小異。

'''
    連接配接sql server資料庫,增删改查,語句可以在資料庫寫好,複制進來,因為這裡是字元串,不容易發現錯誤
    '''
    def insertData(self,lesson):
        connect = pymssql.connect('192.168.0.248','***','***','***')    # 伺服器名,賬戶,密碼,資料庫名
        if connect:
            print('連接配接成功!')
            cursor = connect.cursor()   # 建立一個遊标對象,python裡的sql語句都要通過cursor來執行
            sql = "insert into Lesson(parentId,lessonType,lessonImg,lessonName,abstract,lessonFileUrl) values('{}','{}','{}','{}','{}','{}')".format(self.parentId,self.lessonType,self.lessonImg,self.lessonName,self.abstract,self.lessonFileUrl)
            cursor.execute(sql)    # 執行sql語句
            connect.commit()    # 送出
            cursor.close()
            connect.close()
           

4. 源代碼

注意看注釋喔~ 有問題随時微信聯系我們吧。下載下傳的音頻檔案路徑需更改。

import requests,bs4,time
import time
import hashlib
import random
import pymssql

'''
插入資料庫的資料對象,封裝對象,友善調用
'''
class lesson(object):
    def __init__(self,parentId,lessonType,lessonImg,lessonName,abstract,lessonFileUrl):
        self.parentId = parentId
        self.lessonType = lessonType
        self.lessonImg = lessonImg
        self.lessonName = lessonName
        self.abstract = abstract
        self.lessonFileUrl = lessonFileUrl

class ximalaya(lesson):

    def __init__(self):
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36"
        }
 
    def getServerTime(self):
        """
        擷取喜馬拉雅伺服器的時間戳
        :return:
        """
        # 這個位址就是傳回伺服器時間戳的接口
        serverTimeUrl = "https://www.ximalaya.com/revision/time"
        response = requests.get(serverTimeUrl,headers = self.headers)
        return response.text
 
    def getSign(self,serverTime):
        """
        生成 xm-sign
        規則是 md5(himalaya-伺服器時間戳)(100以内随機數)伺服器時間戳(100以内随機數)現在時間戳
        :param serverTime:
        :return:
        """
        nowTime = str(round(time.time()*1000))
 
        sign = str(hashlib.md5("himalaya-{}".format(serverTime).encode()).hexdigest()) + "({})".format(str(round(random.random()*100))) + serverTime + "({})".format(str(round(random.random()*100))) + nowTime
        # 将xm-sign添加到請求頭中
        self.headers["xm-sign"] = sign
        return sign
    '''
    下載下傳音頻檔案,路徑需要設定
    '''
    def downVoice(self,url,fileName):
        serverTime = self.getServerTime()   # 每次執行下載下傳都需要送出請求,xm-sign的現在時間戳和伺服器時間戳需要不斷更新,才能通過驗證
        self.getSign(serverTime)    # 将xm-sign添加到請求頭中, 不然無法擷取到資料
        try:
            res = requests.get(url,headers=self.headers)
            down_file_url = res.json()['data']['src']   # json解析報錯  
            # print(down_file_url)
            down_file_content = requests.get(down_file_url,headers=self.headers)
            with open('D:\\you-get\\喜馬拉雅\\'+fileName+'.mp3', 'wb') as file: #儲存到本地的檔案名, 檔案名一樣會被替換(這裡是特殊情況,可根據需要區分檔案名)
                print('正在下載下傳...:'+fileName)
                file.write(down_file_content.content)
                file.flush()
            print('下載下傳完成!!!:'+fileName)
            print('=' * 30)
        except Exception as e:
            print('=====報錯的url為:'+url)
            print(e)
    '''
    擷取課程下的所有課件清單
    '''
    def getAllLesson(self,url):
        res = requests.get(url,headers=self.headers)
        list_data = res.json()['data']['tracksAudioPlay']
        for data in list_data:
            voice_data_url = 'https://www.ximalaya.com/revision/play/v1/audio?id={}&ptype=1'.format(data['trackId'])
            trackName = data['trackName']
            # lesson.lessonName = trackName     # 裝載資料到lesson對象
            # ximalaya.insertData(self,lesson)    # 傳入lesson對象
            self.downVoice(voice_data_url,trackName)
            time.sleep(3)

    """
    擷取課程下分頁的頁碼(名稱,圖檔,簡介,)
    """
    def getPageCount(self,url):
        page_count = 0
        # 分頁資料測試
        res = requests.get(url,headers=self.headers)
        try:
            # print(res.text)
            soup = bs4.BeautifulSoup(res.text,'html.parser')
            # print(soup)
            data_list = soup.find('ul',class_='pagination-page _Xo').find_all('li')     # 頁碼所在的html結構
            
            # lesson_title = soup.find('h1',class_='title lO_').text    # 名稱
            # lesson_img = soup.find('img',class_='img lO_')['src']     # 圖檔
            # lesson_abstract = soup.find('article',class_='intro aB_')     # 簡介
            for data in data_list:
                if(data.text.isdigit()):    # 如果是數字,代表是分頁的
                    page_count+=1
        except:
            print('='*30 + '不存在分頁' + '='*30)
        if(page_count == 0):
            page_count = 1  # 分頁從1開始,為第一頁

        return page_count
        # return lesson_title,lesson_img,lesson_abstract,page_count

    '''
    連接配接sql server資料庫,增删改查,語句可以在資料庫寫好,複制進來,因為這裡是字元串,不容易發現錯誤
    '''
    def insertData(self,lesson):
        connect = pymssql.connect('***','***','***','***')    # 伺服器名,賬戶,密碼,資料庫名
        if connect:
            print('連接配接成功!')
            cursor = connect.cursor()   # 建立一個遊标對象,python裡的sql語句都要通過cursor來執行
            sql = "insert into Lesson(parentId,lessonType,lessonImg,lessonName,abstract,lessonFileUrl) values('{}','{}','{}','{}','{}','{}')".format(self.parentId,self.lessonType,self.lessonImg,self.lessonName,self.abstract,self.lessonFileUrl)
            cursor.execute(sql)    # 執行sql語句
            connect.commit()    # 送出
            cursor.close()
            connect.close()
'''
程式入口
'''
if __name__ == '__main__':
    # https://www.ximalaya.com/ertong/38839711/
    # 擷取課程的所有課件清單資料
    # lesson_id = '38839711'
    # lesson_id = '31966031'
    lesson_id = '238474'  # Url位址欄中的一串數字

    # 課程詳情url,包含課程下所有的章節資料頁面
    # lesson_url = 'https://www.ximalaya.com/ertong/{}/'.format(lesson_id)
    # lesson_url = 'https://www.ximalaya.com/youshengshu/{}/'.format(lesson_id)
    lesson_url = 'https://www.ximalaya.com/xiangsheng/{}/'.format(lesson_id)

    ximalaya = ximalaya()

    page_count = ximalaya.getPageCount(lesson_url)

    for count in range(1,page_count+1):
        # json資料Url
        json_url = 'https://www.ximalaya.com/revision/play/v1/show?id={}&num={}&sort=1&size=30&ptype=0'.format(lesson_id,count)
        ximalaya.getAllLesson(json_url)


           

聯系我們,一起學Python吧

分享Python實戰代碼,入門資料,進階資料,基礎文法,爬蟲,資料分析,web網站,機器學習,深度學習等等。

Python_爬蟲_喜馬拉雅音頻

​關注公衆号「Python家庭」領取1024G整套教材、交流群學習、商務合作

Python_爬蟲_喜馬拉雅音頻

Python_爬蟲_喜馬拉雅音頻

Python_爬蟲_喜馬拉雅音頻
Python_爬蟲_喜馬拉雅音頻