1. 程式人生 > >Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

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

初探

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

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

Tokyo真的有那麼hot?

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

我和狗子說“等著吧”

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

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

自己動手,豐衣足食!

環境 & 依賴

Win10 64bit

IDE: PyCharm

Python 3.6

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

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

網站解析

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

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

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

大概介面是這樣

進群:548377875   即可獲取數十套PDF以及大量的學習資料哦!

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

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

然後,根據提示,逐層深入標籤找到視訊所在具體位置

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

顯然視訊就放在這個這個video-player中

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

Easy好吧!

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

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

 

What???

這是什麼???

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

為啥這麼小???

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

Network Traffic

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

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

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

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

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

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

 

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

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

9s。。。。。

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

視訊片段爬取

答案是當然不用。

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

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

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

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

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

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

Ctrl+F全域性搜尋duration

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

Yes!!!

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

 

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

動態代理

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

主要程式碼如下所示

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

合併檔案

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

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

執行

copy/b %s*.ts %s ew.ts

很快,我們就得到了合成好的視訊檔案

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

成果如下

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

優化—呼叫DOS命令 + 解析m3u8

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

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

引用os模組進行資料夾切換,在程式中直接執行合併命令

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

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

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

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

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

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

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

pip install m3u8

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

Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!

 

 

這個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:SpiderVideoSpiderDOWN"
 self.final_path = r"F:SpiderVideoSpiderFINAL"
 try:
 self.name = re.findall(r'/[A-Za-z]*-[0-9]*',self.url)[0][1:]
 except:
 self.name = "uncensord"
 self.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, headers=self.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, headers = self.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()