python批量拷贝文件
普通批量拷贝文件
import os
import shutil
import logging
from logging import handlers
from colorama import Fore, Style, init import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR) # 加入环境变量
from utils.time_utils import run_time
from conf import settings class Colorlog(object):
"""
记录日志,添加颜色
"""
init(autoreset=True) # 初始化,并且设置颜色设置自动恢复 # 根据信息不同设置不同的颜色格式
info_color = Fore.GREEN + Style.BRIGHT
warn_color = Fore.YELLOW + Style.BRIGHT
debug_color = Fore.MAGENTA + Style.BRIGHT
error_color = Fore.RED + Style.BRIGHT def __init__(self, name):
# 日志格式
log_format = '[%(asctime)s - %(levelname)s - %(name)s ] %(message)s '
self.logger = logging.getLogger(name)
self.logger.setLevel(settings.LOG_LEVEL) console_handler = logging.StreamHandler()
# 文件绝对路径
logfile_path = os.path.join(settings.LOG_DIR, "log", settings.LOG_FILE)
if not os.path.exists(logfile_path):
# 创建log目录
os.mkdir(os.path.join(settings.LOG_DIR, "log"))
# 每天创建一个日志文件,文件数不超过20个
file_handler = handlers.TimedRotatingFileHandler(
logfile_path, when="D", interval=1, backupCount=20) self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler) file_format = logging.Formatter(fmt=log_format)
console_format = logging.Formatter(
fmt=log_format, datefmt='%Y-%m-%d %H:%M:%S ') console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format) def warn(self, message):
self.logger.warning(Colorlog.warn_color + message) def info(self, message):
self.logger.info(Colorlog.info_color + message) def error(self, message):
self.logger.error(Colorlog.info_color + message) def debug(self, message):
self.logger.debug(Colorlog.info_color + message) cp_log = Colorlog("cp") def copy_file(local_file_path, dst_file_path):
size = bytes2human(os.path.getsize(local_file_path))
# cp_log.debug(
# 'copy file {} to {}, file size {}'.format(
# local_file_path, dst_file_path, size))
shutil.copy(local_file_path, dst_file_path) # copy file @run_time
def upload_file(src_path, dst_path):
"""
上传文件
:param src_path:
:param dst_path:
:return:
"""
cp_log.info('upload_file %s %s' % (src_path, dst_path))
# 目标目录是否存在,不存在则创建
if not os.path.exists(dst_path):
os.makedirs(dst_path)
cp_log.info('Create Dest Dir %s' % dst_path) # 判断是否为目录,存在则把文件拷贝到目标目录下
if os.path.isdir(src_path):
all_file_nums = 0
for root, dirs, files in os.walk(src_path):
# 遍历目录下所有文件根,目录下的每一个文件夹(包含它自己),
# 产生3-元组 (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名称】
for f in files:
local_file_path = os.path.join(root, f) # 本地文件路径 如/src/q.txt
dst_file_path = os.path.abspath(
local_file_path.replace(
src_path, dst_path)) # 目标文件路径 如/dst/q.txt
dst_dir = os.path.dirname(dst_file_path) # 目标文件路径文件夹 如/dst/
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir) # 创建目录
cp_log.debug('Create Dest Dir %s' % dst_path) copy_file(local_file_path, dst_file_path) # 拷贝文件
cp_log.info('copy file {} complete '.format(local_file_path))
all_file_nums += 1 cp_log.info(
'copy all files complete , files count = {}'.format(all_file_nums))
else:
cp_log.warn('Dir is not exists %s' % dst_path) def bytes2human(n):
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
# << 左移” 左移一位表示乘2 即1 << 1=2,二位就表示4 即1 << 2=4,
# 10位就表示1024 即1 << 10=1024 就是2的n次方
prefix[s] = 1 << (i + 1) * 10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%sBytes" % n if __name__ == '__main__':
src = 'D://test1'
dst = 'D://copytest2'
upload_file(src, dst)
输出结果
[2018-06-29 15:14:04 - INFO - cp ] upload_file D://test1 D://copytest2
[2018-06-29 15:14:04 - INFO - cp ] Create Dest Dir D://copytest2
[2018-06-29 15:14:04 - DEBUG - cp ] Create Dest Dir D://copytest2
[2018-06-29 15:14:04 - INFO - cp ] copy file D://test1\20180601\20180601_test.txt complete
[2018-06-29 15:14:04 - DEBUG - cp ] Create Dest Dir D://copytest2
[2018-06-29 15:14:19 - INFO - cp ] copy file D://test1\20180601\wmv\01文件操作和异常.wmv.pbb complete
[2018-06-29 15:14:19 - DEBUG - cp ] Create Dest Dir D://copytest2
[2018-06-29 15:14:19 - INFO - cp ] copy file D://test1\20180602\20180602_test.txt complete
……
[2018-06-29 15:16:20 - INFO - cp ] copy file D://test1\Tesseract-OCR\tessdata\tessconfigs\nobatch complete
[2018-06-29 15:16:20 - INFO - cp ] copy file D://test1\Tesseract-OCR\tessdata\tessconfigs\segdemo complete
[2018-06-29 15:16:20 - INFO - cp ] copy all files complete , files count = 164
[2018-06-29 15:16:20 - DEBUG - runtime - time_utils.py - decor- 59 ] func {upload_file} run { 135.2727}s
使用多线程批量拷贝文件
#!/usr/bin/python
# -*- coding: utf-8 -*-
# @Time : 2018/6/29 10:28
# @Author : hyang
# @File : batch_copy.py
# @Software: PyCharm import os
import shutil
import logging
from logging import handlers
from colorama import Fore, Style, init
from multiprocessing.dummy import Pool as ThreadPool
import queue import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR) # 加入环境变量
from utils.time_utils import run_time
from conf import settings class Colorlog(object):
"""
记录日志,添加颜色
"""
init(autoreset=True) # 初始化,并且设置颜色设置自动恢复 # 根据信息不同设置不同的颜色格式
info_color = Fore.GREEN + Style.BRIGHT
warn_color = Fore.YELLOW + Style.BRIGHT
debug_color = Fore.MAGENTA + Style.BRIGHT
error_color = Fore.RED + Style.BRIGHT def __init__(self, name):
# 日志格式
log_format = '[%(asctime)s - %(levelname)s - %(name)s ] %(message)s '
self.logger = logging.getLogger(name)
self.logger.setLevel(settings.LOG_LEVEL) console_handler = logging.StreamHandler()
# 文件绝对路径
logfile_path = os.path.join(settings.LOG_DIR, "log", settings.LOG_FILE)
if not os.path.exists(logfile_path):
# 创建log目录
os.mkdir(os.path.join(settings.LOG_DIR, "log"))
# 每天创建一个日志文件,文件数不超过20个
file_handler = handlers.TimedRotatingFileHandler(
logfile_path, when="D", interval=1, backupCount=20) self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler) file_format = logging.Formatter(fmt=log_format)
console_format = logging.Formatter(
fmt=log_format, datefmt='%Y-%m-%d %H:%M:%S ') console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format) def warn(self, message):
self.logger.warning(Colorlog.warn_color + message) def info(self, message):
self.logger.info(Colorlog.info_color + message) def error(self, message):
self.logger.error(Colorlog.info_color + message) def debug(self, message):
self.logger.debug(Colorlog.info_color + message) cp_log = Colorlog("cp") def copy_file(local_file_path, dst_file_path, q):
size = bytes2human(os.path.getsize(local_file_path))
# cp_log.debug(
# 'copy file {} to {}, file size {}'.format(
# local_file_path, dst_file_path, size))
shutil.copy(local_file_path, dst_file_path) # copy file
q.put(local_file_path) # 加入队列 @run_time
def upload_file(src_path, dst_path):
"""
上传文件
:param src_path:
:param dst_path:
:return:
"""
pool = ThreadPool(3) # 开启3个线程
q = queue.Queue() # 开启一个队列
cp_log.info('upload_file %s %s' % (src_path, dst_path))
# 目标目录是否存在,不存在则创建
if not os.path.exists(dst_path):
os.makedirs(dst_path)
cp_log.info('Create Dest Dir %s' % dst_path) # 判断是否为目录,存在则把文件拷贝到目标目录下
if os.path.isdir(src_path):
all_file_nums = 0
for root, dirs, files in os.walk(src_path):
# 遍历目录下所有文件根,目录下的每一个文件夹(包含它自己),
# 产生3-元组 (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名称】
for f in files:
all_file_nums += 1
local_file_path = os.path.join(root, f) # 本地文件路径 如/src/q.txt
dst_file_path = os.path.abspath(
local_file_path.replace(
src_path, dst_path)) # 目标文件路径 如/dst/q.txt
dst_dir = os.path.dirname(dst_file_path) # 目标文件路径文件夹 如/dst/
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir) # 创建目录
cp_log.debug('Create Dest Dir %s' % dst_path)
pool.apply_async(
func=copy_file, args=(
local_file_path, dst_file_path, q)) pool.close() # close()执行后不会有新的进程加入到pool
# pool.join() # join函数等待所有子进程结束 print('all_file_nums ', all_file_nums)
num = 0
while True:
if not q.empty():
item = q.get()
cp_log.info('copy file {} complete '.format(item))
num += 1
copy_rate = float(num / all_file_nums) * 100
cp_log.warn("\r 进度为:%.2f%%" % copy_rate)
if int(copy_rate) >= 100:
break
cp_log.info(
'copy all files complete , files count = {}'.format(all_file_nums))
else:
cp_log.warn('Dir is not exists %s' % dst_path) def bytes2human(n):
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
# << 左移” 左移一位表示乘2 即1 << 1=2,二位就表示4 即1 << 2=4,
# 10位就表示1024 即1 << 10=1024 就是2的n次方
prefix[s] = 1 << (i + 1) * 10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%sBytes" % n if __name__ == '__main__':
src = 'D://test1'
dst = 'D://copy_thread_test2'
upload_file(src, dst)
输出结果
[2018-06-29 15:26:13 - INFO - cp ] copy file D://test1\20180601\20180601_test.txt complete
进度为:0.61%
[2018-06-29 15:26:13 - INFO - cp ] copy file D://test1\20180602\20180602_test.txt complete
进度为:1.22%
[2018-06-29 15:26:13 - INFO - cp ] copy file D://test1\20180602\教程目录及说明.txt complete
进度为:1.83%
all_file_nums 164
[2018-06-29 15:26:15 - INFO - cp ] copy file D://test1\20180602\MongoDB权威指南(中文版).pdf complete
进度为:2.44%
[2018-06-29 15:26:15 - INFO - cp ] copy file D://test1\ibooks\AIX_HACMP_40pages.pdf complete
进度为:3.05%
……
[2018-06-29 15:29:02 - INFO - cp ] copy file D://test1\Tesseract-OCR\tessdata\tessconfigs\nobatch complete
进度为:99.39%
[2018-06-29 15:29:02 - INFO - cp ] copy file D://test1\Tesseract-OCR\tessdata\tessconfigs\segdemo complete
进度为:100.00%
[2018-06-29 15:29:02 - INFO - cp ] copy all files complete , files count = 164
[2018-06-29 15:29:02 - DEBUG - runtime - time_utils.py - decor- 59 ] func {upload_file} run { 168.7767}s
使用协程批量拷贝文件
#!/usr/bin/env python3
# -*- coding: utf-8 -*- from gevent import monkey;monkey.patch_all()
import os
import shutil
import logging
import time
from functools import wraps
from logging import handlers
from colorama import Fore, Style, init
from multiprocessing.pool import ThreadPool
import queue
import gevent import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR) # 加入环境变量 class Colorlog(object):
"""
记录日志,添加颜色
"""
init(autoreset=True) # 初始化,并且设置颜色设置自动恢复 # 根据信息不同设置不同的颜色格式
info_color = Fore.GREEN + Style.BRIGHT
warn_color = Fore.YELLOW + Style.BRIGHT
debug_color = Fore.MAGENTA + Style.BRIGHT
error_color = Fore.RED + Style.BRIGHT def __init__(self, name):
# 日志格式
log_format = '[%(asctime)s - %(levelname)s - %(name)s ] %(message)s '
self.logger = logging.getLogger(name)
self.logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler()
# 文件绝对路径
logfile_path = 'test.log' # 每天创建一个日志文件,文件数不超过20个
file_handler = handlers.TimedRotatingFileHandler(
logfile_path, when="D", interval=1, backupCount=20) self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler) file_format = logging.Formatter(fmt=log_format)
console_format = logging.Formatter(
fmt=log_format, datefmt='%Y-%m-%d %H:%M:%S ') console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format) def warn(self, message):
self.logger.warning(Colorlog.warn_color + message) def info(self, message):
self.logger.info(Colorlog.info_color + message) def error(self, message):
self.logger.error(Colorlog.info_color + message) def debug(self, message):
self.logger.debug(Colorlog.info_color + message) cp_log = Colorlog("cp") def run_time(func):
"""
计算程序运行时间的装饰器
:param func:
:return:
""" @wraps(func)
def decor(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("func {%s} run {%10.4f}s " % (func.__name__, (end - start)))
return res return decor def copy_file(local_file_path, dst_file_path):
# size = bytes2human(os.path.getsize(local_file_path))
# cp_log.debug(
# 'copy file {} to {}, file size {}'.format(
# local_file_path, dst_file_path, size))
shutil.copy(local_file_path, dst_file_path) # copy file
cp_log.info(
'copy file {} , size= {} complete '.format(
local_file_path, bytes2human(
os.path.getsize(dst_file_path)))) def getdirsize(dir):
"""
获得文件夹中所有文件大小
:param dir:
:return:
"""
size = 0
for root, dirs, files in os.walk(dir):
size += sum([os.path.getsize(os.path.join(root, name))
for name in files])
return bytes2human(size) @run_time
def upload_file(src_path, dst_path):
"""
上传文件
:param src_path:
:param dst_path:
:return:
""" cp_log.info('upload_file %s %s' % (src_path, dst_path))
# 目标目录是否存在,不存在则创建
if not os.path.exists(dst_path):
os.makedirs(dst_path)
cp_log.info('Create Dest Dir %s' % dst_path) tasklist = [] # 任务列表
# 判断是否为目录,存在则把文件拷贝到目标目录下
if os.path.isdir(src_path):
all_file_nums = 0
all_file_size = getdirsize(src_path)
cp_log.info('all_file_size = %s' % all_file_size)
for root, dirs, files in os.walk(src_path):
# 遍历目录下所有文件根,目录下的每一个文件夹(包含它自己),
# 产生3-元组 (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名称】
for f in files:
all_file_nums += 1
local_file_path = os.path.join(root, f) # 本地文件路径 如/src/q.txt
dst_file_path = os.path.abspath(
local_file_path.replace(
src_path, dst_path)) # 目标文件路径 如/dst/q.txt
dst_dir = os.path.dirname(dst_file_path) # 目标文件路径文件夹 如/dst/
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir) # 创建目录
cp_log.debug('Create Dest Dir %s' % dst_dir) tasklist.append(
gevent.spawn(
copy_file,
local_file_path,
dst_file_path)) # 开启协程 gevent.joinall(tasklist) # 阻塞等待所有操作都执行完毕 print('all_file_nums ', all_file_nums) cp_log.info(
'copy all files complete , files count = {} , size = {}'.format(all_file_nums, getdirsize(dst_path)))
else:
cp_log.warn('Dir is not exists %s' % dst_path) def bytes2human(n):
symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols):
# << 左移” 左移一位表示乘2 即1 << 1=2,二位就表示4 即1 << 2=4,
# 10位就表示1024 即1 << 10=1024 就是2的n次方
prefix[s] = 1 << (i + 1) * 10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return '%.1f%s' % (value, s)
return "%sB" % n if __name__ == '__main__':
src = 'C://pythonStudy/python爬虫参考资料'
dst = 'C://pythonStudy/copy_thread_test2'
upload_file(src, dst)
输出结果
"C:\Program Files\Python36\python.exe" batch_copy.py
[2018-06-29 22:50:22 - INFO - cp ] upload_file C://pythonStudy/python爬虫参考资料 C://pythonStudy/copy_thread_test2
[2018-06-29 22:50:22 - INFO - cp ] Create Dest Dir C://pythonStudy/copy_thread_test2
[2018-06-29 22:50:22 - INFO - cp ] all_file_size = 620.6M
[2018-06-29 22:50:22 - DEBUG - cp ] Create Dest Dir C:\pythonStudy\copy_thread_test2\python-scraping-master
[2018-06-29 22:50:22 - DEBUG - cp ] Create Dest Dir C:\pythonStudy\copy_thread_test2\python-scraping-master\chapter1
[2018-06-29 22:50:22 - DEBUG - cp ] Create Dest Dir C:\pythonStudy\copy_thread_test2\python-scraping-master\chapter10
[2018-06-29 22:50:22 - DEBUG - cp ] Create Dest Dir ……
[2018-06-29 22:50:23 - INFO - cp ] copy file C://pythonStudy/python爬虫参考资料\python-scraping-master\chapter12\2-seleniumCookies.py , size= 528B complete
[2018-06-29 22:50:23 - INFO - cp ] copy file C://pythonStudy/python爬虫参考资料\python-scraping-master\chapter12\3-honeypotDetection.py , size= 539B complete
[2018-06-29 22:50:23 - INFO - cp ] copy file
[2018-06-29 22:50:24 - INFO - cp ] copy file C://pythonStudy/python爬虫参考资料\python-scraping-master\chapter9\5-BasicAuth.py , size= 229B complete
all_file_nums 130
[2018-06-29 22:50:24 - INFO - cp ] copy file C://pythonStudy/python爬虫参考资料\python-scraping-master\files\test.csv , size= 114B complete
func {upload_file} run { 1.2971}s
[2018-06-29 22:50:24 - INFO - cp ] copy all files complete , files count = 130 , size = 620.6M Process finished with exit code 0
工具文件
time_utils.py
def run_time(func):
"""
计算程序运行时间的装饰器
:param func:
:return:
"""
@wraps(func)
def decor(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
end = time.time()
log.debug("func {%s} run {%10.4f}s " % (func.__name__,(end - start)))
return res return decor
python批量拷贝文件的更多相关文章
- python批量进行文件修改操作
python批量修改文件扩展名 在网上下载了一些文件,因为某种原因,扩展名多了一个后缀'.xxx',手动修改的话因为文件太多,改起来费时费力,于是决定写个小脚本进行修改. 1.要点: import r ...
- python 批量创建文件及文件夹(文件夹里再创文件)
python 批量创建文件及文件夹(文件夹里再创文件)思路:文件建到哪>文件名字叫啥>创建文件夹>去新建的文件下>新建文件>给文件里边写东西>写个反馈给控制台> ...
- python之拷贝文件
做了个小实验, 用于拷贝文件夹下面的jpg. 用于拓展, 可以引入类和方法, 拷贝你指定的任意类型的文件. import os src = 'C:\\Users\\Administrator\\Des ...
- python批量json文件转xml文件脚本(附代码)
场景:在使用了mask rcnn跑实验后标注了大量地json格式文件,现在打算使用yolo和faster rcnn 跑实验 所以需要将之前地json文件转为xml 但是找了很久,没发现有批量处 ...
- python批量处理文件夹中文件的问题
用os模块读取文件夹中文件 原来的代码: import osfrom scipy.misc import imread filenames=os.listdir(r'./unprocess')for ...
- python批量修改文件名称
参考文章:http://www.cnblogs.com/ma6174/archive/2012/05/04/2482378.html 最近遇到一个问题,在网上下载了一批视频课程,需要将每节课的名称标号 ...
- python批量删除文件
敲代码測试时总会碰到要删除日志目录下的日志或者删除一些历史文件.每次都会生成,再測试的时候为了查找错误原因方便总是要在測试前删除这些文件.手动删除比較麻烦.所以写一个批量删除脚本 import os ...
- python批量删除文件夹
制作的python程序跑一次就占200多内存在temp下面,关键是还不释放,最开始都没有发现这个问题,知道自己的c盘越来越小才发现问题所在.所以就有了去删除temp下生成的文件 代码如下: impor ...
- python 批量下载文件
file.txt 的内容为: http://183.xxx.xxx.54:188/my/qqq.ico::qq.exe::0::http://183.xxx.xxx.54:186/my/ddnf.ic ...
随机推荐
- Java设计模式——结构型模式
Java设计模式中共有7种结构型模式:适配器模式.装饰模式.代理模式.外观模式.桥接模式.组合模式.享元模式.其中对象的适配器模式是各种模式的起源,其关系如下面的图:1.适配器模式 适配器模式将某个类 ...
- spring+springMVC+mybatis+maven+mysql环境搭建(一)
环境搭建是最基础的,但是发现平时很多时候大家都是ctrl c+ctrl v,这样对于很多细节完全不清楚,来,一起深入了解下 一.准备工作 首先得准备好maven.mysql啥的,这些略... 并且my ...
- 百度图片http://img[0-9]\.imgtn.*?g此形式的链接图片下载方式
"""给出图片链接列表, 下载图片""" print(pic_urls) for pic_url in pic_urls: try: hos ...
- jq 某个时间段的倒计时
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- HDU 4310 Hero (贪心)
题意:给定你有 n 个敌人,你的伤害是 1,给出每个敌人的伤害,和敌人的血量,每一回合你可以攻击一个敌人,并且所有敌人都会攻击你,除非它已经死了,问你最少要多少要消耗多少血量. 析:一个很明显的贪心问 ...
- HTTP请求模型和头信息参考
发送HTTP请求:一个请求由四个部分组成:请求行.请求头标.空行和请求数据 请求行 请求行由三个标记组成:请求方法.请求URI和HTTP版本,它们用空格分隔.例如:GET /index.html HT ...
- 整理mianshi
对象锁和类锁wait sleepAMSactivity启动流程handler消息机制JNI相关 1.looper.prepare()做了啥操作https://www.cnblogs.com/ganch ...
- day36(动态代理)
动态代理 动态代理:是实现增强类中的一种方式,jdk中的动态代理:Proxy对象,使用最广泛的是在AOP切面编程中. 实现一个简单的动态代理来了解其中的运行机制. 创建一个接口:Person类型的接口 ...
- (完全背包) Piggy-Bank (hdu 1114)
题目大意: 告诉你钱罐的初始重量和装满的重量, 你可以得到这个钱罐可以存放钱币的重量,下面有 n 种钱币, n 组, 每组告诉你这种金币的价值和它的重量,问你是否可以将这个钱 ...
- web-day11
第11章WEB11-Cookie&Session篇 今日任务 显示用户的上次访问时间 登录系统以后显示商品浏览记录 购买商品将商品添加到购物车 系统登录的验证码的校验 教学导航 教学目标 了解 ...