Python爬蟲不管什麼妖魔鬼怪都能爬!爬取日本愛情電影!節制點!
肉身翻牆後,感受一下外面的骯髒世界。牆內的朋友叫苦不迭,由於某些原因,VPN能用的越來越少。上週我的好朋友狗子和我哭訴說自己常用的一個VPN終於也壽終正寢了,要和眾多的日本小姐姐說再見了。作為“外面人”,我還是要幫他一把……
初探
狗子給我的網站還算良心,只跳了五個彈窗就消停了。
然後看到的就是各種穿不起衣服的女生的賣慘視訊,我趕緊閉上眼睛,默唸了幾句我佛慈悲。
Tokyo真的有那麼hot?
給狗子發了一張大的截圖,狗子用塗鴉給我圈出了其中一個。
我和狗子說“等著吧”
(放心網站截圖我是打了碼也不敢放的。。。)
點進去之後,可以線上播放。
右下角有一個 Download 按鈕,點選之後需要註冊付費。
當時我就火了,這種賣慘視訊毒害我兄弟精神,還敢收錢?!
自己動手,豐衣足食!
環境 & 依賴
Win10 64bit
IDE: PyCharm
Python 3.6
python-site-packegs: requests + BeautifulSoup + lxml + re + m3u8
在已經安裝pip的環境下均可直接命令列安裝
網站解析
將連結複製到Chrome瀏覽器開啟
(我平時用獵豹,也是Chrome核心,介面比較舒服,但是這個時候必須大喊一聲谷歌大法好)
選單——更多工具——開發者選項(或者快捷鍵F12)進入Chrome內建的開發者模式
大概介面是這樣
進群:548377875 即可獲取數十套PDF以及大量的學習資料哦!
(唉打碼真的累。。。。)
然後,根據提示,逐層深入標籤找到視訊所在具體位置
這個網站存放的位置是 …->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 ew.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: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()