APScheduler定时器使用:django中使用apscheduler,使用mysql做存储后端

一、基本环境

python版本:3.8.5

APScheduler==3.10.4
Django==3.2.7
djangorestframework==3.15.1
SQLAlchemy==2.0.29
PyMySQL==1.1.0

二、django基本设置

2.1、新增一个app

该app用来写apscheduler相关的代码

python manage.py startapp gs_scheduler

2.2、修改配置文件settings.py

#使用pymysql做客户端
import pymysql
pymysql.install_as_MySQLdb()

INSTALLED_APPS = [
    
    'rest_framework', #注册restful 应用
    'gs_scheduler', #注册新增的app
]

#配置mysql
MYSQL_HOST = '127.0.0.1'
MYSQL_PORT = 3306
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'admin-root'
MYSQL_NAME = 'study_websocket'

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': MYSQL_HOST,
        'PORT': MYSQL_PORT,
        'USER': MYSQL_USER,
        'PASSWORD': MYSQL_PASSWORD,
        'NAME': MYSQL_NAME,
    }
}

2.3、gs_scheduler创建urls.py

1、gs_scheduler/urls.py

from django.urls import path
from . import views
urlpatterns = [
   
]

2、根路由urls.py

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/scheduler/',include('gs_scheduler.urls')),
]

三、配置gs_scheduler应用

3.1、配置api接口

这些接口,用来展示定时任务的运行情况

1、urls.py

from django.urls import path
from . import views
urlpatterns = [
    path('run_next/', views.JobNextRunTimeAPIView.as_view()),#定时任务下次运行
    path('run_history/',views.JobRunTimeHistory.as_view()),#定时任务运行历史
    path('run_error/',views.JobRunErrorHistory.as_view()), #定时任务最近运行错误
]

2、models.py

from django.db import models

# Create your models here.
import sqlite3
from gs_scheduler.management.commands.config import MYSQL_HOST,MYSQL_NAME,MYSQL_PORT,MYSQL_PASSWORD,MYSQL_USER,MYSQL_CHARSET
from datetime import datetime,timedelta
from django.conf import settings

import pymysql
from concurrent.futures import ThreadPoolExecutor

#时间戳转时间字符串
def timestamp_to_time_str(timestamp):
    # 使用 datetime 模块将时间戳转换为 datetime 对象
    dt = datetime.fromtimestamp(timestamp)
    # 将 datetime 对象格式化为时间字符串
    time_str = dt.strftime('%Y-%m-%d %H:%M:%S')
    return time_str

class MysqlDB:
    DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
    # 创建数据库连接池
    def __init__(self):
        self.conn = pymysql.connect(
            host=MYSQL_HOST,
            port=MYSQL_PORT,
            user=MYSQL_USER,
            password=MYSQL_PASSWORD,
            db=MYSQL_NAME,
            charset=MYSQL_CHARSET,
            cursorclass=pymysql.cursors.DictCursor
        )

    # 执行数据库增删改查
    def _execute_sql(self,query):
        sql,args = query
        try:
            with self.conn.cursor() as cursor:
                # 执行sql语句
                if args:
                    if isinstance(args,(tuple,list)):
                        # args = (value1,value2)
                        cursor.execute(sql,args)
                    else:
                        # args = value1
                        cursor.execute(sql,(args,))
                else:
                    cursor.execute(sql)
                # 不同类型sql,设置不同返回值
                if sql.strip().lower()[:6] == 'select':
                    #查询语句
                    rows = cursor.fetchall() or []
                    return rows
                elif sql.strip().lower()[:4] == 'show':
                    #查看表是否存在
                    result = cursor.fetchall()
                    return result
                else:
                    #增删改语句
                    self.conn.commit()
                    return True
        except Exception as e:
            print('sql执行失败',e)
            pass

    # 线程池执行sql语句
    def start_sql_with_pool(self, sql:str,args=None):
        with ThreadPoolExecutor() as executor:
            result = executor.submit(self._execute_sql, (sql,args)).result()
            return result

    #创建记录定时任务历史表(调度器使用)
    def create_apscheduler_history(self):
        #创建表语句:表名不能是占位符
        sql = """CREATE TABLE apscheduler_history (
                id INTEGER AUTO_INCREMENT PRIMARY KEY,
                job_id VARCHAR(128),
                run_time DATETIME,
                is_error TINYINT,
                error_msg VARCHAR(256) NULL)"""
        #判断表存在不,不存在再创建
        results = self.start_sql_with_pool('SHOW TABLES')
        is_exist = False
        for dic in results:
            if dic['Tables_in_{}'.format(MYSQL_NAME,)] == 'apscheduler_history':
                is_exist = True
        #表不存在才创建
        if not is_exist:
            # print('表不存在,执行创建表')
            create = self.start_sql_with_pool(sql)
            # 创建索引
            self.start_sql_with_pool(db.conn.cursor().execute("CREATE INDEX run_time_index ON apscheduler_history (run_time)"))
        return True

    # 将任务运行的结果记录到数据库中(调度器使用)
    def insert_into_apscheduler_history(self, data_list: list):
        if len(data_list) == 4:  # 异常执行的任务
            sql = """
            INSERT INTO apscheduler_history (job_id,run_time,is_error,error_msg) VALUES (%s,%s,%s,%s)
            """
        else:  # 正常执行的任务
            sql = """
            INSERT INTO apscheduler_history (job_id,run_time,is_error) VALUES (%s,%s,%s)
            """
        #使用线程池执行插入语句
        self.start_sql_with_pool(sql,data_list)

    # 删除8小时前的历史记录(调度器使用)
    def delete_8hour_before_history(self):
        before_8hour = (datetime.now() - timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S')
        sql = '''
        DELETE FROM apscheduler_history WHERE run_time < %s'''
        self.start_sql_with_pool(sql,(before_8hour,))

    # 查询每个任务的下次运行时间(api展示)
    def fetch_all_next_run_time(self):
        sql = 'SELECT id,next_run_time FROM apscheduler_jobs'
        # 获取查询结果
        rows = self.start_sql_with_pool(sql)
        for row in rows:
            row['next_run_time'] = timestamp_to_time_str(row['next_run_time'])
        return rows

    # 查询所有任务最近10次运行记录(api展示)
    def fetch_all_run_history(self):
        '''
        :param query:'SELECT id,next_run_time,job_state FROM apscheduler_jobs'
        :return:
        '''
        #获取所有定时任务id
        data_list = self.fetch_all_next_run_time()
        id_list = (dic.get('id') for dic in data_list)
        # 创建一个游标对象
        sql = """
           SELECT id,job_id,run_time FROM apscheduler_history WHERE is_error=0 AND job_id = %s ORDER BY run_time DESC LIMIT 10
           """
        ret_list = []
        for id in id_list:
            # 执行查询
            rows = self.start_sql_with_pool(sql,(id,)) or []
            dic = {'id': id, 'last_run_time': [], 'msg': '最近10次运行时间'}
            for row in rows:
                run_time = row.get('run_time')
                run_time = run_time.strftime(self.DATETIME_FORMAT) if isinstance(run_time,datetime) else run_time
                dic['last_run_time'].append(run_time)
            ret_list.append(dic)
        return ret_list

    # 获取任务最近运行失败情况(api展示)
    def fetch_all_error_history(self):
        data_list = self.fetch_all_next_run_time()
        id_list = (dic.get('id') for dic in data_list)
        sql = """
        SELECT run_time,error_msg FROM apscheduler_history WHERE is_error=1 AND job_id = %s ORDER BY id DESC LIMIT 5
        """
        ret_list = []
        for id in id_list:
            # 获取查询结果
            rows = self.start_sql_with_pool(sql, (id,)) or []
            dic = {'id': id, 'last_run_time': [], 'msg': '最近5次执行失败'}
            for row in rows:
                run_time = row.get('run_time')
                run_time = run_time.strftime(self.DATETIME_FORMAT) if isinstance(run_time,datetime) else run_time
                error = row.get('error_msg')
                dic['last_run_time'].append({'run_time': run_time, 'error': error})
            ret_list.append(dic)
        return ret_list

    # 关闭连接
    def close(self):
        self.conn.close()



if __name__ == '__main__':
    db = MysqlDB()
    db.create_apscheduler_history()
    # for i in range(1,10):
    #     db.insert_into_apscheduler_history(['send_to_big_data','2024-05-01 13:{}:12'.format(str(i).zfill(2)),1,'执行失败了'])
    # db.delete_8hour_before_history()
    # ret = db.fetch_all_run_history()
    # print(ret,'history')
    # ret = db.fetch_all_next_run_time()
    # print(ret,'next')
    # ret = db.fetch_all_error_history()
    # print(ret,'error')
    db.close()



3、views.py

from django.shortcuts import render

# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import MysqlDB

#任务下次运行时间
class JobNextRunTimeAPIView(APIView):
    authentication_classes = []
    def get(self,request):
        db = MysqlDB()
        data = db.fetch_all_next_run_time()
        db.close()
        ret = {
            'code':200,
            'status':'success',
            'data':data,
        }

        return Response(ret)

#任务最近运行历史
class JobRunTimeHistory(APIView):
    authentication_classes = []
    def get(self,request):
        db = MysqlDB()
        data = db.fetch_all_run_history()
        db.close()
        ret = {
            'code':200,
            'status':'success',
            'data':data
        }
        return Response(ret)

#任务最近运行错误
class JobRunErrorHistory(APIView):
    authentication_classes = []
    def get(self,request):
        db = MysqlDB()
        data = db.fetch_all_error_history()
        db.close()
        ret = {
            'code': 200,
            'status': 'success',
            'data': data
        }
        return Response(ret)

3.2、在gs_scheduler创建

1、config.py 代码

该文件存放的是启动APScheduler调度器的一些配置数据

import os
from apscheduler.jobstores.memory import MemoryJobStore #内存做后端存储
#from apscheduler.jobstores.redis import RedisJobStore #redis做后端存储
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore #mysql等做后端存储
# from django.conf import settings
from study_apscheduler import settings
#mysql://root:ldc-root@127.0.0.1:3306/jobs?charset=utf8
MYSQL_CONFIG = settings.DATABASES.get('default')
MYSQL_USER = MYSQL_CONFIG.get('USER')
MYSQL_PASSWORD = MYSQL_CONFIG.get('PASSWORD')
MYSQL_HOST = MYSQL_CONFIG.get('HOST')
MYSQL_PORT = MYSQL_CONFIG.get('PORT')
MYSQL_NAME = MYSQL_CONFIG.get('NAME')
MYSQL_CHARSET = 'utf8mb4'
URL = 'mysql://{}:{}@{}:{}/{}?charset={}'.format(MYSQL_USER,MYSQL_PASSWORD,MYSQL_HOST,MYSQL_PORT,MYSQL_NAME,MYSQL_CHARSET)
#时区
TIME_ZONE = 'Asia/Shanghai'
#job的默认配置
JOB_DEFAULTS =  {
        'coalesce': True, #系统挂掉,任务积攒多次为执行,True是合并成一次执行,False是执行所有的次数。 持久化存储才有效
        'max_instances': 3 # 同一个任务同一时间最多只能有3个实例在运行。
    }
#job的存储后端
JOB_STORE = {
    'default': SQLAlchemyJobStore(url=URL)
}

#监听事件对应的情况
LISTENER={
    1:'调度程序启动',
    2:'调度程序关闭',
    4:'调度程序中任务处理暂停',
    64:'将任务存储添加到调度程序中',
    8192:'任务在执行期间引发异常',
    4096:'任务执行成功',
}

2、task.py

所有的定时任务都存放在这里

import os
from datetime import datetime, timedelta, date
from gs_scheduler.models import MysqlDB
from utils.log_util import info_log
from utils.send_monitor_data import SendData

#推送到数据仓的告警信息:5分钟执行一次
send_to_big_data = SendData().send

#清除定时任务历史运行记录
def delete_apscheduler_history():
    db = MysqlDB()
    db.delete_8hour_before_history()

if __name__ == '__main__':
    print(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

3、crontab.py

实例化调度器

# 导入所需的调度器类和触发器类
from apscheduler.schedulers.background import BackgroundScheduler #后台运行
from apscheduler.schedulers.blocking import BlockingScheduler  #主进程运行,需要单独运行
from apscheduler.triggers.interval import IntervalTrigger #时间间隔
from apscheduler.triggers.cron import CronTrigger #复杂的定时任务
from apscheduler.triggers.date import DateTrigger #一次性定时任务
from django.core.management.base import BaseCommand
from apscheduler import events
from pytz import timezone
from threading import RLock
from datetime import datetime, timedelta
from gs_scheduler.models import MysqlDB
#定时任务
from .task import delete_apscheduler_history
from .task import send_to_big_data
#日志
from utils.log_util import info_log
from .config import LISTENER
from .config import TIME_ZONE,JOB_DEFAULTS,JOB_STORE

#脚本运行:python manage.py crontab
class Command(BaseCommand):
    TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
    #初始化调度器
    def _scheduler_obj(self):
        scheduler = BlockingScheduler()
        scheduler.configure(
            timezone = TIME_ZONE, #时区
            job_defaults=JOB_DEFAULTS, #job的默认配置
            jobstores=JOB_STORE, #job的存储后端
        )
        return scheduler

    #添加任务
    def _add_job(self,scheduler:BlockingScheduler):
        #每5分钟执行一次推送告警到大数据仓
        scheduler.add_job(
            send_to_big_data,
            trigger=IntervalTrigger(minutes=1),
            id='send_to_big_data',
            replace_existing=True,
            coalesce=True,
        )
        #每隔8个小时,清除历史的记录
        scheduler.add_job(
            delete_apscheduler_history,
            trigger=IntervalTrigger(hours=8),
            id='delete_apscheduler_history',
            replace_existing=True,
            coalesce=True,
        )

    #添加监听器
    def _listener(self,event:events):
        code = event.code
        run_time = datetime.now().strftime(self.TIME_FORMAT)
        msg = LISTENER.get(code)
        db = MysqlDB()
        if msg:
            if code == 4096:
                #成功运行
                job_id = event.job_id
                #记录到数据库中
                db.insert_into_apscheduler_history([job_id,run_time,0])
            elif code == 8192:
                #运行异常了
                job_id = event.job_id
                #记录到数据库中
                db.insert_into_apscheduler_history([job_id,run_time,1,msg])
            else:
                info_log(msg)
        db.close()
    def start(self):
        scheduler = self._scheduler_obj()
        # 创建记录运行记录
        db = MysqlDB()
        db.create_apscheduler_history()
        db.close()
        # 设置监听器
        scheduler.add_listener(self._listener)
        # 设置定时任务
        self._add_job(scheduler)
        try:
            # print('{},定时器启动成功,等待定时任务执行...'.format(datetime.now().strftime(self.TIME_FORMAT)))
            scheduler.start()
        except KeyboardInterrupt:
            scheduler.shutdown()

    # python manage.py crontab运行 就是调用该方法
    def handle(self, *args, **options):
        self.start()

#伴随django,在后台运行的。 在wsgi.py 文件中,调用BackRunScheduler().start()
class BackRunScheduler(Command):
    # 初始化调度器
    def _scheduler_obj(self):
        scheduler = BackgroundScheduler()
        scheduler.configure(
            timezone=TIME_ZONE,  # 时区
            job_defaults=JOB_DEFAULTS,  # job的默认配置
            jobstores=JOB_STORE,  # job的存储后端
        )
        return scheduler

3.3、启动方式:

方式一: python manage.py  crontab    (生产环境,推荐使用此方式,单独运行)

方式二:在settings.py中,调用BackRunScheduler().start()  , 后台运行

四、测试

4.1、启动

启动django项目:python manage.py runserver 8080

启动定时器:python manage.py crontab

4.2、等待一段时间

1、查询任务下次执行时间

请求:http://127.0.0.1:8080/api/scheduler/run_next/

2、查询任务最近运行情况

请求:http://127.0.0.1:8080/api/scheduler/run_next/

3、查询任务最近异常情况

请求:http://127.0.0.1:8080/api/scheduler/run_error/

五、源代码下载

码云地址:

django应用定时器: django下使用定时器的方法icon-default.png?t=N7T8https://gitee.com/liuhaizhang/django-application-timer/

目前有两套代码:一个以mysql存储任务,一个用sqlite存储任务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/587896.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Typora+PicGo+阿里云OSS搭建个人博客图床(2024最新详细搭建教程)

创作者&#xff1a;Code_流苏(CSDN) 目录 一、什么是图床&#xff1f;二、准备工作三、配置PicGo四、配置Typora五、使用 很高兴你打开了这篇博客&#xff0c;如有疑问&#xff0c;欢迎评论。 更多好用的软件工具&#xff0c;请关注我&#xff0c;订阅专栏《实用软件与高效工具…

基于肤色模型的人脸识别FPGA实现,包含tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 matlab2022a的测试结果如下&#xff1a; vivado2019.2的仿真结果如下&#xff1a; 将数据导入到matlab中&#xff0c; 系统的RTL结构图如下图所示…

安装“STM32F4 Discovery Board Programming with Embedded Coder”MATLAB获取硬件支持包失败

安装“STM32F4 Discovery Board Programming with Embedded Coder”MATLAB获取硬件支持包失败 -完美解决方法 显示请续订您的软件维护服务&#xff0c;解决办法 根据知乎的文章 MATLAB获取硬件支持包失败&#xff0c;显示请续订您的软件维护服务&#xff0c;解决办法&#xff…

为家庭公网IP配置DDNS域名

文章目录 域名配置域名更新frp配置修改 在成功完成frp改造Windows笔记本实现家庭版免费内网穿透之后&#xff0c;某天我突然发现内网穿透失效了&#xff0c;一番排查之后原来是路由器对应的公网IP更换了。果然我分到的并不是固定的公网IP&#xff0c;而是会定期变化的。为了免受…

头歌:SparkSQL简单使用

第1关&#xff1a;SparkSQL初识 任务描述 本关任务&#xff1a;编写一个sparksql基础程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1. 什么是SparkSQL 2. 什么是SparkSession。 什么是SparkSQL Spark SQL是用来操作结构化和半结构化数据的接口。…

【深耕 Python】Data Science with Python 数据科学(18)Scikit-learn机器学习(三)

写在前面 关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 往期数据科学博文一览&#xff1a; 【深耕 Python】Data Science with Python 数据科学&#xff08;2&…

2024五一杯数学建模C题思路分享 - 煤矿深部开采冲击地压危险预测

文章目录 1 赛题选题分析 2 解题思路2.1 问题重述2.2 第一问完整思路2.2 二、三问思路更新 3 最新思路更新 1 赛题 C题 煤矿深部开采冲击地压危险预测 煤炭是中国的主要能源和重要的工业原料。然而&#xff0c;随着开采深度的增加&#xff0c;地应力增大&#xff0c;井下煤岩动…

搜索引擎的设计与实现参考论文(论文 + 源码)

【免费】搜索引擎的设计与实现.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89249705?spm1001.2014.3001.5501 搜索引擎的设计与实现 摘要&#xff1a; 我们处在一个大数据的时代&#xff0c;伴随着网络信息资源的庞大&#xff0c;人们越来越多地注重怎样才能…

汽车车灯的材料是什么?汽车车灯的灯罩如果破损破裂破洞了要怎么修复?

汽车车灯的材料主要包括灯罩和灯底座两部分&#xff0c;它们所使用的材料各不相同。 车灯罩的材料主要是透明且具有良好耐热性和耐紫外线性能的塑料。其中&#xff0c;聚碳酸酯&#xff08;PC&#xff09;是一种常用的材料&#xff0c;它具有高抗冲击性、耐化学品腐蚀和优良的…

Pandas入门篇(二)-------Dataframe篇4(进阶)(Dataframe的进阶用法)(机器学习前置技术栈)

目录 概述一、复合索引&#xff08;一&#xff09;创建具有复合索引的 DataFrame1. 使用 set_index 方法&#xff1a;2.在创建 DataFrame 时直接指定索引&#xff1a; &#xff08;二&#xff09;使用复合索引进行数据选择和切片&#xff08;三&#xff09;重置索引&#xff08…

Spring Cloud Kubernetes 本地开发环境调试

一、Spring Cloud Kubernetes 本地开发环境调试 上面文章使用 Spring Cloud Kubernetes 在 k8s 环境中实现了服务注册发现、服务动态配置&#xff0c;但是需要放在 k8s 环境中才能正常使用&#xff0c;在本地开发环境中可能没有 k8s 环境&#xff0c;如何本地开发调试呢&#…

1. 深度学习笔记--神经网络中常见的激活函数

1. 介绍 每个激活函数的输入都是一个数字&#xff0c;然后对其进行某种固定的数学操作。激活函数给神经元引入了非线性因素&#xff0c;如果不用激活函数的话&#xff0c;无论神经网络有多少层&#xff0c;输出都是输入的线性组合。激活函数的意义在于它能够引入非线性特性&am…

小程序wx.getlocation接口如何开通?

小程序地理位置接口有什么功能&#xff1f; 随着小程序生态的发展&#xff0c;越来越多的小程序开发者会通过官方提供的自带接口来给用户提供便捷的服务。但是当涉及到地理位置接口时&#xff0c;却经常遇到申请驳回的问题&#xff0c;反复修改也无法通过&#xff0c;给的理由…

计算机网络chapter1——家庭作业

文章目录 复习题1.1节&#xff08;1&#xff09; “主机”和“端系统”之间有何不同&#xff1f;列举几种不同类型的端系统。web服务器是一种端系统吗&#xff1f;&#xff08;2&#xff09;协议一词常用来用来描述外交关系&#xff0c;维基百科是如何描述外交关系的&#xff1…

十大排序算法之->插入排序

一、插入排序 插入排序的基本思想是将一个记录插入到已经排好序的有序表中&#xff0c;从而形成一个新的、记录数增1的有序表。 排序过程&#xff1a; 1、外层循环&#xff1a;从第二个元素开始&#xff0c;依次选取未排序的元素。 2、内层循环&#xff1a;将当前选取的元素…

【UnityRPG游戏制作】Unity_RPG项目_玩家逻辑相关

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

Typescript精进:前端必备的5大技巧(AI写作)

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

通过自然语言处理执行特定任务的AI Agents;大模型控制NPC执行一系列的动作;个人化的电子邮件助手Panza

✨ 1: OpenAgents 通过自然语言处理执行特定任务的AI代理 OpenAgents是一个开放平台&#xff0c;旨在使语言代理&#xff08;即通过自然语言处理执行特定任务的AI代理&#xff09;的使用和托管变得更加便捷和实用。它特别适合于日常生活中对数据分析、工具插件获取和网络浏览…

【Mac】Mac安装软件常见问题解决办法

前言 刚开始用Mac系统的小伙伴或者在更新系统版本后运行App的朋友会经常碰到弹窗提示「xxx已损坏&#xff0c;无法打开&#xff0c;您应该将它移到废纸篓」、「打不开xxx&#xff0c;因为Apple无法检查其是否包含恶意软件」、「打不开xxx&#xff0c;因为它来自身份不明的开发…

Pandas入门篇(三)-------数据可视化篇3(seaborn篇)(pandas完结撒花!!!)

目录 概述一、语法二、常用单变量绘图1. 直方图&#xff08;histplot&#xff09;2. 核密度预估图&#xff08;kdeplot&#xff09;3. 计数柱状图&#xff08;countplot&#xff09; 三、常用多变量绘图1.散点图(1) scatterplot(2)regplot 散点图拟合回归线(3)jointplot 散点图…