├── .env ├── README.md ├── docker-compose.yml ├── tests ├── drissionPage_test │ └── test.py ├── playwright_test │ └── test.py ├── quotes_spider │ ├── quotes.json │ ├── quotes_spider │ │ ├── __init__.py │ │ ├── items.py │ │ ├── middlewares.py │ │ ├── pipelines.py │ │ ├── settings.py │ │ └── spiders │ │ │ ├── __init__.py │ │ │ └── quotes.py │ ├── scrapy.cfg │ └── scrapy.log ├── selenium_test │ └── test.py └── 逆向案例 │ ├── decrypt-v.js │ ├── main.py │ ├── package-lock.json │ └── package.json └── 更新日志.md /.env: -------------------------------------------------------------------------------- 1 | FRONTEND_PORT=8080 2 | BACKEND_PORT=9000 3 | SERVER_NAME=localhost 4 | WORKERS=1 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TaskPyro 是什么? 2 | 3 | TaskPyro 是一个轻量级的 Python 任务调度平台,专注于提供简单易用的任务管理和爬虫调度解决方案。它能够帮助您轻松管理和调度 Python 任务,特别适合需要定时执行的爬虫任务和数据处理任务。 4 | 5 | ![登录界面](https://www.helloimg.com/i/2025/03/06/67c8f0ad6c9a1.png) 6 | ![主界面](https://pic1.imgdb.cn/item/6809ec0158cb8da5c8c8e736.jpg) 7 | 8 | ## 开发背景 9 | 10 | 在当今数字化时代,自动化数据采集和处理变得越来越重要。然而,现有的任务调度解决方案要么过于复杂,要么缺乏针对 Python 环境的特定优化。TaskPyro 正是为了解决这些痛点而诞生的,旨在为 Python 开发者提供一个简单、高效、可靠的任务调度平台。 11 | 12 | ## 为什么选择TaskPyro? 13 | 14 | - 🚀 **轻量级设计**:占用资源小,运行高效 15 | - 🔄 **灵活调度**:支持多种调度方式,满足各类需求 16 | - 🐍 **Python环境管理**:自由分配不同的Python虚拟环境 17 | - 📊 **可视化监控**:直观的任务运行状态展示 18 | - 🔒 **安全可靠**:完善的异常处理和错误恢复机制 19 | 20 | 21 | ## 适用人群 22 | 23 | TaskPyro 特别适合以下用户群体: 24 | 25 | - 🔍 **数据工程师**:需要定期执行数据采集、清洗和处理任务 26 | - 🕷️ **爬虫开发者**:需要管理和调度多个爬虫任务 27 | - 📊 **数据分析师**:需要自动化数据分析流程 28 | - 🛠️ **系统运维人员**:需要执行定时系统维护任务 29 | - 🚀 **创业团队**:需要一个轻量级但功能完整的任务调度解决方案 30 | 31 | ## 使用流程 32 | - 配置Python环境和Python版本(系统会默认基础Python版本) 33 | - 创建项目 34 | - 创建定时任务 35 | 36 | ## 核心功能 37 | 38 | TaskPyro 提供了一系列强大的功能,帮助您高效管理 Python 任务: 39 | 40 | - 📅 **灵活的任务调度** 41 | - 支持 Cron 表达式定时调度 42 | - 支持固定间隔调度 43 | - 支持一次性任务执行 44 | - 支持任务依赖关系配置 45 | 46 | - 🔧 **Python 环境管理** 47 | - 支持多个 Python 虚拟环境 48 | - 环境隔离,避免依赖冲突 49 | - 支持 pip 包管理 50 | - 支持官网资源包自动解压安装 51 | - 内置Python版本管理工具 52 | 53 | - 🕷️ **爬虫框架支持** 54 | - 支持 Scrapy 等主流爬虫框架 55 | - 支持 Selenium、Playwright、DrissionPage 等浏览器自动化工具 56 | - 提供完整的框架运行环境配置 57 | 58 | - 📊 **任务监控与管理** 59 | - 实时任务状态监控 60 | - 详细的执行日志记录 61 | - 任务执行统计分析 62 | - 支持钉钉、飞书、企业微信通知 63 | - 异常通知与告警 64 | 65 | - 💼 **用户友好** 66 | - 直观的 Web 操作界面 67 | - 详细的使用文档 68 | - 简单的部署流程 69 | - 完善的错误处理机制 70 | 71 | 72 | # Docker 安装 73 | 74 | TaskPyro 提供了基于 Docker 的快速部署方案,让您能够轻松地在任何支持 Docker 的环境中运行。 75 | 76 | ## 前置条件 77 | 78 | 在开始安装之前,请确保您的系统已经安装了以下软件: 79 | 80 | ### Docker 安装 81 | 82 | - Docker(本人使用的版本为 26.10.0,低于此版本安装可能会存在问题,建议删除旧版本,升级新版本docker) 83 | 84 | ### Docker Compose 安装 85 | 86 | - Docker Compose(版本 2.0.0 或更高) 87 | - 注意:如果您使用的是 Docker 26.1.0 版本,建议安装最新版本的 Docker Compose 以确保兼容性 88 | 89 | ## 安装步骤 90 | 91 | ### 0. 拉取代码 92 | 93 | gitub 94 | ```bash 95 | git clone https://github.com/taskPyroer/taskpyro.git 96 | ``` 97 | 98 | gitee 99 | 100 | ```bash 101 | git clone https://gitee.com/taskPyroer/taskpyrodocker.git 102 | ``` 103 | 104 | > 可以直接拉取上面的代码,或者按下面的1、2、3步骤创建文件 105 | 106 | ### 1. 创建项目目录 107 | 108 | ```bash 109 | mkdir taskpyro 110 | cd taskpyro 111 | ``` 112 | 113 | ### 2. 创建 docker-compose.yml 文件 114 | 115 | 在项目目录中创建 `docker-compose.yml` 文件,内容如下: 116 | 117 | ```yaml 118 | version: '3' 119 | 120 | services: 121 | frontend: 122 | image: crpi-7ub5pdu5y0ps1uyh.cn-hangzhou.personal.cr.aliyuncs.com/taskpyro/taskpyro-frontend:1.0 123 | ports: 124 | - "${FRONTEND_PORT:-7789}:${FRONTEND_PORT:-7789}" 125 | environment: 126 | - PORT=${FRONTEND_PORT:-7789} 127 | - SERVER_NAME=${SERVER_NAME:-localhost} 128 | - BACKEND_PORT=${BACKEND_PORT:-8000} 129 | - API_URL=http://${SERVER_NAME}:${BACKEND_PORT:-8000} 130 | - TZ=Asia/Shanghai 131 | env_file: 132 | - .env 133 | depends_on: 134 | - api 135 | 136 | api: 137 | image: crpi-7ub5pdu5y0ps1uyh.cn-hangzhou.personal.cr.aliyuncs.com/taskpyro/taskpyro-api:1.0 138 | ports: 139 | - "${BACKEND_PORT:-8000}:${BACKEND_PORT:-8000}" 140 | environment: 141 | - PORT=${BACKEND_PORT:-8000} 142 | - PYTHONPATH=/app 143 | - CORS_ORIGINS=http://localhost:${FRONTEND_PORT:-7789},http://127.0.0.1:${FRONTEND_PORT:-7789} 144 | - TZ=Asia/Shanghai 145 | - WORKERS=${WORKERS:-1} 146 | volumes: 147 | - /opt/taskpyrodata/static:/app/../static 148 | - /opt/taskpyrodata/logs:/app/../logs 149 | - /opt/taskpyrodata/data:/app/data 150 | env_file: 151 | - .env 152 | init: true 153 | restart: unless-stopped 154 | ``` 155 | 156 | ### 3. 创建 .env 文件 157 | 158 | 在项目目录中创建 `.env` 文件,用于配置环境变量: 159 | 160 | ```env 161 | FRONTEND_PORT=8080 162 | BACKEND_PORT=9000 163 | SERVER_NAME=localhost 164 | WORKERS=1 165 | ``` 166 | 167 | 168 | ### 4. 启动服务 169 | 170 | ```bash 171 | docker-compose up -d 172 | ``` 173 | 启动后直接在浏览器中访问至 http://:8080 174 | 175 | ## 安装注意事项 176 | 177 | 1. **数据持久化** 178 | - 数据文件会保存在 `/opt/taskpyrodata` 目录下,包含以下子目录: 179 | - `static`:静态资源文件 180 | - `logs`:系统日志文件 181 | - `data`:应用数据文件 182 | - 建议定期备份这些目录,特别是 `data` 目录 183 | 184 | 2. **环境变量配置** 185 | - 在 `.env` 文件中配置以下必要参数: 186 | - `FRONTEND_PORT`:前端服务端口(默认8080) 187 | - `BACKEND_PORT`:后端服务端口(默认9000) 188 | - `SERVER_NAME`:服务器域名或IP(默认localhost,不用修改) 189 | - `WORKERS`:后端工作进程数(默认1,不用修改) 190 | - 确保 `SERVER_NAME` 配置正确,否则可能导致API调用失败 191 | 192 | 3. **端口配置** 193 | - 前端服务默认使用8080端口 194 | - 后端服务默认使用9000端口 195 | - 确保这些端口未被其他服务占用 196 | - 如需修改端口,只需要更新 `.env` 文件中的配置 197 | 198 | 4. **容器资源配置** 199 | - 建议为容器预留足够的CPU和内存资源 200 | - 可通过Docker的资源限制参数进行调整 201 | - 监控容器资源使用情况,适时调整配置 202 | 203 | ## 常见问题 204 | 205 | 1. **前端服务无法访问** 206 | - 检查 `FRONTEND_PORT` 端口是否被占用 207 | - 确认前端容器是否正常启动:`docker-compose ps frontend` 208 | - 查看前端容器日志:`docker-compose logs frontend` 209 | - 验证 `SERVER_NAME` 配置是否正确 210 | 211 | 2. **后端API连接失败** 212 | - 检查 `BACKEND_PORT` 端口是否被占用 213 | - 确认后端容器是否正常启动:`docker-compose ps api` 214 | - 查看后端容器日志:`docker-compose logs api` 215 | - 验证 `CORS_ORIGINS` 配置是否包含前端访问地址 216 | 217 | 3. **容器启动失败** 218 | - 检查 Docker 服务状态:`systemctl status docker` 219 | - 确认 docker-compose.yml 文件格式正确 220 | - 验证环境变量配置是否完整 221 | - 检查数据目录权限:`ls -l /opt/taskpyrodata` 222 | 223 | 4. **数据持久化问题** 224 | - 确保 `/opt/taskpyrodata` 目录存在且有正确的权限 225 | - 检查磁盘空间是否充足 226 | - 定期清理日志文件避免空间占用过大 227 | - 建议配置日志轮转策略 228 | 229 | 5. **资源配置** 230 | - 根据实际需求调整 Docker 容器的资源限制 231 | - 监控服务器资源使用情况,适时调整配置 232 | 233 | 234 | ## 升级说明 235 | 236 | 要升级到新版本,请执行以下步骤: 237 | 238 | ```bash 239 | # 拉取最新镜像 240 | docker-compose pull 241 | 242 | # 重启服务 243 | docker-compose up -d 244 | ``` 245 | 246 | ## 卸载说明 247 | 248 | 如果需要卸载 TaskPyro,可以执行以下命令: 249 | 250 | ```bash 251 | # 停止并删除容器 252 | docker-compose down 253 | 254 | # 如果需要同时删除数据(谨慎操作!) 255 | rm -rf /opt/taskpyrodata 256 | ``` 257 | 258 | # 系统资源监控 259 | 260 | 仪表盘提供了实时的系统资源使用情况监控,帮助您及时了解系统的运行状态。 261 | ![仪表盘界面](https://pic1.imgdb.cn/item/6809ec0158cb8da5c8c8e736.jpg) 262 | ## CPU使用率 263 | 264 | 显示当前系统的CPU使用百分比,以及最近的CPU负载情况。 265 | ## 内存使用率 266 | 267 | 展示系统内存的使用情况,包括: 268 | - 已使用内存/总内存 269 | - 使用率百分比 270 | 271 | 例如:11.9 GB / 15.8 GB,使用率75.1% 272 | 273 | ## 磁盘使用率 274 | 275 | 监控系统磁盘存储空间的使用情况: 276 | - 已使用空间/总空间 277 | - 使用率百分比 278 | 279 | 例如:57.8 GB / 341.2 GB,使用率16.9% 280 | 281 | # 任务执行统计 282 | 283 | ## 任务成功率 284 | 285 | 展示系统中任务的整体执行情况: 286 | - 成功任务数/总任务数 287 | - 成功率百分比 288 | 289 | 例如:16/18个任务成功,成功率89% 290 | 291 | ## 每日任务执行统计 292 | 293 | 通过图表形式展示每日任务执行的详细统计: 294 | - 成功任务:显示绿色 295 | - 失败任务:显示红色 296 | - 错过任务:显示黄色 297 | 298 | 图表可以直观地展示任务执行的趋势和分布情况,帮助您更好地了解系统运行状况。 299 | 300 | ## 项目管理功能 301 | 302 | TaskPyro 提供了直观的项目管理界面,支持添加和编辑项目。本文将详细介绍项目管理的各项功能。 303 | 304 | ## 查看项目列表 305 | 306 | 在项目管理界面,您可以查看已创建的项目列表。每个项目都包含以下信息: 307 | 308 | ![项目界面](https://pic1.imgdb.cn/item/681b18f958cb8da5c8e29333.png) 309 | 310 | ## 添加/编辑项目 311 | 312 | 在项目管理界面,您可以通过点击"新建项目"按钮来创建新项目。新建项目界面如下: 313 | 314 | ![新建ZIP项目界面](https://pic1.imgdb.cn/item/681b18f958cb8da5c8e29332.png) 315 | ![新建GIT项目界面](https://pic1.imgdb.cn/item/681b18f958cb8da5c8e29331.png) 316 | 317 | 以下是各个字段的详细说明: 318 | 319 | ### 项目名称 320 | 321 | - 为您的项目设置一个唯一的名称 322 | - 建议包含版本信息,便于管理 323 | 324 | ### 工作路径 325 | 326 | 工作路径是项目文件的执行路径,系统会根据上传的ZIP文件结构自动推荐合适的工作路径: 327 | 328 | - 单文件情况:如果ZIP解压后只有一个Python文件,工作路径默认设置为 `/` 329 | - 文件夹情况:如果ZIP解压后包含项目文件夹(如 `Demo` 文件夹),且Python文件位于该文件夹中,则工作路径会设置为 `/Demo`,即工作路径为项目文件夹名称 330 | - GIT仓库情况:如果选择Git仓库方式导入项目,工作路径默认设置为 `/` 331 | 332 | ::: tip 提示 333 | 正确设置工作路径对项目的执行至关重要,它决定了Python文件的相对导入路径。 334 | ::: 335 | 336 | ### 项目描述 337 | 338 | - 可以添加项目的详细说明 339 | - 支持描述项目的功能、用途、注意事项等信息 340 | 341 | ### 项目标签 342 | 343 | - 支持为项目添加多个标签 344 | - 标签可用于项目分类和快速筛选 345 | - 在输入框中输入标签名称,点击"添加"按钮即可创建新标签 346 | 347 | ### 项目来源 348 | 349 | 您可以通过以下两种方式导入项目代码: 350 | 351 | #### ZIP上传 352 | 353 | - 支持上传ZIP格式的压缩文件 354 | - ZIP文件应包含完整的项目代码和相关资源 355 | - 可以通过拖拽或点击选择文件的方式上传 356 | 357 | ::: warning 注意 358 | 请确保ZIP文件的组织结构合理,便于系统正确识别工作路径。 359 | ::: 360 | 361 | #### Git仓库 362 | 363 | - 支持从Git仓库拉取代码 364 | - 需要提供完整的Git仓库地址(HTTPS格式) 365 | - 可以选择指定的分支(默认为main分支) 366 | - 支持私有仓库认证(可选填写用户名和密码) 367 | 368 | ::: tip 提示 369 | 使用Git仓库方式可以更方便地管理和更新项目代码。建议使用HTTPS格式的仓库地址,如:https://github.com/username/repository.git 370 | ::: 371 | 372 | ## 使用建议 373 | 374 | 1. 项目命名建议包含版本信息,便于版本管理 375 | 2. 合理使用标签系统,便于项目分类和检索 376 | 3. 在上传ZIP文件前,建议检查项目结构的合理性 377 | 4. 确保工作路径设置正确,避免执行时出现导入错误 378 | 379 | # Python虚拟环境管理 380 | 381 | TaskPyro提供了强大而灵活的Python虚拟环境管理功能,默认支持Python 3.9.21版本。通过直观的Web界面,您可以轻松创建、编辑和管理虚拟环境,为您的任务提供独立的运行环境。 382 | 383 | ## 核心特性 384 | 385 | ### 1. 环境复用与管理 386 | 387 | - 🔄 **一对多关系**:一个虚拟环境可以同时服务于多个定时任务,提高资源利用效率 388 | - ⚙️ **灵活配置**:支持自定义环境名称和依赖包,满足不同任务的需求 389 | 390 | ### 2. 实时安装日志 391 | 392 | - 📝 **详细记录**:完整记录包安装过程,包括下载进度、依赖解析等信息 393 | - 🔍 **错误追踪**:清晰显示安装过程中的警告和错误信息,便于问题排查 394 | - ⚡ **实时反馈**:安装过程实时展示,无需等待即可了解安装状态 395 | 396 | ### 3. 镜像源管理 397 | 398 | - 🌐 **多源支持**:内置多个常用PyPI镜像源,包括: 399 | - 官方PyPI源 400 | - 阿里云镜像源 401 | - 清华大学镜像源 402 | - 中国科技大学镜像源 403 | - 华为云镜像源 404 | - 腾讯云镜像源 405 | - ✏️ **自定义配置**:支持添加、编辑和删除镜像源 406 | - 🔄 **灵活切换**:可随时切换到最适合的镜像源,优化包下载速度 407 | 408 | ## 相比使用Docker创建定时任务的优势 409 | 410 | ### 1. 资源效率 411 | 412 | - 🚀 **更低的资源占用**:无需为每个任务创建独立容器,显著减少系统资源消耗 413 | - 💾 **更少的磁盘空间**:环境复用避免重复安装相同的依赖包 414 | - ⚡ **更快的启动速度**:直接使用虚拟环境,无需等待容器启动 415 | 416 | ### 2. 管理便捷 417 | 418 | - 🎯 **集中管理**:统一的Web界面管理所有虚拟环境 419 | - 🔄 **即时生效**:环境更新后立即生效,无需重建容器 420 | - 📊 **资源监控**:直观展示环境使用情况和任务关联关系 421 | 422 | ### 3. 灵活性 423 | 424 | - 🔗 **环境共享**:多个任务可共享同一个虚拟环境 425 | - 🛠️ **快速调整**:随时添加或移除依赖包,无需重新构建镜像 426 | - 🔍 **便于调试**:直接访问虚拟环境,简化问题排查流程 427 | 428 | ## 使用建议 429 | 430 | 1. 根据项目依赖合理规划虚拟环境,相似依赖的任务可以共用同一环境 431 | 2. 定期检查和更新依赖包,确保安全性和稳定性 432 | 3. 选择地理位置较近的镜像源,提升包下载速度 433 | 4. 保留关键依赖包的版本号,避免版本更新导致的兼容性问题 434 | 435 | ## 界面展示 436 | ### 入口界面 437 | ![入口界面](https://www.helloimg.com/i/2025/03/06/67c8f2c0f18a5.png) 438 | 439 | ### 新建环境 440 | ![新建环境](https://pic1.imgdb.cn/item/6809faba58cb8da5c8c8ec07.png) 441 | 442 | ### 安装日志 443 | ![安装日志](https://www.helloimg.com/i/2025/03/06/67c8f2c09fdd6.png) 444 | 445 | ### 镜像源管理 446 | ![镜像源管理](https://www.helloimg.com/i/2025/03/06/67c8f2c09a765.png) 447 | 448 | # Python版本管理 449 | 450 | ## 功能介绍 451 | 452 | TaskPyro提供了强大的Python版本管理功能,支持多个Python版本的并存和切换。通过简单的操作,您可以轻松地下载、安装和管理不同版本的Python环境,为不同的爬虫项目提供独立的运行环境。 453 | 454 | ## 添加Python版本 455 | 456 | ![添加Python版本界面](https://pic1.imgdb.cn/item/6809fc2158cb8da5c8c8ec90.png) 457 | 458 | 1. 从官网(https://www.python.org/downloads/source/) 下载所需要的版本,请下载 Stable Releases 中的 XZ compressed source tarball 类型文件,即.tar.xz格式 459 | 2. 填写版本名称、下载地址 460 | 3. 点击「添加Python版本」按钮 461 | 3. 系统会自动从Python官网下载对应的安装包 462 | 4. 下载完成后,系统会自动解压并完成安装,整个过程耗时会较长,请耐心等待 463 | 464 | ::: tip 提示 465 | - 仅支持下载Python官方发布的Stable版本 466 | - 下载源为Python官方网站,请确保网络连接正常 467 | - 选择.tar.xz格式的压缩包,下载速度更快 468 | ::: 469 | 470 | # 任务管理 471 | 472 | TaskPyro提供了强大而灵活的任务管理功能,让您能够轻松创建和管理Python脚本的定时任务。 473 | 474 | ## 创建任务 475 | 476 | 在TaskPyro中创建新任务时,您可以: 477 | 478 | 1. 为任务指定一个描述性的名称 479 | 2. 选择已创建的项目和对应的Python虚拟环境 480 | 3. 设置要执行的Python脚本命令(例如:`python script.py`) 481 | 4. 配置任务的调度方式 482 | 483 | ![新建任务界面](https://www.helloimg.com/i/2025/03/06/67c8f2eb516fd.png) 484 | 485 | ### 立即执行 486 | 487 | 不需要时间调度,立即执行一次任务。 488 | 489 | ## 调度类型 490 | 491 | TaskPyro支持多种调度类型,以满足不同的任务执行需求: 492 | 493 | ### 间隔执行 494 | 495 | 按照固定的时间间隔重复执行任务。您可以设置: 496 | - 间隔时长(支持秒、分钟、小时、天等单位) 497 | - 首次执行时间 498 | 499 | ### 一次性执行 500 | 501 | 在指定的日期和时间执行一次任务。 502 | 503 | ### Cron表达式 504 | 505 | 使用标准的Cron表达式来定义复杂的执行计划,支持: 506 | - 分钟级别的精确控制 507 | - 每天、每周、每月的定时执行 508 | - 复杂的组合调度规则 509 | 510 | ## 任务列表 511 | 512 | TaskPyro的主界面提供了丰富的任务调度信息和操作功能: 513 | 514 | ### 基本信息 515 | - 任务名称和描述 516 | - 所属项目和Python虚拟环境 517 | - 执行命令和参数 518 | - 下次执行时间 519 | - 任务状态(活跃中、暂停、错误) 520 | 521 | ### 任务操作 522 | - 暂停/启动调度任务 523 | - 强制终止正在运行的任务实例 524 | - 编辑任务配置 525 | - 添加/编辑任务标签,方便分类管理 526 | 527 | ![任务列表界面](https://www.helloimg.com/i/2025/03/06/67c8f2ec0adeb.png) 528 | 529 | ## 执行历史 530 | 531 | 每个任务都有详细的执行历史记录,您可以查看: 532 | 533 | - 历次执行的开始和结束时间 534 | - 任务执行状态(成功/失败) 535 | - 执行耗时统计 536 | - 错误信息(如果执行失败) 537 | 538 | ![执行历史界面](https://www.helloimg.com/i/2025/03/06/67c8f2eb6f17c.png) 539 | 540 | ## 运行日志 541 | 542 | TaskPyro提供了强大的日志查看功能: 543 | 544 | ### 日志筛选 545 | - 按时间范围筛选 546 | - 支持关键词搜索 547 | - 按日志级别过滤(INFO、ERROR等) 548 | 549 | ### 实时查看 550 | - 自动刷新最新日志 551 | - 支持暂停自动刷新 552 | - 可查看历史日志记录 553 | 554 | ![运行日志界面](https://www.helloimg.com/i/2025/03/06/67c8f2ebdfdbc.png) 555 | 556 | ## 并发实例管理 557 | 558 | TaskPyro提供了灵活的并发实例管理功能: 559 | 560 | 1. 默认情况下,如果上一个任务实例还在运行,新的调度时间到达时将跳过执行 561 | 2. 通过设置最大并发实例数,可以允许同一个任务的多个实例同时运行 562 | 3. 适用场景示例: 563 | - 任务执行时间为1分钟 564 | - 调度间隔为30秒 565 | - 设置并发实例后,新的任务实例将在下一个时间点启动,不需要等待上一个实例完成 566 | - 不设置并发实例时,将等待上一个实例完成后,在下一个调度点执行 567 | 568 | ## 任务编辑 569 | 570 | 您可以随时编辑已创建的任务: 571 | - 修改任务名称和描述 572 | - 更新Python环境配置 573 | - 调整调度设置 574 | - 启用/禁用并发实例 575 | - 管理任务标签 576 | 577 | 通过这些功能,TaskPyro为您提供了一个完整的Python任务调度解决方案,帮助您高效管理自动化任务。 578 | 579 | # 设置 580 | 581 | ## 用户设置 582 | 583 | 在用户设置页面,您可以管理您的账户设置并修改密码。系统默认的管理员账户信息如下: 584 | 585 | - 用户名:admin 586 | - 默认密码:admin123 587 | 588 | 为了系统安全,建议您在首次登录后立即修改默认密码。修改密码时,需要输入当前密码和新密码,并确认新密码。 589 | 590 | ## 许可证设置 591 | 592 | 许可证设置页面显示了您当前的许可证状态和使用限制。免费版用户可以使用以下功能: 593 | 594 | - 创建最多 5 个定时任务 595 | - 创建最多 2 个项目 596 | - 创建最多 2 个虚拟环境 597 | 598 | 如需突破以上限制,您可以[购买许可证]以获得无限制使用权限。点击"激活"按钮,输入有效的许可证密钥即可激活高级功能。如需购买许可证,请订阅。 599 | 600 | ## 邮件设置 601 | 602 | 邮件设置功能允许您配置系统的邮件通知功能。当启用邮件通知后,系统会在定时任务执行出错时自动发送警报邮件。 603 | 604 | 配置邮件通知需要设置以下信息: 605 | 606 | 1. SMTP服务器地址 607 | 2. SMTP端口 608 | 3. 邮箱用户名 609 | 4. 邮箱密码 610 | 5. 启用通知 611 | 完成上面配置后,点击保存按钮即可。 612 | 613 | 配置完成后,添加收件人邮箱地址,您可以点击"测试邮件" 614 | 615 | ## 信息设置 616 | 617 | 支持钉钉机器人、飞书、企业微信机器人 618 | 619 | # 订阅方案 620 | 621 | ## 免费版 622 | 623 | 免费版本为您提供以下功能限制: 624 | 625 | - 创建最多 5 个定时任务 626 | - 创建最多 2 个项目 627 | - 创建最多 2 个虚拟环境 628 | 629 | ## 付费许可证 630 | 631 | 购买许可证后,您可以享受无限制的功能: 632 | 633 | - 无限制创建定时任务 634 | - 无限制创建项目 635 | - 无限制创建虚拟环境 636 | 637 | ::: tip 重要提示 638 | 在授权有效期内,您可以享受完全无限制的功能,并且支持更换绑定的服务器。 639 | ::: 640 | 641 | ## 价格方案 642 | 643 | 我们提供多种灵活的付费方案,满足您不同的需求: 644 | 645 | - 半年付:66元/6个月 646 | - 年付:99元/年 647 | - 永久:688元 648 | 649 | ## 购买方式 650 | 651 | 请添加微信:**PJ221BBB** 652 | 653 | ::: tip 备注说明 654 | 加好友时请备注:taskpyro 655 | ::: 656 | 657 | # 学习交流 658 | 659 | | 微信:PJ221BBB | 公众号:布鲁的Python之旅 | 交流群 | 660 | |-------------|-----------------|-----------------| 661 | | ![个人微信](https://www.helloimg.com/i/2025/03/06/67c8f41cc017f.png) | ![公众号](https://www.helloimg.com/i/2025/03/06/67c8f41ca7f2a.png) | [https://www.helloimg.com/i/2025/05/10/681f0669f2cd9.png](https://www.helloimg.com/i/2025/05/10/681f0669f2cd9.png) | 662 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | frontend: 5 | image: crpi-7ub5pdu5y0ps1uyh.cn-hangzhou.personal.cr.aliyuncs.com/taskpyro/taskpyro-frontend:1.0 6 | ports: 7 | - "${FRONTEND_PORT:-7789}:${FRONTEND_PORT:-7789}" 8 | environment: 9 | - PORT=${FRONTEND_PORT:-7789} 10 | - SERVER_NAME=${SERVER_NAME:-localhost} 11 | - BACKEND_PORT=${BACKEND_PORT:-8000} 12 | - API_URL=http://${SERVER_NAME}:${BACKEND_PORT:-8000} 13 | - TZ=Asia/Shanghai 14 | env_file: 15 | - .env 16 | depends_on: 17 | - api 18 | 19 | api: 20 | image: crpi-7ub5pdu5y0ps1uyh.cn-hangzhou.personal.cr.aliyuncs.com/taskpyro/taskpyro-api:1.0 21 | ports: 22 | - "${BACKEND_PORT:-8000}:${BACKEND_PORT:-8000}" 23 | environment: 24 | - PORT=${BACKEND_PORT:-8000} 25 | - PYTHONPATH=/app 26 | - CORS_ORIGINS=http://localhost:${FRONTEND_PORT:-7789},http://127.0.0.1:${FRONTEND_PORT:-7789} 27 | - TZ=Asia/Shanghai 28 | - WORKERS=${WORKERS:-1} 29 | volumes: 30 | - /opt/taskpyrodata/static:/app/../static 31 | - /opt/taskpyrodata/logs:/app/../logs 32 | - /opt/taskpyrodata/data:/app/data 33 | env_file: 34 | - .env 35 | init: true 36 | restart: unless-stopped -------------------------------------------------------------------------------- /tests/drissionPage_test/test.py: -------------------------------------------------------------------------------- 1 | 2 | from DrissionPage import ChromiumOptions, ChromiumPage 3 | 4 | try: 5 | co = ChromiumOptions().auto_port() 6 | co.set_argument('--no-sandbox') 7 | co.set_argument('--headless=new') 8 | co.set_argument('--single-process') # 单进程模式 9 | co.set_argument('--disable-gpu') # 禁用GPU加速 10 | page = ChromiumPage(co) 11 | page.get('about:blank') 12 | page.get('http://www.bilibili.com/') 13 | title = page.title 14 | print(title) 15 | page.close() 16 | page.quit() 17 | finally: 18 | if 'page' in locals(): 19 | page.quit() -------------------------------------------------------------------------------- /tests/playwright_test/test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from playwright.async_api import async_playwright 3 | import subprocess 4 | 5 | # 安装 Chromium 6 | subprocess.run(["playwright", "install", "chromium"], check=True) 7 | 8 | async def main(): 9 | async with async_playwright() as p: 10 | # 启动 Chromium 浏览器 11 | browser = await p.chromium.launch(headless=True) 12 | # 创建一个新的浏览器上下文 13 | context = await browser.new_context() 14 | # 在上下文中创建一个新的页面 15 | page = await context.new_page() 16 | 17 | try: 18 | # 打开百度首页 19 | await page.goto('http://www.bilibili.com/') 20 | 21 | # 获取搜索结果页面的标题 22 | title = await page.title() 23 | print(f"搜索结果页面的标题是: {title}") 24 | 25 | except Exception as e: 26 | print(f"发生错误: {e}") 27 | finally: 28 | # 关闭浏览器 29 | await browser.close() 30 | 31 | 32 | if __name__ == "__main__": 33 | asyncio.run(main()) -------------------------------------------------------------------------------- /tests/quotes_spider/quotes.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taskPyroer/taskpyro/e8e13d127b81a37f152f866fd420a83737a4cdca/tests/quotes_spider/quotes.json -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taskPyroer/taskpyro/e8e13d127b81a37f152f866fd420a83737a4cdca/tests/quotes_spider/quotes_spider/__init__.py -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/items.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your scraped items 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/items.html 5 | 6 | import scrapy 7 | 8 | 9 | class QuotesSpiderItem(scrapy.Item): 10 | # define the fields for your item here like: 11 | # name = scrapy.Field() 12 | pass 13 | -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/middlewares.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your spider middleware 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 5 | 6 | from scrapy import signals 7 | 8 | # useful for handling different item types with a single interface 9 | from itemadapter import is_item, ItemAdapter 10 | 11 | 12 | class QuotesSpiderSpiderMiddleware: 13 | # Not all methods need to be defined. If a method is not defined, 14 | # scrapy acts as if the spider middleware does not modify the 15 | # passed objects. 16 | 17 | @classmethod 18 | def from_crawler(cls, crawler): 19 | # This method is used by Scrapy to create your spiders. 20 | s = cls() 21 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 22 | return s 23 | 24 | def process_spider_input(self, response, spider): 25 | # Called for each response that goes through the spider 26 | # middleware and into the spider. 27 | 28 | # Should return None or raise an exception. 29 | return None 30 | 31 | def process_spider_output(self, response, result, spider): 32 | # Called with the results returned from the Spider, after 33 | # it has processed the response. 34 | 35 | # Must return an iterable of Request, or item objects. 36 | for i in result: 37 | yield i 38 | 39 | def process_spider_exception(self, response, exception, spider): 40 | # Called when a spider or process_spider_input() method 41 | # (from other spider middleware) raises an exception. 42 | 43 | # Should return either None or an iterable of Request or item objects. 44 | pass 45 | 46 | def process_start_requests(self, start_requests, spider): 47 | # Called with the start requests of the spider, and works 48 | # similarly to the process_spider_output() method, except 49 | # that it doesn’t have a response associated. 50 | 51 | # Must return only requests (not items). 52 | for r in start_requests: 53 | yield r 54 | 55 | def spider_opened(self, spider): 56 | spider.logger.info("Spider opened: %s" % spider.name) 57 | 58 | 59 | class QuotesSpiderDownloaderMiddleware: 60 | # Not all methods need to be defined. If a method is not defined, 61 | # scrapy acts as if the downloader middleware does not modify the 62 | # passed objects. 63 | 64 | @classmethod 65 | def from_crawler(cls, crawler): 66 | # This method is used by Scrapy to create your spiders. 67 | s = cls() 68 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 69 | return s 70 | 71 | def process_request(self, request, spider): 72 | # Called for each request that goes through the downloader 73 | # middleware. 74 | 75 | # Must either: 76 | # - return None: continue processing this request 77 | # - or return a Response object 78 | # - or return a Request object 79 | # - or raise IgnoreRequest: process_exception() methods of 80 | # installed downloader middleware will be called 81 | return None 82 | 83 | def process_response(self, request, response, spider): 84 | # Called with the response returned from the downloader. 85 | 86 | # Must either; 87 | # - return a Response object 88 | # - return a Request object 89 | # - or raise IgnoreRequest 90 | return response 91 | 92 | def process_exception(self, request, exception, spider): 93 | # Called when a download handler or a process_request() 94 | # (from other downloader middleware) raises an exception. 95 | 96 | # Must either: 97 | # - return None: continue processing this exception 98 | # - return a Response object: stops process_exception() chain 99 | # - return a Request object: stops process_exception() chain 100 | pass 101 | 102 | def spider_opened(self, spider): 103 | spider.logger.info("Spider opened: %s" % spider.name) 104 | -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/pipelines.py: -------------------------------------------------------------------------------- 1 | # Define your item pipelines here 2 | # 3 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting 4 | # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html 5 | 6 | 7 | # useful for handling different item types with a single interface 8 | from itemadapter import ItemAdapter 9 | 10 | 11 | class QuotesSpiderPipeline: 12 | def process_item(self, item, spider): 13 | return item 14 | -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/settings.py: -------------------------------------------------------------------------------- 1 | # Scrapy settings for quotes_spider project 2 | # 3 | # For simplicity, this file contains only settings considered important or 4 | # commonly used. You can find more settings consulting the documentation: 5 | # 6 | # https://docs.scrapy.org/en/latest/topics/settings.html 7 | # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 8 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 9 | 10 | BOT_NAME = "quotes_spider" 11 | 12 | SPIDER_MODULES = ["quotes_spider.spiders"] 13 | NEWSPIDER_MODULE = "quotes_spider.spiders" 14 | 15 | 16 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 17 | #USER_AGENT = "quotes_spider (+http://www.yourdomain.com)" 18 | 19 | # Obey robots.txt rules 20 | ROBOTSTXT_OBEY = True 21 | 22 | # Configure maximum concurrent requests performed by Scrapy (default: 16) 23 | #CONCURRENT_REQUESTS = 32 24 | 25 | # Configure a delay for requests for the same website (default: 0) 26 | # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay 27 | # See also autothrottle settings and docs 28 | #DOWNLOAD_DELAY = 3 29 | # The download delay setting will honor only one of: 30 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16 31 | #CONCURRENT_REQUESTS_PER_IP = 16 32 | 33 | # Disable cookies (enabled by default) 34 | #COOKIES_ENABLED = False 35 | 36 | # Disable Telnet Console (enabled by default) 37 | #TELNETCONSOLE_ENABLED = False 38 | 39 | # Override the default request headers: 40 | #DEFAULT_REQUEST_HEADERS = { 41 | # "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 42 | # "Accept-Language": "en", 43 | #} 44 | 45 | # Enable or disable spider middlewares 46 | # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html 47 | #SPIDER_MIDDLEWARES = { 48 | # "quotes_spider.middlewares.QuotesSpiderSpiderMiddleware": 543, 49 | #} 50 | 51 | # Enable or disable downloader middlewares 52 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 53 | #DOWNLOADER_MIDDLEWARES = { 54 | # "quotes_spider.middlewares.QuotesSpiderDownloaderMiddleware": 543, 55 | #} 56 | 57 | # Enable or disable extensions 58 | # See https://docs.scrapy.org/en/latest/topics/extensions.html 59 | #EXTENSIONS = { 60 | # "scrapy.extensions.telnet.TelnetConsole": None, 61 | #} 62 | 63 | # Configure item pipelines 64 | # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html 65 | #ITEM_PIPELINES = { 66 | # "quotes_spider.pipelines.QuotesSpiderPipeline": 300, 67 | #} 68 | 69 | # Enable and configure the AutoThrottle extension (disabled by default) 70 | # See https://docs.scrapy.org/en/latest/topics/autothrottle.html 71 | #AUTOTHROTTLE_ENABLED = True 72 | # The initial download delay 73 | #AUTOTHROTTLE_START_DELAY = 5 74 | # The maximum download delay to be set in case of high latencies 75 | #AUTOTHROTTLE_MAX_DELAY = 60 76 | # The average number of requests Scrapy should be sending in parallel to 77 | # each remote server 78 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 79 | # Enable showing throttling stats for every response received: 80 | #AUTOTHROTTLE_DEBUG = False 81 | 82 | # Enable and configure HTTP caching (disabled by default) 83 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings 84 | #HTTPCACHE_ENABLED = True 85 | #HTTPCACHE_EXPIRATION_SECS = 0 86 | #HTTPCACHE_DIR = "httpcache" 87 | #HTTPCACHE_IGNORE_HTTP_CODES = [] 88 | #HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage" 89 | 90 | # Set settings whose default value is deprecated to a future-proof value 91 | TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor" 92 | FEED_EXPORT_ENCODING = "utf-8" 93 | import logging 94 | 95 | # 在 Scrapy 项目的 settings.py 中配置日志格式 96 | LOG_FORMAT = '%(asctime)s [%(name)s] %(levelname)s: %(message)s' 97 | -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | # This package will contain the spiders of your Scrapy project 2 | # 3 | # Please refer to the documentation for information on how to create and manage 4 | # your spiders. 5 | -------------------------------------------------------------------------------- /tests/quotes_spider/quotes_spider/spiders/quotes.py: -------------------------------------------------------------------------------- 1 | import scrapy 2 | 3 | 4 | class QuotesSpider(scrapy.Spider): 5 | # 爬虫的名称,必须唯一 6 | name = "quotes" 7 | # 起始 URL 列表,爬虫将从这些 URL 开始爬取 8 | start_urls = [ 9 | 'https://quotes.toscrape.com', 10 | ] 11 | 12 | def parse(self, response): 13 | # 遍历每个名言元素 14 | for quote in response.css('div.quote'): 15 | # 提取名言文本 16 | text = quote.css('span.text::text').get() 17 | # 提取作者姓名 18 | author = quote.css('small.author::text').get() 19 | # 提取标签列表 20 | tags = quote.css('div.tags a.tag::text').getall() 21 | 22 | # 生成包含提取信息的字典,并通过 yield 返回 23 | yield { 24 | 'text': text, 25 | 'author': author, 26 | 'tags': tags 27 | } 28 | 29 | # 查找下一页的链接 30 | next_page = response.css('li.next a::attr(href)').get() 31 | if next_page is not None: 32 | # 如果存在下一页链接,使用 response.follow 方法继续爬取下一页,并调用 parse 方法处理响应 33 | yield response.follow(next_page, self.parse) -------------------------------------------------------------------------------- /tests/quotes_spider/scrapy.cfg: -------------------------------------------------------------------------------- 1 | # Automatically created by: scrapy startproject 2 | # 3 | # For more information about the [deploy] section see: 4 | # https://scrapyd.readthedocs.io/en/latest/deploy.html 5 | 6 | [settings] 7 | default = quotes_spider.settings 8 | 9 | [deploy] 10 | #url = http://localhost:6800/ 11 | project = quotes_spider 12 | -------------------------------------------------------------------------------- /tests/quotes_spider/scrapy.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taskPyroer/taskpyro/e8e13d127b81a37f152f866fd420a83737a4cdca/tests/quotes_spider/scrapy.log -------------------------------------------------------------------------------- /tests/selenium_test/test.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service 3 | import os 4 | 5 | service = Service() 6 | options = webdriver.ChromeOptions() 7 | options.add_argument('--no-sandbox') 8 | options.add_argument('--headless') 9 | options.add_argument('--disable-gpu') 10 | options.add_argument('--disable-software-rasterizer') 11 | options.add_argument('--disable-dev-shm-usage') 12 | 13 | driver = webdriver.Chrome(service=service, options=options) 14 | 15 | try: 16 | driver.get('https://www.bilibili.com') 17 | title = driver.title 18 | print(f"B 站网站的标题是: {title}") 19 | except Exception as e: 20 | print(f"发生错误: {e}") 21 | finally: 22 | driver.quit() -------------------------------------------------------------------------------- /tests/逆向案例/decrypt-v.js: -------------------------------------------------------------------------------- 1 | const CryptoJS = require('crypto-js'); 2 | 3 | function encryptMessage(message, secretKey) { 4 | return CryptoJS.AES.encrypt(message, secretKey).toString(); 5 | } 6 | 7 | function decryptMessage(encryptedMessage, secretKey) { 8 | const bytes = CryptoJS.AES.decrypt(encryptedMessage, secretKey); 9 | return bytes.toString(CryptoJS.enc.Utf8); 10 | } -------------------------------------------------------------------------------- /tests/逆向案例/main.py: -------------------------------------------------------------------------------- 1 | import execjs 2 | 3 | with open('decrypt-v.js', 'r') as f: 4 | jscontent = f.read() 5 | context= execjs.compile(jscontent) 6 | 7 | 8 | # 调用加密和解密函数 9 | encrypted_message = context.call('encryptMessage', 'Hello, World!', 'secret-key') 10 | print(f"Encrypted Message: {encrypted_message}") 11 | 12 | decrypted_message = context.call('decryptMessage', encrypted_message, 'secret-key') 13 | print(f"Decrypted Message: {decrypted_message}") -------------------------------------------------------------------------------- /tests/逆向案例/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "逆向案例", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "crypto-js": "^4.2.0" 9 | } 10 | }, 11 | "node_modules/crypto-js": { 12 | "version": "4.2.0", 13 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", 14 | "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", 15 | "license": "MIT" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/逆向案例/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "crypto-js": "^4.2.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /更新日志.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志 3 | icon: clock 4 | order: 1 5 | head: 6 | - - meta 7 | - name: description 8 | content: TaskPyro版本更新日志,记录每个版本的新功能、改进和Bug修复 9 | - - meta 10 | - name: keywords 11 | content: TaskPyro更新日志,版本更新,功能发布,Python环境管理,用户管理,消息通知 12 | --- 13 | 14 | # 更新日志 15 | 16 | ## v1.4.0 (2025-05-25) 17 | 18 | ### 新功能 19 | 20 | - **日志管理** 21 | - 新增日志管理,支持定时清理过大的日志文件 22 | 23 | - **其他** 24 | - 优化部分前端功能 25 | 26 | ## v1.3.0 (2025-05-06) 27 | 28 | ### 新功能 29 | 30 | - **项目管理** 31 | - 新增Git代码拉取方式,支持从Git仓库直接导入项目 32 | 33 | - **任务调度** 34 | - 新增立即执行的调度方式,支持任务的即时触发 35 | 36 | ### Bug修复 37 | 38 | - 修复了项目管理中文件时间显示的问题 39 | 40 | 41 | ## v1.2.0 (2025-04-23) 42 | 43 | ### 新功能 44 | 45 | - **Python版本管理** 46 | - 支持通过官网下载Python资源包 47 | - 自动解压和安装Python环境 48 | - 提供多版本Python环境管理 49 | 50 | - **用户管理** 51 | - 新增用户账号创建功能 52 | - 支持用户权限管理 53 | 54 | - **消息通知集成** 55 | - 新增钉钉机器人通知支持 56 | - 新增飞书机器人通知支持 57 | - 新增企业微信机器人通知支持 58 | - 支持任务执行状态实时推送 59 | 60 | ### Bug修复 61 | 62 | - 修复了项目上传功能中无法拖拽压缩包到页面的问题 63 | 64 | ### 如何更新 65 | 要升级到新版本,请执行以下步骤: 66 | 67 | ```bash 68 | # 拉取最新镜像 69 | docker-compose pull 70 | 71 | # 重启服务 72 | docker-compose up -d 73 | ``` 74 | --------------------------------------------------------------------------------