增加通过用户uniqueId获取播放量前十的视频功能
This commit is contained in:
parent
2713cba136
commit
4ccfc764f7
Binary file not shown.
Binary file not shown.
@ -94,6 +94,9 @@ DATABASES = {
|
|||||||
'PASSWORD': '123456',
|
'PASSWORD': '123456',
|
||||||
'HOST': 'localhost',
|
'HOST': 'localhost',
|
||||||
'PORT': '3306',
|
'PORT': '3306',
|
||||||
|
'OPTIONS': {
|
||||||
|
'charset': 'utf8mb4',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,4 +24,5 @@ urlpatterns = [
|
|||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('monitor/', include('monitor.urls')),
|
path('monitor/', include('monitor.urls')),
|
||||||
path('', lambda request: redirect('monitor/')), # 将根路径重定向到 monitor/
|
path('', lambda request: redirect('monitor/')), # 将根路径重定向到 monitor/
|
||||||
|
path('', include('monitor.urls')), # 假设您的应用名为 'monitor'
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
BIN
media/tiktok_videos/bp_l0v3/7385354336072682757.mp4
Normal file
BIN
media/tiktok_videos/bp_l0v3/7385354336072682757.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/bp_l0v3/7468467735228370181.mp4
Normal file
BIN
media/tiktok_videos/bp_l0v3/7468467735228370181.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7463426155811507474.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7463426155811507474.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7464635025129032968.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7464635025129032968.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7467121865929067783.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7467121865929067783.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7467842813909060872.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7467842813909060872.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7468279621377690898.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7468279621377690898.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7469043803631013128.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7469043803631013128.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7470050249797651720.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7470050249797651720.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7470824759668903175.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7470824759668903175.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7472652207901396231.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7472652207901396231.mp4
Normal file
Binary file not shown.
BIN
media/tiktok_videos/iamurhope/7473859764154289415.mp4
Normal file
BIN
media/tiktok_videos/iamurhope/7473859764154289415.mp4
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
33
monitor/migrations/0004_douyinuservideos.py
Normal file
33
monitor/migrations/0004_douyinuservideos.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-05 05:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('monitor', '0003_allresourceprocess'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DouyinUserVideos',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('sec_user_id', models.CharField(max_length=100, unique=True, verbose_name='用户ID')),
|
||||||
|
('nickname', models.CharField(max_length=100, verbose_name='昵称')),
|
||||||
|
('signature', models.TextField(blank=True, verbose_name='签名')),
|
||||||
|
('follower_count', models.IntegerField(default=0, verbose_name='粉丝数')),
|
||||||
|
('total_favorited', models.IntegerField(default=0, verbose_name='获赞数')),
|
||||||
|
('avatar_url', models.CharField(blank=True, max_length=255, verbose_name='头像URL')),
|
||||||
|
('video_paths', models.TextField(blank=True, verbose_name='视频路径JSON')),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '抖音用户视频',
|
||||||
|
'verbose_name_plural': '抖音用户视频',
|
||||||
|
'db_table': 'douyin_user_videos',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-05 05:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('monitor', '0004_douyinuservideos'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='douyinuservideos',
|
||||||
|
name='videos_folder',
|
||||||
|
field=models.CharField(blank=True, max_length=255, verbose_name='视频文件夹路径'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='douyinuservideos',
|
||||||
|
name='video_paths',
|
||||||
|
field=models.TextField(blank=True, verbose_name='视频信息JSON'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-05 06:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('monitor', '0005_douyinuservideos_videos_folder_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TiktokUserVideos',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('sec_user_id', models.CharField(max_length=255, unique=True)),
|
||||||
|
('nickname', models.CharField(max_length=255)),
|
||||||
|
('signature', models.TextField(blank=True, null=True)),
|
||||||
|
('follower_count', models.IntegerField(default=0)),
|
||||||
|
('total_favorited', models.IntegerField(default=0)),
|
||||||
|
('avatar_url', models.URLField(blank=True, null=True)),
|
||||||
|
('videos_folder', models.CharField(max_length=255)),
|
||||||
|
('video_paths', models.TextField(blank=True, null=True)),
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'TikTok用户视频',
|
||||||
|
'verbose_name_plural': 'TikTok用户视频',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='DouyinUserVideos',
|
||||||
|
),
|
||||||
|
]
|
18
monitor/migrations/0007_alter_tiktokuservideos_avatar_url.py
Normal file
18
monitor/migrations/0007_alter_tiktokuservideos_avatar_url.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.6 on 2025-03-05 06:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('monitor', '0006_tiktokuservideos_delete_douyinuservideos'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tiktokuservideos',
|
||||||
|
name='avatar_url',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -73,3 +73,24 @@ class AllResourceProcess(models.Model):
|
|||||||
db_table = 'all_resource_process'
|
db_table = 'all_resource_process'
|
||||||
verbose_name = '全资源监控'
|
verbose_name = '全资源监控'
|
||||||
verbose_name_plural = verbose_name
|
verbose_name_plural = verbose_name
|
||||||
|
|
||||||
|
class TiktokUserVideos(models.Model):
|
||||||
|
"""TikTok用户视频信息"""
|
||||||
|
sec_user_id = models.CharField(max_length=255, unique=True)
|
||||||
|
nickname = models.CharField(max_length=255)
|
||||||
|
signature = models.TextField(blank=True, null=True)
|
||||||
|
follower_count = models.IntegerField(default=0)
|
||||||
|
total_favorited = models.IntegerField(default=0)
|
||||||
|
avatar_url = models.TextField(blank=True, null=True)
|
||||||
|
videos_folder = models.CharField(max_length=255)
|
||||||
|
video_paths = models.TextField(blank=True, null=True)
|
||||||
|
create_time = models.DateTimeField(auto_now_add=True)
|
||||||
|
update_time = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'TikTok用户视频'
|
||||||
|
verbose_name_plural = 'TikTok用户视频'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.nickname} ({self.sec_user_id})"
|
||||||
|
|
@ -15,4 +15,6 @@ urlpatterns = [
|
|||||||
path('status/<int:pid>/', views.get_process_status, name='get_process_status'),
|
path('status/<int:pid>/', views.get_process_status, name='get_process_status'),
|
||||||
path('stop-directory-monitor/', views.stop_directory_monitor, name='stop_directory_monitor'),
|
path('stop-directory-monitor/', views.stop_directory_monitor, name='stop_directory_monitor'),
|
||||||
path('directory-status/', views.get_directory_status, name='directory_status'),
|
path('directory-status/', views.get_directory_status, name='directory_status'),
|
||||||
|
path('tiktok/user-videos/', views.get_tiktok_user_videos, name='get-tiktok-user-videos'),
|
||||||
|
path('api/tiktok/fetch_videos/', views.fetch_tiktok_videos, name='fetch_tiktok_videos'),
|
||||||
]
|
]
|
||||||
|
355
monitor/views.py
355
monitor/views.py
@ -2,7 +2,7 @@ from django.http import JsonResponse
|
|||||||
from .tasks import monitor_process, get_process_gpu_usage
|
from .tasks import monitor_process, get_process_gpu_usage
|
||||||
import threading
|
import threading
|
||||||
import psutil
|
import psutil
|
||||||
from .models import HighCPUProcess, HighGPUProcess, HighMemoryProcess, AllResourceProcess
|
from .models import HighCPUProcess, HighGPUProcess, HighMemoryProcess, AllResourceProcess, TiktokUserVideos
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -16,10 +16,9 @@ import pytz
|
|||||||
from pathlib import Path # 使用 pathlib 处理跨平台路径
|
from pathlib import Path # 使用 pathlib 处理跨平台路径
|
||||||
import json
|
import json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
directory_monitoring = {}
|
directory_monitoring = {}
|
||||||
|
|
||||||
# 全局变量来控制检测线程
|
# 全局变量来控制检测线程
|
||||||
@ -30,6 +29,12 @@ is_monitoring = False
|
|||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
LOG_DIR = os.path.join(BASE_DIR, 'logs', 'process_monitor')
|
LOG_DIR = os.path.join(BASE_DIR, 'logs', 'process_monitor')
|
||||||
|
|
||||||
|
# 创建保存视频的基本路径
|
||||||
|
TIKTOK_VIDEOS_PATH = os.path.join(BASE_DIR, 'media', 'tiktok_videos')
|
||||||
|
|
||||||
|
# 确保基本目录存在
|
||||||
|
os.makedirs(TIKTOK_VIDEOS_PATH, exist_ok=True)
|
||||||
|
|
||||||
# 确保基础目录结构存在,添加 'all' 目录
|
# 确保基础目录结构存在,添加 'all' 目录
|
||||||
for resource_type in ['cpu', 'memory', 'gpu', 'all']:
|
for resource_type in ['cpu', 'memory', 'gpu', 'all']:
|
||||||
os.makedirs(os.path.join(LOG_DIR, resource_type), exist_ok=True)
|
os.makedirs(os.path.join(LOG_DIR, resource_type), exist_ok=True)
|
||||||
@ -40,6 +45,9 @@ logger = logging.getLogger('monitor')
|
|||||||
# 全局变量来跟踪监控的目录
|
# 全局变量来跟踪监控的目录
|
||||||
monitored_directories = set()
|
monitored_directories = set()
|
||||||
|
|
||||||
|
# 在文件顶部添加 API 基础 URL
|
||||||
|
API_BASE_URL = "http://81.69.223.133:45268"
|
||||||
|
|
||||||
# 添加新的监控线程函数
|
# 添加新的监控线程函数
|
||||||
def monitor_directory(directory):
|
def monitor_directory(directory):
|
||||||
"""持续监控目录的后台任务"""
|
"""持续监控目录的后台任务"""
|
||||||
@ -1012,3 +1020,342 @@ def get_open_files(proc):
|
|||||||
return [f.path for f in proc.open_files()]
|
return [f.path for f in proc.open_files()]
|
||||||
except:
|
except:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def fetch_tiktok_videos(request):
|
||||||
|
"""获取TikTok用户视频并下载播放量前十的视频"""
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
unique_id = data.get('unique_id')
|
||||||
|
|
||||||
|
if not unique_id:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': '请提供TikTok用户ID(unique_id)'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 调用API获取用户资料和secUid
|
||||||
|
logger.info(f"正在获取用户 {unique_id} 的资料...")
|
||||||
|
user_profile = fetch_user_profile(unique_id)
|
||||||
|
|
||||||
|
if not user_profile or 'data' not in user_profile:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'无法获取用户 {unique_id} 的资料'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 从API响应中提取secUid和其他用户信息
|
||||||
|
try:
|
||||||
|
user_info = user_profile['data']['userInfo']['user']
|
||||||
|
sec_uid = user_info['secUid']
|
||||||
|
|
||||||
|
# 提取其他用户信息
|
||||||
|
nickname = user_info.get('nickname', f'用户_{unique_id}')
|
||||||
|
signature = user_info.get('signature', '')
|
||||||
|
avatar_url = user_info.get('avatarLarger', '')
|
||||||
|
user_stats = user_profile['data']['userInfo'].get('stats', {})
|
||||||
|
follower_count = user_stats.get('followerCount', 0)
|
||||||
|
heart_count = user_stats.get('heartCount', 0)
|
||||||
|
|
||||||
|
logger.info(f"成功获取用户secUid: {sec_uid}")
|
||||||
|
except (KeyError, TypeError) as e:
|
||||||
|
logger.error(f"解析用户资料出错: {e}")
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'解析用户资料出错: {str(e)}'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 确保用户目录存在
|
||||||
|
user_dir = os.path.join(TIKTOK_VIDEOS_PATH, unique_id)
|
||||||
|
os.makedirs(user_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 获取用户视频
|
||||||
|
videos_data = fetch_user_videos(sec_uid)
|
||||||
|
|
||||||
|
# 提取视频信息并按播放量排序
|
||||||
|
all_videos = []
|
||||||
|
if videos_data and isinstance(videos_data, dict) and 'data' in videos_data and 'itemList' in videos_data['data']:
|
||||||
|
for video in videos_data['data']['itemList']:
|
||||||
|
try:
|
||||||
|
# 确保提取正确的视频ID
|
||||||
|
video_id = video.get('id', '')
|
||||||
|
if not video_id or not str(video_id).isdigit():
|
||||||
|
logger.warning(f"跳过无效的视频ID: {video_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 安全地获取统计数据
|
||||||
|
stats = video.get('stats', {})
|
||||||
|
if not isinstance(stats, dict):
|
||||||
|
stats = {}
|
||||||
|
|
||||||
|
play_count = int(stats.get('playCount', 0))
|
||||||
|
|
||||||
|
# 收集视频信息
|
||||||
|
all_videos.append({
|
||||||
|
'id': video_id,
|
||||||
|
'desc': video.get('desc', ''),
|
||||||
|
'create_time': video.get('createTime', 0),
|
||||||
|
'cover': video.get('cover', ''),
|
||||||
|
'play_count': play_count,
|
||||||
|
'comment_count': int(stats.get('commentCount', 0)),
|
||||||
|
'digg_count': int(stats.get('diggCount', 0)),
|
||||||
|
'share_count': int(stats.get('shareCount', 0))
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理视频数据出错: {str(e)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 按播放量排序并获取前10个
|
||||||
|
all_videos.sort(key=lambda x: x['play_count'], reverse=True)
|
||||||
|
top_videos = all_videos[:10]
|
||||||
|
|
||||||
|
# 下载视频
|
||||||
|
downloaded_videos = []
|
||||||
|
for i, video in enumerate(top_videos):
|
||||||
|
# 获取视频ID
|
||||||
|
video_id = video['id'] # 这是数字ID
|
||||||
|
|
||||||
|
# 构建保存路径
|
||||||
|
save_path = os.path.join(user_dir, f"{video_id}.mp4")
|
||||||
|
|
||||||
|
logger.info(f"开始下载第 {i+1} 个热门视频(ID: {video_id}): {video['desc'][:20]}...")
|
||||||
|
# 使用正确的ID调用下载函数
|
||||||
|
if download_video(video_id, unique_id, save_path):
|
||||||
|
video['download_path'] = save_path
|
||||||
|
downloaded_videos.append(video)
|
||||||
|
logger.info(f"下载成功: {save_path}")
|
||||||
|
else:
|
||||||
|
logger.error(f"下载失败: {video_id}")
|
||||||
|
|
||||||
|
# 避免频繁请求被限制
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 保存用户信息和视频信息到数据库
|
||||||
|
video_info_json = json.dumps([{
|
||||||
|
'id': v['id'],
|
||||||
|
'desc': v['desc'],
|
||||||
|
'play_count': v['play_count']
|
||||||
|
} for v in downloaded_videos], ensure_ascii=False)
|
||||||
|
|
||||||
|
user_record = TiktokUserVideos.objects.update_or_create(
|
||||||
|
sec_user_id=sec_uid,
|
||||||
|
defaults={
|
||||||
|
'nickname': nickname,
|
||||||
|
'signature': signature,
|
||||||
|
'follower_count': follower_count,
|
||||||
|
'total_favorited': heart_count,
|
||||||
|
'avatar_url': avatar_url,
|
||||||
|
'videos_folder': user_dir,
|
||||||
|
'video_paths': video_info_json
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'success',
|
||||||
|
'message': '处理完成',
|
||||||
|
'user_info': {
|
||||||
|
'nickname': nickname,
|
||||||
|
'unique_id': unique_id,
|
||||||
|
'sec_uid': sec_uid,
|
||||||
|
'avatar': avatar_url,
|
||||||
|
'follower_count': follower_count,
|
||||||
|
'total_favorited': heart_count,
|
||||||
|
'signature': signature
|
||||||
|
},
|
||||||
|
'videos_count': len(videos_data['data']['itemList']) if videos_data and 'data' in videos_data and 'itemList' in videos_data['data'] else 0,
|
||||||
|
'downloaded_videos': len(downloaded_videos),
|
||||||
|
'videos': [{'id': v['id'], 'desc': v['desc'][:50], 'play_count': v['play_count']} for v in downloaded_videos],
|
||||||
|
'video_directory': user_dir # 添加视频目录路径方便查找
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理TikTok视频失败: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f"详细错误: {traceback.format_exc()}")
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'处理TikTok视频失败: {str(e)}'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
@require_http_methods(["GET"])
|
||||||
|
def get_tiktok_user_videos(request):
|
||||||
|
"""获取已下载的TikTok用户视频列表"""
|
||||||
|
try:
|
||||||
|
sec_user_id = request.GET.get('sec_user_id')
|
||||||
|
|
||||||
|
if not sec_user_id:
|
||||||
|
# 如果没有指定用户ID,返回所有用户列表
|
||||||
|
users = TiktokUserVideos.objects.all().values('sec_user_id', 'nickname', 'follower_count', 'videos_folder', 'create_time')
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'success',
|
||||||
|
'users': list(users)
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 查询指定用户信息
|
||||||
|
try:
|
||||||
|
user = TiktokUserVideos.objects.get(sec_user_id=sec_user_id)
|
||||||
|
# 解析视频信息JSON
|
||||||
|
video_info = json.loads(user.video_paths) if user.video_paths else []
|
||||||
|
|
||||||
|
# 获取文件夹中的文件列表
|
||||||
|
videos_folder = user.videos_folder
|
||||||
|
video_files = []
|
||||||
|
if os.path.exists(videos_folder):
|
||||||
|
video_files = [f for f in os.listdir(videos_folder) if os.path.isfile(os.path.join(videos_folder, f))]
|
||||||
|
|
||||||
|
except TiktokUserVideos.DoesNotExist:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'用户 {sec_user_id} 不存在'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'success',
|
||||||
|
'user_info': {
|
||||||
|
'sec_user_id': user.sec_user_id,
|
||||||
|
'nickname': user.nickname,
|
||||||
|
'signature': user.signature,
|
||||||
|
'follower_count': user.follower_count,
|
||||||
|
'total_favorited': user.total_favorited,
|
||||||
|
'avatar_url': user.avatar_url,
|
||||||
|
'create_time': user.create_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'update_time': user.update_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
},
|
||||||
|
'videos_folder': videos_folder,
|
||||||
|
'video_files': video_files,
|
||||||
|
'video_info': video_info
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取TikTok视频列表失败: {str(e)}")
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f'获取TikTok视频列表失败: {str(e)}'
|
||||||
|
}, json_dumps_params={'ensure_ascii': False})
|
||||||
|
|
||||||
|
# 辅助函数
|
||||||
|
|
||||||
|
def fetch_user_videos(sec_uid, max_cursor=0, count=20):
|
||||||
|
"""获取用户视频列表"""
|
||||||
|
url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_post?secUid={sec_uid}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"获取视频列表失败: {response.status_code}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取视频列表异常: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_user_profile(unique_id):
|
||||||
|
"""获取用户基本信息"""
|
||||||
|
# 添加User-Agent头,模拟浏览器访问
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
'Referer': 'https://www.tiktok.com/'
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_profile?uniqueId={unique_id}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"正在请求用户资料: {url}")
|
||||||
|
# 添加重试机制
|
||||||
|
for attempt in range(3):
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, timeout=30)
|
||||||
|
break
|
||||||
|
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
|
||||||
|
logger.warning(f"请求超时或连接错误,尝试第{attempt+1}次重试: {e}")
|
||||||
|
if attempt == 2: # 最后一次重试失败
|
||||||
|
raise
|
||||||
|
time.sleep(2) # 等待2秒后重试
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
logger.info(f"获取用户资料成功: {unique_id}")
|
||||||
|
|
||||||
|
# 打印完整响应以便调试
|
||||||
|
logger.info(f"API原始响应: {data}")
|
||||||
|
|
||||||
|
# 验证数据完整性
|
||||||
|
if 'data' not in data or not data['data']:
|
||||||
|
logger.error(f"API响应缺少data字段: {data}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if 'userInfo' not in data['data'] or not data['data']['userInfo']:
|
||||||
|
logger.error(f"API响应缺少userInfo字段: {data['data']}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if 'user' not in data['data']['userInfo'] or not data['data']['userInfo']['user']:
|
||||||
|
logger.error(f"API响应缺少user字段: {data['data']['userInfo']}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 打印用户信息
|
||||||
|
logger.info(f"用户信息: {data['data']['userInfo']['user']}")
|
||||||
|
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.error(f"API响应不是有效的JSON: {response.text[:500]}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
logger.error(f"获取用户信息失败: HTTP {response.status_code}, 响应: {response.text[:500]}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取用户信息异常: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def download_video(video_id, unique_id, save_path):
|
||||||
|
"""使用API的直接下载接口下载TikTok视频"""
|
||||||
|
# 确保视频ID是纯数字
|
||||||
|
if not str(video_id).isdigit():
|
||||||
|
logger.error(f"无效的视频ID: {video_id},必须是纯数字")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 构建标准TikTok视频URL
|
||||||
|
tiktok_url = f"https://www.tiktok.com/@{unique_id}/video/{video_id}"
|
||||||
|
logger.info(f"构建的TikTok URL: {tiktok_url}")
|
||||||
|
|
||||||
|
# 构建完整的API请求URL
|
||||||
|
api_url = f"http://81.69.223.133:45268/api/download"
|
||||||
|
full_url = f"{api_url}?url={tiktok_url}&prefix=true&with_watermark=false"
|
||||||
|
logger.info(f"完整的API请求URL: {full_url}")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 直接使用完整URL发送请求
|
||||||
|
response = requests.get(full_url, headers=headers, stream=True, timeout=60)
|
||||||
|
|
||||||
|
# 检查响应状态
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"下载视频失败: {response.status_code} - {response.text[:200] if response.text else '无响应内容'}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取内容类型
|
||||||
|
content_type = response.headers.get('Content-Type', '')
|
||||||
|
logger.info(f"响应内容类型: {content_type}")
|
||||||
|
|
||||||
|
# 保存文件
|
||||||
|
with open(save_path, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
file_size = os.path.getsize(save_path)
|
||||||
|
logger.info(f"视频已下载到: {save_path},文件大小: {file_size}字节")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"下载视频异常: {e}")
|
||||||
|
import traceback
|
||||||
|
logger.error(f"详细错误: {traceback.format_exc()}")
|
||||||
|
return False
|
||||||
|
BIN
response.json
Normal file
BIN
response.json
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user