【音樂爬蟲】Python爬蟲-selenium+browsermob-proxy 解決動態網頁 js渲染問題

1.一般的python爬蟲很簡單,直接請求對應網址,解析返回的數據即可,但是有很多網站的數據的js動態渲染的,你直接請求是得不到對應的數據的

  這時就需要其它手段來處理了。

2.以一個例子來說明,整個過程,爬取一個音樂網站的對應歌手的歌曲。

  1. 目標網址http://tool.liumingye.cn/music/?page=searchPage,在搜索框輸入歌手名字即可得到歌曲。

     

  2. 如果我們直接請求這個網址:http://tool.liumingye.cn/music/?page=audioPage&type=migu&name=周杰倫,返回的數據不是我們想要的

     

  3. 在inspect裏分析js或xhr數據找到真正的網址,可以看到在NetWork下的XHR中找到我們想要的網址,還有POSt請求時發送的數據

     

     

  4.  

    我們仿照他發起一個post請求,返回的數據確實是我們想要的,裏面包含歌曲名字,還有下載地址,歌詞等信息,直接請求歌曲下載地址即可。

    import requests
    headers = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'}
    data={'data':'3c7frUZZlQooe5YYDmZVHi9O4qOxgj8oXuivPGr2sfTT65IGXo85FOqjwtoJPmO-t6ew_q_59dY1SSl7','v':'2'}
    r=requests.post('https://app.onenine.cc/m/api/search',headers=headers,data=data)
    data
    =r.json()#轉爲json print(data['data']['list'])

  5.  大家不要覺得這樣就可以了喔,其實問題還有很多。第一,你要構造POST請求必須要得到data參數的數據,就是那一長串字符串,如果沒有data的話,會返回狀態碼403,禁止訪問
  6. 而且每一個歌手或歌曲的data參數都是動態生成的,沒有規律的,這樣的話,你每構造一個POST請求就要在遊覽器那裏看一下data參數是多少,根本不符合爬蟲的要求。
  7. 第二,返回的數據只有二十首歌曲,想要得到更多,就必須點擊‘下一頁'按鈕,網站纔會去請求下一個二十首,然後渲染到網頁上,所以就算有data參數,也無法爬到更多數據。

 

3.這時就到了Seleninum和Browsermob-proxy出場了。Seleninum是一個用於測試網站的自動化測試工具,支持各種瀏覽器包括Chrome、Firefox、Safari等主流界面瀏覽器,同時也支持phantomJS無界面瀏覽器。它主要得功能就是可以模擬遊覽器行爲,包括執行js代碼,尋找各種元素,發送點擊,滑動事件等等。這樣我們就可以實現點擊功能了。selenium的教程推薦這個,他寫得不錯,比我寫得好

https://blog.csdn.net/qq_32502511/article/details/101536325

Browsermob-proxy是一個開源的Java編寫的基於LittleProxy的代理服務。Browsermob-Proxy的具體流程有點類似與Flidder或Charles。即開啓一個端口並作爲一個標準代理存在,當HTTP客戶端(瀏覽器等)設置了這個代理,則可以抓取所有的請求細節並獲取返回內容.簡單來說就是它可以將網站的請求和返回的數據獲取到,就如遊覽器的'inspect'功能,這樣我們就可以獲取到‘seacher’的數據了

Browsermob-proxy的教程推薦這個,不難的,看一下就可以上手了。https://blog.csdn.net/qq_32502511/article/details/101536325

 

4.我們分析一下獲取到的數據,包含名字和歌詞,封面,下載地址。歌曲的下載地址有四種,url_m4a是流暢,128是標準,320是高品,flac則是無損音樂。url那一欄是在線播放

4.有上面這些準備後就可以寫代碼爬取數據了,代碼如下(有詳細註釋)

from selenium import webdriver
import time
import os
import threading
import simplejson as json
from browsermobproxy import Server
import requests
from selenium.webdriver.chrome.options import Options

#Browsermob-proxy的路徑
server=Server(r"D:\Python3.7\browser-proxy.bat\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat")
server.start()#啓動browsermob-proxy
proxy=server.create_proxy()
chrome_options=Options()

#獲取遊覽器對象,設置無界面模式
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
chrome_options.add_argument('--headless')#無頭模式,不開啓遊覽器界面
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--incognito')
driver=webdriver.Chrome(chrome_options=chrome_options)#啓用谷歌遊覽器

#獲取音樂的信息
def getDownloadUrls(name):
    
    musicUrl='http://tool.liumingye.cn/music/?page=audioPage&type=migu&name='+str(name)
    proxy.new_har('music',options={'captureHeaders':True,'captureContent':True})
    driver.get(musicUrl)#請求網站
    time.sleep(3)#等待頁面加載完成,必須要延時,不然獲取不到數據
    lists=[]
    #獲取3頁數據一共60首歌
    for i in range(3):
        index=0
        result=proxy.har#獲取所有請求網址
        for entry in result['log']['entries']:
            url=entry['request']['url']
            #找對應的網址,我們要的是,search網址的
            if "app.onenine.cc/m/api/search" in url:
                index+=1
                '''
                因爲每點擊下一頁都會產生一個新的‘search',
                而proxy每次都會把所有的請求獲取到,所以上一個的’search‘也會獲取到
                所以要判斷排除掉,只獲取最新的’search'
                '''
                if index>i:
                    content = entry['response']['content']
                    text=content['text']
                    data=json.loads(text)['data']
                    lists.extend(data['list']) #這裏就是獲取到的json數據.每一次添加20首歌曲的信息進去
                
        #滑到底部
        js="window.scrollTo(0,10000)"#滑動滾動條的js代碼,如果元素不在頁面上顯示,是找不到的,所以滑動,讓下一頁的按鈕顯示出來
        driver.execute_script(js)#執行·js代碼
        #點擊下一頁,獲取更多歌曲
        nextBtn=driver.find_element_by_css_selector('#player>div.aplayer-more')#找到下一頁的按鈕
        nextBtn.click()#發送點擊事件
        time.sleep(1)#延時1秒等待數據加載
    return lists

#用於下載音樂代碼
def download(path,musicName,folder,ext):
    
    r=requests.get(path)
    f=open(folder+'/'+musicName+ext,'wb')
    f.write(r.content)
    f.close()

#在lists例表中提取音樂的下載地址和歌曲名字
def downloadMusic(name,type,lists):
    '''
    name:歌手名字,爲每一個歌手都新建一個文件夾
    type:表示要下載何種音質
    lists:獲取的音樂數據列表
    '''
    isExists=os.path.exists(name)
    if not isExists:
        os.makedirs(name)
    musicName=''
    path=''
    ext=''
    for item in lists:
        for key,value in item.items():
            ext='.mp3'
            key=str(key)
            if key=='name':
                musicName=value
            elif key=='artist':
                musicName=musicName+'-'+value
            elif key=='url_m4a' and type==0:#m4a表示流暢
                path=value
            elif key=='url_128' and type==1:#128是標準
                path=value
            elif key=='url_320' and type==2:#320是高品質
                path=value
            elif key=='url_flac'and type==3:#flac表示無損
                path=value
                ext='.flac'

            if len(path)!=0:
                #每一首歌開啓一個線程去下載
                t = threading.Thread(target=download,args=(path,musicName,name,ext))
                t.start()
                t.join()
                path=''
                musicName=''

if __name__=='__main__':
    
    signer=input("輸入歌手名字:")
    lists=getDownloadUrls(signer)
    print('獲取完成,開始下載音樂')
    downloadMusic(signer,0,lists)#0表示下載流暢音質的
    print('下載結束')

等待一段時間就爬取成功了。

 

 5.這個網站上還有歌單和各種音樂榜單,我們一樣可以爬取下來,原理都一樣,我就不多解釋了。你只要掌握上面的東西,爬取這些東西,還是很簡單的,只要給點耐心去學一下。

 

 

  附上完整代碼,包括爬取歌單和榜單

from selenium import webdriver
import time
import os
import threading
import simplejson as json
from browsermobproxy import Server
import requests
from selenium.webdriver.chrome.options import Options

#Browsermob-proxy的路徑
server=Server(r"D:\Python3.7\browser-proxy.bat\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat")
server.start()#啓動browsermob-proxy
proxy=server.create_proxy()
chrome_options=Options()

#獲取遊覽器對象,設置無界面模式
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
chrome_options.add_argument('--headless')#無頭模式,不開啓遊覽器界面
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--incognito')
driver=webdriver.Chrome(chrome_options=chrome_options)#啓用谷歌遊覽器

#獲取音樂的信息
def getDownloadUrls(name):
    
    musicUrl='http://tool.liumingye.cn/music/?page=audioPage&type=migu&name='+str(name)
    proxy.new_har('music',options={'captureHeaders':True,'captureContent':True})
    driver.get(musicUrl)#請求網站
    time.sleep(3)#等待頁面加載完成,必須要延時,不然獲取不到數據
    lists=[]
    #獲取3頁數據一共60首歌
    for i in range(3):
        index=0
        result=proxy.har#獲取所有請求網址
        for entry in result['log']['entries']:
            url=entry['request']['url']
            #找對應的網址,我們要的是,search網址的
            if "app.onenine.cc/m/api/search" in url:
                index+=1
                '''
                因爲每點擊下一頁都會產生一個新的‘search',
                而proxy每次都會把所有的請求獲取到,所以上一個的’search‘也會獲取到
                所以要判斷排除掉,只獲取最新的’search'
                '''
                if index>i:
                    content = entry['response']['content']
                    text=content['text']
                    data=json.loads(text)['data']
                    lists.extend(data['list']) #這裏就是獲取到的json數據.每一次添加20首歌曲的信息進去
                
        #滑到底部
        js="window.scrollTo(0,10000)"#滑動滾動條的js代碼,如果元素不在頁面上顯示,是找不到的,所以滑動,讓下一頁的按鈕顯示出來
        driver.execute_script(js)#執行·js代碼
        #點擊下一頁,獲取更多歌曲
        nextBtn=driver.find_element_by_css_selector('#player>div.aplayer-more')#找到下一頁的按鈕
        nextBtn.click()#發送點擊事件
        time.sleep(1)#延時1秒等待數據加載
    return lists

#用於下載音樂代碼
def download(path,musicName,folder,ext):
    
    r=requests.get(path)
    f=open(folder+'/'+musicName+ext,'wb')
    f.write(r.content)
    f.close()

#在lists例表中提取音樂的下載地址和歌曲名字
def downloadMusic(name,type,lists):
    '''
    name:歌手名字,爲每一個歌手都新建一個文件夾
    type:表示要下載何種音質
    lists:獲取的音樂數據列表
    '''
    isExists=os.path.exists(name)
    if not isExists:
        os.makedirs(name)
    musicName=''
    path=''
    ext=''
    for item in lists:
        for key,value in item.items():
            ext='.mp3'
            key=str(key)
            if key=='name':
                musicName=value
            elif key=='artist':
                musicName=musicName+'-'+value
            elif key=='url_m4a' and type==0:#m4a表示流暢
                path=value
            elif key=='url_128' and type==1:#128是標準
                path=value
            elif key=='url_320' and type==2:#320是高品質
                path=value
            elif key=='url_flac'and type==3:#flac表示無損
                path=value
                ext='.flac'

            if len(path)!=0:
                #每一首歌開啓一個線程去下載
                t = threading.Thread(target=download,args=(path,musicName,name,ext))
                t.start()
                t.join()
                path=''
                musicName=''


#獲取歌單的id和歌單名字
def getSongsSheetId(type):

    id=[]
    sheetNames=[]
    #這裏的                                       limit表示獲取多少個歌單,2表示兩個
    url='https://musicapi.leanapp.cn/top/playlist?limit=2&offset=0&order=hot&cat='+str(type)
    r=requests.get(url);
    data=r.json()
    playlist=data['playlists']
    for item in playlist:
        id.append(item['id'])
        sheetNames.append(item['name'])
        print(id)
    
    return id,sheetNames
#下載歌單的歌曲
def getSongsSheetSongs(type):

    id,sheetNames=getSongsSheetId('日語')#這裏可以做成變量,比如華語,歐美,流行....
    url='http://tool.liumingye.cn/music/?page=audioPage&type=YQD&name=https%3A%2F%2Fmusic.163.com%2F%23%2Fplaylist%3Fid%3D'
    lists=[]
    for i in range(len(id)):
        reqUrl=url+str(id[i])
        proxy.new_har('lists',options={'captureHeaders':True,'captureContent':True})
        driver.get(reqUrl)
        time.sleep(3)
        result=proxy.har
        for entry in result['log']['entries']:
            repUrl=entry['request']['url']
            if "app.onenine.cc/m/api/search" in repUrl:
                content = entry['response']['content']
                text=content['text']
                data=json.loads(text)['data']
                lists=data['list'][:]
                downloadMusic(sheetNames[i],type,lists)

#榜單,榜單比較少所以可以,直接複製data數據然後直接請求
def getMusicListSongs(listType,quality):
    '''
    listType:何種榜單
    quality:何種音質
    '''
    musicLists={'飆升榜':'9a96BCVWtsdySz5JAlEM4ETawT0kPc0PKnwYwhGTCdL_ABeeIChrZMDWVoEkAy8xVORmVw_mS9xE1maSBbzV1ISsUCDTq2_sWMDY2T-KnB7cqYjoRo9EYTROqsURCA',
    '新歌榜':'0eb8XS226D93TYlB6f0YRLwpd88EElQZglIn4CnZo9CUa8yqKtPDCG-pL3GKi_9UoBxJClL04dhhdgjAh-EAho2EkPBct2PF2tJl3F8p2atv3IwIxgPYE9iDGFKD',
    '原創':'9cecTgCD-VsNzA2od9fsyX6c2KpOCdI1VfGRMO26vy4XgwyApkwNtsii9u2La2FS1xoBsJL-nPFpORJ1t9Vd3T0aSzg-0R2LWbS8Yj8wJeYsTVlnnbzFdMeO88OV',
    '熱歌榜':'bfaFw92RlNQ6e3pJQ9tIkNtitPFqTUbXZHDzE8yuiJ-nNo7gpsDldd5R6AQ83eqQCuE9Id2GiS6ycC9Q8wqeB-HNfxC-Q-Mbv-Amdir7AHal8MKsHnZYeiCWKGG',
    'ACG音樂榜':'b91b2EKQBotaf_vdDg3ttFZwGBGJ_uFpbSf8oSjga2p7NKAsNCCHFdj_dywOpfalhPUQz78HAwDNqp127vWDFCP5Ie4pKJEbigRXjr0FwVgPcrhyR5A_RvbJibnGlw',
    '歐美熱歌榜':'10e2UIqDLgcQvg74T9Y6BU0mlxUHvstcHkV5UKHPkSDzyi3TRBORkKAKLtAspirLXvbbTwN4B8Jm_IYzis6kbq1VkFnbcpM28AV54vdGk9Trj1ycEP1FbclVuvxGAXuX',
    '抖音熱歌榜':'b0758Dllkk2pO4ripW44AWtSVQMHsmPgxNcVsJZmQKtNgoIi4dCd0CVRsKOIt69f6TFRffmQhJKCp6dBO9l1UGyiwUks3T5xlxHndE5oOz0NJogMrE49W6e2S8xc-sZb',
    '古典音樂榜':'c6540n5sOF11X1eYySdN9elyUeEtSPKAzU0b-Pm41g5Kpt7WGD7E6pEVyNNMEsMb5OL5f5NC-bAt6Prqvesz9xIwYWDrHCbfAsWldzvzMDJCNybErloRvh98iev2oQ'
    }
    lists=[]
    url='https://app.onenine.cc/m/api/search'
    headers={'content-type':'application/x-www-form-urlencoded; charset=UTF-8','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36'}
    data={'data':'b91b2EKQBotaf_vdDg3ttFZwGBGJ_uFpbSf8oSjga2p7NKAsNCCHFdj_dywOpfalhPUQz78HAwDNqp127vWDFCP5Ie4pKJEbigRXjr0FwVgPcrhyR5A_RvbJibnGlw','v':'2'}
    r=requests.post(url,headers=headers,data=data)#構造post請求
    data=r.json()
    print(len(data))
    lists=data['data']['list']
    
    #data=data['list']

    #print(data)
    downloadMusic(listType,quality,lists)

if __name__=='__main__':
    
    signer=input("輸入歌手名字:")
    lists=getDownloadUrls(signer)
    print('獲取完成,開始下載音樂')
    downloadMusic(signer,0,lists)#0表示下載流暢音質的
    print('下載結束')
    '''
    #這個是下載歌單的
    getSongsSheetSongs(0)
    '''
    '''
    #這個是下載榜單的
    getMusicListSongs('ACG音樂榜',1)
    '''
    # getSongsSheetSongs(1)
    # getMusicListSongs('ACG音樂榜',1)
    # print('end')
    server.stop()
    driver.quit()

效果

 

 

6.在Windows下安裝Browsermob-proxy,可能比較麻煩一點,如果不會,可以找。

   有其他問題也可以隨時留言。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章