电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

5分鐘快速掌握Python定時任務框架的實現(xiàn)

瀏覽:64日期:2022-06-29 10:20:20
APScheduler 簡介

在實際開發(fā)中我們經(jīng)常會碰上一些重復性或周期性的任務,比如像每天定時爬取某個網(wǎng)站的數(shù)據(jù)、一定周期定時運行代碼訓練模型等,類似這類的任務通常需要我們手動來進行設定或調度,以便其能夠在我們設定好的時間內運行。

在 Windows 上我們可以通過計劃任務來手動實現(xiàn),而在 Linux 系統(tǒng)上往往我們會用到更多關于 crontab 的相關操作。但手動管理并不是一個很好的選擇,如果我們需要有十幾個不同的定時任務需要管理,那么每次通過人工來進行干預未免有些笨拙,那這時候就真的是「人工智能」了。

所以將這些定時任務的調度代碼化才是能夠讓我們很好地從這種手動管理的純人力操作中解脫出來。

在 Python 生態(tài)中對于定時任務的一些操作主要有那么幾個:

schedule:第三方模塊,該模塊適合比較輕量級的一些調度任務,但卻不適用于復雜時間的調度 APScheduler:第三方定時任務框架,是對 Java 第三方定時任務框架 Quartz 的模仿與移植,能提供比 schedule 更復雜的應用場景,并且各種組件都是模塊化,易于使用與二次開發(fā)。 Celery Beat:屬于 celery 這分布式任務隊列第三方庫下的一個定時任務組件,如果使用需要配合 RabbitMQ 或 Redis 這類的消息隊列套件,需要花費一定的時間在環(huán)境搭建上,但在高版本中已經(jīng)不支持 Windows。

所以為了滿足能夠相對復雜的時間條件,又不需要在前期的環(huán)境搭建上花費很多時間的前提下,選擇 APScheduler 來對我們的調度任務或定時任務進行管理是個性價比極高的選擇。而本文主要會帶你快速上手有關 APScheduler 的使用。

APScheduler 概念與組件

雖然說官方文檔上的內容不是很多,而且所列舉的 API 不是很多,但這側面也反映了這一框架的簡單易用。所以在使用 APScheduler 之前,我們需要對這個框架的一些概念簡單了解,主要有那么以下幾個:

觸發(fā)器(trigger) 任務持久化(job stores) 執(zhí)行器(executor) 調度器(scheduler)觸發(fā)器(trigger)

所謂的觸發(fā)器就是用以觸發(fā)定時任務的組件,在 APScheduler 中主要是指時間觸發(fā)器,并且主要有三類時間觸發(fā)器可供使用:

date:日期觸發(fā)器。日期觸發(fā)器主要是在某一日期時間點上運行任務時調用,是 APScheduler 里面最簡單的一種觸發(fā)器。所以通常也適用于一次性的任務或作業(yè)調度。 interval:間隔觸發(fā)器。間隔觸發(fā)器是在日期觸發(fā)器基礎上擴展了對時間部分,比如時、分、秒、天、周這幾個部分的設定。是我們用以對重復性任務進行設定或調度的一個常用調度器。設定了時間部分之后,從起始日期開始(默認是當前)會按照設定的時間去執(zhí)行任務。 cron:cron 表達式觸發(fā)器。cron 表達式觸發(fā)器就等價于我們 Linux 上的 crontab,它主要用于更復雜的日期時間進行設定。但需要注意的是,APScheduler 不支持 6 位及以上的 cron 表達式,最多只支持到 5 位。任務持久化(job stores)

任務持久化主要是用于將設定好的調度任務進行存儲,即便是程序因為意外情況,如斷電、電腦或服務器重啟時,只要重新運行程序時,APScheduler 就會根據(jù)對存儲好的調度任務結果進行判斷,如果出現(xiàn)已經(jīng)過期但未執(zhí)行的情況會進行相應的操作。

APScheduler 為我們提供了多種持久化任務的途徑,默認是使用 memory 也就是內存的形式,但內存并不是持久化最好的方式。最好的方式則是通過像數(shù)據(jù)庫這樣的載體來將我們的定時任務寫入到磁盤當中,只要磁盤沒有損壞就能將數(shù)據(jù)給恢復。

APScheduler 支持的且常用的數(shù)據(jù)庫主要有:

sqlalchemy 形式的數(shù)據(jù)庫,這里就主要是指各種傳統(tǒng)的關系型數(shù)據(jù)庫,如 MySQL、PostgreSQL、SQLite 等。 mongodb 非結構化的 Mongodb 數(shù)據(jù)庫,該類型數(shù)據(jù)庫經(jīng)常用于對非結構化或版結構化數(shù)據(jù)的存儲或操作,如 JSON。 redis 內存數(shù)據(jù)庫,通常用作數(shù)據(jù)緩存來使用,當然通過一些主從復制等方式也能實現(xiàn)當中數(shù)據(jù)的持久化或保存。

通常我們可以在創(chuàng)建 Scheduler 實例時創(chuàng)建,或是單獨為任務指定。配置的方式相對簡單,我們只需要指定對應的數(shù)據(jù)庫鏈接即可。

執(zhí)行器(executor)

執(zhí)行器顧名思義就是執(zhí)行我們任務的對象,在計算機內通常要么是 CPU 調度任務,要么是單獨維護一個線程來運行任務。所以 APScheduler 里的執(zhí)行器通常就是 ThreadPoolExecutor 或 ProcessPoolExecutor 這樣的線程池和進程池兩種。

當然如果是和協(xié)程或異步相關的任務調度,還可以使用對應的 AsyncIOExecutor、TwistedExecutor 和 GeventExecutor 三種執(zhí)行器。

調度器(scheduler)

調度器的選擇主要取決于你當前的程序環(huán)境以及 APScheduler 的用途。根據(jù)用途的不同,APScheduler 又提供了以下幾種調度器:

BlockingScheduler:阻塞調度器,當程序中沒有任何存在主進程之中運行東西時,就則使用該調度器。 BackgroundScheduler:后臺調度器,在不使用后面任何的調度器且希望在應用程序內部運行時的后臺啟動時才進行使用,如當前你已經(jīng)開啟了一個 Django 或 Flask 服務。 AsyncIOScheduler:AsyncIO 調度器,如果代碼是通過 asyncio 模塊進行異步操作,使用該調度器。 GeventScheduler:Gevent 調度器,如果代碼是通過 gevent 模塊進行協(xié)程操作,使用該調度器 TornadoScheduler:Tornado 調度器,在 Tornado 框架中使用 TwistedScheduler:Twisted 調度器,在基于 Twisted 的框架或應用程序中使用 QtScheduler:Qt 調度器,在構建 Qt 應用中進行使用。

通常情況下如果不是和 Web 項目或應用集成共存,那么往往都首選 BlockingScheduler 調度器來進行操作,它會在當前進程中啟動相應的線程來進行任務調度與處理;反之,如果是和 Web 項目或應用共存,那么需要選擇 BackgroundScheduler 調度器,因為它不會干擾當前應用的線程或進程狀況。

基于對以上的概念和組件認識,我們就能基本上摸清 APScheduler 的運行流程:

設定調度器(scheduler)用以對任務的調度與安排進行全局統(tǒng)籌 對相應的函數(shù)或方法上設定相應的觸發(fā)器(trigger),并添加到調度器中 如有任務持久化(job stores)需要則需要設定對應的持久化層,否則默認使用內存存儲任務 當觸發(fā)器被觸發(fā)時,就將任務交由執(zhí)行器(executor)進行執(zhí)行APScheduler 快速上手

雖然 APScheduler 里面的概念和組件看起來有點多,但在使用上并不算很復雜,我們可以通過本節(jié)的示例就能夠很快使用。

選擇對應的 scheduler

在使用之前我們需要先實例化一個 scheduler 對象,所有的 scheduler 對象都被放在了 apscheduler.schedulers 模塊下,我們可以直接通過查看 API 文檔或者借助 IDE 補全的提示來獲取相應的 scheduler 對象。

這里我直接選取了最基礎的 BlockingScheduler:

# main.py from apscheduler.schedulers.blocking import BlockingScheduler scheduler = BlockingScheduler()

配置 scheduler

對于 scheduler 的一些配置我們可以直接在實例化對象時就進行配置,當然也可以在創(chuàng)建實例化對象之后再進行配置。

實例化時進行參數(shù)配置:

# main.pyfrom datetime import datetime from apscheduler.executors.pool import ThreadPoolExecutorfrom apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStorefrom apscheduler.schedulers.blocking import BlockingScheduler # 任務持久化 使用 SQLitejobstores = { ’default’: SQLAlchemyJobStore(url = ’sqlite:///jobs.db’)}# 執(zhí)行器配置executors = { ’default’: ThreadPoolExecutor(20),}# 關于 Job 的相關配置,見官方文檔 APIjob_defaults = { ’coalesce’: False, ’next_run_time’: datetime.now()}scheduler = BlockingScheduler( jobstores = jobstores, executors = executors, job_defaults = job_defaults, timezone = ’Asia/Shanghai’)

或是通過 scheduler.configure 方法進行同樣的操作:

scheduler = BlockingScheduler()scheduler.configure(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=’Asia/Shanghai’)添加并執(zhí)行你的任務

創(chuàng)建 scheduler 對象之后,我們需要調用其下的 add_job() 或是 scheduled_job() 方法來將我們需要執(zhí)行的函數(shù)進行注冊。前者是以傳參的形式指定對應的函數(shù)名,而后者則是以裝飾器的形式直接對我們要執(zhí)行的函數(shù)進行修飾。

比如我現(xiàn)在有一個輸出此時此刻時間的函數(shù) now():

from datetime import datetime def now(trigger): print(f'trigger:{trigger} -> {datetime.now()}')

然后我打算每 5 秒的時候運行一次,那我們使用 add_job() 可以這樣寫:

if __name__ == ’__main__’: scheduler.add_job(now, trigger = 'interval', args = ('interval',), seconds = 5) scheduler.start()

在調用 start() 方法之后調度器就會開始執(zhí)行,并在控制臺上看到對應的結果了:

trigger:interval -> 2021-01-16 21:19:43.356674trigger:interval -> 2021-01-16 21:19:46.679849trigger:interval -> 2021-01-16 21:19:48.356595

當然使用 @scheduled_job 的方式來裝飾我們的任務或許會更加自由一些,于是上面的例子就可以寫成這樣:

@scheduler.scheduled_job(trigger = 'interval', args = ('interval',), seconds = 5)def now(trigger): print(f'trigger:{trigger} -> {datetime.now()}') if __name__ == ’__main__’: scheduler.start()

運行之后就會在控制臺看到同樣的結果了。

不過需要注意的是,添加任務一定要在 start() 方法執(zhí)行前調用,否則會找不到任務或是拋出異常。

將 APScheduler 集成到 Web 項目中

如果你是正在做有關的 Web 項目且存在一些定時任務,那么得益于 APScheduler 由于多樣的調度器,我們能夠將其和我們的項目結合到一起。

如果你正在使用 Flask,那么 Flask-APScheduler 這一別人寫好的第三方包裝庫就很適合你,雖然它沒有相關的文檔,但只要你了解了前面我所介紹的有關于 APScheduler 的概念和組件,你就能很輕易地看懂這個第三方庫倉庫里的示例代碼。

如果你使用的不是 Flask 框架,那么 APScheduler 本身也提供了一些對任務或作業(yè)的增刪改查操作,我們可以自己編寫一套合適的 API。

這里我使用的是 FastAPI 這一目前流行的 Web 框架。demo 項目結構如下:

temp-scheduler├── config.py # 配置項├── main.py # API 文件└── scheduler.py # APScheduler 相關設置安裝依賴

這里我們需要的依賴不多,只需要簡單幾個即可:

pip install fastapi apscheduler sqlalchemy uvicorn配置項

如果項目中模塊過多,那么使用一個文件或模塊來進行統(tǒng)一管理是最好的選擇。這里的 config.py 我們主要像 Flask 的配置那樣簡單設定:

from apscheduler.executors.pool import ThreadPoolExecutorfrom apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStorefrom apscheduler.schedulers.blocking import BlockingScheduler class SchedulerConfig: JOBSTORES = {'default': SQLAlchemyJobStore(url='sqlite:///job.db')} EXECUTORS = {'default': ThreadPoolExecutor(20)} JOB_DEFAULTS = {'coalesce': False} @classmethod def to_dict(cls): return { 'jobstores': cls.JOBSTORES, 'executors': cls.EXECUTORS, 'job_defaults': cls.JOB_DEFAULTS, }

在 SchedulerConfig 配置項中我們可以自己實現(xiàn)一個 to_dict() 類方法,以便我們后續(xù)傳參時通過解包的方式直接傳入配置參數(shù)即可。

Scheduler 相關設置

scheduler.py 模塊的設定也比較簡單,即設定對應的 scheduler 調度器即可。由于是演示 demo 我還將要定期執(zhí)行的任務也放在了這個模塊當中:

import loggingfrom datetime import datetime from apscheduler.schedulers.background import BackgroundScheduler from config import SchedulerConfig scheduler = BackgroundScheduler()logger = logging.getLogger(__name__) def init_scheduler() -> None: # config scheduler scheduler.configure(**SchedulerConfig.to_dict()) logger.info('scheduler is running...') # schedule test scheduler.add_job( func=mytask, trigger='date', args=('APScheduler Initialize.',), next_run_time=datetime.now(), ) scheduler.start() def mytask(message: str) -> None: print(f'[{datetime.now()}] message: {message}')

在這一部分中:

init_scheduler() 方法主要用于在 API 服務啟動時被調用,然后對 scheduler 對象的配置以及測試 mytask() 則是我們要定期執(zhí)行的任務,后續(xù)我們可以通過 APScheduler 提供的方法來自行添加任務API 設置

在 main.py 模塊就主要存放著我們由 FastAPI 所構建的相關 API。如果在后續(xù)開發(fā)時存在多個接口,此時就需要將不同接口放在不同模塊文件中,以達到路由的分發(fā)與管理,類似于 Flask 的藍圖模式。

import loggingimport uuidfrom datetime import datetimefrom typing import Any, Dict, Optional, Sequence, Union from fastapi import FastAPIfrom pydantic import BaseModel from scheduler import init_scheduler, mytask, scheduler logger = logging.getLogger(__name__) app = FastAPI(title='APScheduler API')app.add_event_handler('startup', init_scheduler) class Job(BaseModel): id: Union[int, str, uuid.UUID] name: Optional[str] = None func: Optional[str] = None args: Optional[Sequence[Optional[str]]] = None kwargs: Optional[Dict[str, Any]] = None executor: Optional[str] = None misfire_grace_time: Optional[str] = None coalesce: Optional[bool] = None max_instances: Optional[int] = None next_run_time: Optional[Union[str, datetime]] = None @app.post('/add')def add_job( message: str, trigger: str, trigger_args: Optional[dict], id: Union[str, int, uuid.UUID],): try: scheduler.add_job( func=mytask, trigger=trigger, kwargs={'message': message}, id=id, **trigger_args, ) except Exception as e: logger.exception(e.args) return {'status_code': 0, 'message': '添加失敗'} return {'status_code': 1, 'message': '添加成功'} @app.delete('/delete/{id}')def delete_job(id: Union[str, int, uuid.UUID]): '''delete exist job by id''' try: scheduler.remove_job(job_id=id) except Exception: return dict( message='刪除失敗', status_code=0, ) return dict( message='刪除成功', status_code=1, ) @app.put('/reschedule/{id}')def reschedule_job( id: Union[str, int, uuid.UUID], trigger: str, trigger_args: Optional[dict]): try: scheduler.reschedule_job(job_id=id, trigger=trigger, **trigger_args) except Exception as e: logger.exception(e.args) return dict( message='修改失敗', status_code=0, ) return dict( message='修改成功', status_code=1, ) @app.get('/job')def get_all_jobs(): jobs = None try: job_list = scheduler.get_jobs() if job_list: jobs = [Job(**task.__getstate__()) for task in job_list] except Exception as e: logger.exception(e.args) return dict( message='查詢失敗', status_code=0, jobs=jobs, ) return dict( message='查詢成功', status_code=1, jobs=jobs, ) @app.get('/job/{id}')def get_job_by_id(id: Union[int, str, uuid.UUID]): jobs = [] try: job = scheduler.get_job(job_id=id) if job: jobs = [Job(**job.__getstate__())] except Exception as e: logger.exception(e.args) return dict( message='查詢失敗', status_code=0, jobs=jobs, ) return dict( message='查詢成功', status_code=1, jobs=jobs, )

以上代碼看起來很多,其實核心的就那么幾點:

FastAPI 對象 app 的初始化。這里用到的 add_event_handler() 方法就有點像 Flask 中的 before_first_request,會在 Web 服務請求伊始進行操作,理解為初始化相關的操作即可。

API 接口路由。路由通過 app 對象下的對應 HTTP 方法來實現(xiàn),如 GET、POST、PUT 等。這里的裝飾器用法其實也和 Flask 很類似,就不多贅述。

scheduler 對象的增刪改查。從 scheduler.py 模塊中引入我們創(chuàng)建好的 scheduler 對象之后就可以直接用來做增刪改查的操作:

增:使用 add_job() 方法,其主要的參數(shù)是要運行的函數(shù)(或方法)、觸發(fā)器以及觸發(fā)器參數(shù)等 刪:使用 delete_job() 方法,我們需要傳入一個對應任務的 id 參數(shù),用以能夠查找到對應的任務 改:使用 reschedule_job() 方法,這里也需要一個對應任務的 id 參數(shù),以及需要重新修改的觸發(fā)器及其參數(shù) 查:使用 get_jobs() 和 get_job() 兩個方法,前者是直接獲取到當前調度的所有任務,返回的是一個包含了 APScheduler.job.Job 對象的列表,而后者是通過 id 參數(shù)來查找對應的任務對象;這里我通過底層源碼使用 __getstate__() 來獲取到任務的相關信息,這些信息我們通過事先設定好的 Job 對象來對其進行序列化,最后將信息從接口中返回。運行

完成以上的所有操作之后,我們就可以打開控制臺,進入到該目錄下并激活我們的虛擬環(huán)境,之后運行:

uvicorn main:app

之后我們就能在 FastAPI 默認的地址 http://127.0.0.1:8000/docs 中看到關于全部接口的 Swagger 文檔頁面了:

5分鐘快速掌握Python定時任務框架的實現(xiàn)

fastapi 集成的 swagger 頁面

之后我們可以直接在文檔里面或使用 Postman 來自己進行接口測試即可。

結尾

本文介紹了有關于 APScheduler 框架的概念及其用法,并進行了簡單的實踐。

得益于 APScheduler 的模塊化設計才可以讓我們更方便地去理解、使用它,并將其運用到我們實際的開發(fā)過程中。

從 APScheduler 目前的 Github 倉庫代碼以及 issue 來看,作者已經(jīng)在開始重構 4.0 版本,當中的一些源代碼和 API 也有較大的變動,相信在 4.0 版本中將會引入更多的新特性。

但如果現(xiàn)階段你正打算使用或已經(jīng)使用 APScheduler 用于實際生產(chǎn)中,那么希望本文能對會你有所幫助。

到此這篇關于5分鐘快速掌握Python定時任務框架的實現(xiàn)的文章就介紹到這了,更多相關Python 定時任務內容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持好吧啦網(wǎng)!

標簽: Python 編程
相關文章:
主站蜘蛛池模板: 根系分析仪,大米外观品质检测仪,考种仪,藻类鉴定计数仪,叶面积仪,菌落计数仪,抑菌圈测量仪,抗生素效价测定仪,植物表型仪,冠层分析仪-杭州万深检测仪器网 | SMN-1/SMN-A ABB抽屉开关柜触头夹紧力检测仪-SMN-B/SMN-C-上海徐吉 | 真空冷冻干燥机_国产冻干机_冷冻干燥机_北京四环冻干 | 压力控制器,差压控制器,温度控制器,防爆压力控制器,防爆温度控制器,防爆差压控制器-常州天利智能控制股份有限公司 | 地源热泵一体机,地源热泵厂家-淄博汇能环保设备有限公司 | 商标转让-购买商标专业|放心的商标交易网-蜀易标商标网 | 免费分销系统 — 分销商城系统_分销小程序开发 -【微商来】 | 电销卡_稳定企业大语音卡-归属地可选-世纪通信 | 美国查特CHART MVE液氮罐_查特杜瓦瓶_制造全球品质液氮罐 | 东莞猎头公司_深圳猎头公司_广州猎头公司-广东万诚猎头提供企业中高端人才招聘服务 | 大功率金属激光焊接机价格_不锈钢汽车配件|光纤自动激光焊接机设备-东莞市正信激光科技有限公司 定制奶茶纸杯_定制豆浆杯_广东纸杯厂_[绿保佳]一家专业生产纸杯碗的厂家 | PVC地板|PVC塑胶地板|PVC地板厂家|地板胶|防静电地板-无锡腾方装饰材料有限公司-咨询热线:4008-798-128 | 韦伯电梯有限公司| 手术示教系统-数字化手术室系统-林之硕医疗云智能视频平台 | 离子色谱自动进样器-青岛艾力析实验科技有限公司 | 胶水,胶粘剂,AB胶,环氧胶,UV胶水,高温胶,快干胶,密封胶,结构胶,电子胶,厌氧胶,高温胶水,电子胶水-东莞聚力-聚厉胶粘 | 冷却塔改造厂家_不锈钢冷却塔_玻璃钢冷却塔改造维修-广东特菱节能空调设备有限公司 | SDI车窗夹力测试仪-KEMKRAFT方向盘测试仪-上海爱泽工业设备有限公司 | 股票入门基础知识_股票知识_股票投资大师_格雷厄姆网 | 铝扣板-铝方通-铝格栅-铝条扣板-铝单板幕墙-佳得利吊顶天花厂家 elisa试剂盒价格-酶联免疫试剂盒-猪elisa试剂盒-上海恒远生物科技有限公司 | 扫地车厂家-山西洗地机-太原电动扫地车「大同朔州吕梁晋中忻州长治晋城洗地机」山西锦力环保科技有限公司 | 课件导航网_ppt课件_课件模板_课件下载_最新课件资源分享发布平台 | 杭州|上海贴标机-百科 | 铝单板_铝窗花_铝单板厂家_氟碳包柱铝单板批发价格-佛山科阳金属 | 优考试_免费在线考试系统_培训考试系统_题库系统_组卷答题系统_匡优考试 | 3d可视化建模_三维展示_产品3d互动数字营销_三维动画制作_3D虚拟商城 【商迪3D】三维展示服务商 广东健伦体育发展有限公司-体育工程配套及销售运动器材的体育用品服务商 | 高温热泵烘干机,高温烘干热泵,热水设备机组_正旭热泵 | 合肥防火门窗/隔断_合肥防火卷帘门厂家_安徽耐火窗_良万消防设备有限公司 | WF2户外三防照明配电箱-BXD8050防爆防腐配电箱-浙江沃川防爆电气有限公司 | 除湿机|工业除湿机|抽湿器|大型地下室车间仓库吊顶防爆除湿机|抽湿烘干房|新风除湿机|调温/降温除湿机|恒温恒湿机|加湿机-杭州川田电器有限公司 | 至顶网| 无线讲解器-导游讲解器-自助讲解器-分区讲解系统 品牌生产厂家[鹰米讲解-合肥市徽马信息科技有限公司] | 照相馆预约系统,微信公众号摄影门店系统,影楼管理软件-盟百网络 | 石油/泥浆/不锈钢防腐/砂泵/抽砂泵/砂砾泵/吸砂泵/压滤机泵 - 专业石油环保专用泵厂家 | 别墅图纸超市|别墅设计图纸|农村房屋设计图|农村自建房|别墅设计图纸及效果图大全 | 筛分机|振动筛分机|气流筛分机|筛分机厂家-新乡市大汉振动机械有限公司 | 鼓风干燥箱_真空烘箱_高温干燥箱_恒温培养箱-上海笃特科学仪器 | 粘度计NDJ-5S,粘度计NDJ-8S,越平水分测定仪-上海右一仪器有限公司 | 清水混凝土修复_混凝土色差修复剂_混凝土色差调整剂_清水混凝土色差修复_河南天工 | 中国品牌门窗网_中国十大门窗品牌_著名门窗品牌 | 【MBA备考网】-2024年工商管理硕士MBA院校/报考条件/培训/考试科目/提前面试/考试/学费-MBA备考网 |