肉身翻牆後,感受一下外面的骯髒世界。牆內的朋友叫苦不迭,由於某些原因,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()