歡迎光臨
每天分享高質量文章

Python爬取影片之日本愛情電影

肉身翻牆後,感受一下外面的骯髒世界。牆內的朋友叫苦不迭,由於某些原因,VPN能用的越來越少。上週我的好朋友狗子和我哭訴說自己常用的一個VPN終於也壽終正寢了,要和眾多的日本小姐姐說再見了。作為“外面人”,我還是要幫他一把……

作者:永無鄉
源自:https://blog.csdn.net/JosephPai/article/details/78897370

初探

狗子給我的網站還算良心,只跳了五個彈窗就消停了。 

然後看到的就是各種穿不起衣服的女生的賣慘影片,我趕緊閉上眼睛,默唸了幾句我佛慈悲。 

Tokyo真的有那麼hot? 

給狗子發了一張大的截圖,狗子用塗鴉給我圈出了其中一個。 

我和狗子說“等著吧” 

(放心網站截圖我是打了碼也不敢放的。。。)

點進去之後,可以線上播放。 

右下角有一個 Download 按鈕,點選之後需要註冊付費。 

當時我就火了,這種賣慘影片毒害我兄弟精神,還敢收錢?! 

自己動手,豐衣足食!

環境 & 依賴

Win10 64bit 

IDE: PyCharm 

Python 3.6 

python-site-packegs: requests + BeautifulSoup + lxml + re + m3u8

在已經安裝pip的環境下均可直接命令列安裝

網站解析

將連結複製到Chrome瀏覽器開啟 

(我平時用獵豹,也是Chrome核心,介面比較舒服,但是這個時候必須大喊一聲谷歌大法好) 

選單——更多工具——開發者選項(或者快捷鍵F12)進入Chrome內建的開發者樣式 

大概介面是這樣

(唉打碼真的累。。。。) 

然後,根據提示,逐層深入標簽找到影片所在具體位置 

這個網站存放的位置是 …->flash->video_container->video-player 

顯然影片就放在這個這個video-player中 

在這個標簽中,有一個名字為 source 的連結,src=”http://#%@就不告訴你#@¥” 

Easy好吧! 

這點小把戲還難得到我?我已經準備和狗子要紅包了 

複製該連結到位址列貼上並轉到,然後,神奇的一幕出現了!!

What???

這是什麼???

為啥這麼小???

科普概念如上,那也就是說,m3u8記錄了真實的影片所在的地址。

Network Traffic

想要從原始碼直接獲得真實下載地址怕是行不通了。 

這時候再和我一起讀“谷歌大法好!” 

很簡單,瀏覽器在伺服器中Get到影片呈現到我們面前,那這個過程必定經過瞭解析這一步。 

那我們也可以利用瀏覽器這個功能來進行解析

依舊在開發者樣式,最上面一行的導航欄,剛剛我們在Elements選項卡,現在切換到Network 

我們監聽影片播放的時候的封包應該就可以得到真實的影片地址了,試試看!

我們驚喜的發現,一個又一個的 .ts 檔案正在載入了 

(如果在圖片裡發現任何url請友情提醒我謝謝不然怕是水錶難保)

 

知識點!這都是知識點!(敲黑板!)

點開其中的一個.ts檔案看一下 

這裡可以看到請求頭,雖然url被我走心的碼掉了,但這就是真實的影片地址了

複製這個URL到位址列,下載 

9s。。。。。 

每一個小影片只有9s,難道要一個又一個的去複製嗎?

影片片段爬取

答案是當然不用。 

這裡我們要請出網路資料採集界的裝逼王:Python爬蟲!!!

首先進行初始化,包括路徑設定,請求頭的偽裝等。

採集部分主要是將requests的get方法放到了for迴圈當中 

這樣做可行的原因在於,在Network監聽的圖中我們可以看到.ts檔案的命名是具有規律的 seg-i-v1-a1,將i作為迴圈數

那麼問題又來了,我怎麼知道迴圈什麼時候結束呢?也就是說我怎麼知道i的大小呢? 

等等,我好像記得在影片播放的框框右下角有時間來著? 

在開發者樣式中再次回到Element選項卡,定位到影片框右下角的時間,標簽為duration,這裡的時間格式是 時:分:秒格式的,我們可以計算得到總時長的秒數 

但是呢,這樣需要我們先獲取這個時間,然後再進行字串的拆解,再進行數學運算,太複雜了吧,狗子已經在微信催我了 

Ctrl+F全域性搜尋duration 

Yes!!!

好了,可以點選執行然後去喝杯咖啡,哦不,我喜歡喝茶。

一杯茶的功夫,回來之後已經下載完成。我開啟檔案夾check一下,發現從編號312之後的clip都是隻有573位元組,開啟播放的話,顯示的是資料損壞。 

沒關係,從312開始繼續下載吧。然而下載得到的結果還是一樣的573位元組,而且下了兩百多個之後出現了拒絕訪問錯誤。

動態代理

顯然我的IP被封了。之前的多個小專案,或是因為網站防護不夠嚴格,或是因為資料條目數量較少,一直沒有遇到過這種情況,這次的資料量增加,面對這種情況採取兩種措施,一種是休眠策略,另一種是動態代理。現在我的IP已經被封了,所以休眠也為時已晚,必須採用動態IP了。 

主要程式碼如下所示 

合併檔案

然後,我們得到了幾百個9s的.ts小影片

然後,在cmd命令列下,我們進入到這些小影片所在的路徑 

執行

copy/b %s\*.ts %s\new.ts

很快,我們就得到了合成好的影片檔案 

當然這個前提是這幾百個.ts檔案是按順序排列好的。

成果如下

最佳化—呼叫DOS命令 + 解析m3u8

為了盡可能的減少人的操作,讓程式做更多的事 

我們要把儘量多的操作寫在code中

取用os模組進行檔案夾切換,在程式中直接執行合併命令 

並且,在判斷合併完成後,使用清除幾百個ts檔案

這樣,我們執行程式後,就真的可以去喝一杯茶,回來之後看到的就是沒有任何多餘的一個完整的最終影片

也就是說,要獲得一個完整的影片,我們現在需要輸入影片網頁連結,還需要使用chrome的network解析得到真實下載地址。第二個部分顯然不夠友好,還有提升空間。

所以第一個嘗試是,可不可以有一個工具或者一個包能嗅探到指定網頁的network traffic,因為我們剛剛已經看到真實地址其實就在requestHeader中,關鍵在於怎樣讓程式自動獲取。

查閱資料後,嘗試了Selenium + PhantomJS的組合模擬瀏覽器訪問,用一個叫做browsermobProxy的工具嗅探儲存HAR(HTTP archive)。在這個上面花費了不少時間,但是關於browsermobProxy的資料實在是太少了,即使是在google上,搜到的也都是基於java的一些資料,面向的python的API也是很久沒有更新維護了。此路不通。

在放棄之前,我又看一篇網站的原始碼,再次把目光投向了m3u8,上面講到這個檔案應該是包含檔案真實地址的索引,索引能不能把在這上面做些文章呢?

Python不愧是萬金油語言,packages多到令人髮指,m3u8處理也是早就有熟肉。

pip install m3u8

這是一個比較小眾的包,沒有什麼手冊,只能自己讀原始碼。

這個class中已經封裝好了不少可以直接供使用的資料型別,回頭抽時間可以寫一寫這個包的手冊。

現在,我們可以從requests獲取的原始碼中,首先找到m3u8的下載地址,首先下載到本地,然後用m3u8包進行解析,獲取真實下載地址。

並且,解析可以得到所有地址,意味著可以省略上面的獲取duration計算碎片數目的步驟。

最終

最終,我們現在終於可以,把影片網頁連結丟進url中,點選執行,然後就可以去喝茶了。 

再來總結一下實現這個的幾個關鍵點: 

– 網頁解析 

– m3u8解析 

– 動態代理設定 

– DOS命令列的呼叫

動手是最好的老師,尤其這種網站,兼具趣味性和挑戰性,就是身體一天不如一天。。。

完整程式碼

# -*- coding:utf-8 -*-
import requests
from bs4 import BeautifulSoup
import os
import lxml
import time
import random
import re
import m3u8

class ViedeoCrawler():
    def __init__(self):
        self.url = ""
        self.down_path = r"F:\Spider\VideoSpider\DOWN"
        self.final_path = r"F:\Spider\VideoSpider\FINAL"
        try:
            self.name = re.findall(r'/[A-Za-z]*-[0-9]*',self.url)[0][1:]
        except:
            self.name = "uncensord"
        self.essay-headers = {
            'Connection''Keep-Alive',
            'Accept''text/html, application/xhtml+xml, */*',
            'Accept-Language''en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
            'User-Agent':'Mozilla/5.0 (Linux; U; Android 6.0; zh-CN; MZ-m2 note Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/40.0.2214.89 MZBrowser/6.5.506 UWS/2.10.1.22 Mobile Safari/537.36'
        }

    def get_ip_list(self):
        print("正在獲取代理串列...")
        url = 'http://www.xicidaili.com/nn/'
        html = requests.get(url=url, essay-headers=self.essay-headers).text
        soup = BeautifulSoup(html, 'lxml')
        ips = soup.find(id='ip_list').find_all('tr')
        ip_list = []
        for i in range(1, len(ips)):
            ip_info = ips[i]
            tds = ip_info.find_all('td')
            ip_list.append(tds[1].text + ':' + tds[2].text)
        print("代理串列抓取成功.")
        return ip_list

    def get_random_ip(self,ip_list):
        print("正在設定隨機代理...")
        proxy_list = []
        for ip in ip_list:
            proxy_list.append('http://' + ip)
        proxy_ip = random.choice(proxy_list)
        proxies = {'http': proxy_ip}
        print("代理設定成功.")
        return proxies

    def get_uri_from_m3u8(self,realAdr):
        print("正在解析真實下載地址...")
        with open('temp.m3u8''wb'as file:
            file.write(requests.get(realAdr).content)
        m3u8Obj = m3u8.load('temp.m3u8')
        print("解析完成.")
        return m3u8Obj.segments

    def run(self):
        print("Start!")
        start_time = time.time()
        os.chdir(self.down_path)
        html = requests.get(self.url).text
        bsObj = BeautifulSoup(html, 'lxml')
        realAdr = bsObj.find(id="video-player").find("source")['src']

        # duration = bsObj.find('meta', {'property': "video:duration"})['content'].replace("\"", "")
        # limit = int(duration) // 10 + 3

        ip_list = self.get_ip_list()
        proxies = self.get_random_ip(ip_list)
        uriList = self.get_uri_from_m3u8(realAdr)
        i = 1   # count
        for key in uriList:
            if i%50==0:
                print("休眠10s")
                time.sleep(10)
            if i%120==0:
                print("更換代理IP")
                proxies = self.get_random_ip(ip_list)
            try:
                resp = requests.get(key.uri, essay-headers = self.essay-headers, proxies=proxies)
            except Exception as e:
                print(e)
                return
            if i 10:
                name = ('clip00%d.ts' % i)
            elif i > 100:
                name = ('clip%d.ts' % i)
            else:
                name = ('clip0%d.ts' % i)
            with open(name,'wb'as f:
                f.write(resp.content)
                print('正在下載clip%d' % i)
            i = i+1
        print("下載完成!總共耗時 %d s" % (time.time()-start_time))
        print("接下來進行合併……")
        os.system('copy/b %s\\*.ts %s\\%s.ts' % (self.down_path,self.final_path, self.name))
        print("合併完成,請您欣賞!")
        y = input("請檢查檔案完整性,並確認是否要刪除碎片源檔案?(y/n)")
        if y=='y':
            files = os.listdir(self.down_path)
            for filena in files:
                del_file = self.down_path + '\\' + filena
                os.remove(del_file)
            print("碎片檔案已經刪除完成")
        else:
            print("不刪除,程式結束。")

if __name__=='__main__':
    crawler = ViedeoCrawler()
    crawler.run()

贊(0)

分享創造快樂