diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..30f5e9f
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+manage.py
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e1c8734..f26f5de 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -31,20 +31,20 @@
- {
+ "keyToString": {
+ "RunOnceActivity.OpenDjangoStructureViewOnStart": "true",
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
+ "RunOnceActivity.ShowReadmeOnStart": "true",
+ "last_opened_file_path": "D:/pythonProject/ecosscloud-cloud-services-frontend",
+ "node.js.detected.package.eslint": "true",
+ "node.js.detected.package.tslint": "true",
+ "node.js.selected.package.eslint": "(autodetect)",
+ "node.js.selected.package.tslint": "(autodetect)",
+ "nodejs_package_manager_path": "npm",
+ "vue.rearranger.settings.migration": "true"
}
-}]]>
+}
@@ -93,6 +93,7 @@
+
diff --git a/automated_task_monitor/__init__.py b/automated_task_monitor/__init__.py
index e69de29..047db9e 100644
--- a/automated_task_monitor/__init__.py
+++ b/automated_task_monitor/__init__.py
@@ -0,0 +1,6 @@
+from __future__ import absolute_import, unicode_literals
+
+# 导入celery app
+from .celery import app as celery_app
+
+__all__ = ('celery_app',)
\ No newline at end of file
diff --git a/automated_task_monitor/__pycache__/__init__.cpython-310.pyc b/automated_task_monitor/__pycache__/__init__.cpython-310.pyc
index d83c3a1..7050a85 100644
Binary files a/automated_task_monitor/__pycache__/__init__.cpython-310.pyc and b/automated_task_monitor/__pycache__/__init__.cpython-310.pyc differ
diff --git a/automated_task_monitor/__pycache__/celery.cpython-310.pyc b/automated_task_monitor/__pycache__/celery.cpython-310.pyc
new file mode 100644
index 0000000..3d4d153
Binary files /dev/null and b/automated_task_monitor/__pycache__/celery.cpython-310.pyc differ
diff --git a/automated_task_monitor/__pycache__/settings.cpython-310.pyc b/automated_task_monitor/__pycache__/settings.cpython-310.pyc
index a392092..f8cc186 100644
Binary files a/automated_task_monitor/__pycache__/settings.cpython-310.pyc and b/automated_task_monitor/__pycache__/settings.cpython-310.pyc differ
diff --git a/automated_task_monitor/celery.py b/automated_task_monitor/celery.py
new file mode 100644
index 0000000..efcef48
--- /dev/null
+++ b/automated_task_monitor/celery.py
@@ -0,0 +1,31 @@
+from __future__ import absolute_import, unicode_literals
+import os
+import platform
+from celery import Celery
+
+# 将'your_project'替换为'automated_task_monitor'
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'automated_task_monitor.settings')
+
+# 同样这里改为正确的项目名
+app = Celery('automated_task_monitor')
+
+# 使用CELERY_前缀的设置
+app.config_from_object('django.conf:settings', namespace='CELERY')
+
+# 根据操作系统调整Celery配置
+if platform.system() == 'Windows':
+ # Windows 开发环境配置
+ app.conf.update(
+ worker_pool='eventlet',
+ worker_concurrency=50,
+ )
+else:
+ # Linux 生产环境配置
+ app.conf.update(
+ worker_concurrency=8, # 设置为服务器CPU核心数
+ worker_max_tasks_per_child=1000, # 处理1000个任务后重启worker(防止内存泄漏)
+ worker_prefetch_multiplier=4, # 提前获取任务数量
+ )
+
+# 自动从所有注册的app中加载任务
+app.autodiscover_tasks()
\ No newline at end of file
diff --git a/automated_task_monitor/settings.py b/automated_task_monitor/settings.py
index cb0bfd2..788aa94 100644
--- a/automated_task_monitor/settings.py
+++ b/automated_task_monitor/settings.py
@@ -27,11 +27,11 @@ SECRET_KEY = 'django-insecure-!1v8ca8ows01-*m5(&9)qgk5jc-my^q+4d+0)s_*^n&vne^&w9
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = [
- '127.0.0.1', # 本地开发
- 'localhost', # 本地开发
- '81.69.223.133', # 你的服务器IP
-]
+ALLOWED_HOSTS = ['*']
+
+# 允许所有跨域请求
+CORS_ALLOW_ALL_ORIGINS = True
+
# 监控配置
MONITOR_INTERVAL = 5 # 缩短为5秒
@@ -49,9 +49,11 @@ INSTALLED_APPS = [
'django.contrib.messages',
'django.contrib.staticfiles',
'monitor.apps.MonitorConfig',
+ 'corsheaders',
]
MIDDLEWARE = [
+ 'corsheaders.middleware.CorsMiddleware', # 必须放在第一位
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -96,7 +98,10 @@ DATABASES = {
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
+ 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
+ 'connect_timeout': 60, # 连接超时时间
},
+ 'CONN_MAX_AGE': 0, # 强制Django在每次请求后关闭连接
}
}
@@ -180,3 +185,11 @@ os.makedirs('logs', exist_ok=True)
# 添加版本号,防止缓存
STATIC_VERSION = int(time.time())
+# Celery配置
+CELERY_BROKER_URL = 'redis://localhost:6379/0'
+CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
+CELERY_ACCEPT_CONTENT = ['json']
+CELERY_TASK_SERIALIZER = 'json'
+CELERY_RESULT_SERIALIZER = 'json'
+CELERY_TIMEZONE = 'Asia/Shanghai'
+
diff --git a/db.sqlite3 b/db.sqlite3
new file mode 100644
index 0000000..e69de29
diff --git a/logs/process_monitor/all/2136_20250225_all.log b/logs/process_monitor/all/2136_20250225_all.log
new file mode 100644
index 0000000..cb522fd
--- /dev/null
+++ b/logs/process_monitor/all/2136_20250225_all.log
@@ -0,0 +1,23 @@
+=== 2025-02-25 13:44:25 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 14/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.0%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/22424_20250225_all.log b/logs/process_monitor/all/22424_20250225_all.log
new file mode 100644
index 0000000..0eab47f
--- /dev/null
+++ b/logs/process_monitor/all/22424_20250225_all.log
@@ -0,0 +1,184 @@
+=== 2025-02-25 13:47:34 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 13/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.1%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:50:09 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 71.8%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:50:57 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:57:53 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 71.6%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:58:54 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:00:19 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 71.4%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:00:56 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.0%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:05:30 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 7/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 71.9%
+└─ 交换空间使用: 2.7%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/22740_20250225_all.log b/logs/process_monitor/all/22740_20250225_all.log
new file mode 100644
index 0000000..112a582
--- /dev/null
+++ b/logs/process_monitor/all/22740_20250225_all.log
@@ -0,0 +1,184 @@
+=== 2025-02-25 13:47:34 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 117/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.1%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 837次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:50:09 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 285/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 71.8%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 914次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:50:57 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 340/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 937次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:57:53 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 797/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 71.6%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1147次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 13:58:54 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 871/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1177次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:00:20 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 982/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 71.4%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1220次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:00:56 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1029/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1238次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:05:30 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1344/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 71.7%
+└─ 交换空间使用: 2.7%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1374次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/27836_20250225_all.log b/logs/process_monitor/all/27836_20250225_all.log
new file mode 100644
index 0000000..c218c57
--- /dev/null
+++ b/logs/process_monitor/all/27836_20250225_all.log
@@ -0,0 +1,92 @@
+=== 2025-02-25 14:11:01 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 98/0
+内存信息:
+├─ 物理内存: 9.4MB (0.1%)
+├─ 虚拟内存: 5.3MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.3%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 832次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:14:03 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 309/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.6%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 923次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:16:50 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 508/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.0%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1007次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:17:16 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 541/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.7%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1020次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/31964_20250225_all.log b/logs/process_monitor/all/31964_20250225_all.log
new file mode 100644
index 0000000..6fd6eb6
--- /dev/null
+++ b/logs/process_monitor/all/31964_20250225_all.log
@@ -0,0 +1,483 @@
+=== 2025-02-25 14:42:27 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 93/0
+内存信息:
+├─ 物理内存: 9.4MB (0.1%)
+├─ 虚拟内存: 5.3MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 75.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 831次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:43:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 152/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 75.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 862次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:44:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 227/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 75.4%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 892次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:48:04 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 476/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 75.4%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1000次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:48:52 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 540/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.2MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.7%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1024次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:49:56 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 623/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.4%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1056次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:50:42 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 683/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.7%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1079次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:56:17 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1068/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1247次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:57:22 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1147/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1279次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:59:48 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1317/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1351次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:00:49 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1393/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.5%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1382次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:03:18 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1571/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1456次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:04:20 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1650/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.8%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1487次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:05:22 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1722/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.1%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1518次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:05:24 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1724/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1519次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:06:04 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1781/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 74.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1539次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:06:52 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1847/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.4%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1562次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:07:09 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1870/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1571次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:07:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 1893/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.6%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1581次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:09:16 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 2030/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 1.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1634次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:10:17 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 2106/0
+内存信息:
+├─ 物理内存: 9.3MB (0.1%)
+├─ 虚拟内存: 5.1MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 73.5%
+└─ 交换空间使用: 1.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 1665次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/35500_20250225_all.log b/logs/process_monitor/all/35500_20250225_all.log
new file mode 100644
index 0000000..df39657
--- /dev/null
+++ b/logs/process_monitor/all/35500_20250225_all.log
@@ -0,0 +1,92 @@
+=== 2025-02-25 14:11:01 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 14/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.3%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:14:03 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 8/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.6%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:16:50 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 12/0
+内存信息:
+├─ 物理内存: 3.6MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.0%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:17:16 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 12/0
+内存信息:
+├─ 物理内存: 3.6MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.8%
+└─ 交换空间使用: 2.8%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/37712_20250225_all.log b/logs/process_monitor/all/37712_20250225_all.log
new file mode 100644
index 0000000..4ab9b8c
--- /dev/null
+++ b/logs/process_monitor/all/37712_20250225_all.log
@@ -0,0 +1,460 @@
+=== 2025-02-25 14:42:27 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 15/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 75.3%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:43:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 75.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:44:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 75.4%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 40次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:48:04 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 13/0
+内存信息:
+├─ 物理内存: 3.6MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 75.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:48:52 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 13/0
+内存信息:
+├─ 物理内存: 3.6MB (0.0%)
+├─ 虚拟内存: 0.8MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.7%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:49:56 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.5%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:50:42 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.7%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 42次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:56:17 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:57:22 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 14:59:48 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.5%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:00:49 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.5%
+└─ 交换空间使用: 2.0%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:03:18 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:04:20 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.8%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:05:23 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.2%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:05:25 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.3%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:06:04 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 74.3%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:06:53 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.4%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:07:29 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 72.7%
+└─ 交换空间使用: 2.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:09:16 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.1%
+└─ 交换空间使用: 1.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 15:10:17 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 9/0
+内存信息:
+├─ 物理内存: 3.5MB (0.0%)
+├─ 虚拟内存: 0.7MB
+├─ 内存映射: 11个
+├─ 系统内存使用: 73.5%
+└─ 交换空间使用: 1.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (2次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 43次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/all/7616_20250225_all.log b/logs/process_monitor/all/7616_20250225_all.log
new file mode 100644
index 0000000..d8e8873
--- /dev/null
+++ b/logs/process_monitor/all/7616_20250225_all.log
@@ -0,0 +1,23 @@
+=== 2025-02-25 13:44:25 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 93/0
+内存信息:
+├─ 物理内存: 9.4MB (0.1%)
+├─ 虚拟内存: 5.3MB
+├─ 内存映射: 25个
+├─ 系统内存使用: 72.0%
+└─ 交换空间使用: 2.9%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.3MB (69次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 832次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/cpu/1132_20250225_cpu.log b/logs/process_monitor/cpu/1132_20250225_cpu.log
new file mode 100644
index 0000000..e22dc78
--- /dev/null
+++ b/logs/process_monitor/cpu/1132_20250225_cpu.log
@@ -0,0 +1,46 @@
+=== 2025-02-25 10:12:46 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 4.7s
+├─ 内核态时间: 2.6s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 392063/0
+内存信息:
+├─ 物理内存: 296.9MB (1.9%)
+├─ 虚拟内存: 177.0MB
+├─ 内存映射: 325个
+├─ 系统内存使用: 78.2%
+└─ 交换空间使用: 0.7%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 3906.9MB (303763次)
+├─ 写入: 49.0MB (5023次)
+└─ 其他IO: 108714次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-25 10:13:46 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 4.7s
+├─ 内核态时间: 2.6s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 413451/0
+内存信息:
+├─ 物理内存: 234.0MB (1.5%)
+├─ 虚拟内存: 177.0MB
+├─ 内存映射: 327个
+├─ 系统内存使用: 73.3%
+└─ 交换空间使用: 0.7%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 3906.9MB (303770次)
+├─ 写入: 49.0MB (5032次)
+└─ 其他IO: 112589次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/cpu/6432_20250220_cpu.log b/logs/process_monitor/cpu/6432_20250220_cpu.log
new file mode 100644
index 0000000..f526469
--- /dev/null
+++ b/logs/process_monitor/cpu/6432_20250220_cpu.log
@@ -0,0 +1,138 @@
+=== 2025-02-20 12:25:46 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-20 12:25:46 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 72.2%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-20 12:27:32 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 72.3%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-20 12:27:32 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 72.3%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-20 20:30:34 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 73.3%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
+=== 2025-02-20 20:30:34 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 86/0
+内存信息:
+├─ 物理内存: 9.0MB (0.1%)
+├─ 虚拟内存: 1.8MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 73.2%
+└─ 交换空间使用: 5.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (0次)
+├─ 写入: 0.0MB (0次)
+└─ 其他IO: 74次
+进程状态: running
+--------------------------------------------------
diff --git a/logs/process_monitor/cpu/6432_20250222_cpu.log b/logs/process_monitor/cpu/6432_20250222_cpu.log
new file mode 100644
index 0000000..e4808ea
--- /dev/null
+++ b/logs/process_monitor/cpu/6432_20250222_cpu.log
@@ -0,0 +1,23 @@
+=== 2025-02-22 10:43:55 ===
+CPU信息:
+├─ 使用率: 0.0%
+├─ 用户态时间: 0.0s
+├─ 内核态时间: 0.0s
+├─ CPU核心数: 16
+├─ CPU频率: 4001.0MHz
+└─ 上下文切换: 717/0
+内存信息:
+├─ 物理内存: 13.3MB (0.1%)
+├─ 虚拟内存: 4.4MB
+├─ 内存映射: 0个
+├─ 系统内存使用: 72.5%
+└─ 交换空间使用: 0.1%
+GPU信息:
+├─ 使用率: 0.0%
+└─ 显存使用: 0.0MB
+IO信息:
+├─ 读取: 0.0MB (3次)
+├─ 写入: 0.0MB (1次)
+└─ 其他IO: 275次
+进程状态: running
+--------------------------------------------------
diff --git a/monitor/__pycache__/models.cpython-310.pyc b/monitor/__pycache__/models.cpython-310.pyc
index 393fa2d..7878e90 100644
Binary files a/monitor/__pycache__/models.cpython-310.pyc and b/monitor/__pycache__/models.cpython-310.pyc differ
diff --git a/monitor/__pycache__/urls.cpython-310.pyc b/monitor/__pycache__/urls.cpython-310.pyc
index 80ff0ae..450a4d1 100644
Binary files a/monitor/__pycache__/urls.cpython-310.pyc and b/monitor/__pycache__/urls.cpython-310.pyc differ
diff --git a/monitor/__pycache__/views.cpython-310.pyc b/monitor/__pycache__/views.cpython-310.pyc
index efe17e9..4bf2a0d 100644
Binary files a/monitor/__pycache__/views.cpython-310.pyc and b/monitor/__pycache__/views.cpython-310.pyc differ
diff --git a/monitor/migrations/0008_tiktokuservideos_following_count_and_more.py b/monitor/migrations/0008_tiktokuservideos_following_count_and_more.py
new file mode 100644
index 0000000..6288f66
--- /dev/null
+++ b/monitor/migrations/0008_tiktokuservideos_following_count_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.1.6 on 2025-03-06 07:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('monitor', '0007_alter_tiktokuservideos_avatar_url'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tiktokuservideos',
+ name='following_count',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='tiktokuservideos',
+ name='video_count',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/monitor/migrations/0009_tiktokuservideos_private_account_and_more.py b/monitor/migrations/0009_tiktokuservideos_private_account_and_more.py
new file mode 100644
index 0000000..ed51f5a
--- /dev/null
+++ b/monitor/migrations/0009_tiktokuservideos_private_account_and_more.py
@@ -0,0 +1,92 @@
+# Generated by Django 5.1.6 on 2025-03-11 05:39
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('monitor', '0008_tiktokuservideos_following_count_and_more'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='tiktokuservideos',
+ name='private_account',
+ field=models.BooleanField(default=False, verbose_name='是否私密账号'),
+ ),
+ migrations.AddField(
+ model_name='tiktokuservideos',
+ name='unique_id',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='唯一标识符'),
+ ),
+ migrations.AddField(
+ model_name='tiktokuservideos',
+ name='verified',
+ field=models.BooleanField(default=False, verbose_name='是否认证'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='avatar_url',
+ field=models.TextField(blank=True, null=True, verbose_name='头像URL'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='create_time',
+ field=models.DateTimeField(auto_now_add=True, verbose_name='创建时间'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='follower_count',
+ field=models.IntegerField(default=0, verbose_name='粉丝数'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='following_count',
+ field=models.IntegerField(default=0, verbose_name='关注数'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='nickname',
+ field=models.CharField(blank=True, max_length=255, null=True, verbose_name='昵称'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='sec_user_id',
+ field=models.CharField(max_length=255, unique=True, verbose_name='安全用户ID'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='signature',
+ field=models.TextField(blank=True, null=True, verbose_name='签名'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='total_favorited',
+ field=models.IntegerField(default=0, verbose_name='获赞总数'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='update_time',
+ field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='video_count',
+ field=models.IntegerField(default=0, verbose_name='视频数'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='video_paths',
+ field=models.TextField(blank=True, null=True, verbose_name='视频路径'),
+ ),
+ migrations.AlterField(
+ model_name='tiktokuservideos',
+ name='videos_folder',
+ field=models.CharField(max_length=255, verbose_name='视频文件夹'),
+ ),
+ migrations.AddIndex(
+ model_name='tiktokuservideos',
+ index=models.Index(fields=['unique_id'], name='monitor_tik_unique__dd6fa8_idx'),
+ ),
+ ]
diff --git a/monitor/migrations/0010_remove_tiktokuservideos_video_paths.py b/monitor/migrations/0010_remove_tiktokuservideos_video_paths.py
new file mode 100644
index 0000000..a52194d
--- /dev/null
+++ b/monitor/migrations/0010_remove_tiktokuservideos_video_paths.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.1.6 on 2025-03-11 05:50
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('monitor', '0009_tiktokuservideos_private_account_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='tiktokuservideos',
+ name='video_paths',
+ ),
+ ]
diff --git a/monitor/migrations/__pycache__/0008_tiktokuservideos_following_count_and_more.cpython-310.pyc b/monitor/migrations/__pycache__/0008_tiktokuservideos_following_count_and_more.cpython-310.pyc
new file mode 100644
index 0000000..4fec5f1
Binary files /dev/null and b/monitor/migrations/__pycache__/0008_tiktokuservideos_following_count_and_more.cpython-310.pyc differ
diff --git a/monitor/migrations/__pycache__/0009_tiktokuservideos_private_account_and_more.cpython-310.pyc b/monitor/migrations/__pycache__/0009_tiktokuservideos_private_account_and_more.cpython-310.pyc
new file mode 100644
index 0000000..e2238dc
Binary files /dev/null and b/monitor/migrations/__pycache__/0009_tiktokuservideos_private_account_and_more.cpython-310.pyc differ
diff --git a/monitor/migrations/__pycache__/0010_remove_tiktokuservideos_video_paths.cpython-310.pyc b/monitor/migrations/__pycache__/0010_remove_tiktokuservideos_video_paths.cpython-310.pyc
new file mode 100644
index 0000000..4c06c70
Binary files /dev/null and b/monitor/migrations/__pycache__/0010_remove_tiktokuservideos_video_paths.cpython-310.pyc differ
diff --git a/monitor/models.py b/monitor/models.py
index 510e25f..951453b 100644
--- a/monitor/models.py
+++ b/monitor/models.py
@@ -76,21 +76,46 @@ class AllResourceProcess(models.Model):
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)
+ sec_user_id = models.CharField(max_length=255, unique=True, verbose_name="安全用户ID")
+ unique_id = models.CharField(max_length=255, blank=True, null=True, verbose_name="唯一标识符")
+ nickname = models.CharField(max_length=255, blank=True, null=True, verbose_name="昵称")
+ signature = models.TextField(blank=True, null=True, verbose_name="签名")
+
+ # 用户统计数据
+ follower_count = models.IntegerField(default=0, verbose_name="粉丝数")
+ following_count = models.IntegerField(default=0, verbose_name="关注数")
+ video_count = models.IntegerField(default=0, verbose_name="视频数")
+ total_favorited = models.IntegerField(default=0, verbose_name="获赞总数")
+
+ # 用户资料信息
+ avatar_url = models.TextField(blank=True, null=True, verbose_name="头像URL")
+ verified = models.BooleanField(default=False, verbose_name="是否认证")
+ private_account = models.BooleanField(default=False, verbose_name="是否私密账号")
+
+ # 视频存储相关
+ videos_folder = models.CharField(max_length=255, verbose_name="视频文件夹")
+
+ # 时间记录
+ create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
+ update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = 'TikTok用户视频'
verbose_name_plural = 'TikTok用户视频'
+ indexes = [
+ models.Index(fields=['unique_id']),
+ ]
def __str__(self):
- return f"{self.nickname} ({self.sec_user_id})"
+ display_name = self.nickname if self.nickname else self.unique_id if self.unique_id else self.sec_user_id
+ return f"{display_name} ({self.sec_user_id[:8]}...)"
+
+ def get_stats_summary(self):
+ """获取用户统计数据摘要"""
+ return {
+ 'followers': self.follower_count,
+ 'following': self.following_count,
+ 'videos': self.video_count,
+ 'likes': self.total_favorited,
+ }
\ No newline at end of file
diff --git a/monitor/urls.py b/monitor/urls.py
index 3ffa78a..c098671 100644
--- a/monitor/urls.py
+++ b/monitor/urls.py
@@ -17,5 +17,12 @@ urlpatterns = [
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'),
- path('api/recursive_fetch_videos', views.recursive_fetch_videos, name='recursive_fetch_videos'),
+ path('api/fetch_videos_with_callback', views.fetch_videos_with_callback, name='fetch_videos_with_callback'),
+ path('api/start_recursive_fetch_videos', views.start_recursive_fetch_videos, name='start_recursive_fetch_videos'),
+ path('api/fetch_task_status/', views.check_fetch_task_status, name='check_fetch_task_status'),
+ path('api/fetch_task_result/', views.get_fetch_task_result, name='get_fetch_task_result'),
+ path('api/list_fetch_tasks', views.list_fetch_tasks, name='list_fetch_tasks'),
+ path('api/clean_completed_tasks', views.clean_completed_tasks, name='clean_completed_tasks'),
+ path('api/reset_task_status/', views.reset_task_status, name='reset_task_status'),
+ path('api/check_task_status_detail/', views.check_task_status_detail, name='check_task_status_detail'),
]
diff --git a/monitor/views.py b/monitor/views.py
index 8b65bb4..665dc14 100644
--- a/monitor/views.py
+++ b/monitor/views.py
@@ -18,6 +18,14 @@ import json
from django.conf import settings
import requests
import threading
+from collections import deque
+from celery import shared_task
+import uuid
+from functools import wraps
+from datetime import timedelta
+import redis
+from concurrent.futures import ThreadPoolExecutor, as_completed
+from yt_dlp import YoutubeDL
directory_monitoring = {}
@@ -48,6 +56,30 @@ monitored_directories = set()
# 在文件顶部添加 API 基础 URL
API_BASE_URL = "http://81.69.223.133:45268"
+# 创建Redis连接
+redis_client = redis.Redis.from_url(settings.CELERY_BROKER_URL)
+
+# 替代原来的TASK_STATUS字典方法
+def set_task_status(task_id, status_data):
+ """在Redis中存储任务状态"""
+ redis_client.set(f"task_status:{task_id}", json.dumps(status_data))
+
+def get_task_status(task_id):
+ """从Redis获取任务状态"""
+ data = redis_client.get(f"task_status:{task_id}")
+ if data:
+ return json.loads(data)
+ return None
+
+def update_task_status(task_id, updates):
+ """更新任务状态的部分字段"""
+ status = get_task_status(task_id)
+ if status:
+ status.update(updates)
+ set_task_status(task_id, status)
+ return True
+ return False
+
# 添加新的监控线程函数
def monitor_directory(directory):
"""持续监控目录的后台任务"""
@@ -1024,8 +1056,15 @@ def get_open_files(proc):
@csrf_exempt
@require_http_methods(["POST"])
def fetch_tiktok_videos(request):
- """获取TikTok用户视频并下载播放量前十的视频"""
+ """获取TikTok用户播放量前10的视频"""
try:
+ # 添加全局变量引用
+ global all_downloaded_videos
+
+ # 如果变量未初始化,则初始化为空列表
+ if 'all_downloaded_videos' not in globals():
+ all_downloaded_videos = []
+
data = json.loads(request.body)
unique_id = data.get('unique_id')
@@ -1054,11 +1093,12 @@ def fetch_tiktok_videos(request):
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', {})
+ user_stats = user_profile['data']['userInfo']['stats']
follower_count = user_stats.get('followerCount', 0)
heart_count = user_stats.get('heartCount', 0)
+ video_count = user_stats.get('videoCount', 0)
- logger.info(f"成功获取用户secUid: {sec_uid}")
+ logger.info(f"成功获取用户secUid: {sec_uid}, 该用户有 {video_count} 个视频")
except (KeyError, TypeError) as e:
logger.error(f"解析用户资料出错: {e}")
return JsonResponse({
@@ -1070,66 +1110,38 @@ def fetch_tiktok_videos(request):
user_dir = os.path.join(TIKTOK_VIDEOS_PATH, unique_id)
os.makedirs(user_dir, exist_ok=True)
- # 获取用户视频
- videos_data = fetch_user_videos(sec_uid)
+ # 使用辅助函数获取播放量前10的视频
+ logger.info(f"开始获取用户 {nickname} 的全部视频并查找播放量前10...")
+ top_videos = get_top_n_videos_by_play_count(sec_uid, n=10)
- # 提取视频信息并按播放量排序
- 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)
+ for i, (play_count, video_id, desc) in enumerate(top_videos, 1):
+ try:
+ save_path = os.path.join(user_dir, f"{video_id}.mp4")
+
+ logger.info(f"下载第 {i}/{len(top_videos)} 个视频 (ID: {video_id}),播放量: {play_count}")
+
+ if download_video(video_id, unique_id, save_path):
+ video_info = {
+ 'id': video_id,
+ 'desc': desc,
+ 'play_count': play_count,
+ 'user_unique_id': unique_id
+ }
+ downloaded_videos.append(video_info)
+ logger.info(f"视频 {video_id} 下载成功")
+ else:
+ logger.error(f"视频 {video_id} 下载失败")
+
+ # 避免过快请求
+ time.sleep(2)
+
+ except Exception as e:
+ logger.error(f"下载视频时出错: {e}")
+ continue
+
+ all_downloaded_videos.extend(downloaded_videos)
# 保存用户信息和视频信息到数据库
video_info_json = json.dumps([{
@@ -1147,7 +1159,8 @@ def fetch_tiktok_videos(request):
'total_favorited': heart_count,
'avatar_url': avatar_url,
'videos_folder': user_dir,
- 'video_paths': video_info_json
+ 'video_paths': video_info_json,
+ 'video_count': video_count
}
)
@@ -1161,9 +1174,11 @@ def fetch_tiktok_videos(request):
'avatar': avatar_url,
'follower_count': follower_count,
'total_favorited': heart_count,
- 'signature': signature
+ 'signature': signature,
+ 'video_count': video_count
},
- 'videos_count': len(videos_data['data']['itemList']) if videos_data and 'data' in videos_data and 'itemList' in videos_data['data'] else 0,
+ 'total_videos_count': video_count,
+ 'processed_videos_count': len(top_videos),
'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 # 添加视频目录路径方便查找
@@ -1236,73 +1251,56 @@ def get_tiktok_user_videos(request):
# 辅助函数
-def fetch_user_videos(sec_uid, max_cursor=0, count=20):
+def fetch_user_videos(sec_uid, cursor=0, count=10):
"""获取用户视频列表"""
- url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_post?secUid={sec_uid}"
+ url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_post?secUid={sec_uid}&cursor={cursor}&count={count}"
try:
- response = requests.get(url)
+ response = requests.get(url, timeout=30)
+
if response.status_code == 200:
- return response.json()
+ data = response.json()
+ logger.info(f"成功获取用户视频,共 {len(data['data'].get('itemList', []))} 个视频")
+ return data
else:
- logger.error(f"获取视频列表失败: {response.status_code}")
+ logger.error(f"获取用户视频失败: {response.status_code}")
return None
except Exception as e:
- logger.error(f"获取视频列表异常: {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秒后重试
+ response = requests.get(url, timeout=30)
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]}")
+ 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
else:
logger.error(f"获取用户信息失败: HTTP {response.status_code}, 响应: {response.text[:500]}")
return None
@@ -1326,13 +1324,9 @@ def download_video(video_id, unique_id, save_path):
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)
+ response = requests.get(full_url, stream=True, timeout=60)
# 检查响应状态
if response.status_code != 200:
@@ -1360,19 +1354,29 @@ def download_video(video_id, unique_id, save_path):
logger.error(f"详细错误: {traceback.format_exc()}")
return False
-def fetch_user_followings(sec_uid):
- """获取用户关注列表"""
- url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_follow?secUid={sec_uid}"
+def fetch_user_followings(sec_uid, max_cursor=0, min_cursor=0, page_size=30):
+ """
+ 获取用户的关注列表
+
+ Args:
+ sec_uid: 用户的安全ID
+ max_cursor: 保持为0
+ min_cursor: 分页游标,从上一次响应获取
+ page_size: 每页大小,默认30条记录
+
+ Returns:
+ 用户关注列表数据
+ """
+ url = f"{API_BASE_URL}/api/tiktok/web/fetch_user_follow?secUid={sec_uid}&count={page_size}&maxCursor={max_cursor}&minCursor={min_cursor}"
+
+ logger.info(f"请求关注列表URL: {url}")
try:
- 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',
- }
- response = requests.get(url, headers=headers, timeout=30)
+ response = requests.get(url, timeout=30)
if response.status_code == 200:
data = response.json()
- logger.info(f"成功获取用户关注列表,共 {len(data['data'].get('userList', []))} 个关注")
+ logger.info(f"成功获取用户关注列表,共 {len(data['data'].get('userList', []))} 个用户, minCursor={data['data'].get('minCursor')}")
return data
else:
logger.error(f"获取用户关注列表失败: {response.status_code}")
@@ -1395,217 +1399,1045 @@ def filter_users_by_followers(user_list, min_followers=5000, max_followers=50000
return filtered_users
-@csrf_exempt
-@require_http_methods(["POST"])
-def recursive_fetch_videos(request):
- """递归获取关注列表中的用户视频"""
+def get_top_n_videos_by_play_count(sec_uid, n=10):
+ """
+ 获取用户播放量前N的视频
+
+ Args:
+ sec_uid: 用户的安全ID
+ n: 需要获取的前n个视频,默认为10
+
+ Returns:
+ 按播放量降序排列的前n个视频列表,格式为字典
+ """
+ import heapq
+ top_videos_heap = [] # 小顶堆,元组格式: (播放量, 视频ID, 描述)
+
+ # 分页获取所有视频
+ cursor = 0
+ has_more = True
+ page = 1
+ total_videos = 0
+
try:
- data = json.loads(request.body)
- start_unique_id = data.get('unique_id')
- max_depth = int(data.get('max_depth', 3)) # 默认递归深度为3
-
- if not start_unique_id:
- return JsonResponse({
- 'status': 'error',
- 'message': '请提供起始TikTok用户ID(unique_id)'
- }, json_dumps_params={'ensure_ascii': False})
-
- # 获取起始用户资料和secUid
- user_profile = fetch_user_profile(start_unique_id)
- if not user_profile or 'data' not in user_profile:
- return JsonResponse({
- 'status': 'error',
- 'message': f'无法获取用户 {start_unique_id} 的资料'
- }, json_dumps_params={'ensure_ascii': False})
-
- # 提取secUid和其他用户信息
- try:
- user_info = user_profile['data']['userInfo']['user']
- start_sec_uid = user_info['secUid']
+ while has_more:
+ logger.info(f"获取第{page}页视频,游标: {cursor}")
+ videos_data = fetch_user_videos(sec_uid, cursor=cursor)
- # 提取起始用户的详细信息
- nickname = user_info.get('nickname', '')
- signature = user_info.get('signature', '')
- avatar_url = user_info.get('avatarLarger', '')
-
- # 提取统计信息
- stats = user_profile['data']['userInfo'].get('stats', {})
- follower_count = stats.get('followerCount', 0)
- following_count = stats.get('followingCount', 0)
- heart_count = stats.get('heartCount', 0) or stats.get('diggCount', 0)
- video_count = stats.get('videoCount', 0)
-
- # 为起始用户创建目录
- start_user_dir = os.path.join(TIKTOK_VIDEOS_PATH, start_unique_id)
- os.makedirs(start_user_dir, exist_ok=True)
-
- # 保存起始用户信息到数据库
- TiktokUserVideos.objects.update_or_create(
- sec_user_id=start_sec_uid,
- defaults={
- 'nickname': nickname,
- 'signature': signature,
- 'follower_count': follower_count,
- 'following_count': following_count,
- 'total_favorited': heart_count,
- 'video_count': video_count,
- 'avatar_url': avatar_url,
- 'videos_folder': start_user_dir
- }
- )
-
- logger.info(f"成功获取并保存起始用户信息: {start_unique_id}, secUid: {start_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})
-
- # 开始递归获取视频
- all_downloaded_videos = []
- processed_users = set() # 已处理的用户集合,避免重复处理
-
- def process_user(sec_uid, unique_id, depth=0):
- """递归处理用户,获取视频和关注用户"""
- if depth >= max_depth or sec_uid in processed_users:
- return
-
- processed_users.add(sec_uid)
- logger.info(f"处理用户 {unique_id},递归深度: {depth}")
-
- # 确保用户目录存在
- 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:
- video_id = video.get('id', '')
- if not video_id or not str(video_id).isdigit():
- 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', ''),
- 'play_count': play_count
- })
- except Exception as e:
- logger.error(f"处理视频数据出错: {str(e)}")
+ if not videos_data or 'data' not in videos_data:
+ logger.error("获取视频失败,中止获取")
+ break
+
+ videos = videos_data['data'].get('itemList', [])
+ if not videos:
+ logger.info("当前页没有视频,结束获取")
+ break
+
+ # 处理当前页的每个视频
+ for video in videos:
+ try:
+ video_id = video.get('id', '')
+ if not video_id:
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):
- video_id = video['id']
- save_path = os.path.join(user_dir, f"{video_id}.mp4")
+
+ play_count = int(video.get('stats', {}).get('playCount', 0))
+ desc = video.get('desc', '')
- logger.info(f"下载用户 {unique_id} 的第 {i+1} 个热门视频: {video_id}")
- if download_video(video_id, unique_id, save_path):
- video['download_path'] = save_path
- video['user_unique_id'] = unique_id
- downloaded_videos.append(video)
- all_downloaded_videos.append(video)
-
- 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)
-
- TiktokUserVideos.objects.update_or_create(
- sec_user_id=sec_uid,
- defaults={
- 'nickname': unique_id,
- 'videos_folder': user_dir,
- 'video_paths': video_info_json
- }
- )
+ # 维护前n个最高播放量的视频
+ if len(top_videos_heap) < n:
+ # 堆还未满,直接添加
+ heapq.heappush(top_videos_heap, (play_count, video_id, desc))
+ elif play_count > top_videos_heap[0][0]:
+ # 当前视频播放量比堆中最小的高,替换
+ heapq.heappushpop(top_videos_heap, (play_count, video_id, desc))
+ except Exception as e:
+ logger.error(f"处理视频信息出错: {e}")
- # 获取关注列表
- followings_data = fetch_user_followings(sec_uid)
- if followings_data and 'data' in followings_data and 'userList' in followings_data['data']:
- user_list = followings_data['data']['userList']
+ total_videos += len(videos)
+ logger.info(f"已处理 {total_videos} 个视频,当前保存了 {len(top_videos_heap)} 个候选视频")
+
+ # 检查是否有更多页
+ has_more = videos_data['data'].get('hasMore', False)
+ if has_more:
+ cursor = videos_data['data'].get('cursor', 0)
+
+ page += 1
+ time.sleep(1)
+
+ # 将堆转换为字典列表而不是元组列表,并按播放量降序排序
+ top_videos_list = []
+ # 从堆中转换为临时列表并排序
+ temp_list = [(play_count, video_id, desc) for play_count, video_id, desc in top_videos_heap]
+ temp_list.sort(reverse=True) # 从高到低排序
+
+ # 将元组转换为字典
+ for play_count, video_id, desc in temp_list:
+ top_videos_list.append({
+ 'id': video_id,
+ 'desc': desc,
+ 'play_count': play_count
+ })
+
+ logger.info(f"从 {total_videos} 个视频中找到播放量最高的 {len(top_videos_list)} 个")
+ return top_videos_list
+
+ except Exception as e:
+ logger.error(f"获取热门视频时发生错误: {str(e)}")
+ import traceback
+ logger.error(traceback.format_exc())
+ return []
+
+
+# 辅助函数,用于操作所有任务
+def list_all_tasks():
+ """获取所有任务列表"""
+ tasks = []
+ for key in redis_client.keys("task_status:*"):
+ task_id = key.decode().split(":", 1)[1]
+ task_data = get_task_status(task_id)
+ if task_data:
+ task_data['task_id'] = task_id
+ tasks.append(task_data)
+ return tasks
+
+@shared_task
+def async_fetch_videos_task(task_id, start_unique_id, max_depth=3, target_users=None, skip_user_profile=False, start_sec_uid=None):
+ """异步执行视频获取任务"""
+ try:
+ # 更新任务状态为处理中
+ update_task_status(task_id, {
+ 'status': 'processing',
+ 'progress': 0
+ })
+
+ # 创建视频获取器实例
+ fetcher = TiktokVideoFetcher(
+ start_unique_id=start_unique_id,
+ max_depth=max_depth,
+ target_users=target_users,
+ skip_user_profile=skip_user_profile, # 传递测试模式
+ start_sec_uid=start_sec_uid # 传递临时secUid
+ )
+
+ # 添加进度回调
+ fetcher.progress_callback = lambda progress, message: update_task_progress(task_id, progress, message)
+
+ # 执行处理
+ if fetcher.initialize() and fetcher.process():
+ # 处理成功,保存结果
+ result = fetcher.get_result()
+ update_task_status(task_id, {
+ 'status': 'completed',
+ 'result': result,
+ 'progress': 100
+ })
+ return True
+ else:
+ # 处理失败
+ update_task_status(task_id, {
+ 'status': 'failed',
+ 'error': fetcher.error_message
+ })
+ return False
+ except Exception as e:
+ # 处理异常
+ logger.error(f"异步任务执行失败: {str(e)}")
+ import traceback
+ logger.error(f"详细错误信息: {traceback.format_exc()}")
+
+ update_task_status(task_id, {
+ 'status': 'failed',
+ 'error': str(e)
+ })
+ return False
+
+def update_task_progress(task_id, progress, message=None):
+ """更新任务进度"""
+ updates = {'progress': progress}
+ if message:
+ updates['message'] = message
+ update_task_status(task_id, updates)
+
+class TiktokVideoFetcher:
+ """抖音视频获取器,支持递归获取用户关注的视频"""
+
+ def __init__(self, start_unique_id=None, start_sec_uid=None,
+ mode="by_users", target_users=None, max_depth=1,
+ skip_user_profile=False, max_videos_per_user=10):
+ """
+ 初始化抖音视频获取器
+ :param start_unique_id: 起始用户的uniqueId
+ :param start_sec_uid: 起始用户的secUid
+ :param mode: 模式,'by_users'(按用户数)或'by_depth'(按深度)
+ :param target_users: 目标用户数量,仅在by_users模式下有效
+ :param max_depth: 最大层级深度,仅在by_depth模式下有效
+ :param skip_user_profile: 是否跳过获取用户资料
+ :param max_videos_per_user: 每个用户最多获取的视频数量
+ """
+ # 统一参数命名:确保使用 start_sec_uid
+ self.start_unique_id = start_unique_id
+ self.start_sec_uid = start_sec_uid # 注意这里保持为start_sec_uid
+ self.mode = mode
+ self.target_users = target_users
+ self.max_depth = max_depth
+ self.skip_user_profile = skip_user_profile
+ self.max_videos_per_user = max_videos_per_user or 10
+
+ # 状态变量
+ self.status = "created" # 状态: created, initializing, ready, processing, completed, failed
+ self.user_count = 0 # 已处理用户数量
+ self.video_count = 0 # 已下载视频数量
+ self.progress = 0 # 进度百分比
+ self.progress_message = "" # 进度消息
+ self.error_message = "" # 错误消息
+
+ # 队列和集合
+ self.user_queue = deque() # 待处理用户队列,元素为 (sec_uid, unique_id, depth)
+ self.processed_users = set() # 已处理的用户集合
+
+ # 创建目录
+ os.makedirs(TIKTOK_VIDEOS_PATH, exist_ok=True)
+
+ logger.info(f"初始化TiktokVideoFetcher: mode={mode}, max_depth={max_depth}, target_users={target_users}")
+
+ def initialize(self):
+ """初始化处理,获取起始用户信息"""
+ try:
+ self.status = "initializing"
+ logger.info(f"开始初始化,起始用户: {self.start_unique_id or '使用secUid'}")
+
+ # 如果直接提供secUid,则直接使用
+ if self.start_sec_uid:
+ logger.info(f"直接使用提供的secUid: {self.start_sec_uid}")
- # 筛选粉丝数在5000-50000之间的用户
- filtered_users = filter_users_by_followers(user_list, 5000, 50000)
- logger.info(f"用户 {unique_id} 的关注列表中有 {len(filtered_users)} 个粉丝数在5000-50000之间")
+ # 如果未提供unique_id,使用secUid的一部分作为替代
+ display_id = self.start_unique_id or f"user_{self.start_sec_uid[:8]}"
- # 取前5个用户
- for user_data in filtered_users[:5]:
+ # 简化队列元素:只保留sec_uid、unique_id和深度
+ self.user_queue.append((self.start_sec_uid, display_id, 0))
+ self.status = "ready"
+ return True
+
+ # 如果只提供了uniqueId,需要获取secUid
+ elif self.start_unique_id:
+ logger.info(f"通过uniqueId获取用户secUid: {self.start_unique_id}")
+ user_profile = fetch_user_profile(self.start_unique_id)
+
+ # 检查错误
+ if isinstance(user_profile, dict) and 'error' in user_profile:
+ error_message = user_profile['error']
+ self.error_message = f'获取用户资料出错: {error_message}'
+ self.status = "failed"
+ return False
+
+ if not user_profile or 'data' not in user_profile or not user_profile['data']:
+ self.error_message = f'无法获取用户资料或用户不存在: {self.start_unique_id}'
+ self.status = "failed"
+ return False
+
+ try:
+ user_data = user_profile['data']
+ sec_uid = user_data.get('secUid', '')
+ if not sec_uid:
+ self.error_message = f'无法获取用户secUid: {self.start_unique_id}'
+ self.status = "failed"
+ return False
+
+ # 添加到队列中,深度为0
+ self.user_queue.append((sec_uid, self.start_unique_id, 0))
+
+ # 创建或更新用户数据库记录
try:
- # 直接从关注列表中提取用户信息
- user_obj = user_data['user']
- following_sec_uid = user_obj['secUid']
- following_unique_id = user_obj['uniqueId']
-
- # 获取用户详细信息
- nickname = user_obj.get('nickname', '')
- signature = user_obj.get('signature', '')
- avatar_url = user_obj.get('avatarLarger', '')
-
- # 获取统计信息
- stats = user_data.get('stats', {})
- follower_count = stats.get('followerCount', 0)
- following_count = stats.get('followingCount', 0)
- heart_count = stats.get('heartCount', 0)
- video_count = stats.get('videoCount', 0)
-
- # 保存用户信息到数据库(即使尚未下载视频)
- follow_user_dir = os.path.join(TIKTOK_VIDEOS_PATH, following_unique_id)
TiktokUserVideos.objects.update_or_create(
- sec_user_id=following_sec_uid,
+ sec_user_id=sec_uid,
defaults={
- 'nickname': nickname,
- 'signature': signature,
- 'follower_count': follower_count,
- 'following_count': following_count,
- 'total_favorited': heart_count,
- 'video_count': video_count,
- 'avatar_url': avatar_url,
- 'videos_folder': follow_user_dir
+ 'unique_id': self.start_unique_id,
+ 'nickname': user_data.get('nickname', ''),
+ 'follower_count': user_data.get('followerCount', 0),
+ 'following_count': user_data.get('followingCount', 0),
+ 'video_count': user_data.get('videoCount', 0),
+ 'videos_folder': os.path.join('videos', self.start_unique_id)
}
)
-
- # 递归处理关注的用户
- process_user(following_sec_uid, following_unique_id, depth + 1)
except Exception as e:
- logger.error(f"处理关注用户时出错: {e}")
+ logger.warning(f"保存用户数据到数据库失败,但不影响继续处理: {e}")
+
+ self.status = "ready"
+ return True
+ except KeyError:
+ self.error_message = f'用户资料结构不完整,无法提取secUid'
+ self.status = "failed"
+ return False
+
+ # 两者都没提供
+ else:
+ self.error_message = "未提供secUid或uniqueId,无法初始化"
+ self.status = "failed"
+ return False
+
+ except Exception as e:
+ logger.error(f"初始化失败: {e}")
+ import traceback
+ logger.error(f"详细错误信息: {traceback.format_exc()}")
+ self.error_message = f"初始化失败: {str(e)}"
+ self.status = "failed"
+ return False
+
+ def process(self):
+ """执行主处理逻辑"""
+ if self.status != "ready":
+ if not self.initialize():
+ return False
+
+ self.status = "processing"
+ self._report_progress(5, "初始化完成,开始处理")
+
+ try:
+ # 预先估算总任务量
+ total_users = self.target_users if self.mode == "by_users" and self.target_users else 50
+
+ # 主处理循环
+ while self.user_queue:
+ # 按人数抓取模式下,达到目标人数则停止
+ if self.mode == "by_users" and self.target_users is not None and self.user_count >= self.target_users:
+ logger.info(f"已达到目标用户数 {self.target_users},停止处理")
+ break
+
+ # 队列中只有sec_uid、unique_id和depth
+ sec_uid, unique_id, depth = self.user_queue.popleft()
+
+ # 如果超过最大深度且是按层级模式,则跳过此用户
+ if self.mode == "by_depth" and depth > self.max_depth:
+ continue
+
+ # 如果用户已处理,跳过
+ if sec_uid in self.processed_users:
+ continue
+
+ # 处理单个用户
+ self._process_single_user(sec_uid, unique_id, depth)
+
+ # 报告进度
+ progress = min(95, int(self.user_count / total_users * 90) + 5)
+ self._report_progress(progress, f"已处理 {self.user_count} 个用户,已下载 {self.video_count} 个视频")
+
+ self.status = "completed"
+ self._report_progress(100, f"处理完成,共处理 {self.user_count} 个用户,下载 {self.video_count} 个视频")
+ return True
+
+ except Exception as e:
+ logger.error(f"处理过程中出错: {e}")
+ import traceback
+ logger.error(f"详细错误信息: {traceback.format_exc()}")
+ self.error_message = f"处理过程中出错: {str(e)}"
+ self.status = "failed"
+ self._report_progress(0, f"处理失败: {str(e)}")
+ return False
+
+ def _process_single_user(self, sec_uid, unique_id, depth):
+ """处理单个用户的视频和关注列表 - 简化版"""
+ # 标记为已处理
+ self.processed_users.add(sec_uid)
+ self.user_count += 1
+
+ logger.info(f"开始处理用户 {unique_id},深度: {depth}")
+
+ try:
+ # 创建基本用户记录(如果不存在)
+ try:
+ TiktokUserVideos.objects.get_or_create(
+ sec_user_id=sec_uid,
+ defaults={
+ 'unique_id': unique_id,
+ 'videos_folder': os.path.join('videos', unique_id)
+ }
+ )
+ except Exception as e:
+ logger.error(f"创建基本用户记录失败: {e}")
+
+ # 获取并下载视频
+ downloaded_videos = self._fetch_user_videos(sec_uid, unique_id, depth)
+
+ # 处理用户的关注列表
+ if self.mode == "by_depth" and depth < self.max_depth:
+ next_depth = depth + 1
+ self._process_user_followings(sec_uid, next_depth)
+ elif self.mode == "by_users" and self.user_count < self.target_users:
+ next_depth = depth + 1
+ self._process_user_followings(sec_uid, next_depth)
+
+ # 报告进度
+ self._report_progress(
+ min(95, int(self.user_count / (self.target_users or 50) * 90) + 5),
+ f"已处理 {self.user_count} 个用户,已下载 {self.video_count} 个视频"
+ )
+
+ except Exception as e:
+ logger.error(f"处理用户 {unique_id} 时出错: {e}")
+ import traceback
+ logger.error(traceback.format_exc())
+
+ def _fetch_user_videos(self, sec_uid, unique_id, depth):
+ """获取并下载用户视频,优先处理字典格式数据"""
+ # 导入traceback
+ import traceback
+
+ downloaded_videos = []
+
+ # 使用unique_id创建视频文件夹
+ videos_folder = os.path.join(TIKTOK_VIDEOS_PATH, unique_id)
+ os.makedirs(videos_folder, exist_ok=True)
+
+ logger.info(f"开始处理用户 {unique_id} 的视频")
+
+ try:
+ # 获取视频列表 - 现在统一返回字典列表
+ logger.info(f"调用 get_top_n_videos_by_play_count({sec_uid}, n={self.max_videos_per_user})")
+ top_videos = get_top_n_videos_by_play_count(sec_uid, n=self.max_videos_per_user)
+
+ # 记录返回值类型
+ logger.info(f"获取到视频列表: 类型={type(top_videos).__name__}, 数量={len(top_videos)}")
+
+ # 如果没有获取到视频,仅更新文件夹路径
+ if not top_videos:
+ logger.warning(f"未能获取到用户 {unique_id} 的任何视频")
+ try:
+ TiktokUserVideos.objects.filter(sec_user_id=sec_uid).update(
+ videos_folder=videos_folder
+ )
+ except Exception as e:
+ logger.error(f"更新用户视频文件夹失败: {e}")
+ logger.error(traceback.format_exc())
+ return []
+
+ # 处理每个视频 - 显示进度
+ total_videos = len(top_videos)
+ logger.info(f"开始处理 {total_videos} 个视频")
+
+ for i, video_data in enumerate(top_videos):
+ current_num = i + 1
+ progress_str = f"({total_videos}/{current_num})" # 进度显示格式:(总数/当前)
+ logger.info(f"{progress_str} 开始处理第 {current_num}/{total_videos} 个视频")
+
+ try:
+ # 视频信息提取 - 优先按字典处理,确保兼容性
+ if isinstance(video_data, dict):
+ video_id = str(video_data.get('id', ''))
+ play_count = video_data.get('play_count', 0)
+ desc = video_data.get('desc', '')
+ logger.info(f"{progress_str} 字典数据: ID={video_id}, 播放={play_count}")
+ else:
+ # 如果不是字典,尝试按元组处理(向后兼容)
+ logger.warning(f"{progress_str} 收到非字典数据: {type(video_data).__name__}")
+ if isinstance(video_data, tuple) and len(video_data) >= 2:
+ play_count, video_id, *rest = video_data
+ desc = rest[0] if rest else ""
+ video_id = str(video_id)
+ logger.info(f"{progress_str} 元组数据: ID={video_id}, 播放={play_count}")
+ else:
+ logger.error(f"{progress_str} 无法解析数据: {video_data}")
+ continue
+
+ # 视频ID必须存在
+ if not video_id:
+ logger.warning(f"{progress_str} 视频ID为空,跳过")
continue
+
+ # 视频文件名
+ video_filename = f"{video_id}.mp4"
+ video_path = os.path.join(videos_folder, video_filename)
+
+ # 如果文件已存在且大小大于0,则跳过下载
+ if os.path.exists(video_path) and os.path.getsize(video_path) > 0:
+ logger.info(f"{progress_str} 视频已存在: {video_id}")
+
+ # 创建字典并添加到列表 - 统一使用字典格式
+ video_dict = {
+ 'id': video_id,
+ 'desc': desc,
+ 'play_count': play_count
+ }
+ downloaded_videos.append(video_dict)
+ continue
+
+ # 下载视频 - 显示下载进度
+ logger.info(f"{progress_str} 开始下载视频: {video_id}")
+ if download_video(video_id, unique_id, video_path):
+ logger.info(f"{progress_str} 视频下载成功: {video_id}")
+ self.video_count += 1
+
+ # 创建字典并添加到列表
+ video_dict = {
+ 'id': video_id,
+ 'desc': desc,
+ 'play_count': play_count
+ }
+ downloaded_videos.append(video_dict)
+ else:
+ logger.error(f"{progress_str} 下载视频失败: {video_id}")
+
+ except Exception as e:
+ logger.error(f"{progress_str} 处理单个视频时出错: {e}")
+ logger.error(traceback.format_exc())
+ continue
+
+ # 避免过快请求
+ logger.info(f"{progress_str} 处理完成,等待2秒...")
+ time.sleep(2)
+
+ # 更新数据库 - 只更新模型中实际存在的字段
+ try:
+ TiktokUserVideos.objects.filter(sec_user_id=sec_uid).update(
+ videos_folder=videos_folder
+ )
+
+ # 记录视频信息,但不保存到数据库
+ video_info_json = json.dumps(downloaded_videos, ensure_ascii=False)
+ logger.info(f"已处理 {len(downloaded_videos)}/{total_videos} 个视频")
+ logger.info(f"数据库更新成功: 更新了 videos_folder={videos_folder}")
+
+ except Exception as e:
+ logger.error(f"更新数据库失败: {e}")
+ logger.error(traceback.format_exc())
- # 开始递归处理
- process_user(start_sec_uid, start_unique_id)
+ except Exception as e:
+ logger.error(f"处理用户 {unique_id} 视频时发生错误: {str(e)}")
+ logger.error(traceback.format_exc())
+ return downloaded_videos
+
+ def _process_user_followings(self, sec_uid, next_depth):
+ """处理用户的关注列表,添加符合粉丝数条件的前5个用户到队列中"""
+ try:
+ logger.info(f"获取用户关注列表: {sec_uid}, 深度: {next_depth}")
+
+ max_cursor = 0 # maxCursor保持为0
+ min_cursor = 0 # 初始minCursor为0
+ max_time = time.time()
+ filtered_users_count = 0 # 已筛选出的符合条件的用户数量
+ max_filtered_users = 5 # 只获取前5个符合条件的用户
+ processed_sec_uids = set() # 用于去重
+ page = 1 # 页码计数
+
+ while True:
+ # 如果已经找到足够的用户,停止获取
+ if filtered_users_count >= max_filtered_users:
+ logger.info(f"已找到 {max_filtered_users} 个符合条件的用户,停止获取")
+ break
+
+ # 获取关注列表,使用正确的游标参数
+ logger.info(f"获取关注列表第{page}页: maxCursor={max_cursor}, minCursor={min_cursor}")
+ followings_data = fetch_user_followings(sec_uid, max_cursor=max_cursor, min_cursor=min_cursor, page_size=30)
+
+ if not followings_data or 'data' not in followings_data:
+ logger.warning(f"无法获取用户关注列表: {sec_uid}")
+ break
+
+ # 获取用户列表
+ user_list = followings_data['data'].get('userList', [])
+
+ if not user_list:
+ logger.info("没有更多关注用户")
+ break
+
+ logger.info(f"获取到 {len(user_list)} 个关注用户")
+
+ # 使用filter_users_by_followers函数筛选符合粉丝数条件的用户
+ filtered_users = filter_users_by_followers(user_list)
+ logger.info(f"本页筛选出 {len(filtered_users)} 个粉丝数符合条件的用户")
+
+ # 处理筛选后的用户
+ for following in filtered_users:
+ # 如果已经找到足够的用户,停止处理
+ if filtered_users_count >= max_filtered_users:
+ break
+
+ # 获取用户信息
+ user_data = following.get('user', {})
+ stats_data = following.get('stats', {})
+
+ follower_sec_uid = user_data.get('secUid')
+ follower_unique_id = user_data.get('uniqueId')
+
+ # 检查必要字段
+ if not follower_sec_uid or not follower_unique_id:
+ logger.warning("用户数据不完整,缺少必要字段")
+ continue
+
+ # 检查是否已处理过该用户
+ if follower_sec_uid in processed_sec_uids or follower_sec_uid in self.processed_users:
+ continue
+
+ # 添加到已处理集合
+ processed_sec_uids.add(follower_sec_uid)
+ filtered_users_count += 1
+
+ # 添加到队列
+ self.user_queue.append((follower_sec_uid, follower_unique_id, next_depth))
+ logger.info(f"添加符合条件的用户到队列: {follower_unique_id}, 粉丝数: {stats_data.get('followerCount', 0)}")
+
+ # 保存到数据库
+ try:
+ TiktokUserVideos.objects.update_or_create(
+ sec_user_id=follower_sec_uid,
+ defaults={
+ 'unique_id': follower_unique_id,
+ 'nickname': user_data.get('nickname', ''),
+ 'follower_count': stats_data.get('followerCount', 0),
+ 'following_count': stats_data.get('followingCount', 0),
+ 'video_count': stats_data.get('videoCount', 0)
+ }
+ )
+ except Exception as e:
+ logger.error(f"保存关注用户到数据库失败: {follower_unique_id}, {e}")
+
+ # 检查是否已找到足够的用户
+ if filtered_users_count >= max_filtered_users:
+ break
+
+ # 更新游标处理
+ old_min_cursor = min_cursor
+ min_cursor = followings_data['data'].get('minCursor', 0)
+ has_more = followings_data['data'].get('hasMore', False)
+
+ # 记录游标更新
+ logger.info(f"游标更新: 旧min_cursor={old_min_cursor} -> 新min_cursor={min_cursor}, has_more={has_more}")
+
+ # 检查游标是否有效和是否有更多数据
+ if not has_more or min_cursor == old_min_cursor or not min_cursor:
+ logger.info("没有更多数据或游标无效,结束获取")
+ break
+
+ # 防止过度请求
+ logger.info("等待1秒后获取下一页...")
+ time.sleep(1)
+
+ # 增加页码
+ page += 1
+
+ # 时间限制
+ if time.time() - max_time > 60: # 最多获取1分钟
+ logger.warning(f"达到时间限制,停止获取更多关注用户")
+ break
+
+ logger.info(f"用户关注处理完成,共筛选出 {filtered_users_count} 个符合条件的用户")
+
+ except Exception as e:
+ logger.error(f"处理用户关注列表出错: {e}")
+ import traceback
+ logger.error(traceback.format_exc())
+
+ def _report_progress(self, progress, message):
+ """报告处理进度"""
+ self.progress = progress
+ self.progress_message = message
+ logger.info(f"进度更新: {progress}%, {message}")
+
+ def get_progress(self):
+ """获取当前进度信息"""
+ return {
+ 'status': self.status,
+ 'progress': self.progress,
+ 'message': self.progress_message,
+ 'error': self.error_message,
+ 'user_count': self.user_count,
+ 'video_count': self.video_count
+ }
+
+ def get_result(self):
+ """获取处理结果,包括统计信息"""
+ return {
+ 'status': self.status,
+ 'processed_users': self.user_count,
+ 'downloaded_videos': self.video_count,
+ 'error': self.error_message,
+ 'completed_at': datetime.now().isoformat()
+ }
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def start_recursive_fetch_videos(request):
+ """启动异步视频获取任务"""
+ try:
+ # 导入traceback
+ import traceback
+
+ data = json.loads(request.body)
+ start_unique_id = data.get('start_unique_id')
+ start_sec_uid = data.get('start_sec_uid') # 保持参数一致性
+
+ # 如果提供了sec_uid但没提供start_sec_uid,使用sec_uid
+ if not start_sec_uid and data.get('sec_uid'):
+ start_sec_uid = data.get('sec_uid')
+ logger.info(f"使用sec_uid作为start_sec_uid: {start_sec_uid}")
+
+ mode = data.get('mode', 'by_users')
+ max_depth = int(data.get('max_depth', 3))
+ skip_user_profile = data.get('skip_user_profile', False)
+ max_videos_per_user = int(data.get('max_videos_per_user', 10))
+
+ # 检查必要参数
+ if not start_unique_id and not start_sec_uid:
+ return JsonResponse({
+ 'code': 400,
+ 'message': '参数错误',
+ 'error': '请提供起始用户ID(start_unique_id)或用户secUid(start_sec_uid)'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ # 确保目标用户数是整数
+ target_users = None
+ if 'target_users' in data and data['target_users']:
+ try:
+ target_users = int(data['target_users'])
+ except (ValueError, TypeError):
+ return JsonResponse({
+ 'code': 400,
+ 'message': '参数错误',
+ 'error': 'target_users必须是整数'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ # 生成任务ID
+ task_id = str(uuid.uuid4())
+
+ # 记录任务信息
+ logger.info(f"========================= 任务启动 =========================")
+ logger.info(f"任务ID: {task_id}")
+ logger.info(f"起始用户: {start_unique_id or '使用secUid'}")
+ logger.info(f"起始secUid: {start_sec_uid}")
+ logger.info(f"模式: {mode}")
+ logger.info(f"最大深度: {max_depth}")
+ logger.info(f"目标用户数: {target_users if target_users else '不限'}")
+ logger.info(f"跳过用户资料: {skip_user_profile}")
+ logger.info(f"每用户最大视频数: {max_videos_per_user}")
+ logger.info(f"========================================================")
+
+ # 创建任务参数字典
+ task_params = {
+ 'start_unique_id': start_unique_id,
+ 'start_sec_uid': start_sec_uid, # 保持一致的参数命名
+ 'mode': mode,
+ 'target_users': target_users,
+ 'max_depth': max_depth,
+ 'skip_user_profile': skip_user_profile,
+ 'max_videos_per_user': max_videos_per_user
+ }
+
+ # 初始化任务状态
+ set_task_status(task_id, {
+ 'status': 'pending',
+ 'progress': 0,
+ 'message': '任务已提交,等待处理',
+ 'result': None,
+ 'error': None,
+ 'created_at': datetime.now().isoformat(),
+ 'params': task_params
+ })
+
+ # 启动异步任务 - 统一使用start_sec_uid参数名
+ async_fetch_videos_task.delay(
+ task_id,
+ start_unique_id,
+ max_depth,
+ target_users,
+ skip_user_profile,
+ start_sec_uid # 参数名保持一致
+ )
+
+ # 返回任务ID
return JsonResponse({
- 'status': 'success',
- 'message': '递归获取视频完成',
- 'processed_users_count': len(processed_users),
- 'downloaded_videos_count': len(all_downloaded_videos),
- 'downloaded_videos': [{'id': v['id'], 'desc': v['desc'][:50], 'play_count': v['play_count'], 'user': v['user_unique_id']} for v in all_downloaded_videos[:100]] # 只返回前100个视频信息,避免响应过大
+ 'code': 200,
+ 'message': '任务已提交',
+ 'data': {
+ 'task_id': task_id,
+ 'status': 'pending'
+ }
}, json_dumps_params={'ensure_ascii': False})
except Exception as e:
- logger.error(f"递归获取TikTok视频失败: {e}")
+ logger.error(f"启动异步任务失败: {str(e)}")
import traceback
- logger.error(f"详细错误: {traceback.format_exc()}")
+ logger.error(f"详细错误信息: {traceback.format_exc()}")
+ return JsonResponse({
+ 'code': 500,
+ 'message': '服务器错误',
+ 'error': f'启动异步任务失败: {str(e)}'
+ }, json_dumps_params={'ensure_ascii': False})
+
+@csrf_exempt
+@require_http_methods(["GET"])
+def check_fetch_task_status(request, task_id):
+ """检查任务状态"""
+ task_info = get_task_status(task_id)
+
+ if not task_info:
return JsonResponse({
'status': 'error',
- 'message': f'递归获取TikTok视频失败: {str(e)}'
+ 'message': '任务不存在'
}, json_dumps_params={'ensure_ascii': False})
+
+ # 构建响应数据
+ response_data = {
+ 'status': task_info.get('status', 'unknown'),
+ 'progress': task_info.get('progress', 0),
+ 'message': task_info.get('message', ''),
+ 'error': task_info.get('error'),
+ 'created_at': task_info.get('created_at', '')
+ }
+
+ return JsonResponse(response_data, json_dumps_params={'ensure_ascii': False})
+
+@csrf_exempt
+@require_http_methods(["GET"])
+def get_fetch_task_result(request, task_id):
+ """获取任务完整结果"""
+ task_info = get_task_status(task_id)
+
+ if not task_info:
+ return JsonResponse({
+ 'status': 'error',
+ 'message': '任务不存在'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ if task_info['status'] != 'completed':
+ return JsonResponse({
+ 'status': 'error',
+ 'message': f'任务尚未完成,当前状态: {task_info["status"]}'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ # 返回完整结果
+ return JsonResponse(task_info['result'], json_dumps_params={'ensure_ascii': False})
+
+# 任务管理API
+@csrf_exempt
+@require_http_methods(["GET"])
+def list_fetch_tasks(request):
+ """列出所有任务"""
+ tasks = []
+ all_tasks = list_all_tasks()
+
+ for task_info in all_tasks:
+ tasks.append({
+ 'task_id': task_info.get('task_id'),
+ 'status': task_info.get('status'),
+ 'progress': task_info.get('progress', 0),
+ 'message': task_info.get('message', ''),
+ 'created_at': task_info.get('created_at'),
+ 'unique_id': task_info.get('params', {}).get('unique_id') if 'params' in task_info else None
+ })
+
+ return JsonResponse({
+ 'tasks': sorted(tasks, key=lambda x: x['created_at'] if 'created_at' in x else '', reverse=True)
+ }, json_dumps_params={'ensure_ascii': False})
+
+@csrf_exempt
+@require_http_methods(["DELETE"])
+def clean_completed_tasks(request):
+ """清理已完成的任务"""
+ completed_count = 0
+ all_tasks = list_all_tasks()
+
+ for task_info in all_tasks:
+ task_id = task_info.get('task_id')
+ if task_info.get('status') in ['completed', 'failed']:
+ if 'created_at' in task_info and \
+ datetime.fromisoformat(task_info['created_at']) < datetime.now() - timedelta(days=1):
+ redis_client.delete(f"task_status:{task_id}")
+ completed_count += 1
+
+ return JsonResponse({
+ 'status': 'success',
+ 'message': f'已清理 {completed_count} 个完成超过24小时的任务'
+ }, json_dumps_params={'ensure_ascii': False})
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def fetch_videos_with_callback(request):
+ """启动任务并设置回调URL,任务完成后自动发送结果"""
+ try:
+ data = json.loads(request.body)
+ callback_url = data.get('callback_url')
+
+ if not callback_url:
+ return JsonResponse({
+ 'status': 'error',
+ 'message': '必须提供callback_url参数'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ task_id = str(uuid.uuid4())
+
+ # 使用Redis存储任务状态
+ set_task_status(task_id, {
+ 'status': 'pending',
+ 'progress': 0,
+ 'message': '任务已创建,等待处理',
+ 'error': None,
+ 'callback_url': callback_url,
+ 'params': data,
+ 'created_at': datetime.now().isoformat()
+ })
+
+ # 启动任务
+ fetch_and_callback_task.delay(task_id, data)
+
+ return JsonResponse({
+ 'status': 'accepted',
+ 'message': '任务已启动,完成后将回调通知',
+ 'task_id': task_id
+ }, json_dumps_params={'ensure_ascii': False})
+ except Exception as e:
+ logger.error(f"启动任务失败: {str(e)}")
+ return JsonResponse({
+ 'status': 'error',
+ 'message': f'启动任务失败: {str(e)}'
+ }, json_dumps_params={'ensure_ascii': False})
+
+@shared_task
+def fetch_and_callback_task(task_id, data):
+ """执行视频获取并回调通知"""
+ try:
+ # 更新任务状态为处理中
+ update_task_status(task_id, {
+ 'status': 'processing',
+ 'message': '正在处理中'
+ })
+
+ # 在任务开始时关闭可能存在的旧连接
+ from django.db import connections
+ for conn in connections.all():
+ conn.close()
+
+ fetcher = TiktokVideoFetcher(
+ start_unique_id=data.get('unique_id'),
+ max_depth=int(data.get('max_depth', 3)),
+ target_users=int(data.get('target_users')) if 'target_users' in data and data['target_users'] else None
+ )
+
+ if fetcher.initialize() and fetcher.process():
+ result = fetcher.get_result()
+ update_task_status(task_id, {
+ 'status': 'completed',
+ 'result': result
+ })
+ else:
+ update_task_status(task_id, {
+ 'status': 'failed',
+ 'error': fetcher.error_message
+ })
+ result = {
+ 'status': 'error',
+ 'message': fetcher.error_message
+ }
+
+ # 发送回调通知
+ callback_url = get_task_status(task_id)['callback_url']
+ try:
+ response = requests.post(callback_url, json={
+ 'task_id': task_id,
+ 'result': result
+ }, timeout=30)
+
+ update_task_status(task_id, {
+ 'callback_status': response.status_code
+ })
+ except Exception as e:
+ logger.error(f"回调通知失败: {str(e)}")
+ update_task_status(task_id, {
+ 'callback_status': 'failed',
+ 'callback_error': str(e)
+ })
+
+ # 尝试发送错误回调
+ try:
+ callback_url = get_task_status(task_id)['callback_url']
+ requests.post(callback_url, json={
+ 'task_id': task_id,
+ 'status': 'error',
+ 'message': str(e)
+ }, timeout=30)
+ except:
+ pass
+ except Exception as e:
+ logger.error(f"任务执行失败: {str(e)}")
+ update_task_status(task_id, {
+ 'status': 'failed',
+ 'error': str(e)
+ })
+
+ # 尝试发送错误回调
+ try:
+ callback_url = get_task_status(task_id)['callback_url']
+ requests.post(callback_url, json={
+ 'task_id': task_id,
+ 'status': 'error',
+ 'message': str(e)
+ }, timeout=30)
+ except:
+ pass
+
+@csrf_exempt
+@require_http_methods(["POST"])
+def reset_task_status(request, task_id):
+ """手动重置任务状态(临时解决方案)"""
+ try:
+ task_info = get_task_status(task_id)
+
+ if not task_info:
+ return JsonResponse({
+ 'status': 'error',
+ 'message': '任务不存在'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ # 重置任务状态
+ set_task_status(task_id, {
+ 'status': 'reset',
+ 'message': '任务已手动重置',
+ 'reset_time': datetime.now().isoformat(),
+ 'original_status': task_info.get('status', 'unknown')
+ })
+
+ return JsonResponse({
+ 'status': 'success',
+ 'message': f'任务 {task_id} 已重置'
+ }, json_dumps_params={'ensure_ascii': False})
+ except Exception as e:
+ return JsonResponse({
+ 'status': 'error',
+ 'message': f'重置失败: {str(e)}'
+ }, json_dumps_params={'ensure_ascii': False})
+
+@csrf_exempt
+@require_http_methods(["GET"])
+def check_task_status_detail(request, task_id):
+ """获取任务详细状态信息(包含调试数据)"""
+ task_info = get_task_status(task_id)
+
+ if not task_info:
+ return JsonResponse({
+ 'status': 'error',
+ 'message': '任务不存在'
+ }, json_dumps_params={'ensure_ascii': False})
+
+ # 添加系统诊断信息
+ try:
+ import celery.app.control
+ from automated_task_monitor.celery import app # 替换为您的Celery应用实例
+
+ # 获取活跃Worker
+ inspector = app.control.inspect()
+ active_workers = inspector.active()
+ active_tasks = inspector.active()
+
+ task_info['debug'] = {
+ 'active_workers': bool(active_workers),
+ 'worker_count': len(active_workers) if active_workers else 0,
+ 'active_tasks': active_tasks,
+ 'server_time': datetime.now().isoformat()
+ }
+ except Exception as e:
+ task_info['debug'] = {
+ 'error': str(e),
+ 'server_time': datetime.now().isoformat()
+ }
+
+ return JsonResponse(task_info, json_dumps_params={'ensure_ascii': False})
+
diff --git a/requirements.txt b/requirements.txt
index 1a17f68..aea9d30 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/urls.py b/urls.py
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/urls.py
@@ -0,0 +1 @@
+
\ No newline at end of file