目錄
項目需求:抓取專輯所有音頻檔案。
1. 項目截圖
2. 找資料
3. 項目難點講解
4. 源代碼
聯系我們,一起學Python吧
項目需求:抓取專輯所有音頻檔案。
超詳細講解,按需檢視,文末附源代碼。關注我們,隻漲知識,不掉發。
1. 項目截圖
2. 找資料
搜尋郭德綱相聲,選擇第一個專輯,點選進入。
找到我們需要下載下傳的音頻檔案,一共49個。
這裡以谷歌浏覽器來示範使用開發者工具(F12)來找資料,找資料小技巧:直接先找xhr分類,如沒有資料,再找all裡第一個html,一般情況下,批量采集的資料是以json資料格式擷取的。
喜馬拉雅的音頻資料藏在點選播放後的url裡。截圖中的url正是音頻清單的資料了。
接着點開右側 Preview預覽的檔案,trackId就是區分每個檔案的id。要找到下載下傳資料的地方,就必須找到唯一的關鍵詞。
順着左側的url檢視,發現下面這個url對應的右側資料,有我們需要的.m4a音頻檔案資料。
找資料就搞定了,49個音頻檔案清單資料和對應的音頻檔案位址資料。
3. 項目難點講解
經過url的觀察,發現請求頭裡,都會有個xm-sign的資料。沒有這個資料,則抓取不到我們要的資料。
那我們就生成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家庭」領取1024G整套教材、交流群學習、商務合作