This commit is contained in:
尹舟 2025-04-18 18:40:54 +08:00
parent d378a24f94
commit 6fb23d35ea
7 changed files with 220 additions and 138 deletions

3
config/config.ini Normal file
View File

@ -0,0 +1,3 @@
[test]
DASHSCOPE_API_KEY=sk-063b48fffb914d558ddcddc5166fd34d
[pro]

123
main.py
View File

@ -1,122 +1,67 @@
from fastapi import FastAPI, Query
import uvicorn
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
import uvicorn, os
from utils.music_analysis import music_analysis, chat_analysis
import requests
from utils.response import success_response, error_400_response, error_503_response
from utils.music_analysis import music_analysis
import os
from utils.response import success_response, error_response
app = FastAPI(title="douyin文档", swagger_ui_parameters={
"defaultModelsExpandDepth": -1
})
DEFAULT_PC_VIDEO_URL = 'https://v.douyin.com/-NvlqBdIJo4/'
DEFAULT_mobile_VIDEO_URL = 'https://v.douyin.com/BCfMrTFPYGQ/'
ENVIRONMENT = os.getenv("ENVIRONMENT", "test")
host = '127.0.0.1:9579'
host = 'http://127.0.0.1:9579'
if ENVIRONMENT == "pro":
host = 'http://douyin_tiktok_download_api:80'
@app.get('/douyin_video', tags=["抖音"], summary="返回视屏信息,最全的接口了")
def douyin_video(video_url: str = Query(DEFAULT_mobile_VIDEO_URL, min_length=10)):
print(video_url)
@app.post('/douyin', tags=["抖音"], summary="分享链接获取抖音详细信息")
async def douyin(request: Request):
os.environ["DASHSCOPE_API_KEY"] = "sk-063b48fffb914d558ddcddc5166fd34d"
form_data = await request.form()
video_url = form_data.get('video_url', '').strip()
print(f'原始视频url为{video_url}')
if not video_url:
return {"code": 400, "message": "An error occurred.", "data": "请指定video_url"}
return error_response(400, data='video_url 参数缺失')
elif len(video_url) < 21:
return error_response(400, data='video_url 参数长度不够')
video_url = chat_analysis(video_url)
print(f'截取后视频url为{video_url}')
if not video_url:
return error_response(400, data='解析不到url')
elif len(video_url) < 21:
return error_response(400, data='解析到的url长度不够')
# 获取get请求参数
# 获取get请求参数
url = host + "/api/hybrid/video_data"
print(url)
querystring = {"url": video_url, "minimal": "true"}
response = requests.request("GET", url, params=querystring)
print(response.text)
print(response.json()['data']['music']['play_url'])
try:
if response.json()['code'] == 200:
video_url = response.json()['data']['video_data']['wm_video_url']
video_music = response.json()['data']['music']['play_url']['url_list'][0]
content = music_analysis(video_music)
return success_response({"video_url": video_url, "video_music": video_music, "content": content
})
return success_response(200, data={"video_url": video_url, "video_music": video_music, "content": content
})
except Exception as e:
print(e)
return error_400_response({"data": "解析字段失败."})
return error_response(400, data='解析字段失败')
return error_503_response({"data": "抖音风控稍后请求."})
return error_response(500, data='其他异常')
@app.get('/douyin_content', tags=["抖音"], summary="手机和pc获取文案")
def douyin_content(video_url: str = Query(DEFAULT_mobile_VIDEO_URL, min_length=10)):
print(video_url)
if not video_url:
return {"code": 400, "message": "An error occurred.", "data": "请指定video_url"}
# 获取get请求参数
url = host + "/api/hybrid/video_data"
querystring = {"url": video_url, "minimal": "false"}
response = requests.request("GET", url, params=querystring)
print(response.text)
@app.get('/')
def index():
try:
if response.json()['code'] == 200:
return success_response({"content": response.json()['data']['seo_info']['ocr_content'],
})
except Exception as e:
print(e)
return error_400_response({"data": "解析字段失败."})
return error_503_response({"data": "抖音风控稍后请求."})
@app.get('/douyin_pc', tags=["抖音"], summary="返回pc文案和视频")
def douyin_pc(video_url: str = Query(DEFAULT_PC_VIDEO_URL, min_length=10)):
print(video_url)
if not video_url:
return {"code": 400, "message": "An error occurred.", "data": "请指定video_url"}
# 获取get请求参数
url = host + "/api/hybrid/video_data"
querystring = {"url": video_url, "minimal": "false"}
response = requests.request("GET", url, params=querystring)
print(response.text)
try:
if response.json()['code'] == 200:
return success_response({"content": response.json()['data']['seo_info']['ocr_content'],
"pc_url": response.json()['data']['video']['bit_rate'][0]['play_addr']['url_list']
})
except Exception as e:
print(e)
return error_400_response({"data": "解析字段失败."})
return error_503_response({"data": "抖音风控稍后请求."})
@app.get('/douyin_phone', tags=["抖音"], summary="返回手机视屏地址")
def douyin_phone(video_url: str = Query(DEFAULT_mobile_VIDEO_URL, min_length=10)):
print(video_url)
if not video_url:
return {"code": 400, "message": "An error occurred.", "data": "请指定video_url"}
# 获取get请求参数
url = host + "/api/hybrid/video_data"
querystring = {"url": video_url, "minimal": "true"}
response = requests.request("GET", url, params=querystring)
print(response.text)
try:
if response.json()['code'] == 200:
return success_response({"phone_url": response.json()['data']['video_data']['wm_video_url']
})
except Exception as e:
print(e)
return error_400_response({"data": "解析字段失败."})
return error_503_response({"data": "抖音风控稍后请求."})
return FileResponse('templates/index.html')
except FileNotFoundError:
return {'error': 'index.html 文件未找到'}, 404
if __name__ == '__main__':

Binary file not shown.

133
templates/index.html Normal file
View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>抖音视频解析助手</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, #f3e7e9 0%, #e3eeff 100%);
min-height: 100vh;
}
.glow-box {
box-shadow: 0 0 30px rgba(99, 102, 241, 0.1);
}
.multi-line-ellipsis {
display: -webkit-box;
-webkit-line-clamp: 5;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.5em;
max-height: 7.5em;
transition: max-height 0.3s ease;
}
.multi-line-ellipsis:hover {
-webkit-line-clamp: unset;
max-height: 300px;
overflow-y: auto;
}
</style>
</head>
<body class="flex items-center justify-center p-4">
<div class="w-full max-w-2xl bg-white rounded-2xl p-8 glow-box">
<h1 class="text-3xl font-bold text-indigo-600 mb-6 text-center">
🎥 抖音视频解析器
</h1>
<div class="space-y-6">
<div class="relative">
<input
type="text"
id="videoUrl"
placeholder="粘贴抖音视频链接..."
class="w-full px-4 py-3 rounded-lg border-2 border-indigo-100 focus:border-indigo-400 focus:ring-2 focus:ring-indigo-200 transition-all"
>
<button
onclick="fetchVideoInfo()"
class="absolute right-2 top-2 bg-gradient-to-r from-indigo-500 to-purple-600 text-white px-6 py-2 rounded-md hover:scale-105 transition-transform"
>
立即解析
</button>
</div>
<div id="resultContainer" class="hidden">
<div class="bg-gray-50 p-6 rounded-xl animate-fade-in space-y-4">
<h3 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<svg class="w-6 h-6 mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
解析结果
</h3>
<div class="grid grid-cols-1 gap-4">
<!-- 视频地址 -->
<div class="bg-white p-4 rounded-lg shadow-sm">
<label class="text-indigo-600 font-medium block mb-2">视频地址</label>
<a id="videoUrlLink" target="_blank"
class="text-blue-600 break-all hover:underline hover:text-blue-800 transition-colors">
</a>
</div>
<!-- 背景音乐 -->
<div class="bg-white p-4 rounded-lg shadow-sm">
<label class="text-purple-600 font-medium block mb-2">背景音乐</label>
<a id="musicUrlLink" target="_blank"
class="text-blue-600 break-all hover:underline hover:text-blue-800 transition-colors">
</a>
</div>
<!-- 视频文案 -->
<div class="bg-white p-4 rounded-lg shadow-sm">
<label class="text-green-600 font-medium block mb-2">视频文案</label>
<div id="contentText" class="text-gray-700 leading-relaxed multi-line-ellipsis scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
async function fetchVideoInfo() {
const url = document.getElementById('videoUrl').value;
const resultContainer = document.getElementById('resultContainer');
try {
const formData = new FormData();
formData.append('video_url', url);
const response = await fetch('/douyin', {
method: 'POST',
body: formData,
credentials: 'include'
});
const responseData = await response.json();
if (responseData.code === 200) {
// 填充结构化数据
document.getElementById('videoUrlLink').href = responseData.data.video_url;
document.getElementById('videoUrlLink').textContent = responseData.data.video_url;
document.getElementById('musicUrlLink').href = responseData.data.video_music;
document.getElementById('musicUrlLink').textContent = responseData.data.video_music;
document.getElementById('contentText').textContent = responseData.data.content;
resultContainer.classList.remove('hidden');
} else {
alert(`错误代码 ${responseData.code}: ${responseData.message}`);
}
} catch (error) {
alert('解析失败,请检查链接有效性');
}
}
</script>
</body>
</html>

17
utils/__init__.py Normal file
View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
import os
from configparser import ConfigParser
ENVIRONMENT=os.getenv("ENVIRONMENT", "test")
print(f"当前环境为:{ENVIRONMENT}")
conf = ConfigParser()
path=os.path.dirname(__file__).replace("\\utils", "")
conf.read(f"{path}\\config\\config.ini")
config = conf[ENVIRONMENT]
# 遍历conf
for k, v in config.items():
config[k] = v.strip()
os.environ[k] = v.strip()
print(f"加载配置文件完成")

View File

@ -6,7 +6,12 @@ import requests
import os
from openai import OpenAI
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY","*****")
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY", "*****")
content = """
将文本的链接提取出来只留链接的有效信息
# 输出
只输出链接不需要分析过程
"""
def music_analysis(music_url):
@ -22,7 +27,7 @@ def music_analysis(music_url):
transcribe_response = Transcription.fetch(task=transcribe_response.output.task_id)
if transcribe_response.status_code == HTTPStatus.OK:
url=transcribe_response.output['results'][0]['transcription_url']
url = transcribe_response.output['results'][0]['transcription_url']
print(url)
# 发送GET请求
@ -40,11 +45,12 @@ def music_analysis(music_url):
print(f"请求失败,状态码:{response.status_code}")
return text
def chat_analysis(video_url):
text=''
text = ''
client = OpenAI(
# 若没有配置环境变量请用百炼API Key将下行替换为api_key="sk-xxx",
api_key=os.getenv("DASHSCOPE_API_KEY","*****"),
api_key=os.getenv("DASHSCOPE_API_KEY", "*****"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
completion = client.chat.completions.create(
@ -53,20 +59,19 @@ def chat_analysis(video_url):
messages=[
{
'role': 'system',
'content': """
将文本的链接提取出来只留链接的有效信息
# 输出
只输出链接不需要分析过程
"""
'content': content
},
{'role': 'user', 'content': video_url}],
)
print(completion.model_dump())
print(f'语言模型返回数据: {completion.model_dump()}')
for choices in completion.model_dump()['choices']:
text+=choices['message']['content']
text += choices['message']['content']
return text
def video_url(video_url):
print(video_url)
if __name__ == '__main__':
# music_analysis('https://lf26-music-east.douyinstatic.com/obj/ies-music-hj/7494207652008839996.mp3')
chat_analysis('4.17 复制打开抖音,看看【三维地球科普的作品】河南省最奇怪的城市——信阳市 # 信阳市 # 河南... https://v.douyin.com/RBU9atEeafc/ AGI:/ i@p.QX 11/14 ')
chat_analysis(
'原始视频url为9.79 复制打开抖音,看看【学钓鱼的佳琪的作品】少与人纠缠,多跟鱼拉扯 # dou是钓鱼人 # 爱... https://v.douyin.com/1skvRRYPEgA/ DHI:/ n@D.uf 05/06')

View File

@ -1,54 +1,33 @@
from fastapi import status
from fastapi.responses import JSONResponse
def success_response(data: dict = None, message: str = "Success"):
def success_response(code: int = 200, data: dict = None,
http_status: int = status.HTTP_200_OK):
return JSONResponse(
status_code=status.HTTP_200_OK,
status_code=http_status,
content={
"code": 200,
"message": message,
"data": data or {}
"code": code,
"message": "success",
"data": data
}
)
def error_response(
code: int = 400,
message: str = "Error",
http_status: int = status.HTTP_400_BAD_REQUEST
code: int = 503,
data: dict = None,
http_status: int = status.HTTP_503_SERVICE_UNAVAILABLE
):
return JSONResponse(
status_code=http_status,
content={
"code": code,
"message": message,
"data": None
"message": "fail",
"data": data
}
)
def error_400_response(
code: int = 400,
message: dict = None,
http_status: int = status.HTTP_400_BAD_REQUEST
):
return JSONResponse(
status_code=http_status,
content={
"code": code,
"message": message,
"data": None
}
)
def error_503_response(
code: int = 503,
message: dict = None,
http_status: int = status.HTTP_503_SERVICE_UNAVAILABLE
):
return JSONResponse(
status_code=http_status,
content={
"code": code,
"message": message,
"data": None
}
)
if __name__ == '__main__':
print(error_response())