1. 程式人生 > >Python爬取qq音樂的過程例項

Python爬取qq音樂的過程例項

一、前言


 qq music上的音樂還是不少的,有些時候想要下載好聽的音樂,但有每次在網頁下載都是煩人的登入什麼的。於是,來了個qqmusic的爬蟲。至少我覺得for迴圈爬蟲,最核心的應該就是找到待爬元素所在url吧。

 

二、Python爬取QQ音樂單曲


爬蟲步驟

1.確定目標

首先我們要明確目標,本次爬取的是QQ音樂歌手劉德華的單曲。

(百度百科)->分析目標(策略:url格式(範圍)、資料格式、網頁編碼)->編寫程式碼->執行爬蟲

2.分析目標

歌曲連結:

從左邊的截圖可以知道單曲採用分頁的方式排列歌曲資訊,每頁顯示30條,總共30頁。點選頁碼或者最右邊的">"會跳轉到下一頁,瀏覽器會向伺服器傳送ajax非同步請求,從連結可以看到begin和num引數,分別代表起始歌曲下標(截圖是第2頁,起始下標是30)和一頁返回30條,伺服器響應返回json格式的歌曲資訊(MusicJsonCallbacksinger_track({"code":0,"data":{"list":[{"Flisten_count1":......]})),如果只是單獨想獲取歌曲資訊,可以直接拼接連結請求和解析返回的json格式的資料。這裡不採用直接解析資料格式的方法,我採用的是Python Selenium方式,每獲取和解析完一頁的單曲資訊,點選 ">" 跳轉到下一頁繼續解析,直至解析並記錄所有的單曲資訊。最後請求每個單曲的連結,獲取詳細的單曲資訊。

 

右邊的截圖是網頁的原始碼,所有歌曲資訊都在類名為mod_songlist的div浮層裡面,類名為songlist_list的無序列表ul下,每個子元素li展示一個單曲,類名為songlist__album下的a標籤,包含單曲的連結,名稱和時長等。

 

3.編寫程式碼

1)下載網頁內容,這裡使用Python 的Urllib標準庫,自己封裝了一個download方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

def download(url, user_agent=

'wswp', num_retries=2):

    if url is None:

        return None

    print('Downloading:', url)

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'

}

    request = urllib.request.Request(url, headers=headers)  # 設定使用者代理wswp(Web Scraping with Python)

    try:

        html = urllib.request.urlopen(request).read().decode('utf-8')

    except urllib.error.URLError as e:

        print('Downloading Error:', e.reason)

        html = None

        if num_retries > 0:

            if hasattr(e, 'code') and 500 <= e.code < 600:

                # retry when return code is 5xx HTTP erros

                return download(url, num_retries-1)  # 請求失敗,預設重試2次,

    return html


2)解析網頁內容,這裡使用第三方外掛BeautifulSoup,具體可以參考BeautifulSoup API 。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

def music_scrapter(html, page_num=0):

    try:

        soup = BeautifulSoup(html, 'html.parser')

        mod_songlist_div = soup.find_all('div', class_='mod_songlist')

        songlist_ul = mod_songlist_div[1].find('ul', class_='songlist__list')

        '''開始解析li歌曲資訊'''

        lis = songlist_ul.find_all('li')

        for li in lis:

            a = li.find('div', class_='songlist__album').find('a')

            music_url = a['href']  # 單曲連結

            urls.add_new_url(music_url)  # 儲存單曲連結

            # print('music_url:{0} '.format(music_url))

        print('total music link num:%s' % len(urls.new_urls))

        next_page(page_num+1)

    except TimeoutException as err:

        print('解析網頁出錯:', err.args)

        return next_page(page_num + 1)

    return None

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

def get_music():

     try:

        while urls.has_new_url():

            # print('urls count:%s' % len(urls.new_urls))

            '''跳轉到歌曲連結,獲取歌曲詳情'''

            new_music_url = urls.get_new_url()

            print('url leave count:%s' % str( len(urls.new_urls) - 1))

            html_data_info = download(new_music_url)

            # 下載網頁失敗,直接進入下一迴圈,避免程式中斷

            if html_data_info is None:

                continue

            soup_data_info = BeautifulSoup(html_data_info, 'html.parser')

            if soup_data_info.find('div', class_='none_txt') is not None:

                print(new_music_url, '   對不起,由於版權原因,暫無法檢視該專輯!')

                continue

            mod_songlist_div = soup_data_info.find('div', class_='mod_songlist')

            songlist_ul = mod_songlist_div.find('ul', class_='songlist__list')

            lis = songlist_ul.find_all('li')

            del lis[0]  # 刪除第一個li

            # print('len(lis):$s' % len(lis))

            for li in lis:

                a_songname_txt = li.find('div', class_='songlist__songname').find('span', class_='songlist__songname_txt').find('a')

                if 'https' not in a_songname_txt['href']:  #如果單曲連結不包含協議頭,加上

                    song_url = 'https:' + a_songname_txt['href']

                song_name = a_songname_txt['title']

                singer_name = li.find('div', class_='songlist__artist').find('a').get_text()

                song_time =li.find('div', class_='songlist__time').get_text()

                music_info = {}

                music_info['song_name'] = song_name

                music_info['song_url'] = song_url

                music_info['singer_name'] = singer_name

                music_info['song_time'] = song_time

                collect_data(music_info)

     except Exception as err:  # 如果解析異常,跳過

         print('Downloading or parse music information error continue:', err.args)

4.執行爬蟲

1

<span style="font-size: 16px;">爬蟲跑起來了,一頁一頁地去爬取專輯的連結,並儲存到集合中,最後通過get_music()方法獲取單曲的名稱,連結,歌手名稱和時長並儲存到Excel檔案中。</span><br><span style="font-size: 14px;"><img src="http://img.php.cn/upload/article/000/000/001/a1138f33f00f8d95b52fbfe06e562d24-4.png" alt="" width="748" height="483"><strong><img src="http://img.php.cn/upload/article/000/000/001/9282b5f7a1dc4a90cee186c16d036272-5.png" alt=""></strong></span>

1

<br>

三、Python爬取QQ音樂單曲總結


1.單曲採用的是分頁方式,切換下一頁是通過非同步ajax請求從伺服器獲取json格式的資料並渲染到頁面,瀏覽器位址列連結是不變的,不能通過拼接連結來請求。一開始想過都通過Python Urllib庫來模擬ajax請求,後來想想還是用Selenium。Selenium能夠很好地模擬瀏覽器真實的操作,頁面元素定位也很方便,模擬單擊下一頁,不斷地切換單曲分頁,再通過BeautifulSoup解析網頁原始碼,獲取單曲資訊。

 

2.url連結管理器,採用集合資料結構來儲存單曲連結,為什麼要使用集合?因為多個單曲可能來自同一專輯(專輯網址一樣),這樣可以減少請求次數。

1

<span style="font-size: 14px;">class UrlManager(object):</span><br><span style="font-size: 14px;">    def __init__(self):</span><br><span style="font-size: 14px;">        self.new_urls = set()  # 使用集合資料結構,過濾重複元素</span><br><span style="font-size: 14px;">        self.old_urls = set()  # 使用集合資料結構,過濾重複元素</span>

1

<span style="font-size: 14px;">    def add_new_url(self, url):</span><br><span style="font-size: 14px;">        if url is None:</span><br><span style="font-size: 14px;">            return</span><br><span style="font-size: 14px;">        if url not in self.new_urls and url not in self.old_urls:</span><br><span style="font-size: 14px;">            self.new_urls.add(url)</span><br><br><span style="font-size: 14px;">    def add_new_urls(self, urls):</span><br><span style="font-size: 14px;">        if urls is None or len(urls) == 0:</span><br><span style="font-size: 14px;">            return</span><br><span style="font-size: 14px;">        for url in urls:</span><br><span style="font-size: 14px;">            self.add_new_url(url)</span><br><br><span style="font-size: 14px;">    def has_new_url(self):</span><br><span style="font-size: 14px;">        return len(self.new_urls) != 0</span><br><br><span style="font-size: 14px;">    def get_new_url(self):</span><br><span style="font-size: 14px;">        new_url = self.new_urls.pop()</span><br><span style="font-size: 14px;">        self.old_urls.add(new_url)</span><br><span style="font-size: 14px;">        return new_url<br><br></span>

3.通過Python第三方外掛openpyxl讀寫Excel十分方便,把單曲資訊通過Excel檔案可以很好地儲存起來。

1

<span style="font-size: 14px;">def write_to_excel(self, content):</span><br><span style="font-size: 14px;">    try:</span><br><span style="font-size: 14px;">        for row in content:</span><br><span style="font-size: 14px;">            self.workSheet.append([row['song_name'], row['song_url'], row['singer_name'], row['song_time']])</span><br><span style="font-size: 14px;">        self.workBook.save(self.excelName)  # 儲存單曲資訊到Excel檔案</span><br><span style="font-size: 14px;">    except Exception as arr:</span><br><span style="font-size: 14px;">        print('write to excel error', arr.args)</span><br><br>

四、後語


最後還是要慶祝下,畢竟成功把QQ音樂的單曲資訊爬取下來了。本次能夠成功爬取單曲,Selenium功不可沒,這次只是用到了selenium一些簡單的功能,後續會更加深入學習Selenium,不僅在爬蟲方面還有UI自動化。

後續還需要優化的點:

1.下載的連結比較多,一個一個下載起來比較慢,後面打算用多執行緒併發下載。

2.下載速度過快,為了避免伺服器禁用IP,後面還要對於同一域名訪問過於頻繁的問題,有個等待機制,每個請求之間有個等待間隔。

3. 解析網頁是一個重要的過程,可以採用正則表示式,BeautifulSoup和lxml,目前採用的是BeautifulSoup庫, 在效率方面,BeautifulSoup沒lxml效率高,後面會嘗試採用lxml。