第一部分 功能协调 第一章 整体牙科学的理念 第二部分 功能紊乱 第三部分 治疗 问题本文总结自2015年，[美]Peter E. Dawson的书籍-《功能合学 从颞下颌关节到微笑设计》，请支持正版图书。本文首发于个人博客https://lisper517.top/index.php/archives/4326/，转载请注明出处。第一部分 功能协调第一章 整体牙科学的理念第二部分 功能紊乱第三部分 治疗问题1.全文搜索为什么
第一章 病例选择 第二章 髓腔解剖 第三章 橡皮障隔离术 第四章 开髓 第五章 根管预备 第六章 根管冲洗和根管封药 第七章 根管充填 第八章 根管再治疗 第九章 显微根尖手术 第十章 根管治疗后的牙体修复 第十一章 C形根管系统的诊疗 第十二章 牙内陷的诊疗 第十三章 牙髓再生术 第十四章 镍钛根管器械分离的预防及处理 第十五章 锥形束CT的应用 问题本文总结自2022年，彭彬的书籍-《根管治疗图谱（第2版）》，请支持正版图书。本文首发于个人博客https://lisper517.top/index.php/archives/4322/，转载请注明出处。第一章 病例选择第二章 髓腔解剖第三章 橡皮障隔离术第四章 开髓第五章 根管预备第六章 根管冲洗和根管封药第七章 根管充填第八章 根管再治疗第九章 显微根尖手术第十章 根管治疗后的牙体修复第十一章 C形根管系统的诊疗第十二章 牙内陷的诊疗第十三章 牙髓再生术第十四章 镍钛根管器械分离的预防及处理第十五章 锥形束CT的应用问题1.全文搜索为什么
《全局诊断-牙科诊断和治疗计划的新视角》-J. William Robbins，Jeffrey S. Rouse[美]
第一章 全局诊断：跨学科治疗计划制订的艺术与科学 第二章 全局分析诊断表 第三章 5个CORE问题 第四章 美学牙冠延长术 第五章 组织移植 第六章 成人牙列的牙槽骨压低治疗 第七章 牵引助萌术 第八章 正颌手术 第九章 牙颌面整形 第十章 牙槽骨发育过度：全局诊断中最难处理的部分 第十一章 序列治疗 第十二章 CORE模板 第十三章 病例研究 问题本文总结自2017年，[美]J. William Robbins，Jeffrey S. Rouse的书籍-《全局诊断-牙科诊断和治疗计划的新视角》，请支持正版图书。本文首发于个人博客https://lisper517.top/index.php/archives/4320/，转载请注明出处。第一章 全局诊断：跨学科治疗计划制订的艺术与科学第二章 全局分析诊断表第三章 5个CORE问题第四章 美学牙冠延长术第五章 组织移植第六章 成人牙列的牙槽骨压低治疗第七章 牵引助萌术第八章 正颌手术第九章 牙颌面整形第十章 牙槽骨发育过度：全局诊断中最难处理的部分第十一章 序列治疗第十二章 CORE模板第十三章 病例研究问题1.全文搜索为什么
第一章 定投的理论基础 第二章 定投实践 第三章 辅助工具本文的目的是描述定投的投资策略。本文首发于个人博客https://lisper517.top/archives/4264/，转载请注明出处。本文内容来自各种网络资料，个人总结。任何投资都有风险，本文不构成任何投资建议。理财产品收益率超过6%就要打问号，超过8%很危险，超过10%就要做好损失全部本金的准备（郭树清，陆家嘴金融论坛，2018-06-14）。一定用闲钱投资，不要拿马上急用的钱、贷款借来的钱投资。第一章 定投的理论基础一、定投的概念定投即“定期定额投资”，指在固定的时间（如每月1日，每周一，或者每天），以固定的金额（如每月1000元），投资到指定的同一投资标的（如某支指数基金）中。定投像一种“理财储蓄计划”，让你忽略市场短期波动，通过长期、持续的投入来平滑成本、积累资产。一、定投的特点1.淡化择时定投假设市场总是在波动，不去预测市场的高点或低点，而只需严格按照定投计划操作，避免了“追涨杀跌”。2.平均投资成本在价格高时，定投能买到较少的份额，但已持有的部分在上涨；在价格低时，虽然持有的部分下跌，但定投能买到更多的份额。综合下来看，定投降低了平均购入成本。另外，对于资金不充裕的人来说，无法一次性拿出大量本金，定投也使资金投入更平滑。3.复利效应利滚利，非常恐怖。4.强制储蓄对于年轻人来说，定投也可以避免乱花钱。当然，也不能排除定投亏钱的可能。第二章 定投实践一、选择投资标的定投最适合的是长期上涨、但短期有波动的资产。1.首选宽基指数基金，如纳斯达克100、标普500这些指数的基金（难度稍微大一点的有沪深300、中证500、上证50、科创50等）。2.次选行业、主题指数基金，如消费、医药、科技、新能源、AI等。3.再次选其它主动管理型基金，根据基金经理风格不同而有变化，可能重仓某支个股、板块，也可能一些经理喜欢分散投资。4.其它国债，收益稳定但较低；个股期货，风险太高。笔者的建议是尽量分散投资，既有稳健的国债，也可以适当加一些激进的投资。目前（2025-12-20）关注的有：（1）宽基指数，纳斯达克100、标普500、沪深300、上证50等。前两者关注更多，算风险中等。 （2）贵金属，黄金白银等。黄金近几年涨疯了，目前可能处于高位，应该算风险较大。 （3）现金流ETF，一些现金流充沛的公司，算风险较大。 （4）红利ETF，稳定高分红的公司，也称低波红利（低波动性），算风险较低。但是投资此类ETF需要做好长期打算，五年十年。 （5）短融ETF，短期债券（消费债等等），和国债的风险差不多。 （6）国债，风险最低。风险可以自己评估，最简单的道理：收益越高、风险越大。不要把鸡蛋放在一个篮子里，比如所有资金只拿1/3投资，其它2/3留作它用；在这1/3里，又作如下划分：（1）宽基指数投50%。（2）红利低波，20%（3）现金流，10%。（4）贵金属，10%。（5）短融，5%。（6）国债，5%。上面只是随便举例，不构成投资建议。总之，通过合适搭配，最终目标是跑赢通胀，使所有加和、总的年化收益达到5%，或者8%，甚至10%。二、选择投资平台在支付宝、银行等平台交易称为场外交易（还有京东、且慢、天天基金等），各种券商平台交易则称为场内交易。一般普通人投资在支付宝即可，购入手续费打一折（0.1%左右）。场外交易的优点是方便快捷，可设置自动扣款，省心。缺点有：①整体费率更贵。包括申购费（1.2%左右。支付宝打一折就是0.1%），管理费、托管费（加起来0.2-1%左右不等，看具体基金）等。②交易不自由，比如可能有单日买入限额，T+N（发起买入操作后需要再过N天才能实际入仓，一般是1-2天）等。但是可以设置每日定投规避限额，并且定投本来也是需要长期持有的，笔者感觉影响不大。如果选择场外交易，买的一般都是带“联接”的，长期持有的话选后缀为A的，短期操作选C类。A类的管理费、托管费更低，但买入费更高，而且如果持有时间短就想卖的话还要收费（一般需要持有1周到2年，卖出时才不会收手续费，这也是为了鼓励长期持有）。场内交易的特点有：①整体费率更便宜，但前提是能申请到万分之0.5手续费、5块以下免收的券商账户，否则算下来可能不如支付宝的性价比。②有溢价，一般溢价在1-8%。溢价8%就不适合买入。当然，卖出的时候选择合适时机，也能吃到这部分溢价。A股港股建议在1%以内溢价买，境外ETF建议5%以下再考虑（境外ETF总是有溢价，因为外汇管制等）。有时也可能会折价，可以净值更低的价格买入。③实时交易，不限额、不限T+N。④各家券商平台只能买到自家的金融产品，可能需要下多个APP。三、设置自动扣款每日，每周，或者每月自动购买即可。设置完后就不要再频繁操作，反而容易追涨杀跌、亏钱。定投最考验的是定力，选择了对的标的，就要坚定执行定投计划，以一年、三年甚至五年十年为期望，短期下跌10%，甚至30%、50%都是可能的。不要被某一处损失动摇，比如因为贵金属跌了、其它涨了，就错误地卖出所有贵金属。牢记大目标是总年化达到8%，一处亏了再从其它地方赚就是了。以上介绍的方法属于无脑定投，一定选好标的（长期上涨，短期波动），不要选择长期也波动很大的。四、调仓在定投早期可以不用管（3个月或者半年，具体看每期投资金额。这个初期过程叫建仓）。但定投也不是只买不卖，每季度看一下（或者半年、一年），是否需要调仓（调整仓位）。比如说，定投一开始是这样的：（1）宽基指数占50%。 （2）红利低波，20% （3）现金流，10%。 （4）贵金属，10%。 （5）短融，5%。 （6）国债，5%。经过一段时间定投，有些涨、有些跌，占比可能变成这样：（1）宽基指数占60%。 （2）红利低波，18% （3）现金流，8%。 （4）贵金属，8%。 （5）短融，3%。 （6）国债，3%。此时需要再次平衡，将占比过高的卖出，买入占比较低的，使占比回到一开始。这种再平衡是一种较弱的低买高卖策略。五、定投的止盈策略（零）定投止盈的原则1.早期不止盈，中后期考虑止盈早期仓位不大，止盈意义也不大；中后期时定投慢慢失去了平均投资成本的作用（钝化效应），此时可以考虑止盈。2.尝试分批止盈分2-3次卖出，锁定部分利润，同时也能参与后续上涨。3.止盈后再投资卖出获利后参与新一轮定投。（怎么感觉像dubo，不怕你赢钱，就怕你不玩）4.止盈不止损如果选择了对的标的，即使下跌也要坚持买入（不止损）；但需要有合适的止盈（因为钝化效应）。（一）目标收益率法达到目标收益率后即全部或分批卖出。比如设定10%，15%或者20%。简单易执行，但是可能错过后续上涨。（二）参考市盈率法参考市盈率（PE）历史百分位，达到70%或者80%时卖出（历史PE需要在一些平台查看，比如券商平台等）。这种方法不适用于非指数型主动基金（标的不是指数，而是基金经理个人根据经验选择）。比如一支个股，市盈率是其市值/公司每年利润，可以简单理解为需要多少年回本。打个比方，买下一栋房子（购买资金即市值），租出去（每年租金相当于公司每年利润），需要多少年回本。一般来说PE达到历史平均80%时下跌的风险较大。（三）最大回撤法设置一个目标收益率（比如25%），再设置一个回撤阈值（比如10%），当盈利达到25%后又回撤（下跌）时止盈。需要持续关注市场。第三章 辅助工具笔者参考AI做出来的 excel表格 。手工填入每次的定投操作、购入单价即可。如果需要查看市盈率等，很多地方都可以，这里推荐wind，手机APP端免费（PC端收费）。
《深度学习入门 基于Python的理论与实现》-斋藤康毅（笔记）
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4260/，转载请注明出处。本文的原文写作日期为2020年11月17日，主要目的是总结《深度学习入门 基于Python的理论与实现》。笔者的毕业论文有很多东西来自这本书。下面的内容有些缩进或者图片缺失，可以参考 原word文档 。本书主要用numpy来处理运算，用matplotlib将结果可视化。对于没有基础的人十分友好，只需要高中的线代知识就可以看懂。 1.x=x.flatten() 将矩阵及张量转换为一维数组。x>15，结果得到一个由True和False组成的一维列表，x[x>15]即可输出所有大于15的成员。 矩阵与一维数组的*乘，（数组长度与矩阵列数相同）将一维数组扩张为矩阵形状后简单相乘，符合交换率，符号为*； 矩阵与数组dot乘，（数组长度与矩阵列数相同）将一维数组与每行对应索引相乘求和，得到一维数组，长度为矩阵的行数：如[[1, 2], [3, 4], [7, 8]] dot [5,6]，则得[1*5+2*6, 3*5+4*6,7*5+8*6]，不符合交换律，用np.dot计算。 一次绘图流程：p42 2.感知机：接受一个或多个输入，输出一个信号。有多个输入时，可以给各个输入配置权重。机器学习就是人给机器一些数据，机器根据这些数据选出权重的过程。 P56从图像上，感知机是一条直线，有局限性。叠加多个感知机可以构造类似曲线。 3.对于单层感知机，实际上有两个函数，比如首先有y=b+x1*w1+x2*w2，其中b为偏置，w1、w2为权重；然后通过激活函数，比如h(x)=1或0（当y<=0时为0，否则为1。这种函数的输出是跳跃的，可称为阶跃函数）。若把激活函数换成其他形式，就接近神经网络的概念了。常用的一个激活函数（sigmoid函数）：1/(1+exp(-x))，其中exp(-x)为e的-x次方。 接受numpy数组为参数的阶跃函数见p69。 激活函数也可以使用ReLU函数，其在x<=0时输出0，在x>0时则为线性关系。P74 对于输出层的激活函数，如果是分类问题（类似阶跃函数）用softmax函数，回归问题（连续输出数值）用恒等函数。P89 softmax函数的输出是连续的，总和为1，可以看作概率。为了节省算力，对未知模型进行预测时通常也可以省略输出层的softmax激活函数（接受数据进行学习时可以不省略）。 在分类问题中，输出层的神经元数量通常等于类别的数量。哪个神经元的输出最大，就会归类为该类别。 4.正规化：将数据进行处理，使其限制在某个范围内。正规化是一种预处理。 批处理：相同的神经网络，多个输出，多个结果。批处理可以大大加快运算速度。P101 5.损失函数：描述当前神经网络与测试数据（即监督数据）在多大程度上不拟合。一般可用均方误差函数和交叉熵。均方考虑了非正确解、越接近0（越小）越好，交叉熵只包含正确解、越接近0（越小）越好。 机器学习就是要使得损失函数值最小。如果训练数据太多，可以从中随机选择部分数据来学习，称为mini batch。 不用精确度作为评价标准，因为要设定参数的导数（变化梯度），如果用精确度为指标，容易导致导数为0，参数无法更新（精确度常常是跳跃的，微调无法产生影响，而损失函数是连续变化的）。这也是sigmoid函数优于阶跃函数的地方（激活函数）。 对于矩阵，y[0,2]的写法等同于y[0][2] 梯度指示的方向是损失函数减少量最多的地方，不一定指向损失函数值最小的地方。所以，机器学习可能会陷入局部最小值、鞍点或者学习平原。 鞍点处的梯度也为0，比如一个马鞍形曲面，(0,0,0)处的形状就很像马鞍。 梯度，就是一个一维数组。一个函数有多个输入，那么梯度就是这个多维函数（在某一点处）的导数。梯度数组的大小和输入多少相等，意义是对每一个输入，在保持其他输入不变时求该输入的导数，最终组成一个导数数组。为了使损失函数减小，需要沿负梯度方向下降。 6.反向传播法算梯度的速度比微积分法快许多，主要应用了复合函数的求导（相乘）。 7.有时SGD（随机梯度下降），即沿着梯度最大的地方下山的方法，不一定能最快下山。还有其他一些方法。 Momentum（动量法），如果某一方向上的梯度较小，但是持续很久，就适合用这种方法。模拟静止小球在山中的运动，某一方向累计的梯度会给小球积累该方向的速度，于是在该方向上移动越来越快。 AdaGrad（Adaptive适应梯度法），对梯度中的每一个导数都有一个学习率，该学习率还会不断衰减。AdaGrad是通过记录过去所有梯度的平方和实现的，最终学习率会衰减到0。不想让学习率衰减太快可以用RMSProp法，其会更多地考虑最近的梯度、逐渐减小久远梯度的权重。 Adam（直观上讲，类似于融合了AdaGrad与Momentum法，2015年提出），甚至可对一些超参数调校。 一些技巧： （1）权重的初始值：对于sigmoid或tanh等激活函数，使用Xavier方法，上一层的节点个数为n，本层的初始值大概是标准差1/sqrt(n)的高斯分布； 对relu激活函数，可以使用He初始值。 在手头数据集少时，可以“学习迁移”，即训练完一个数据集，将权重的最终值作为下一次学习的初始值。 （2）调整激活值的分布：使用batch normalization，优点是增大学习率（学的快）、对初始值不会过于敏感、抑制过拟合。 （3）抑制过拟合：过拟合主要是由于训练数据少、网络参数太多（层数多、节点多等）。过拟合的表现常常是训练数据准确度高，测试数据准确度不佳。对简单网络，常用权值衰减来抑制过拟合。对复杂网络，可用Dropout方法，随机删除一些节点。另外，可以用已有的数据生成新的数据，比如对手写数字图片，可对图片进行平移、旋转等操作。 （4）高效寻找超参数：对于网络层数与节点数、batch大小、学习率、权值衰减等超参数，需要高效寻找之。可在训练数据和测试数据之外再分一个验证数据，用来对探讨超参数取什么值，然后让超参数不断*10，在对数尺度上变化，并观测其对学习的影响。一般来说，测试数据应该只在最后用一次。 （5）GPU运算：GPU擅长大量的并行运算，比如矩阵的乘法；而CPU擅长连续的复杂运算。使用GPU，可以将训练和测试时间大大缩短。据书中称，NVIDIA的显卡比AMD的更合适。 （6）分布式学习：和用GPU一样，可缩短学习时间。Google的TensorFlow和微软的CNTK都很重视分布式学习。 8.CNN（Convolutional Neural Network），书中之前实现的网络，隐藏层的结构是Affine+Relu（或者其他激活函数），最后一层是Affine+Softmax，而CNN的隐藏层是Conv（卷积层）+Relu+Pooling（池化层），最后一层不变。普通Affine的问题是没有考虑图片的形状，比如矩阵中相距较远的两个点关联度较小，近大。从原理上，卷积层使用了类似滤波器的方法，用了矩阵的*乘，并且偏置只有一个。*乘，即相同大小矩阵的对应位置乘积之和。 池化层则是将层中的数据减少形成特征化的数据，比如只取最大值、只取平均值。 CNN是图像识别中常用的操作。通过观察，CNN的不同Conv层，大概在进行从基础到抽象的观察。 在各种CNN中，书中介绍了CNN元组LeNet及2012年提出的AlexNet。据称，AlexNet是深度学习热潮的导火索。 Google旗下的Deep Mind公司，开发出了AlphaGo和DQN。其中DQN可以接受游戏的图像输入（比如每秒4帧），学习如何玩游戏，甚至可以做出超越人类的手柄操作。
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4259/，转载请注明出处。本文的原文写作日期为2020年10月22日，主要目的是用Python重构按键精灵。主体程序，主要是一些功能的实现，具体应用需要自己写。写的比较乱：import win32gui, win32api, win32con, win32ui import time from time import sleep import random from random import uniform import logging from logging import info, warning, error from config import VK_CODE import copy import cv2 as cv import traceback # mode为test意为调试模式，real则为实际应用 # In 'test' mode, it will print some hidden info for Debug mode = 'test' # 模式图的存储路径 # The save path of pattern pics pattern_path = 'yourpath\\' # 设置logging的书写格式 # Setting logging configs logging.basicConfig( level=logging.DEBUG, format= '%(lineno)d : %(asctime)s : %(levelname)s : %(funcName)s : %(message)s', filename='yourpath\\log{}.txt'.format( time.strftime('-%Y-%m-%d')), filemode='w') # 用当前时间初始化随机数 # Set random seed random.seed(time.time()) def Get_PosAndHwnd(hwnd=0, ClassName=None, TitleName=None): '''接受句柄、类名、标题名，返回第一个匹配的窗口位置和句柄。 Accepts Handle, ClassName or TitleName of a window, returns the rect-pos of the first window that matches. Args: hwnd: 目标句柄 target handle. ClassName: 目标窗口类名 target ClassName. TitleName: 目标窗口标题 target TitleName. Returns: x1, y1, x2, y2, hwnd 前四个参数描述窗口在显示器中的位置，hwnd为句柄。 x1-y2 describes the pos of the window in screen, last arg is the handle. ''' try: if hwnd == 0: hwnd = win32gui.FindWindow(ClassName, TitleName) x1, y1, x2, y2 = win32gui.GetWindowRect(hwnd) info('找到窗口，句柄：{}，标题名：{}，类名：{}，坐标：{}'.format( hwnd, win32gui.GetWindowText(hwnd), win32gui.GetClassName(hwnd), (x1, y1, x2, y2))) return x1, y1, x2, y2, hwnd except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('Get_PosAndHwnd 出现错误') def Activate_Hwnd(hwnd): '''激活指定句柄的窗口。每次转换时使用。 Activates the window with the matching hwnd. Args: hwnd: 目标句柄 target handle. Returns: No return. ''' try: win32gui.SendMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0) # WA_CLICKACTIVE为通过鼠标激活，WA_ACTIVE为鼠标以外的东西（如键盘）激活，WA_INACTIVE为取消激活 # WA_CLICKACTIVE means activate by mouse, WA_ACTIVE means other equipments( like keyboard).WA_INACTIVE means inactivate it. info('激活句柄为 {} 标题为 {} 的窗口'.format(hwnd, win32gui.GetWindowText(hwnd))) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('激活窗口 出现错误') def LeftClick(hwnd, x1, y1, times='first', platform='PC'): '''左键单击（自带延时） Left Click the window (located by hwnd) once (with human-like delay). Args: hwnd: 目标窗口的句柄 target hwnd. x1, y1: 需要点击的坐标 target pos. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). Returns: No return. ''' try: if platform == 'PC': # 对于桌面版 # for PC tmp_pos = win32api.MAKELONG(x1, y1) #elif flag == 'AM': # 对于模拟器 # for Android manipulator #click_pos = win32gui.ScreenToClient(hwnd, (x1, y1)) # 注意坐标是整个窗口还是客户区的，用ScreenToClient转换 # switch between screen/client pos #tmp_pos = win32api.MAKELONG(click_pos[0], click_pos[1]) if times == 'first': Activate_Hwnd(hwnd) win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, tmp_pos) # 鼠标点击格式为SendMessage(hWnd,WM_LBUTTONDOWN,fwKeys,MAKELONG(xPos,yPos)); # fwKeys可以取：MK_CONTROL、MK_LBUTTON、MK_MBUTTON、MK_RBUTTON、MK_SHIFT等，多个值用|隔开 sleep(uniform(0.05, 0.08)) win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0000, tmp_pos) # 抬起时有些电脑上fwKeys显示为0000，有些可能为MK_LBUTTON # Use spy++ for details sleep(uniform(0.07, 0.095)) info('对句柄 {} 标题 {} 的坐标 {} 进行 左键单击 动作'.format( hwnd, win32gui.GetWindowText(hwnd), (x1, y1))) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('左键单击 出现错误') def LeftClick_sequence(hwnd, points, times='second', platform='PC'): '''左键依序单击（依次点击多个点） Click a sequence of points in order. Args: hwnd: 目标窗口的句柄 target hwnd. points: 需要点击的坐标 target points in the form like ((x1,y1),(x2,y2)...). times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). Returns: No return. ''' try: if times == 'first': Activate_Hwnd(hwnd) for point in points: LeftClick(hwnd, point[0], point[1], 'second', platform) info('对句柄 {} 标题 {} 的坐标 {} 进行 左键依序单击 动作'.format( hwnd, win32gui.GetWindowText(hwnd), points)) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('左键单击 出现错误') def LeftDoubleClick(hwnd, x1, y1, times='first', platform='PC'): '''左键同一位置双击 Click the same pos twice. Args: hwnd: 目标窗口的句柄 target hwnd. x1, y1: 需要点击的坐标 target pos. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). Returns: No return. ''' try: if times == 'first': Activate_Hwnd(hwnd) info('左键同一位置双击--第一次点击') LeftClick(hwnd, x1, y1, 'second', platform) info('左键同一位置双击--第二次点击') LeftClick(hwnd, x1, y1, 'second', platform) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('左键同一位置双击 出现错误') def LeftDifDoubleClick_Rect(hwnd, x1, y1, times='first', platform='PC', delta=(0, 4)): '''左键不同位置双击，范围为双正方形 Left click 2 pos, in a double-square-like area. _________ | ___ | | | | | | |___| | |_______| (x1, y1) is at the center, (x2, y2) between the squares (borders included). Args: hwnd: 目标窗口的句柄 target hwnd. x1, y1: 第一次点击的坐标 first pos to click. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). delta: 第二个点的横坐标在x1+delta[0] 到 x1+delta[1]间（包括端点），纵坐标同样。 x2 varies between x1+delta[0] to x1+delta[1], y2 the same (both ends included). Returns: No return. ''' try: if times == 'first': Activate_Hwnd(hwnd) info('左键正方形双击--第一次点击') LeftClick(hwnd, x1, y1, 'second', platform) point_list = [] for delta_x in range(delta[0], delta[1] + 1): for delta_y in range(delta[0], delta[1] + 1): point_list.append((x1 + delta_x, y1 + delta_y)) point_list.append((x1 + delta_x, y1 - delta_y)) point_list.append((x1 - delta_x, y1 + delta_y)) point_list.append((x1 - delta_x, y1 - delta_y)) point_list = list(set(point_list)) point_list.remove((x1, y1)) x1, y1 = random.sample(point_list, 1)[0] info('左键正方形双击--第二次点击') LeftClick(hwnd, x1, y1, 'second', platform) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('左键正方形双击 出现错误') def LeftDifDoubleClick_Cir(hwnd, x1, y1, times='first', platform='PC', r=4): '''左键不同位置双击，范围为圆形。 Left Click 2 pos in a circle. (x1, y1)为圆心，r为半径，(x2, y2)在圆内及圆上。 (x1, y1) is the center, r the radius. Generates (x2, y2) in the circle (or on the border). Args: hwnd: 目标窗口的句柄 target hwnd. x1, y1: 第一次点击的坐标 first pos to click. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). r: 圆的半径。 radius. Returns: 返回两次点击的坐标(x1,y1,x2,y2)。 Returns 2 clicked pos in (x1,y1,x2,y2). ''' try: if times == 'first': Activate_Hwnd(hwnd) info('左键圆形双击--第一次点击') LeftClick(hwnd, x1, y1, 'second', platform) m, n = x1, y1 point_list = [] final_point_list = [] for delta_x in range(r + 1): for delta_y in range(r + 1): point_list.append((delta_x, delta_y)) point_list.remove((0, 0)) for i in range(len(point_list)): x = point_list[i][0] y = point_list[i][1] if x * x + y * y <= r * r: final_point_list.append((x1 + x, y1 + y)) final_point_list.append((x1 + x, y1 - y)) final_point_list.append((x1 - x, y1 + y)) final_point_list.append((x1 - x, y1 - y)) final_point_list = list(set(final_point_list)) x1, y1 = random.sample(final_point_list, 1)[0] info('左键圆形双击--第二次点击') LeftClick(hwnd, x1, y1, 'second', platform) return (m, n, x1, y1) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('左键圆形双击 出现错误') def Random_Areas_LeftClicks(hwnd, click_areas, times='first', platform='PC', r=4, click_strategy=1): '''从几种点击策略中选一种执行，在一定范围对目标窗口进行点击。 Select a click strategy/combination, click the window in some given areas. --------- 单个点击区域如右。A single click_area looks like left. | ---- | 如果只点击一次或者点击两次中的第一个坐标，该坐标位于小矩形中（包括边界）。 | | | | If only click once, or the first click in 2 clicks, it locates in the minor rect (borders included). | | | | 小矩形的每条边距大矩形距离为r个像素点。 | | | | Each border of the minor rect is r-pixel far from the outside rect. | ---- | --------- 第二次点击的区域不会超出大矩形的范围（包括边界)）。对于一个点击区域[x1,y1,x2,y2]，其中的小矩形为[x1+r,y1+r,x2-r,y2-r]。 The second is in the big rect (borders included). For a click_area like (x1,y1,x2,y2), the minor rect is [x1+r,y1+r,x2-r,y2-r]. Args: hwnd: 目标窗口的句柄 target hwnd. click_areas: 可选的点击范围（矩形）。格式为[[x1,y1,x2,y2],[x3,y3,x4,y4]...]，只有一个区域则可以写成[x1,y1,x2,y2]。 Possible rect-areas to click, given in the form of [[x1,y1,x2,y2],[x3,y3,x4,y4]...]. If only one area, [x1,y1,x2,y2] is also acceptable. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). r: 圆的半径。 radius. click_strategy: 选择的点击策略。看看源码，很容易写自己的策略。主要包括点击方式和概率。 A strategy for clicking, includes combination of clicking functions and its weights. Look at the src code, easy to construct your own click_strategy. Returns: 返回两次点击的坐标(x1,y1,x2,y2)。如果只点击一次，返回(x1, y1, -1, -1) Returns 2 clicked pos in (x1,y1,x2,y2). If only clicked once, return (x1, y1, -1, -1). ''' try: # 缩小边界，并加上一个值，表示区域点的坐标个数（包含边界） total = 0 areas = copy.deepcopy( click_areas ) # python中传列表参是浅拷贝，为了防止改变原click_areas需要写这一句。传字符串、数字是深拷贝 if isinstance(areas[0], int): areas = [areas] for i in range(len(areas)): areas[i][0] = areas[i][0] + r areas[i][1] = areas[i][1] + r areas[i][2] = areas[i][2] - r areas[i][3] = areas[i][3] - r areas[i].append((areas[i][2] - areas[i][0] + 1) * (areas[i][3] - areas[i][1] + 1)) total = total + areas[i][4] rand = random.randint(0, total - 1) # 找出第一次点击位置落在哪个区域，并找出具体坐标 # find out the first click is in which area, and the exact pos. x = 0 y = 0 for i in range(len(areas)): rand = rand - areas[i][4] if rand < 0: rand = rand + areas[i][4] w = areas[i][2] - areas[i][0] + 1 x = rand % w + areas[i][0] y = rand // w + areas[i][1] # 现在python中整型之间用/也会得到float break if click_strategy == 1: # 1号策略，包括左键单击、左键同一位置双击、左键圆形双击三种 #总结果数为173，左键单击:左键同一位置双击:左键圆形双击 的比例为 131:29:13 rand = random.randint(1, 173) if times == 'first': Activate_Hwnd(hwnd) if rand <= 131: # weighs 131 info('随机区域左键 选择 左键单击，坐标{}'.format((x, y))) LeftClick(hwnd, x, y, 'second', platform) return (x, y, -1, -1) elif rand >= 161: # weighs 13 info('随机区域左键 选择 左键圆形双击，第一次点击坐标{}'.format((x, y))) return LeftDifDoubleClick_Cir(hwnd, x, y, 'second', platform, r) else: # weighs 29 info('随机区域左键 选择 左键同位置双击，坐标{}'.format((x, y))) LeftDoubleClick(hwnd, x, y, 'second', platform) return (x, y, x, y) elif click_strategy == 2: # 2号策略，仅包括左键单击，用于只能单击的情况 if times == 'first': Activate_Hwnd(hwnd) info('随机区域左键 选择 左键单击，坐标{}'.format((x, y))) LeftClick(hwnd, x, y, 'second', platform) return (x, y, -1, -1) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('随机区域左键 出现错误') def press_key(hwnd, key='esc', times='second'): '''单次按键（自带延时） Press a key once (with human-like delay). Args: hwnd: 目标窗口的句柄 target hwnd. key: 需要按的键，详见config.VK_CODE。 The key to press, see also in config.VK_CODE. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). Returns: No return. ''' try: if times == 'first': Activate_Hwnd(hwnd) win32api.SendMessage(hwnd, win32con.WM_KEYDOWN, VK_CODE[key], 0) # 暂时将lParam写为0 # 这里不知道是否要暂停 # I don't konw whether to pause here. time.sleep(0.01) win32api.SendMessage(hwnd, win32con.WM_CHAR, VK_CODE[key], 0) sleep(uniform(0.05, 0.08)) win32api.SendMessage(hwnd, win32con.WM_KEYUP, VK_CODE[key], 0) sleep(uniform(0.07, 0.095)) info('对句柄 {} 标题 {} 的窗口 按 {} 键一次'.format(hwnd, win32gui.GetWindowText(hwnd), key)) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('单次按键 出现错误') def press_keys(hwnd, keys, times='second'): '''依序按键 Press a sequence of keys in order. Args: hwnd: 目标窗口的句柄 target hwnd. keys: 需要按的键，详见config.VK_CODE。 Keys to press, see also in config.VK_CODE. times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). Returns: No return. ''' try: if times == 'first': Activate_Hwnd(hwnd) for key in keys: press_key(hwnd, key, 'second') info('对句柄 {} 标题 {} 的窗口 依序按 {} 键'.format(hwnd, win32gui.GetWindowText(hwnd), keys)) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('依序按键 出现错误') def ClientRect_PrtSc(hwnd, area=None, filename=''): '''根据句柄、截图位置和图片路径，对窗口的客户区截图并存到指定位置。 Press PrtSc for a window's client area, save the pic in the indicated path. 从https://blog.csdn.net/zhuisui_woxin/article/details/84345036和https://www.cnblogs.com/Evan-fanfan/p/11097850.html改编 Made some changes and corrected some errors from 2 links above (took me some time to figure the errors). Args: hwnd: 目标窗口的句柄 target hwnd. area: 需要的区域。为None时表示整个客户区。 Needed area. If None, means get the whole client area. filename: 指定路径。为''时表示文件名根据hwnd生成。 Indicate the path. If '', means 'path\{}.bmp'.format. Returns: No return. ''' try: hwnd = hwnd if filename == '': filename = 'yourpath\\{}.bmp'.format( hwnd) hwndDC = win32gui.GetDC( hwnd) # 获取窗口的设备上下文Device Context。GetWindowDC包括了非客户区，而GetDC仅为客户区 # GetWindowDC also gets the non-client area, while GetDC only gets the client area. mfcDC = win32ui.CreateDCFromHandle(hwndDC) # 获取mfcDC saveDC = mfcDC.CreateCompatibleDC() # 创建可兼容DC saveBitMap = win32ui.CreateBitmap() # 创建bitmap以保存图片 '''MonitorDev = win32api.EnumDisplayMonitors( None, None) # 获取显示器信息，枚举显示器，笔记本据说可能有问题''' x1, y1, x2, y2 = win32gui.GetClientRect( hwnd) #GetClientRect获取客户区窗口位置，GetWindowRect获取整个窗口的位置信息 x, y, w, h = (0, 0, 0, 0) if area == None: x = 0 y = 0 w = x2 - x1 h = y2 - y1 else: x, y, m, n = area w = m - x h = n - y saveBitMap.CreateCompatibleBitmap(mfcDC, w, h) # 为bitmap开辟空间 # 对saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)的理解： # 1.mfc相当于一个虚拟屏幕。这里的参数w和h决定了这个屏幕的大小。 # 2.屏幕的初始状态是黑色，每个坐标都是#000000 # 3.之前有mfcDC = win32ui.CreateDCFromHandle(hwndDC)，又有hwndDC = win32gui.GetDC(hwnd) # mfcDC和hwnd窗口之间建立了某种关联，可以将hwnd窗口中的图像放到虚拟屏幕上 saveDC.SelectObject(saveBitMap) # 将截图保存到saveBitMap中 saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY) # 对saveDC.BitBlt(坐标1, (w, h), mfcDC, 坐标2, win32con.SRCCOPY)的理解： # BitBlt的功能大概是把从hwnd窗口截到的图放到虚拟屏幕上，信息转入saveDC。 # 1.坐标1是针对窗口截图的，指定截图放在黑色背景上的位置（指定左上角） # 2.w和h窗口截图的长宽，而坐标2指定了开始截图的位置 # 这两个参数决定了从hwnd窗口的哪里截图、截多大的图 # 3.mfcDC已经和hwnd窗口建立了关联，所以不需要指定虚拟屏幕从哪个窗口获得截图 # 4.SRCCOPY意为将截图直接拷贝到虚拟屏幕中 # 接下来的saveBitMap.SaveBitmapFile(saveDC, filename)则是对虚拟屏幕截图并保存到指定位置 # Google for more info. saveBitMap.SaveBitmapFile(saveDC, filename) # 清除数据 # GetDC一类的需要用ReleaseDC释放，CreateDC一类的用DeleteDC释放，DeleteObject则删除一个逻辑笔、画笔、字体、位图、区域或者调色板 win32gui.DeleteObject(saveBitMap.GetHandle()) saveDC.DeleteDC() mfcDC.DeleteDC() win32gui.ReleaseDC(hwnd, hwndDC) info('对句柄 {} 标题 {} 的窗口截图并保存'.format(hwnd, win32gui.GetWindowText(hwnd))) except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('客户区截图 出现错误') def G(pattern_filename): '''将pattern的文件名加上路径前缀与类型后缀并返回 Add path prefix and '.bmp' for a pattern pic file and return it. Args: pattern_filename: 模式图片名。Pattern pic file name. Returns: opencv2 可用的路径。The usable path for opencv2. ''' try: global pattern_path # 在全局变量中 # a global variable. return pattern_path + pattern_filename + '.bmp' except: global mode if mode == 'test': traceback.print_exc() elif mode == 'real': error('加模式图路径 出现错误') def Find_Pic(hwnd, pattern, return_pos='l', find_pattern_in_area=None, filename=''): '''寻找图片，找到则返回小图在大图中的位置坐标及模板图大小，否则返回(-1,-1,tw,th)。 Find the pattern pic in the target pic. Return the location and pattern's size if found, or (-1,-1,pattern-weight,pattern-height) if not. https://blog.csdn.net/wz2671/article/details/102751549和https://www.cnblogs.com/ssyfj/p/9271883.html很有帮助。 Above 2 links help. Args: hwnd: 目标窗口的句柄 target hwnd. pattern: 模式图，小图。 The pic to be found, pattern pic, smaller pic. return_pos: 为'l'时返回找到的左上角坐标，'c'则返回中心（偏左上）。 If 'l', return the left-top corner, 'c' the centor pos (if odd, lefter and topper). find_pattern_in_area: 指定在大图的哪个区域查找。None表示在全部区域中找。 In which area of the bigger pic to find. None means the whole area. filename: 指定路径。为''时表示文件名根据hwnd生成。 Indicate the bigger-pic's path. If '', means 'path\{}.bmp'.format. Returns: 如果找到，以(x, y, pat_w, pat_h)形式返回小图在大图中的位置；没找到则返回(-1, -1, pat_w, pat_h)。 The location and pattern's size in the form of (x, y, pat_w, pat_h) if found, or (x, y, pat_w, pat_h) if not. ''' try: hwnd = hwnd ClientRect_PrtSc(hwnd, find_pattern_in_area, filename) if filename == '': # 没有提供文件名则需要改成默认文件名 filename = 'youpath\\{}.bmp'.format( hwnd) pat = cv.imread(pattern) # pattern src = cv.imread(filename) # source # imread的文件名必须带上格式后缀 theight, twidth = pat.shape[:2] # 获取pattern的高和宽 result = cv.matchTemplate(src, pat, cv.TM_SQDIFF_NORMED) # 执行模板匹配，采用的匹配方式cv2.TM_SQDIFF_NORMED # matchTemplate(大图像, 小图像(子图像、模式图像)m, 匹配方式, result 和 mask 可选) # 返回一个矩阵result，大小为(W-w+1)*(H-h+1)，比大图像少了右、下一圈，是子图像左上顶点在大图像中可能出现的位置 # result矩阵中的每个位置，其值表示子图像左上顶点放在该位置时，子图像与大图像上相应区域的匹配度 # result中的点位置和大图像保持一致。 # 关于匹配方式：（） # 1.TM_SQDIFF是平方差匹配；TM_SQDIFF_NORMED是标准平方差匹配。利用平方差来进行匹配,最好匹配为0；匹配越差,匹配值越大。 # 2.TM_CCORR是相关性匹配；TM_CCORR_NORMED是标准相关性匹配。采用模板和图像间的乘法操作,数越大表示匹配程度较高, 0表示最坏的匹配效果。 # 3.TM_CCOEFF是相关性系数匹配；TM_CCOEFF_NORMED是标准相关性系数匹配。将模版对其均值的相对值与图像对其均值的相关值进行匹配,1表示完美匹配,-1表示糟糕的匹配,0表示没有任何相关性(随机序列)。 # 总结：随着从简单的测量(平方差)到更复杂的测量(相关系数),我们可获得越来越准确的匹配(同时也意味着越来越大的计算代价)。 min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) # minMaxLoc(矩阵Mat)，在一个矩阵中找出最大值、最小值、对应序号 # 对于TM_SQDIFF_NORMED模式下最匹配的位置，如果不匹配值大于1%，就认为没有找到。 if min_val > 0.01: return (-1, -1, twidth, theight) global mode if mode == 'test': # 以下代码绘制矩形边框，并将匹配区域标注出来，用于调试 # codes after are used for debug, draw the big/small pic and the location. cv.rectangle(src, min_loc, (min_loc[0] + twidth, min_loc[1] + theight), (0, 0, 225), 1) # 在src图像矩阵中加上矩形，范围是模板图的范围 # rectangle(目标图像, 矩形的一个顶点, 矩形对角线上的另一个顶点, 矩形线条颜色, 线条粗细)返回空值 strmin_val = str(min_val) print('\n匹配度' + strmin_val) cv.imshow("MatchResult----MatchingValue=" + strmin_val, src) # 将画上了矩形的图像在窗口中显示出来 cv.imshow('pattern pic', pat) # imshow(窗口名称, 图像矩阵)，在窗口中显示图像，窗口自动调整到图像大小 # cv.imwrite('1.png', template, [int(cv.IMWRITE_PNG_COMPRESSION), 9]) # imwrite(文件名, 图像矩阵, 格式编码) 将图像矩阵以特定编码保存，暂时用不到 # IMWRITE_PNG_COMPRESSION为png格式，0-9为压缩级别，9为不压缩，消耗时间最少 cv.waitKey(0) # 等待键盘输入任意值，参数表示等待时间（ms），0表示一直等 cv.destroyAllWindows() # 关闭所有窗口 if return_pos == 'c': x = min_loc[0] + twidth // 2 y = min_loc[1] + theight // 2 elif return_pos == 'l': x = min_loc[0] y = min_loc[1] info('在句柄 {} 标题 {} 的窗口找到模式{}'.format(hwnd, win32gui.GetWindowText(hwnd), pattern)) return (x, y, twidth, theight) except: if mode == 'test': traceback.print_exc() elif mode == 'real': error('找图 出现错误') def print_hitted_points(hitted_points, hwnd, deltas): '''测试用函数，根据hwnd找到截图文件（不要重新截图）并标注出点击过的点。 Used for debug, find the big pic by hwnd (No PrtSc again) and mark the clicked pos. Args: hitted_points: 点击过的点。clicked points. hwnd: 用于找到大图。Used to find the big pic. deltas: 如果大图是客户区的一部分截图，该变量可描述大图左上角顶点在整个客户区中的位置。 If the big pic is only part of the window, deltas would help describe big-pic's location (deltas is the left-top corner pos). Returns: No return. ''' try: x1, y1, x2, y2 = hitted_points x1, x2 = x1 - deltas[0], x2 - deltas[0] y1, y2 = y1 - deltas[1], y2 - deltas[1] print('deltas = {}, '.format(deltas)) print('hitted_points = ({},{}) ({},{})'.format(x1, y1, x2, y2)) # 这样就得到了应该标注的坐标 filename = 'yourpath\\{}.bmp'.format( hwnd) src = cv.imread(filename) if x2 < 0 and y2 < 0: cv.circle(src, (x1, y1), 1, (0, 0, 225), 4) # cv.circle(img, point, point_size, point_color, thickness)本来用来画圆的 # 参数分别是图像矩阵、圆心、半径、线的颜色、稠度 # 稠度可以取0、4、8，画出来点的大小不同 cv.imshow('single one----hitted points', src) elif x1 == x2 and y1 == y2: cv.circle(src, (x1, y1), 1, (0, 0, 225), 4) cv.imshow('double same----hitted points', src) else: cv.circle(src, (x1, y1), 1, (0, 0, 225), 4) cv.circle(src, (x2, y2), 1, (0, 0, 225), 4) cv.imshow('double diff----hitted points', src) cv.waitKey(0) cv.destroyAllWindows() except: traceback.print_exc() def FindPic_RandomLeftClick(hwnd, pattern, max_try=20, time_interval=0.5, click_strategy=1, times='first', click_areas=None, find_pattern_in_area=None, r=4, platform='PC', click_pos='l', filename=''): '''从指定的句柄窗口寻找图片，若最大次数内找到则执行随机区域左键点击，否则返回False。返回点击的坐标(x1,y1,x2,y2)，若只点击了一次则返回(x1,y1,-1,-1)。 这是一个整合程度高函数，可能要看一下其中用到的其他函数。 Find a pattern pic in the hwnd window, if not found after max_try, return False; if found, operate a click_strategy, and return clicked pos. If clicked only once, return (x1,y1,-1,-1). This func is highly intergrated. Turn to some called sub-funcs if confused. Args in detail: hwnd: 要截图并点击的窗口句柄。The window to PrtSc and click. pattern: 要寻找的模式小图。The pattern-pic's path to be found. max_try: 指定最多寻找的次数。If not found, after max_try, it will let go. time_interval指定每次寻找的间隔时间（实际查找间隔比该时间多一点）。 Interval between each try (a little longer than given, in fact). click_strategy表示点击策略，详见Random_Areas_LeftClicks函数。 Indicate the click strategy, see also in Random_Areas_LeftClicks(). times: 'first'表示从其他窗口转过来点击，用于调用Activate_Hwnd(hwnd)。 If 'first', means target window is inactive/unfocused, thus call Activate_Hwnd(hwnd). click_areas: 可选的点击范围（矩形）。格式为[[x1,y1,x2,y2],[x3,y3,x4,y4]...]，只有一个区域则可以写成[x1,y1,x2,y2]。为None时表示就点模式小图所在的区域。 Possible rect-areas to click, given in the form of [[x1,y1,x2,y2],[x3,y3,x4,y4]...]. If only one area, [x1,y1,x2,y2] is also acceptable. If None, means to click the area where pattern pic is found. find_pattern_in_area: 指定在窗口客户区的哪个区域寻找模式图，为None时表示在整个客户区寻找。 Which rect-area of the client area of the window to find the pattern pic. If None, means find in the whole area of the client area. find_pattern_in_area从技术上是只截取客户区该区域的图实现的。 If not None, technically, it works by only PrtSc the partly area of the window's client area. r: 指示对指定的点击区域，应该内收多少个像素点。对于高或宽<4的模式图，可能需要自己把r设置成更小的值。 Shrink r pixels for each click_area. For pat-pic smaller than 4 pixels, adjust r to a smaller value. platform: 目前无用。'PC'表示当前操作的是桌面版，不是安卓模拟器('AM')。 Useless now. 'PC' means target window is PC, not Android manipulator ('AM'). click_pos: 可取值'l'或'c'，l表示left_top，后者表示center。详见Find_Pic()。 Possible values are 'l' and 'c'. See also in Find_Pic(). 用于指示在窗口中找到模式图后，是返回其左上坐标还是中心点坐标（模式图长宽为奇数时返回值偏左上）。 'l' means left-top corner pos, 'c' means center pos, used by Find_Pic(). 现在暂时只用'l'模式，'c'模式可能用于以后功能拓展。 For now 'l' is enough, 'c' may help expand furthur functions. filename: 手动指定大图文件，从该文件中查找模式图。一般用不到。 Indicate the big-pic's path manually, not used in ordinary applications. Returns: 若最大次数后仍未找到模式图则返回False。 若找到模式图，返回点击的坐标(x1,y1,x2,y2)，若只点击了一次则返回(x1,y1,-1,-1)。 If pattern pic not found after max_try, return False; if found, return clicked pos in (x1,y1,x2,y2). Return (x1,y1,-1,-1) if only clicked once. ''' try: global mode x, y = -1, -1 deltas = ( -1, -1 ) # 这是截图区域与整个客户区的左上顶点的相对位置。用于测试 # Used for debug, in print_hitted_points(). for i in range(max_try): x, y, twidth, theight = Find_Pic(hwnd, pattern, 'l', find_pattern_in_area, filename) if x == -1 and y == -1: if mode == 'test': print('\r第{}次没找到'.format(i + 1), end='') sleep(time_interval) continue else: if find_pattern_in_area == None: # 若使用默认的全客户区查找，返回的坐标是相对于整个客户区的 delta_x, delta_y = 0, 0 else: # 指定了查找区域，则返回的坐标是相对于查找区域的，需要转换一下 delta_x, delta_y = find_pattern_in_area[ 0], find_pattern_in_area[1] if click_areas == None: # 不给出点击区域，则默认点击区域是窗口找到模式图片的区域 click_areas = x + delta_x, y + delta_y, x + twidth + delta_x, y + theight + delta_y if mode == 'test': deltas = (delta_x, delta_y) break if x == -1 and y == -1: info('未能找到并随机点击：对句柄 {} 标题 {} 的窗口未找到模式{}，等待了{}次*{}秒'.format( hwnd, win32gui.GetWindowText(hwnd), pattern, max_try, time_interval)) return False else: if mode == 'test': # 在测试模式下，画出点击了窗口上的哪些点 hitted_points = Random_Areas_LeftClicks( hwnd, click_areas, times, platform, r, click_strategy) print_hitted_points(hitted_points, hwnd, deltas) return hitted_points elif mode == 'real': return Random_Areas_LeftClicks(hwnd, click_areas, times, platform, r, click_strategy) except: if mode == 'test': traceback.print_exc() elif mode == 'real': error('找图并随机左键点击 出现错误') if __name__ == '__main__': try: click_areas = [292, 175, 314, 192] find_pattern_in_area = [44, 82, 245, 313] # [44, 82, 100, 150] print(FindPic_RandomLeftClick(3212840, G('pattern1'), click_strategy=2)) except: traceback.print_exc() 配置文件：# 键盘码 VK_CODE = { 'backspace': 0x08, 'tab': 0x09, 'clear': 0x0C, 'enter': 0x0D, 'shift': 0x10, 'ctrl': 0x11, 'alt': 0x12, 'pause': 0x13, 'caps_lock': 0x14, 'esc': 0x1B, 'spacebar': 0x20, 'page_up': 0x21, 'page_down': 0x22, 'end': 0x23, 'home': 0x24, 'left_arrow': 0x25, 'up_arrow': 0x26, 'right_arrow': 0x27, 'down_arrow': 0x28, 'select': 0x29, 'print': 0x2A, 'execute': 0x2B, 'print_screen': 0x2C, 'ins': 0x2D, 'del': 0x2E, 'help': 0x2F, '0': 0x30, '1': 0x31, '2': 0x32, '3': 0x33, '4': 0x34, '5': 0x35, '6': 0x36, '7': 0x37, '8': 0x38, '9': 0x39, 'a': 0x41, 'b': 0x42, 'c': 0x43, 'd': 0x44, 'e': 0x45, 'f': 0x46, 'g': 0x47, 'h': 0x48, 'i': 0x49, 'j': 0x4A, 'k': 0x4B, 'l': 0x4C, 'm': 0x4D, 'n': 0x4E, 'o': 0x4F, 'p': 0x50, 'q': 0x51, 'r': 0x52, 's': 0x53, 't': 0x54, 'u': 0x55, 'v': 0x56, 'w': 0x57, 'x': 0x58, 'y': 0x59, 'z': 0x5A, 'numpad_0': 0x60, 'numpad_1': 0x61, 'numpad_2': 0x62, 'numpad_3': 0x63, 'numpad_4': 0x64, 'numpad_5': 0x65, 'numpad_6': 0x66, 'numpad_7': 0x67, 'numpad_8': 0x68, 'numpad_9': 0x69, 'multiply_key': 0x6A, 'add_key': 0x6B, 'separator_key': 0x6C, 'subtract_key': 0x6D, 'decimal_key': 0x6E, 'divide_key': 0x6F, 'F1': 0x70, 'F2': 0x71, 'F3': 0x72, 'F4': 0x73, 'F5': 0x74, 'F6': 0x75, 'F7': 0x76, 'F8': 0x77, 'F9': 0x78, 'F10': 0x79, 'F11': 0x7A, 'F12': 0x7B, 'F13': 0x7C, 'F14': 0x7D, 'F15': 0x7E, 'F16': 0x7F, 'F17': 0x80, 'F18': 0x81, 'F19': 0x82, 'F20': 0x83, 'F21': 0x84, 'F22': 0x85, 'F23': 0x86, 'F24': 0x87, 'num_lock': 0x90, 'scroll_lock': 0x91, 'left_shift': 0xA0, 'right_shift ': 0xA1, 'left_control': 0xA2, 'right_control': 0xA3, 'left_menu': 0xA4, 'right_menu': 0xA5, 'browser_back': 0xA6, 'browser_forward': 0xA7, 'browser_refresh': 0xA8, 'browser_stop': 0xA9, 'browser_search': 0xAA, 'browser_favorites': 0xAB, 'browser_start_and_home': 0xAC, 'volume_mute': 0xAD, 'volume_Down': 0xAE, 'volume_up': 0xAF, 'next_track': 0xB0, 'previous_track': 0xB1, 'stop_media': 0xB2, 'play/pause_media': 0xB3, 'start_mail': 0xB4, 'select_media': 0xB5, 'start_application_1': 0xB6, 'start_application_2': 0xB7, 'attn_key': 0xF6, 'crsel_key': 0xF7, 'exsel_key': 0xF8, 'play_key': 0xFA, 'zoom_key': 0xFB, 'clear_key': 0xFE, '+': 0xBB, ',': 0xBC, '-': 0xBD, '.': 0xBE, '/': 0xBF, '`': 0xC0, ';': 0xBA, '[': 0xDB, '\\': 0xDC, ']': 0xDD, "'": 0xDE, '`': 0xC0 }
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4257/，转载请注明出处。本文的原文写作日期为2020年10月21日，主要目的是用Python截取屏幕。最近需要对窗口截图，看到了以下文章：https://www.cnblogs.com/Evan-fanfan/p/11097850.html其中有些错误（乱写文章害人不浅啊）。经过我2天实验，大概搞清楚了3个关键api的用处。注释和代码如下：# 根据句柄、截图位置和图片路径，对窗口的客户区截图并存到指定位置 # GetDC一类的需要用ReleaseDC释放，CreateDC一类的用DeleteDC释放，DeleteObject则删除一个逻辑笔、画笔、字体、位图、区域或者调色板， def ClientRect_PrtSc(hwnd, area=None, filename=''): try: hwnd = hwnd if filename == '': filename = 'C:\\Users\\tadio\\Desktop\\helper\\pics\\targets\\{}.bmp'.format( hwnd) hwndDC = win32gui.GetDC( hwnd) # 获取窗口的设备上下文Device Context。GetWindowDC包括了非客户区，而GetDC仅为客户区 mfcDC = win32ui.CreateDCFromHandle(hwndDC) # 获取mfcDC saveDC = mfcDC.CreateCompatibleDC() # 创建可兼容DC saveBitMap = win32ui.CreateBitmap() # 创建bitmap以保存图片 '''MonitorDev = win32api.EnumDisplayMonitors( None, None) # 获取显示器信息，枚举显示器，笔记本据说可能有问题''' x1, y1, x2, y2 = win32gui.GetClientRect( hwnd) #GetClientRect获取客户区窗口位置，GetWindowRect获取整个窗口的位置信息 x, y, w, h = (0, 0, 0, 0) if area == None: x = 0 y = 0 w = x2 - x1 h = y2 - y1 else: x, y, m, n = area w = m - x h = n - y saveBitMap.CreateCompatibleBitmap(mfcDC, w, h) # 为bitmap开辟空间 # 对saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)的理解： # 1.mfc相当于一个虚拟屏幕。这里的参数w和h决定了这个屏幕的大小。 # 2.屏幕的初始状态是黑色，每个坐标都是#000000 # 3.之前有mfcDC = win32ui.CreateDCFromHandle(hwndDC)，又有hwndDC = win32gui.GetDC(hwnd) # mfcDC和hwnd窗口之间建立了某种关联，可以将hwnd窗口中的图像放到虚拟屏幕上 saveDC.SelectObject(saveBitMap) # 将截图保存到saveBitMap中 saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY) # 对saveDC.BitBlt(坐标1, (w, h), mfcDC, 坐标2, win32con.SRCCOPY)的理解： # BitBlt的功能大概是把从hwnd窗口截到的图放到虚拟屏幕上，信息转入saveDC。 # 1.坐标1是针对窗口截图的，指定截图放在黑色背景上的位置（指定左上角） # 2.w和h窗口截图的长宽，而坐标2指定了开始截图的位置 # 这两个参数决定了从hwnd窗口的哪里截图、截多大的图 # 3.mfcDC已经和hwnd窗口建立了关联，所以不需要指定虚拟屏幕从哪个窗口获得截图 # 4.SRCCOPY意为将截图直接拷贝到虚拟屏幕中 # 接下来的saveBitMap.SaveBitmapFile(saveDC, filename)则是对虚拟屏幕截图并保存到指定位置 saveBitMap.SaveBitmapFile(saveDC, filename) # 清除数据 win32gui.DeleteObject(saveBitMap.GetHandle()) saveDC.DeleteDC() mfcDC.DeleteDC() win32gui.ReleaseDC(hwnd, hwndDC) info('对句柄 {} 标题 {} 的窗口截图并保存'.format(hwnd, win32gui.GetWindowText(hwnd))) except: error('客户区截图 出现错误')
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4252/，转载请注明出处。本文的原文写作日期为2020年9月24日，主要目的是初步了解JavaScript（源自runnoob）。下面的内容有些缩进或者图片缺失，可以参考 原word文档 。https://www.runoob.com/js/js-tutorial.html 1.js直接写入输出流： 设置一个按钮，对时间做出反应： 点我! 用js改变html内容： JavaScript 能改变 HTML 元素的内容。 点击这里 或者改变图片（这里可以使用?:运算符）： 验证输入： 点击这里 2.js应该位于 之间，放在 或 里。通常情况下都放在head里或者统一放在页面底部（脚本可以写在调用处之后）。也可以从调用外部脚本，修改script的src即可： 标签的innerHTML属性，即内容，可以赋值更改。 Document.write在页面加载后使用则会覆盖页面。 3.js输出： window.alert()弹出警告框，document.write()将内容写到HTML文档，innerHTML写出到标签的内容，console.log()写入到浏览器的控制台。 Js字面量：整数、小数、科学计数（123e5）、字符串（单双引号均可）、数组（[]括起来）、对象（由键值对组成）、函数（用function声明）。 声明变量用var。所有皆var（块中的变量可用let。如果不加var或let，则默认为全局变量。花哨一点可用用var x = new 类型(值)）。语句用;分隔（可选）。注释用//和/*。js对大小写敏感。Try对应catch，throw对raise。可用\表示下一行接续（有条件）。Js逐行执行。标签有this指针。 4.HTML常见事件： 事件 描述 onchange HTML 元素改变 onclick 用户点击 HTML 元素 onmouseover 用户在一个HTML元素上移动鼠标 onmouseout 用户从一个HTML元素上移开鼠标 onkeydown 用户按下键盘按键 onload 浏览器已完成页面的加载 5.字符串对象有length方法。其他常用的还有concat、indexOf、match（re匹配）。 ===操作要求类型和值都相同，==则先转换为同类型，只比较值。相对有!=和!==。 Js中也有switch，和c++中一样。这里的switch用的是===判断。 Break或continue可以后接具体的block名。 6.在 JavaScript 中有 6 种不同的数据类型： string number boolean object function symbol 3 种对象类型： Object Date Array 2 个不包含任何值的数据类型： null undefined 6.js中的re：格式为 /正则表达式主体/修饰符(可选) 比如i修饰符，表示不区分大小写。 其他修饰符： i 执行对大小写不敏感的匹配。 g 执行全局匹配（查找所有匹配而非在找到第一个匹配后停止）。 m 执行多行匹配。 search方法和replace方法都可用re。 元字符： \d 查找数字。 \s 查找空白字符。 \b 匹配单词边界。 \uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。 Re有test方法，用来检验一个字符串是否含有自己。 Re详见https://www.runoob.com/jsref/jsref-obj-regexp.html 可用js验证表单。或者在input标签中添加required="required"属性来自动验证非空。Html5中新增了约束验证。 7.异步函数：在网页上执行一些时间较长、当前用不到的事情，比如读取大文件或者发出网络请求，可用在主线程之外用子线程，同时进行。具体用法是setTimeout(函数名，ms时间)，执行时启动一个独立于主线程的子线程，等待函数执行一定时间。 对于嵌套setTimeout，可用promise将其转变为顺序结构。 更多内容见后。以上内容仅用于初步看懂js。
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4250/，转载请注明出处。本文的原文写作日期为2020年9月22日，主要目的是初步了解html（源自runnoob）。下面的内容有些缩进或者图片缺失，可以参考 原word文档 。Runnoob可以提供html的基本知识。 参考手册：https://www.runoob.com/tags/html-reference.html 1.Html不认识\t\n等，都会输出为空格。 （不分大小写）为标准网页声明，位于所有标签之前，在支持html的浏览器中会被解释为网页使用html5编码。 是页面的根元素， 一般包含了文档的meta数据，比如编码格式（一般对于中文可以写charset=”UTF-8”或者”GBK”）； 为网页标题， 为可见内容， 到 为标题， 为段落。链接在标签的href属性中，图像在 中，后面规定名称和尺寸，如 。 myweb my first html my first html instance 按f12，在elements中可找到标签布局。 关于图片的进一步解释： 1、*.html 文件跟 *.jpg 文件(f盘)在不同目录下： 2、*.html 文件跟 *.jpg 图片在相同目录下： 3、*.html 文件跟 *.jpg 图片在不同目录下： a、图片 *.jpg 在 image 文件夹中，*.html 跟 image 在同一目录下： b、图片 *.jpg 在 image 文件夹中，*.html 在 connage 文件夹中，image 跟 connage 在同一目录下： 4、如果图片来源于网络，那么写绝对路径： 关于href和src： href 是 Hypertext Reference 的缩写，表示超文本引用。用来建立当前元素和文档之间的链接。常用的有：link、a。例如： 浏览器会识别该文档为 css 文档，并行下载该文档，并且不会停止对当前文档的处理。这也是建议使用 link，而不采用 @import 加载 css 的原因。 src 是 source 的缩写，src 的内容是页面必不可少的一部分，是引入。src 指向的内容会嵌入到文档中当前标签所在的位置。常用的有：img、script、iframe。例如: 当浏览器解析到该元素时，会暂停浏览器的渲染，直到该资源加载完毕。这也是将js脚本放在底部而不是头部得原因。 简而言之，src 用于替换当前元素；href 用于在当前文档和引用资源之间建立联系。 1. 表示换行，没有结束标签。有些元素为空元素，以开始标签的结束而结束（有些元素只有一个开始标签），但考虑到未来标准，建议后面还是写个 。大多数情况下，即使不写结束标签也可以，但这样不应当。目前标签大小写无所谓，但应该尽量小写。 在开始标签中设置属性，也是尽量小写，单双引号无所谓。Class属性中可为元素定义多个或一个类名（从样式文件引入），id则是唯一的，style规定样式，title描述元素的额外信息。 Html中的标签： New : HTML5新标签 标签 描述 定义注释 定义文档类型 定义超文本链接 定义缩写 定义只取首字母的缩写，不支持HTML5 定义文档作者或拥有者的联系信息 <applet> HTML5中不赞成使用。定义嵌入的 applet。 定义图像映射内部的区域 New 定义一个文章区域 New 定义页面的侧边栏内容 New 定义音频内容 定义文本粗体 定义页面中所有链接的默认地址或默认目标。 <basefont> HTML5不支持，不赞成使用。定义页面中文本的默认字体、颜色或尺寸。 New 允许您设置一段文本，使其脱离其父元素的文本方向设置。 定义文字方向 定义大号文本，HTML5不支持 定义长的引用 定义文档的主体 定义换行 定义一个点击按钮 New 定义图形，比如图表和其他图像,标签只是图形容器，您必须使用脚本来绘制图形 定义表格标题 HTML5不支持，不赞成使用。定义居中文本。 定义引用(citation) 定义计算机代码文本 定义表格中一个或多个列的属性值 定义表格中供格式化的列组 <command>New 定义命令按钮，比如单选按钮、复选框或按钮 New 定义选项列表。请与 input 元素配合使用该元素，来定义 input 可能的值。 定义定义列表中项目的描述 定义被删除文本 New 用于描述文档或文档某个部分的细节 定义定义项目 New 定义对话框，比如提示框 HTML5不支持，不赞成使用。定义目录列表。 定义文档中的节 定义列表详情 定义列表中的项目 定义强调文本 New 定义嵌入的内容，比如插件。 定义围绕表单中元素的边框 New 定义 元素的标题 New 规定独立的流内容（图像、图表、照片、代码等等）。 HTML5不支持，不赞成使用。定义文字的字体、尺寸和颜色。 New 定义 section 或 document 的页脚。 定义了HTML文档的表单 定义框架集的窗口或框架 定义框架集 to 定义 HTML 标题 定义关于文档的信息 New 定义了文档的头部区域 定义水平线 定义 HTML 文档 定义斜体字 定义内联框架 定义图像 定义输入控件 定义被插入文本 定义键盘文本 <keygen>New 规定用于表单的密钥对生成器字段。 定义 input 元素的标注 定义 fieldset 元素的标题。 定义列表的项目 定义文档与外部资源的关系 定义文档的主体部分。 定义图像映射 New 定义带有记号的文本。请在需要突出显示文本时使用 <m> 标签。 不赞成使用。定义菜单列表。 定义关于 HTML 文档的元信息。 New 定义度量衡。仅用于已知最大和最小值的度量。 New 定义导航链接的部分 定义针对不支持框架的用户的替代内容。HTML5不支持 定义针对不支持客户端脚本的用户的替代内容。 定义内嵌对象 定义有序列表。 定义选择列表中相关选项的组合。 定义选择列表中的选项。 New 定义不同类型的输出，比如脚本的输出。 定义段落。 定义对象的参数。 定义预格式文本。 New 定义运行中的进度（进程）。 定义短的引用。 New 标签在 ruby 注释中使用，以定义不支持 ruby 元素的浏览器所显示的内容。 New 标签定义字符（中文注音或字符）的解释或发音。 New 标签定义 ruby 注释（中文注音或字符）。 不赞成使用。定义加删除线的文本。 定义计算机代码样本。 对于不能支持js的浏览器，写一个 ： 抱歉，你的浏览器不支持 JavaScript! Js常用于图片操作、表单提交、动态更新网页。 15.字符实体：对一些键盘上没有的，或者html格式保留字符输出时使用。 如<写成：< 或 < 或 &#060，>写成>。 则表示不间断空格。 还有音标符号等。 16.URL的格式说明： scheme://host.domain:port/path/filename 说明: scheme - 定义因特网服务的类型。最常见的类型是 http host - 定义域主机（http 的默认主机是 www） domain - 定义因特网域名，比如 runoob.com :port - 定义主机上的端口号（http 的默认端口号是 80） path - 定义服务器上的路径（如果省略，则文档必须位于网站的根目录中）。 filename - 定义文档/资源的名称 以下是一些URL scheme： Scheme 访问 用于... http 超文本传输协议 以 http:// 开头的普通网页。不加密。 https 安全超文本传输协议 安全网页，加密所有信息交换。 ftp 文件传输协议 用于将文件下载或上传至网站。 file 您计算机上的文件。 Url只能使用ascii字符集，对于非ascii字符集的需要%后接两位16进制数来代替。空格常用+或者%20替换。 搭建网站见://www.runoob.com/hosting/hosting-tutorial.html Xml：一种信息传输格式，标准类似Html，但html主要用来显示网页。 在xml中只有<和&需要用实体字符替换。
步骤/目录： 无本文首发于个人博客https://lisper517.top/index.php/archives/4248/，转载请注明出处。本文的原文写作日期为2020年9月20日，主要目的是总结《MySQL必知必会》。下面的内容有些缩进或者图片缺失，可以参考 原word文档 。本书的内容十分基础，建议直接看原文，或者忘记语法的时候查阅。初读的时候可以先粗略读一遍，然后把书上的每个例子都打一次。附录c可以作为复习使用。 1.可以创建多个数据库，每个数据库可包含多个表。同一个库中的表不能重名。Schema模式，指库和表的组织形式，可以用describe 表 来查看。表分为行、列，尽量把列细分（数据细分）方便查找；每行数据有一个主键来作为唯一标识，虽然有些数据不一定需要主键才能区分，但一般都应该定义一个主键，Mysql要求任意两行的主键不同，且每行都有一个非NULL的主键，主键也可以定义在多个列上。 定义主键时应该做到：不重用主键（删了以后不重用）；用不可能变的值作为主键；不修改主键值。 除了主键，还可以定义外键，将表中数据关联到其他表。 2.mysql administrator 和 mysql query browser 提供了GUI，可以考虑下载。由于mysql8有一些改动，可能遇到问题。 进入数据库：在cmd输入mysql -uroot -h localhost -p（先将mysql加入环境变量），接下来要求输入密码。如果连接本地服务器，可以省略-h参数。 Mysql命令行中，每个语句末最好加上; mysql不区分大小写，但很多人喜欢将关键字大写。 Mysql不会识别换行符，可以将命令分行写出，便于阅读。 在mysql命令行下，用show命令显示库和表，也可以用show columns from 表名 显示表内列的信息，或者用更简洁的describe 表名。 用help 命令名 获取相关信息。 Show的其他用法：  SHOW STATUS，用于显示广泛的服务器状态信息；  SHOW CREATE DATABASE和SHOW CREATE TABLE，分别用来显示创 建特定数据库或表的MySQL语句；  SHOW GRANTS，用来显示授予用户（所有用户或特定用户）的安 全权限；  SHOW ERRORS和SHOW WARNINGS，用来显示服务器错误或警告消息。 3.select命令返回的顺序可能变化（为底层存储顺序），因为mysql在数据删除后可能重用空间。 如果列中的值有重复，可使用distinct关键字指明查询不同值。Distinct会用于所有列，比如指定了多个列，如果查找到一行，其每个列的数据同前面某行完全一样，那么就不会打印出。 在末尾加limit a,b（a和b为数字），意为指定从行a开始（不包括行a，第一行为0）最多打印b个结果。 用.表示库和表的从属关系。 用order by 语句可以指定用某一列数据排序。该列可以不在显示出来的列中。Order by 后跟多个列时，表示按权重排名。在最后加desc表示降序（对多个列需要分别写一次desc）。Limit必须在order by 之后。Order by 应在where后。 Where：使用between a and b指定数值范围（返回结果包括a和b）。Is null 检索值为NULL的列。检索非某值时不会返回值为NULL的行。 And比or优先。用()分离不同条件，且应该尽量多用()。 In用法类似于python元组。In还可以嵌套where。 Not可以用来否定条件，比如对in、between、exists取反。 正则表达式、通配符：必须用like。 %表示任意字符任意次数（含0次）。%不匹配NULL。 _表示单个字符单次。 通配符搜索比较慢，尤其是放在开头。 用regexp代替like，表示后面跟的是re。不加^和$，regexp默认全部或部分匹配，而like只能全部匹配。Regexp后加binary可区分大小写。 Mysql中的re转义字符要求写成\\而不是单个\。 写的时候要[[]] 写完的re可以用select 字符串 regexp re表达式 的格式来测试，0为未找到。 4.计算字段，通过表中数据经过一定运算得出的数据列，不存在于表中。一般在服务器上就计算出相应数据并返回（因为DBMS的处理快），客户机上并不能区分计算字段和表中数据。 为了使客户机能引用计算字段，需要将计算字段赋予别名，select 计算字段 as 别名 from ... As中，多个计算字段要分别写多个as、赋予不同的别名。 拼接：mysql用conca()函数实现拼接（其他sql用+或||等）。 删掉空格可以用RTrim、LTrim或者Trim函数。 Select省略from时可以用于实验输出。 5.sql的函数移植性较差。更多函数参见https://www.runoob.com/mysql/mysql-functions.html 用函数时区分大小写。 常用的字符处理函数： SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。SOUNDEX考虑了类似的发音字符和音节，使得能对串进行发音比较而不是字母比较。虽然SOUNDEX不是SQL概念，但MySQL（就像多数DBMS一样）都提供对SOUNDEX的支持。 时间日期函数： 日期类型可以用between。 数值处理函数： 6.汇总函数：如果想要找出最大值、最小值，满足特定条件的行数等，不需要返回表中数据而只需要汇总时 所有的聚集函数都可以用运算符进行多个列上的运算。 Avg()函数忽略NULL行；count(*)则可以计数NULL行，如果指定列名则不计数。 Distinct可以修饰聚集函数里的列，从而只计算不重复的值。不可以distinct *，无意义。 7.分组数据： Group by 列名可以将数据按列名分组，在各组里执行相应操作。  GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套， 为数据分组提供更细致的控制。  如果在GROUP BY子句中嵌套了分组，数据将在最后规定的分组上 进行汇总。换句话说，在建立分组时，指定的所有列都一起计算 （所以不能从个别的列取回数据）。  GROUP BY子句中列出的每个列都必须是检索列或有效的表达式 （但不能是聚集函数）。如果在SELECT中使用表达式，则必须在 GROUP BY子句中指定相同的表达式。不能使用别名。  除聚集计算语句外，SELECT语句中的每个列都必须在GROUP BY子 句中给出。  如果分组列中具有NULL值，则NULL将作为一个分组返回。如果列 中有多行NULL值，它们将分为一组。  GROUP BY子句必须出现在WHERE子句之后，ORDER BY子句之前。 对分组进行过滤使用having。其语法和where相同。Where在数据分组前过滤，having在分组后进行过滤。 子句小结（按在mysql命令行中使用顺序）： 8.子查询：可以把一个select查询返回的结果用于另一个select，即嵌套查询。子查询的where语句中，尽量使用表名.列名来完全限定列。子查询最好从里到外慢慢构造。有时子查询的性能不够好，可以使用连结表。嵌套的子查询语句末不用写;，括号抱起来即可。 子查询可以返回一个列表/列，可以用在where中跟在in后面做条件；或者用于创建计算字段，跟在select后面（相当于将两个表每行的数据拼接在一起），即相关子查询（涉及外部），这时由于两个表中相关的列往往是同名的，一般都要完全限定列名。在mysql结构中，列表和列是一样的。 9.联结表：关系数据库。外键：表中某列包含其他表中的主键值。联结在物理存储上不存在，而是存在于查询的过程中。实际上，联结是通过where子句实现的，比如where 表1.列a =表2.列b（两个列大多数情况下应该是同名的，建立了外键），这种称为等值联结或内部联结，也可以使用FROM 表1 INNER JOIN 表2 ON 表1.列a =表2.列b 的语法写出，推荐使用INNER JOIN 写法（但该语句好像只能用于两个表联结）。 笛卡尔积：表的行数之积。联结表时去掉where语句就会得到该结果，表1中的每行对表2中的每行都建立了联结。所以要用where限定联结建立，过滤那些不应该建立的联结。 可以联结多个表，但性能下降的厉害（表的行数之积）。 10.在from里还可以用as起表别名，缩短语句。表别名不返回客户机。 自联结、自然联结和外部联结： 当需要从自身表查询信息并用于自身表的下一次查询就可以自联结。自联结就是分别将一个表给予两个别名，自己联结自己（只涉及一个表），写成子查询的形式，其内外的from后跟的都是自身表。这种情况下多用自联结而不是子查询。 自然联结：从本质上说，两个表必须要有所关联才能联结，就是说具有相同的列。自然联结排除相同的列，返回结果中每个列最多出现一次。自然联结没有特定的语法，在使用联结时，需要自己注意不要在输出时出现重复的列。 外部联结：当需要对照另一个表输出某表中所有内容，包括没有在另一个表中出现的内容时，用left join或right join。此时某表在另一个表中无关联的行会返回NULL。 11.组合查询：返回一个以上的查询结果，但是包含在同一个返回的表中。用union上下隔开两个select即可。如果将大量相同的数据存在了不同的表中（至少应该是多于一个表中的列有重复），想一次查询返回一个表方便查看，就可以组合查询。如果表中数据有重复，union会自动去除，用union all则不会去除。组合查询的order by跟在最后一个select中，应用于全部返回结果。 12.据说mysql自带的fulltext效率不高、仅支持英文。可以用coreseek和xunsearch等第三方应用来完成这个需求。 数据库引擎：mysql中最常用innodb和myisam，后者才支持全文本搜索。在创建表时，用CREATE TABLE 表名( 数据列 PRIMARY KEY(主键列) FULLTEXT(全文本列) )ENGINE=myisam; 来创建全文本搜索。这样全文本列就会自动创建索引（索引都是为了加快查表速度），并且数据更新时自动维护索引。另外，应该在导入完数据后再创建索引。创建索引后，用： Where match(索引列) against(字符串) 来搜索列内包含字符串的行。如果创建了多个全文本列，在match中需要按次序全部列出。 全文本搜索根据字符串的匹配程度对结果排序，比如字符串出现位置靠前的排前面。如果用select输出match() against()，会发现其是一个值，不匹配的返回0，匹配的根据出现位置、次数等返回一个不同的小数。 扩展查询：mysql先用给定词全文本搜索一次，从匹配的行中选择相关的字符串，根据原来的词和相关词再次全文本搜索。使用扩展查询用match(索引列) against(字符串 WITH QUERY EXPANSION)。行数越多，扩展查询返回的结果相关性越好。 布尔查询：match(索引列) against(‘abc -def*’ IN BOOLEAN MODE) 排除def开头的词。 一些常用bool操作符： 例子： 另外：  在索引全文本数据时，短词被忽略且从索引中排除。短词定义为 那些具有3个或3个以下字符的词 （ 如果需要 ， 这个数目可以更改 ） 。  MySQL带有一个内建的非用词 （ stopword ） 列表 ， 这些词在索引全文本数据时总是被忽略。如果需要，可以覆盖这个列表（请参 阅MySQL文档以了解如何完成此工作）。  许多词出现的频率很高，搜索它们没有用处（返回太多的结果）。 因此，MySQL规定了一条50%规则，如果一个词出现在50%以上 的行中，则将它作为一个非用词忽略。50%规则不用于IN BOOLEAN MODE。  如果表中的行数少于3行，则全文本搜索不返回结果（因为每个词 或者不出现，或者至少出现在50%的行中）。  忽略词中的单引号。例如，don't索引为dont。  不具有词分隔符（包括日语和汉语）的语言不能恰当地返回全文 本搜索结果。  如前所述，仅在MyISAM数据库引擎中支持全文本搜索。 13.插入数据用insert into 表名 () VALUES ()。自增的列或允许NULL值列可以用NULL或者不用给出。 对于insert into、delete、update等等语句，可以用low priority降低次序，比如insert low priority into，这样多个用户使用时就会有选择性。 insert into 表名 (列名) VALUES (同序的值), VALUES (同序的值);可以一次插入两行。多个同时插入的性能要更好，有条件时尽量多行数据一起插入表中。 Insert into还可以后接select，将select返回的结果插入表中。格式为insert into ... select ...; 这时的列名类似集合，每个元素都要有，但次序随便。 Update 表名 set 列名1 = 值1, 列名2 = 值2 where 条件 更新表中数据，一定记得加条件。用列名=NULL的方式删除行中的一部分。 Delete删除整行，就不需指定列名了。Delete from 表名 where 条件。 删除所有行应该使用truncate 表名 语句。 删除和更新应该谨慎，最好用select看一下where写对没有。 14.创建表：详见p145 每个表中最多有一个auto_increment列，而且应该是可索引的（比如做主键）。Select last_insert_id()可以返回最后一个增量值。如果在insert中指定了自增列的值（这个值必须没有出现过），后续的列值将在这个基础上增加。 在创建表时列名最后接default 默认值 ，mysql支持用常量或者now()函数作为默认值。注意，对于函数，好像只支持用now()函数，并且列的数据类型是datetime，其他都不行。 指定引擎时，不同引擎有不同的作用，不同引擎的表不能引用外链。 用alter table 表名更改表结构，比如增加列，在后面跟add 列名 数据类型；减少列，后接drop column 列名。一表多动，可以用一个alter接多个逗号分开的改动。 Add还可以添加外链，在后面加ADD CONSTRAINT xxx FOREIGN KEY (在本表中的显示名，应该尽量一致) REFERENCES 引用的表名(外链列)。 重命名：rename 表名 to 新表名 15.视图：视图是一种查询的顺序，按这种顺序返回相应的结果。视图和其他视图或表的名字不应该有重复。视图可以嵌套。 Create view as select ...来创建视图，更新视图可以用replace。创建或替换用create or replace。创建了视图以后，可以将之当作表一样使用，大大简化了查询语句。 在视图中使用的where条件和把视图当表用时的where会合并。 如果视图查询所使用的语句可以确定实际表中的数据，那么可以通过视图更新表。一般来说也不应该通过视图更新实际的表。 16.存储过程：通过一系列操作，将数据存储到库中或者其他对库的操作，由于有时涉及到一些复杂的判断等，需要创建存储过程，即编写函数。 创建存储过程：create procedure 函数名() begin xxx end; 其中的每条独立语句要用;分隔。 调用存储过程：call 函数名(参数表); 在命令行下，;可能引起歧义。可以在定义函数开头加delimiter //，写完后加delimiter ;，表示在这之间用//作为分隔符，这样函数体内写;就不会被认为是函数完结了，相应的end后要加//。除了\符号，其他的字符都可以作为自定义分隔符。 对于有参数的函数： Create procedure 函数名(out a1 参数类型) begin Select avg(...) Into a1 From 表名 End; 表示a1作为传出的参数用out，还有in、inout。不能用一个参数返回多个行列。 然后调用之：call 函数名(@abcd);，接下来select @abcd;，就可显示变量值。Mysql中的用户变量以@开头。 Mysql中的函数编写比较粗糙和简陋。 注释用--开头。帮助文档用comment。 17.游标：使用select返回结果集后，可以用游标查询不同的行。Mysql中的游标只能用于存储过程和函数（只能写在存储过程和函数里）。  在能够使用游标前，必须声明（定义）它。这个过程实际上没有 检索数据，它只是定义要使用的SELECT语句。  一旦声明后，必须打开游标以供使用。这个过程用前面定义的 SELECT语句把数据实际检索出来。  对于填有数据的游标，根据需要取出（检索）各行。  在结束游标使用时，必须关闭游标。 定义游标用declare 游标名 cursor for select ...，open 游标名，打开游标；close 游标名 关闭游标。如果不关，到end时会自动关闭。打开游标后，可以用 fetch 游标名 into 变量名，一次读取一行。 存储过程中的循环：p195 18.触发器：在一些条件下自动执行的存储过程。Mysql中的触发器可以对delete、insert、update做出反应，也可以位于begin和end//之间。 在创建触发器时，需要给出4条信息：  唯一的触发器名；  触发器关联的表；  触发器应该响应的活动（DELETE、INSERT或UPDATE）；（一个表最多6个触发器）  触发器何时执行（处理之前或之后）。 在一个库的触发器最好不要同名。视图和临时表不能使用触发器。 Create trigger 触发器名 after/before delete/insert/update on 表名 内容，比如for each row ... 如果before触发器失败，mysql也不会执行相应的de/in/up操作，并且也不会执行after操作。Drop trigger 触发器名 删除触发器。 对于三种不同类型触发器，见p200 在结束本章之前，我们再介绍一些使用触发器时需要记住的重点。  与其他DBMS相比，MySQL 5中支持的触发器相当初级。未来的 MySQL版本中有一些改进和增强触发器支持的计划。  创建触发器可能需要特殊的安全访问权限，但是，触发器的执行 是自动的。如果INSERT、UPDATE或DELETE语句能够执行，则相关 的触发器也能执行。  应该用触发器来保证数据的一致性（大小写、格式等）。在触发器 中执行这种类型的处理的优点是它总是进行这种处理，而且是透 明地进行，与客户机应用无关。  触发器的一种非常有意义的使用是创建审计跟踪。使用触发器， 把更改（如果需要，甚至还有之前和之后的状态）记录到另一个 表非常容易。  遗憾的是，MySQL触发器中不支持CALL语句。这表示不能从触发 器内调用存储过程。所需的存储过程代码需要复制到触发器内。 19.事务处理：innodb支持事务处理。事务处理是对数据的一组操作，使用事务处理可以保证这些操作要么全执行，要么不执行，维持了库的结构（发生错误时撤销前面的操作）。在事务处理内也可以设置savepoint，进行部分回退。 Start transaction标识事务开始，rollback表示回退至start（Create和drop操作不能撤销）。Commit表示确认没问题就执行。Savepoint 保存点名，rollback to 保存点名 可以撤销至保存点。见p206 Set autocommit=0;，则写完一组语句后不会自动提交。自动提交是对每个连接而不是服务器。 20.字符集、编码和校对（校对指规定字符如何比较的指令）：show character set; 可以查看mysql支持的字符集。Show collation显示mysql支持的校对，一个字符集可能有不止一种校对，比如支不支持大小写。创建表时设置表的字符集和校对：create table ....) default character set 字符集 collate 校对;，还可以对某列指定要用的字符集和校对。 如果要更改，可以用cast()和convert()函数。 进入库后，用show variables like “character%”或”collation%”来显示使用的字符集和校对。 20.数据库安全：给每个用户合适的访问权限。进行访问控制需要创建和管理账号。一般来说不要使用root账号，除非万不得已。在名为mysql的库中有user表，其中一列名为user，存储了用户名。 Create user 用户名 identified by ‘密码’; 创建用户并给出密码。Grant也可以创建用户，理论上还可以直接更改user表来创建用户，不过不推荐。 Rename user 用户名 to 新用户名; Drop user 用户名; 新创建的用户没有权限。用show grants for 用户名; 显示用户权限。用户名@%，%表示默认的主机名。*.*，前面为库名，后面为表名。 为设置权限，使用GRANT语句。GRANT要求你至少给出以下信息：  要授予的权限；  被授予访问权限的数据库或表；  用户名。 以下例子给出GRANT的用法： Grant 权限名 on 库名.表名 to 用户名 ，比如select查询权限。相应的revoke可以撤销权限。多个权限用,分隔。 使用grant和revoke时，用户必须存在，但表和库不一定。而且删除表和库后，相关权限仍然存在，重建表/库后也存在，需要注意。 Set password = password(‘新密码’) 可以为当前用户更新密码，或者set password for 用户名为指定用户改密码。 21.数据库维护： 备份数据： 使用命令行实用程序mysqldump转储所有数据库内容到某个外部 文件。在进行常规备份前这个实用程序应该正常运行，以便能正 确地备份转储文件。  可用命令行实用程序mysqlhotcopy从一个数据库复制所有数据 （并非所有数据库引擎都支持这个实用程序）。  可以使用MySQL的BACKUP TABLE或SELECT INTO OUTFILE转储所 有数据到某个外部文件。这两条语句都接受将要创建的系统文件 名，此系统文件必须不存在，否则会出错。数据可以用RESTORE TABLE来复原。 为了保证所有数据被写到磁盘（包括索引 数据），可能需要在进行备份前使用FLUSH TABLES语句。 一些确保库正常运行的语句： Analyze table 表名; CHECK TABLE用来针对许多问题对表进行检查。在MyISAM表上还对 索引进行检查。CHECK TABLE支持一系列的用于MyISAM表的方式。 CHANGED检查自最后一次检查以来改动过的表。EXTENDED执行最 彻底的检查，FAST只检查未正常关闭的表，MEDIUM检查所有被删 除的链接并进行键检验，QUICK只进行快速扫描。如下所示，CHECK TABLE发现和修复问题： 可以用repair table来修复myisam表，但若经常使用，表示有更大的问题； 从表中删除大量数据后，应该用optimize table收回空间。 在排除系统启动问题时，首先应该尽量用手动启动服务器。MySQL 服务器自身通过在命令行上执行mysqld启动。下面是几个重要的mysqld 命令行选项：  --help显示帮助——一个选项列表；  --safe-mode装载减去某些最佳配置的服务器；  --verbose显示全文本消息（为获得更详细的帮助消息与--help 联合使用）；  --version显示版本信息然后退出。 几个另外的命令行选项（与日志文件的使用有关）在下一节列出。 29.4 查看日志文件 MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下 几种。  错误日志。它包含启动和关闭问题以及任意关键错误的细节。此 日志通常名为hostname.err，位于data目录中。此日志名可用 --log-error命令行选项更改。  查询日志。它记录所有MySQL活动，在诊断问题时非常有用。此 日志文件可能会很快地变得非常大，因此不应该长期使用它。此 日志通常名为hostname.log，位于data目录中。此名字可以用 --log命令行选项更改。  二进制日志。它记录更新过数据（或者可能更新过数据）的所有 语句。此日志通常名为hostname-bin，位于data目录内。此名字 可以用 - -log - bin命令行选项更改 。 注意 ， 这个日志文件是MySQL 5中添加的，以前的MySQL版本中使用的是更新日志。  缓慢查询日志。顾名思义，此日志记录执行缓慢的任何查询。这 个日志在确定数据库何处需要优化很有用。此日志通常名为 hostname-slow.log ，位于 data 目录中。此名字可以用 --log-slow-queries命令行选项更改。 在使用日志时，可用FLUSH LOGS语句来刷新和重新开始所有日志文 件。 22.优化数据库： 数据库管理员把他们生命中的相当一部份时间花在了调整、试验以 改善DBMS性能之上。在诊断应用的滞缓现象和性能问题时，性能不良的 数据库（以及数据库查询）通常是最常见的祸因。 可以看出，下面的内容并不能完全决定MySQL的性能。我们只是 想回顾一下前面各章的重点，提供进行性能优化探讨和分析的一个出 发点。  首先，MySQL（与所有DBMS一样）具有特定的硬件建议。在学 习和研究MySQL时，使用任何旧的计算机作为服务器都可以。但 对用于生产的服务器来说，应该坚持遵循这些硬件建议。  一般来说，关键的生产DBMS应该运行在自己的专用服务器上。  MySQL是用一系列的默认设置预先配置的，从这些设置开始通常 是很好的。但过一段时间后你可能需要调整内存分配、缓冲区大 小等。（为查看当前设置，可使用SHOW VARIABLES;和SHOW STATUS;。）  MySQL是一个多用户多线程的DBMS，换言之，它经常同时执行多 个任务。如果这些任务中的某一个执行缓慢，则所有请求都会执 行缓慢。如果你遇到显著的性能不良，可使用SHOW PROCESSLIST 显示所有活动进程 （ 以及它们的线程ID和执行时间 ） 。 你还可以用 KILL命令终结某个特定的进程（使用这个命令需要作为管理员登 录）。  总是有不止一种方法编写同一条SELECT语句。应该试验联结、并、 子查询等，找出最佳的方法。  使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。  一般来说，存储过程执行得比一条一条地执行其中的各条MySQL 语句快。  应该总是使用正确的数据类型。  决不要检索比需求还要多的数据。换言之，不要用SELECT *（除 非你真正需要每个列）。  有的操作（包括INSERT）支持一个可选的DELAYED关键字，如果 使用它，将把控制立即返回给调用程序，并且一旦有可能就实际 执行该操作。  在导入数据时，应该关闭自动提交。你可能还想删除索引（包括 FULLTEXT索引），然后在导入完成后再重建它们。  必须索引数据库表以改善数据检索的性能。确定索引什么不是一 件微不足道的任务，需要分析使用的SELECT语句以找出重复的 WHERE和ORDER BY子句。如果一个简单的WHERE子句返回结果所花 的时间太长，则可以断定其中使用的列（或几个列）就是需要索 引的对象。  你的SELECT语句中有一系列复杂的OR条件吗？通过使用多条 SELECT语句和连接它们的UNION语句，你能看到极大的性能改 进。  索引改善数据检索的性能，但损害数据插入、删除和更新的性能。 如果你有一些表，它们收集数据且不经常被搜索，则在有必要之 前不要索引它们。（索引可根据需要添加和删除。）  LIKE很慢。一般来说，最好是使用FULLTEXT而不是LIKE。  数据库是不断变化的实体。一组优化良好的表一会儿后可能就面 目全非了。由于表的使用和内容的更改，理想的优化和配置也会 改变。  最重要的规则就是，每条规则在某些条件下都会被打破。