python爬蟲練習--爬取虎牙主播原畫視訊

不離鞘發表於2020-11-28

此文章和程式碼僅供學習交流,請勿他用


前言

  • 使用的python庫:os、requests、bs4
  • 涉及動態資料的爬取
  • 下載視訊時使用斷點續傳技術

爬取過程

開啟虎牙主播的主頁看一下
在這裡插入圖片描述
在這裡插入圖片描述

每一個主播的視訊頁面中,都會對應一個id值。通過這個id值就可以找到虎牙主播的視訊主頁。

我們可以把程式碼封裝起來,想要爬取哪一個主播的視訊,只需要修改主播ID值就好了。

class HuyaSpider:
    def __init__(self,host_id):
        self.url = 'https://v.huya.com/u/{}/video.html'.format(host_id)
    
    def get_html(self):
        pass
    
    def save_file(self):
        pass
    
    def spider_start(self):
        pass

h = HuyaSpider(1199529586692)

大致框架先列出來:

  • 定義一個HuyaSpider類,在例項化的時候需要傳入一個引數:主播ID
  • get_html(self)方法用來獲取網頁內容
  • save_file(self)方法用來把視訊儲存至檔案中
  • spider_start(self)方法用來啟動爬蟲

在這裡插入圖片描述
初始工作已做好,開始分析網頁


檢視主播視訊網頁的原始碼,找到視訊列表:
在這裡插入圖片描述
每一個視訊的網頁地址在原始碼上找得到(經過測試,通過requests請求能夠真實得到)
在這裡插入圖片描述

接下來進入一個視訊網頁看看
在這裡插入圖片描述

分析原始碼,目的是要拿到視訊的具體地址
在這裡插入圖片描述
在瀏覽器檢視的網頁原始碼中,找到了視訊的url,看起來好像都是挺簡單的,其實不然。

經過測試,requests請求得到的原始碼中,是不包含上面方框圈中的<video> </video>標籤的,也就是說從requests請求得到的原始碼中是得不到視訊的真實url。

因為,這是動態載入的資料,真正的視訊url不會放在源網頁中,而是通過別的請求得到之後再放進網頁裡。
所以,現在我們就要找到這個所謂的別的請求,拿到視訊的url。

當然我們可以用selenium來實現抓取動態資料(selenium實現,本文不作講解)

在瀏覽器原始碼中把視訊url拷貝一下,到瀏覽器開發者工具裡的network裡面搜尋一下,
在這裡插入圖片描述
在搜尋結果中有3條請求資料包含視訊url。

仔細檢視一下這些請求的請求體和返回的內容
在這裡插入圖片描述
這是json格式的資料,3號綠色方框中,在definitions的值是一個列表,列表裡有3個元素,每個元素中又是好幾個的字典,包含畫質,視訊的高度和寬度,視訊的大小,最重要的還包含視訊的m3u8連結和url。

現在,我們在這個請求資料中不僅找到原畫視訊的url,而且超清,流暢的視訊url都找到了。

只要我們能構造這個請求就可以搞定了
在這裡插入圖片描述

Request URL :
https://liveapi.huya.com/moment/getMomentContent?callback=jQuery1124009457286560941736_1606489913207&videoId=269035006&_=1606489913213

這個請求是由callback、videoId、uid 組成的,
vedioId很好理解,大概就是視訊的id,另外兩個callback和uid還不知道是什麼,刪掉試試??

刪掉之後:https://liveapi.huya.com/moment/getMomentContent?videoId=269035006
試訪問一下這個連結
在這裡插入圖片描述

我們還是拿到了這個資料,這說明videoId才是構造url的關鍵欄位,另外兩個不是必須的。
那麼這個videoId=269035006是如何來的呢?
在這裡插入圖片描述
這個videoId不就正好在那嘛。

分析完了哦
在這裡插入圖片描述


從頭到尾捋一下爬蟲思路:

  1. 通過主播id構造出主播視訊網頁的url
  2. 從網頁原始碼中獲取每個視訊的網頁url,並記住videoId值,
  3. 通過videoId構造url,請求得到json格式的資料
  4. 從json格式的資料中拿到真正視訊的url,接著下載視訊。
  5. 下載視訊時採用斷點續傳的方式

下載視訊和斷點續傳這裡就不作講解了。

有關斷點續傳的內容可瀏覽:https://blog.csdn.net/FujLiny/article/details/110098858
斷點續傳可避免重複下載同一個視訊以及能夠接著上一次下載的地方繼續下載


爬取效果

(演示的是爬取別的主播)
在這裡插入圖片描述


完整程式碼

# Author:FuJLiny
# CSDN blog homepage:https://blog.csdn.net/FujLiny
# ------version 1-1,Update time:2020/11/28------
import os
import requests
from bs4 import BeautifulSoup


class HuyaSpider:
    def __init__(self, Id):
        self.url = 'https://v.huya.com/u/{}/video.html'.format(Id)
        self.place = 'F:/'  # 儲存路徑
        self.videoAll = []

    def get_html(self):
        soup = BeautifulSoup(requests.get(self.url).text, 'lxml')
        vc = soup.select('a[class="statpid selected"]')[0].text[3:-1]
        vPage = (eval(vc) // 15) + 1  # (視訊總數 // 15) + 1 = 視訊總頁數

        # 遍歷每一頁
        for i in range(1,vPage+1):
            url = self.url + '?sort=news&p=%s' % i
            soup = BeautifulSoup(requests.get(url).text, 'lxml')
            for item in soup.select('div[class="content-list"] ul li a'):
                videoId = item['href'].split('/')[-1][:-5]
                dataUrl = 'https://liveapi.huya.com/moment/getMomentContent?&videoId=%s' % videoId
                data = requests.get(dataUrl).json()
                vTitle = data['data']['moment']['title']
                vSize = data['data']['moment']['videoInfo']['definitions'][0]['size']
                vUrl = data['data']['moment']['videoInfo']['definitions'][0]['url']

                # 把獲取到的視訊標題、視訊大小、視訊url儲存到一個字典中,並把該字典儲存到列表
                self.videoAll.append({'title':vTitle, 'size':vSize, 'url':vUrl})
                print('\r獲取到%s個視訊連結'%len(self.videoAll),end='')

    def save_file(self, name, size, url):
        name = self.place + '%s.mp4' % name
        size = eval(size)
        try:
            # 構造斷點續傳的headers
            # 關於斷點續傳的內容可瀏覽文章:https://blog.csdn.net/FujLiny/article/details/110098858
            fileSize = os.path.getsize(name)
            if fileSize < size:
                head = {'Range': 'bytes=%d-' % fileSize}
                mode = 'ab'
            else:
                return
        except FileNotFoundError:
            fileSize = 0
            head = {'Range': 'bytes=%d-' % 0}
            mode = 'wb'

        print('\n正在下載:', name)
        r = requests.get(url, headers=head, stream=True)
        with open(name, mode=mode)as fp:
            completeSize = 0
            for item in r.iter_content(200000):
                fp.write(item)
                completeSize += len(item)
                dt = completeSize / (size-fileSize)
                print('\r下載進度:'+'*'*(int(dt*50))+'%.2f%%'%(dt*100), end='')

    def spider_start(self):
        self.get_html()
        print('\n該主播共有%s個視訊,開始爬取:'%len(self.videoAll))
        for item in self.videoAll:
            self.save_file(item['title'],item['size'], item['url'])


if __name__ == '__main__':
    h = HuyaSpider(1199529586692)
    h.spider_start()

相關文章