項目地址是:https://www.chenshiyang.com/dytk
接下來我們分析下源碼簡要看下實現(xiàn)原理。
該項目不需要使用模型(models), 最核心的只有兩個頁面:一個主頁面(home)展示包含下載url地址的表單,一個下載頁面(download)處理表單請求,并展示去水印后的視頻文件地址及文件大小,以及用于手機(jī)預(yù)覽的二維碼。
對應(yīng)兩個核心頁面的路由如下所示,每個url對應(yīng)一個視圖函數(shù)。
from django.urls import path from web.views import home, download urlpatterns = [ path('home', home), path('downloader', download), ]
from django.http import HttpResponse from django.shortcuts import render, redirect # Create your views here. from common.utils import format_duration, load_media from common.DouYin import DY def home(request): """首頁""" return render(request, 'home.html') def download(request): """下載""" url = request.POST.get('url', None) assert url != None dy = DY() data = dy.parse(url) mp4_path, mp4_content_length = load_media(data['mp4'], 'mp4') mp3_path, mp3_content_length = load_media(data['mp3'], 'mp3') realpath = ''.join(['https://www.chenshiyang.com', mp4_path]) print('realpath---------------------', realpath) if len(data['desc'].split('#')) > 2: topic = data['desc'].split('#')[2].rstrip('#') return render(request, 'download.html', locals())
可以看出通過home頁面表單提交過來的下載url會交由download函數(shù)處理。common模塊的DouYin.py中定義的DY類負(fù)責(zé)對url繼續(xù)解析,爬取相關(guān)視頻地址,通過自定義utils.py中的load_media方法下載文件,并返回文件路徑以及文件大小。
由于解析下載url,從抖音爬取數(shù)據(jù)的代碼都封裝到DY類里了,所以我們有必要貼下這個類的代碼。另外,我們還需要貼下load_media這個方法的代碼。
# -*- coding: utf-8 -*- # @Time : 2020-07-03 13:10 # @Author : chenshiyang # @Email : chenshiyang@blued.com # @File : DouYin.py # @Software: PyCharm import re from urllib.parse import urlparse import requests from common.utils import format_duration class DY(object): def __init__(self, app=None): self.app = app if app is not None: self.init_app(app) self.headers = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', # 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'cookie': 'sid_guard=2e624045d2da7f502b37ecf72974d311%7C1591170698%7C5184000%7CSun%2C+02-Aug-2020+07%3A51%3A38+GMT; uid_tt=0033579d9229eec4a4d09871dfc11271; sid_tt=2e624045d2da7f502b37ecf72974d311; sessionid=2e624045d2da7f502b37ecf72974d311', 'pragma': 'no-cache', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1' } self.domain = ['www.douyin.com', 'v.douyin.com', 'www.snssdk.com', 'www.amemv.com', 'www.iesdouyin.com', 'aweme.snssdk.com'] def init_app(self, app): self.app = app def parse(self, url): share_url = self.get_share_url(url) share_url_parse = urlparse(share_url) if share_url_parse.netloc not in self.domain: raise Exception("無效的鏈接") dytk = None vid = re.findall(r'\/share\/video\/(\d*)', share_url_parse.path)[0] match = re.search(r'\/share\/video\/(\d*)', share_url_parse.path) if match: vid = match.group(1) response = requests.get( share_url, headers=self.headers, allow_redirects=False) match = re.search('dytk: "(.*?)"', response.text) if match: dytk = match.group(1) if vid: return self.get_data(vid, dytk) else: raise Exception("解析失敗") def get_share_url(self, url): response = requests.get(url, headers=self.headers, allow_redirects=False) if 'location' in response.headers.keys(): return response.headers['location'] elif '/share/video/' in url: return url else: raise Exception("解析失敗") def get_data(self, vid, dytk): url = f"https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={vid}dytk={dytk}" response = requests.get(url, headers=self.headers, ) result = response.json() if not response.status_code == 200: raise Exception("解析失敗") item = result.get("item_list")[0] author = item.get("author").get("nickname") mp4 = item.get("video").get("play_addr").get("url_list")[0] cover = item.get("video").get("cover").get("url_list")[0] mp4 = mp4.replace("playwm", "play") res = requests.get(mp4, headers=self.headers, allow_redirects=True) mp4 = res.url desc = item.get("desc") mp3 = item.get("music").get("play_url").get("url_list")[0] data = dict() data['mp3'] = mp3 data['mp4'] = mp4 data['cover'] = cover data['nickname'] = author data['desc'] = desc data['duration'] = format_duration(item.get("duration")) return data
從代碼你可以看到返回的data字典里包括了mp3和mp4源文件地址,以及視頻的封面,作者昵稱及描述等等。
接下來你可以看到load_media方法爬取了視頻到本地,并提供了新的path和大小。
# -*- coding: utf-8 -*- # @Time : 2020-06-29 17:26 # @Author : chenshiyang # @Email : chenshiyang@blued.com # @File : utils.py # @Software: PyCharm import os import time import requests def format_duration(duration): """ 格式化時長 :param duration 毫秒 """ total_seconds = int(duration / 1000) minute = total_seconds // 60 seconds = total_seconds % 60 return f'{minute:02}:{seconds:02}' SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']} def approximate_size(size, a_kilobyte_is_1024_bytes=True): '''Convert a file size to human-readable form. Keyword arguments: size -- file size in bytes a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024 if False, use multiples of 1000 Returns: string ''' if size 0: raise ValueError('number must be non-negative') multiple = 1024 if a_kilobyte_is_1024_bytes else 1000 for suffix in SUFFIXES[multiple]: size /= multiple if size multiple: return '{0:.1f} {1}'.format(size, suffix) raise ValueError('number too large') def do_load_media(url, path): """ 對媒體下載 :param url: 多媒體地址 :param path: 文件保存路徑 :return: None """ try: headers = { "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"} pre_content_length = 0 # 循環(huán)接收視頻數(shù)據(jù) while True: # 若文件已經(jīng)存在,則斷點續(xù)傳,設(shè)置接收來需接收數(shù)據(jù)的位置 if os.path.exists(path): headers['Range'] = 'bytes=%d-' % os.path.getsize(path) res = requests.get(url, stream=True, headers=headers) content_length = int(res.headers['content-length']) # 若當(dāng)前報文長度小于前次報文長度,或者已接收文件等于當(dāng)前報文長度,則可以認(rèn)為視頻接收完成 if content_length pre_content_length or ( os.path.exists(path) and os.path.getsize(path) == content_length): break pre_content_length = content_length # 寫入收到的視頻數(shù)據(jù) with open(path, 'ab') as file: file.write(res.content) file.flush() print('receive data,file size : %d total size:%d' % (os.path.getsize(path), content_length)) return approximate_size(content_length, a_kilobyte_is_1024_bytes=False) except Exception as e: print('視頻下載異常:{}'.format(e)) def load_media(url, path): basepath = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) # 生成13位時間戳 suffixes = str(int(round(time.time() * 1000))) path = ''.join(['/media/', path, '/', '.'.join([suffixes, path])]) targetpath = ''.join([basepath, path]) content_length = do_load_media(url, targetpath) return path, content_length def main(url, suffixes, path): load_media(url, suffixes, path) if __name__ == "__main__": # url = 'https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200fe70000br155v26tgq06h08e0lgratio=720pline=0' # suffixes = 'test' # main(url, suffixes, 'mp4',) print(approximate_size(3726257, a_kilobyte_is_1024_bytes=False))
接下來我們看下模板, 這個沒什么好說的。
{% extends "base.html" %} {% block content %} div class="jumbotron custom-jum no-mrg"> div class="container"> div class="row"> div class="col-md-12"> div class="center"> div class="home-search"> h1>抖音無水印視頻下載器/h1> h2>將抖音無水印視頻下載到Mp4和Mp3/h2> /div> div class="form-home-search"> form id="form_download" action='https://www.chenshiyang.com/dytk/downloader' method='POST'> div class="input-group col-lg-10 col-md-10 col-sm-10"> input name="url" class="form-control input-md ht58" placeholder="輸入抖音視頻 URL ..." type="text" required="" value=""> span class="input-group-btn">button class="btn btn-primary input-md btn-download ht58" type="submit" id="btn_submit">下載/button>/span> /div> /form> /div> /div> /div> /div> /div> /div> /div> {% endblock %}
{% extends "base.html" %} {% block content %} div class="page-content"> div class="container"> div class="row"> div class="col-lg-12 col-centered"> div class="ads mrg-bt20 text-center"> ins class="adsbygoogle" style="display:inline-block;width:728px;height:90px" data-ad-client="ca-pub-2984659695526033" data-ad-slot="5734284394">/ins> /div> div class="card"> div class="row"> div class="col-md-4 col-sm-4"> a href="{{mp4_path}}" rel="external nofollow" rel="external nofollow" data-toggle="modal" class="card-aside-column img-video" style="height: 252px; background: url(quot;{{data.cover}}quot;) 0% 0% / cover;" title=""> span class="btn-play-video">i class="glyphicon glyphicon-play">/i>/span> p class="time-video" id="time">{{data.duration}}/p> /a> h5>作者: {{data.nickname}}/h5> h5>a href="#" rel="external nofollow" >{{topic}} i class="open-new-window">/i>/a>/h5> p class="card-text">{{data.desc}}/p> /div> div class="col-md-8 col-sm-8 col-table"> table class="table"> thead> tr> th>format/th> th>size/th> th>Downloads/th> /tr> /thead> tbody> tr> td>mp4/td> td>{{mp4_content_length}}/td> td> a href="{{mp4_path}}" rel="external nofollow" rel="external nofollow" class="btn btn-download" download="">下載/a> /td> /tr> tr> td>mp3/td> td>{{mp3_content_length}}/td> td> a href="{{mp3_path}}" rel="external nofollow" class="btn btn-download" download="">下載/a> /td> /tr> /tbody> /table> /div> /div> /div> div class="card card-qrcode"> div class="row"> div class="col-md-12 qrcode"> div class="text-center"> p class="qrcode-p">掃描下面的二維碼直接下載到您的智能手機(jī)或平板電腦!/p> /div> /div> div class="col-md-4 col-centered qrcode"> div id="qrcode" title="{{realpath}}"> script src="/static/js/qrcode.min.js">/script> script type="text/javascript"> new QRCode(document.getElementById("qrcode"), { text: "{{realpath}}", width: 120, height: 120, correctLevel: QRCode.CorrectLevel.L }); /script> /div> /div> /div> /div> /div> /div> /div> /div> {% endblock %}
https://github.com/tinysheepyang/python_api。
以上就是Django實現(xiàn)在線無水印抖音視頻下載(附源碼及地址)的詳細(xì)內(nèi)容,更多關(guān)于Django 無水印抖音視頻下載的資料請關(guān)注腳本之家其它相關(guān)文章!
標(biāo)簽:喀什 宿遷 黃山 濟(jì)南 三沙 臺灣 欽州 景德鎮(zhèn)
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Django實現(xiàn)在線無水印抖音視頻下載(附源碼及地址)》,本文關(guān)鍵詞 Django,實現(xiàn),在線,無,水印,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。