使用Python快速實現抖音無水印視頻自動下載微信機器人

前言

最近短視頻是越來越火,也出現了很多自媒體公司。剛好我有個朋友就是做短視頻這塊的業務,因爲前期發展需要,避免不了使用很多營銷號通過搬運別人視頻的方式來吸引一些粉絲。比如很火的抖音短視頻,搬運一般需要的是沒有水印的視頻,所以就產生一個去水印的服務。說是去水印倒不如說是下載無水印的視頻,因爲本身並沒有涉及到任何去水印的技術,僅僅是根據接口返回的數據裏面提取出來無水印視頻的下載地址然後下載回來就完事兒了。這篇水文就來快速實現一個自動下載抖音無水印視頻的微信機器人。

市面上有很多提供下載抖音無水印視頻的服務,一般包月也就20塊錢左右,實際上原理非常簡單,希望朋友們看完這篇文章能自己動手實現下載抖音無水印視頻的功能。

聲明

本文並不是爲了鼓勵大家完全去搬運別人的視頻,畢竟別人辛苦拍攝出來的視頻,如果你喜歡他的視頻,你可以下載回來自己觀看,未徵得視頻原作者同意前請不要肆意傳播,這是對視頻拍攝者最基本的尊重。

分析

抖音短視頻App本身除了app客戶端以外,還有web網頁端,雖然功能比較簡陋,都是看視頻是足夠了。市面上大多數人下載無水印視頻基本上都是基於web端,而在觀察了很多開源項目之後發現,原理其實非常簡單,之所以能夠下載無水印視頻,是因爲抖音web端的機制本身就有問題。

抖音短視頻的web端,在看視頻的時候都是會帶一個抖音的logo,如果直接抓取這個視頻地址,下載回來的視頻也是帶有logo的,不過可以直接替換視頻地址裏面的參數就可以實現無水印。

既然拿到了視頻地址,那麼直接下載不就完事了麼?No,下載視頻必須帶上一個簽名,否則是拒絕下載的,那麼只要拿到這個簽名就可以了。

網上很多開源代碼用了一個很好的辦法,直接執行官方計算簽名的js,把結果輸出這樣就得到了簽名字符串,所以就直接拿來用。

辦法雖好,但是博主今天寫的並不是這種辦法,而是一種更加簡單的方法,都不用計算簽名,幾行代碼就搞定。

說了這麼多,還是說說整個流程吧,首先打開抖音App,隨便找一個視頻,點右下角那個轉發分享按鈕

在彈出的菜單裏面選擇 

這個時候就拿到了這個短視頻的一個鏈接

#在抖音,記錄美好生活#屏幕前的女孩,願你往後餘生,遇見的都是溫暖。#這也太好看了吧 都給我用這個特效 https://v.douyin.com/CtCgMT/ 複製此鏈接,打開【抖音短視頻】,直接觀看視頻!

這個鏈接是可以直接通過瀏覽器打開視頻的,我們把這個鏈接放到電腦上通過瀏覽器打開注意url地址欄,已經重定向到一個新的地址

https://www.iesdouyin.com/share/video/6747483623986908429/?region=CN∣=6714175953607740173&u_code=145f873fm&titleType=title&utm_source=copy_link&utm_campaign=client_share&utm_medium=android&app=aweme

這個新的地址裏面包含真實的視頻id爲 6747483623986908429 還有一個mid猜測是這個視頻的媒體id爲 6714175953607740173 其他的參數先不用管,使用開發者工具查看頁面元素的視頻播放地址發現一個url爲

https://aweme.snssdk.com/aweme/v1/playwm/?s_vid=93f1b41336a8b7a442dbf1c29c6bbc560f673350224950d4f1ad574051b6150ff882f3e79be07a356f310ec5292afc7323c3edec04e9e40016866270650f2d06&line=0

這個url地址裏面應該就是播放視頻的媒體流,我們使用開發者工具隱藏頭部那個打開app的按鈕,然後點擊播放,這個時候我們看到視頻上是有logo水印的

這個地址肯定不是我們想要的視頻地址,再仔細觀察這個地址裏面有個playwm,注意,這裏是個關鍵點,其他開發者大多數就是從這裏入手的,只需要把playwm改成play就得到了無水印的視頻地址,我們改完之後再點擊播放,是不是視頻就沒有水印了

這看起來也太簡單了吧,完全沒有技術含量啊,難道博主我這麼快就水完一篇文章了?不不不,博主我不是那樣的人!!

看似非常簡單,只需要得到前面的s_vid參數就可以了,查看代碼後發現,這個s_vid是一個類似簽名的東西,由web頁面的js生成,至於生成的方法就不多說了,網上很多人都已經給出了方法。

這不是本文的重點,本文並不是通過這個方法下載無水印的視頻,而是使用更簡單的方法。

捷徑

打開開發者工具中的Network,找到item_ids的那個接口

觀察返回的json數據,整理一下如下所示(在此感謝 https://tool.lu/json )

{ "status_code": 0, "item_list": [ { "video_labels": null, "comment_list": null, "label_top_text": null, "desc": "屏幕前的女孩,願你往後餘生,遇見的都是溫暖。#這也太好看了吧 都給我用這個特效", "cha_list": null, "video": { "cover": { "uri": "tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e", "url_list": [ "https://p1-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large", "https://p3-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large", "https://p9-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large" ] }, "ratio": "540p", "play_addr_lowbr": { "uri": "v0200ffa0000bmhu16581shqv3ocmidg", "url_list": [ "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0∶=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1", "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1∶=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1" ] }, "duration": 12712, "play_addr": { "uri": "v0200ffa0000bmhu16581shqv3ocmidg", "url_list": [ "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0∶=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1", "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1∶=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1" ] }, "height": 1280, "width": 720, "dynamic_cover": { "uri": "tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b", "url_list": [ "https://p1-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large", "https://p3-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large", "https://p9-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large" ] }, "origin_cover": { "uri": "large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874", "url_list": [ "http://p3-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large", "http://p1-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large", "http://p9-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large" ] }, "download_addr": { "uri": "v0200ffa0000bmhu16581shqv3ocmidg", "url_list": [ "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0∶=540p&watermark=1&media_type=4&vr_type=0&improve_bitrate=0&logo_name=aweme", "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1∶=540p&watermark=1&media_type=4&vr_type=0&improve_bitrate=0&logo_name=aweme" ] }, "has_watermark": true, "bit_rate": null, "vid": "v0200ffa0000bmhu16581shqv3ocmidg" }, "duration": 12712, "image_infos": null, "video_text": null, "promotions": null, "long_video": null, "aweme_id": "6747483623986908429", "statistics": { "comment_count": 27, "digg_count": 70, "aweme_id": "6747483623986908429" }, "text_extra": null, "position": null, "uniqid_position": null, "geofencing": null } ], "extra": { "now": 1574998475000, "logid": "2019112911343501001404602000C59A64" } }
python學習裙:10667510,最全學習資料,萌新程序員大本營

觀察數據發現兩個關鍵參數 play_addr和donwload_addr這兩個地址看起來甚是眼熟,好像跟視頻播放地址有那麼一絲絲相似,先不管了,替換一下看看

通過實驗發現返回的地址裏面play_addr的確可以播放,而且沒有水印,而download_addr是有水印的。

至此我們已經可以直接從接口拿到無水印視頻地址了。

那麼接下來就是看請求這個接口需要哪些參數了,接口完整地址如下

https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6747483623986908429&dytk=da461b821c2dcb9b0393eab92afc11e561006d3758ec869bb9b121de4f665ca3

很好,請求這個接口地址只需要一個item_ids和一個dytk,item_ids也就是url地址欄裏面的視頻id,dytk也很容易拿到,在網頁上點擊右鍵,查看網頁源代碼,在最底下就有

好了,基本工作已經完成,代碼寫起來。

代碼

根據以上需求分析,只需要使用requests模擬請求就可以完成,步驟如下:

1、打開分享的鏈接 https://v.douyin.com/xxxx/

2、拿到重定向的url地址,並進行判斷(這裏判斷的目的是因爲分享視頻作者個人頁面的url格式也是這樣,但是重定向之後的url不一樣,分享單個視頻重定向之後有/share/video/,而作者個人頁面的是/share/user/)

3、從返回的頁面html代碼中提取dytk,然後從url中提取視頻id,接着模擬請求接口

4、拿到無水印地址後模擬請求下載無水印視頻

5、結合itchat庫和阿里雲oss實現下載完自動上傳雲端oss保存,這樣直接通過遠程url地址就可以下載查看。

from ipaddress import ip_address from contextlib import closing import requests import itchat import random import oss2 import time import sys import re # 生成x位隨機字符串 def create_random_string(x): chars = "abcdefghijklmnABCDEFGHIJKLMNXYZopqrstuvwxyz" random_string = random.sample(chars, x) return "".join(random_string) def create_headers(): rip = ip_address('0.0.0.0') while rip.is_private: rip = ip_address('.'.join(map(str, (random.randint(0, 255) for _ in range(4))))) headers = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9', 'pragma': 'no-cache', 'cache-control': 'no-cache', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1', 'X-Real-IP': str(rip), 'X-Forwarded-For': str(rip), } return headers def do_download(video_url, v_info): size = 0 headers = create_headers() with closing(requests.get(video_url, headers=headers, stream=True)) as response: chunk_size = 1024 content_size = int(response.headers['content-length']) if response.status_code == 200: local_file = "%s/Download/%s.mp4" % (sys.path[0], v_info.get("video_author") + '-[' + v_info.get("video_name") +']') with open(local_file, 'wb') as file: for data in response.iter_content(chunk_size=chunk_size): file.write(data) size += len(data) file.flush() reset_name = str(time.strftime('%Y%m%d%H%M%S')) + create_random_string(5) + ".mp4" remote_path = "auto-upload" + time.strftime('/%Y-%m-%d/') + reset_name try: auth = oss2.Auth("ossid", "osskey") bucket = oss2.Bucket(auth, 'https://xxxx.aliyuncs.com', 'xxxx') bucket.put_object_from_file(remote_path, local_file) oss_path_url = "https://xxxx.oss-cn-shenzhen.aliyuncs.com/" + remote_path result = "成功" except Exception as e: result = "視頻上傳雲端失敗,請微信聯繫開發者" oss_path_url = "無" else: result = "下載視頻失敗,請微信聯繫開發者" oss_path_url = "無" return '[作者]:%s\n[描述]:%s\n[大小]:%0.2f MB\n[狀態]:%s\n[下載地址]:%s' % ( v_info.get("video_author"), v_info.get("video_name"), content_size / chunk_size / 1024, result, oss_path_url ) @itchat.msg_register(itchat.content.TEXT) def text_reply(msg): return deal_wx_msg(msg) @itchat.msg_register(itchat.content.TEXT, isGroupChat=True) def text_reply(msg): return deal_wx_msg(msg) def deal_wx_msg(msg): if "v.douyin.com" in msg.text: complete_url = "https://v.douyin.com/%s/" % (msg.text.split("/")[3]) html_first_response = requests.get(url=complete_url, headers=create_headers()) redirect_url = html_first_response.url html_content = html_first_response.content.decode("utf8") if "share/video" in redirect_url: dytk = re.findall(r'dytk: "(.+?)"', html_content)[0] video_id = re.findall(r"video\/(.+?)\/\?region", redirect_url)[0] video_info_url = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=%s&dytk=%s" % (video_id, dytk) video_json = requests.get(url=video_info_url).json() if len(video_json.get("item_list")) > 0: video_clear_url = video_json.get("item_list")[0].get("video").get("play_addr").get("url_list")[0] video_name = video_json.get("item_list")[0].get("desc") video_author = re.findall(r'<p class="user-info-name">(.+?)<', html_content)[0] video_info = {"video_name": video_name, "video_author": video_author} return do_download(video_clear_url, video_info) else: return "未檢測到視頻信息" else: return "" itchat.auto_login(hotReload=True, enableCmdQR=2) itchat.run()
python學習裙:10667510,最全學習資料,萌新程序員大本營

代碼中涉及到oss這塊的信息已去除,請自行替換成自己的信息。代碼實現的效果功能如圖所示

微信個人消息和羣消息都可以,當然還可以實現@某個人的功能,itchat非常強大。

總結

不知道這種方法能用多久,這應該是最簡單的方法了吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章