获取笔记链接
Python超详细的学习笔记
在这里插入图片描述

一,逆向加密模块

1,Python中运行JS代码

1.1 解决中文乱码或者报错问题

import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')
import execjs

1.2 常用函数

print(execjs.get().name) # 获取js代码执行环境

res = execjs.eval(js)  # 执行一段js代码

#先编译
jj = execjs.compile("""  
    function an(a, b){
        return a + b    
    }
""")
# call() 运行代码中的xxx函数. 后续的参数是xxx的参数
ret = jj.call("an", 10, 20)

#读取js文件
f = open("01.js",mode="r",encoding="utf-8")
js_code = f.read()
# 执行js代码函数
js = execjs.compile(js_code)
js.call(函数)

obj = re.compile(r"window\._INIT_STATE__ = (?P<code>.*?);",re.S) # 正则表达式
code = obj.search(js_code).group("code") #匹配正则表达式
print(type(code))  #这里的code是字符串

# execjs这个库会把运行的结果自动转化成对象
result = execjs.eval(code)# 拿到的是一个JS代码运行的结果
print(result) #是个字典,Python会自动转换成字典
print(type(result)) # dict



execjs._exceptions.ProgramError: Error: Malformed UTF-8 data
js报错了,返回的数据两端有引号或者双引号,需要去掉

2,CryptoJS加密特征

function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }


#转换为UTF8
CryptoJS.enc.Utf8
# 转换为base64
CryptoJS.enc.Base64

3,RAS加密特征

setMaxDigits,
RSAKeyPair,
encryptedString

4,md5加密解密

from hashlib import md5

obj = md5()
obj.update("alex".encode("utf-8"))
# obj.update("wusir".encode('utf-8'))  # 可以添加多个被加密的内容

bs = obj.hexdigest()
print(bs)

# 加盐
from hashlib import md5
salt = "我是盐.把我加进去就没人能破解了"

obj = md5(salt.encode("utf-8"))  # 加盐
obj.update("alex".encode("utf-8"))

bs = obj.hexdigest()
print(bs)

5,傻系列加密

sha1,sha256,sha512用法跟md5一样

from hashlib import sha1, sha256
sha = sha256(b'salt')
sha.update(b'alex')
print(sha.hexdigest())

6,URLencode

from urllib.parse import quote, unquote, quote_plus, unquote_plus, urlencode

# 单独编码字符串 用 quote
wq = "米饭怎么吃"
print(quote(wq))  # %E7%B1%B3%E9%A5%AD%E6%80%8E%E4%B9%88%E5%90%83
print(quote(wq, encoding="gbk")) # %C3%D7%B7%B9%D4%F5%C3%B4%B3%D4

# 多个数据统一进行编码 用urlencode ,比如字典进行编码
dic = {
    "wq": "米饭怎么吃",
    "new_wq": "想怎么吃就怎么吃"
}

print(urlencode(dic))  # wq=%E7%B1%B3%E9%A5%AD%E6%80%8E%E4%B9%88%E5%90%83&new_wq=%E6%83%B3%E6%80%8E%E4%B9%88%E5%90%83%E5%B0%B1%E6%80%8E%E4%B9%88%E5%90%83
print(urlencode(dic, encoding="utf-8"))  # 也可以指定字符集

# 一个完整的url编码过程
base_url = "http://www.baidu.com/s?"
params = {
    "wd": "大王"
}

url = base_url + urlencode(params)
print(url)  # http://www.baidu.com/s?wd=%E5%A4%A7%E7%8E%8B

#解码
s = "http://www.baidu.com/s?wd=%E5%A4%A7%E7%8E%8B"
print(unquote(s))  # http://www.baidu.com/s?wd=大王

print(quote("a /b/c=", safe=""))  # 传递safe=""  可以保持和浏览器一致
print(quote_plus("a /b/c="))

7,base64编码

import base64

bs = "我要吃饭".encode("utf-8")
# 把字节转化成b64
print(base64.b64encode(bs).decode())

# 把b64字符串转化成字节
s = "5oiR6KaB5ZCD6aWt"
print(base64.b64decode(s).decode("utf-8"))

#base64报错问题
import base64

s = "ztKwrsTj0b0"
bb = base64.b64decode(s)
print(bb)

此时运行出现以下问题
Traceback (most recent call last):
  File "D:/PycharmProjects/rrrr.py", line 33, in <module>
    bb = base64.b64decode(s)
  File "D:\Python38\lib\base64.py", line 87, in b64decode
    return binascii.a2b_base64(s)
binascii.Error: Incorrect padding

# 解决办法

s = "ztKwrsTj0b0"
s += ("=" * (4 - len(s) % 4))
print("填充后", s)
bb = base64.b64decode(s).decode("gbk")
print(bb)

# 注意事项
由于标准的Base64编码后可能出现字符+和/,但是这两个字符在URL中就不能当做参数传递,所以就出现了Base64URL,下面是它们的区别:

1. Base64编码后出现的+和/在Base64URL会分别替换为-和_
2. Base64编码中末尾出现的=符号用于补位,这个字符和queryString中的key=value键值对会发生冲突,所以在Base64URL中=符号会被省略,去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。

我们的应对方案:
在处理base64的时候.如果遇到了没有+和/的情况. 可以采用下面的方案来替换掉+和/
b64 = base64.b64decode(mi, b"-_")

8,AES加密

1,加密规则

第一种
# AES加密
from Crypto.Cipher import AES
import base64
"""
长度
    16: *AES-128*
    24: *AES-192*
    32: *AES-256*

MODE 加密模式.常见的 
    ECB  可以没有iv
    CBC	 需要iv的
"""
# 创建加密器 注意秘钥和iv必须是16个字节
aes = AES.new( key= b"alexissbalexissb", mode=AES.MODE_CBC, iv=b"0102030405060708") # 分别是秘钥,模式,iv
data = "我吃饭了"

# 加密的内容必须是字节,所以先进行编码
data_bs = data.encode("utf-8")

# 需要加密的数据必须是16的倍数
# 填充规则: 缺少数据量的个数 * chr(缺少数据量个数)
pad_len = 16 - len(data_bs) % 16
data_bs += (pad_len * chr(pad_len)).encode("utf-8")
# 再对编码后的字节进行加密
bs = aes.encrypt(data_bs)
#用base64对结果进行编码
result = base64.b64encode(bs).decode()
第二种,用pad对字节进行填充达到规定的长度
# AES加密
from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import pad
"""
长度
    16: *AES-128*
    24: *AES-192*
    32: *AES-256*

MODE 加密模式.常见的 
    ECB  可以没有iv
    CBC	 需要iv的
"""
# 创建加密器 注意秘钥和iv必须是16个字节
aes = AES.new( key= b"alexissbalexissb", mode=AES.MODE_CBC, iv=b"0102030405060708") # 分别是秘钥,模式,iv
data = "我吃饭了"

# 加密的内容必须是字节,所以先进行编码
data_bs = data.encode("utf-8")

# 需要加密的数据必须是16的倍数
# 用pad工具进行填充
data_bs = pad(data_bs,16)
# 再对编码后的字节进行加密
bs = aes.encrypt(data_bs)
#用base64对结果进行编码
result = base64.b64encode(bs).decode()
print(result)

2, 加密结果转换base64,hex

# 转换成base64 bs是AES加密得到的字节
result = base64.b64encode(bs).decode()

#转换成16进制
import binascii
res = binascii.b2a_hex(bs).decode()

# 也可以转换成16进制,跟上面一个效果一样
bs.hex()

3,解密规则

from Crypto.Util.Padding import pad,unpad

# base64编码后的密文
s = '9noPO0fcQizMbPkXcVOTDg=='
# 创建解密器
aes = AES.new(key= b"alexissbalexissb", mode=AES.MODE_CBC, iv=b"0102030405060708")
# 首先把base64编码转换成正常的字节
data = base64.b64decode(s)
res = aes.decrypt(data)
# 明文有可能有问题,因为字节是填充过得
# 用unpad 去除填充的内容,注:需要导入unpad
res = unpad(res,16)
# 得到明文
mingwen = res.decode("utf-8")
print(mingwen)

9,DES加密

1,加密规则

from Crypto.Cipher import DES
from Crypto.Util.Padding import pad,unpad
import base64
mingwen = '艾尼在学爬虫'

# DES key 是 8个字节
# iv 在CBC 模式下使用 长度8个字节
des = DES.new(key=b'aininora', mode=DES.MODE_CBC,iv=b'ainiaini')

# 明文进行编码
data = mingwen.encode('utf-8')
# 对编码后的字节进行填充
r = pad(data,8)
# 对填充后的字节进行des加密后的字节
res = des.encrypt(r)
# 对加密后的字节进行base64编码
base64_res = base64.b64encode(res).decode()
print(base64_res)

2,DES解密规则

from Crypto.Cipher import DES
from Crypto.Util.Padding import pad,unpad
import base64

#base64密文
miwen = 't4TYyzRnIkmVmI81n+cdsVQfprHN5AtG'
#转换成base64字节
base64_byt = base64.b64decode(miwen)
# 创建解密器对象
des = DES.new(key=b'aininora', mode=DES.MODE_CBC,iv=b'ainiaini')
# 进行解密
r = des.decrypt(base64_byt)
# 得到的结果去掉填充部分
data = unpad(r,8)
# 对得到的字节进行解码
mingwen = data.decode('utf-8')
print(mingwen)

注:DES3 与AES,DES加密,解密规则一样,都是兄弟

注:js里的秘钥可以超过8位,js默认去前八位进行解密

10,RSA加密

1,RSA生成秘钥和公钥

#生成和处理秘钥的
from Crypto.PublicKey import RSA
# 加密和解密
from Crypto.Cipher import PKCS1_v1_5

import base64

# 生成私钥
#bits = 2048 是秘钥的长度
rsa_key = RSA.generate(bits=2048)
# 把秘钥导出来
# 秘钥的本质是字节

# export_key 有个参数叫format,默认为PEM,背后的含义是把秘钥转换成了base64
key = rsa_key.export_key().decode()

with open('./rsa_miyao.pem',mode='w',encoding='utf-8') as f:
	f.write(key)


# 把format换成DER,拿到的是字节
# key = rsa_key.export_key(format='DER')

# 把字节手动转换成base64
# result = base64.b64encode(key).decode()


#生成公钥
public_key = rsa_key.public_key()
# 把公钥导出来
p_key = public_key.export_key()

with open('./rsa_gongyao.pem',mode='wb') as f:
	f.write(p_key)

2, RSA进行加密

#加密解密
from Crypto.Cipher import PKCS1_v1_5
#加载key
from Crypto.PublicKey import RSA

import base64

# 导入的key可以是PEM格式的秘钥,或者是直接形式的秘钥也可以
# 读取秘钥
f = open('./rsa_gongyao.pem',mode='r',encoding='utf-8')
# 拿到已经生成好的秘钥
pub_key = f.read()
f.close()

mingwen = "我要好好学习爬虫"

rsa_key = RSA.importKey(pub_key)
# 生成加密对象
rsa = PKCS1_v1_5.new(key=rsa_key)
# 对明文进行编码,处理成字节
mingwen_bs = mingwen.encode('utf-8')
# 对明文字节进行加密,得到密文字节
mi_bs = rsa.encrypt(mingwen_bs)
# 转换成base64
base_64 = base64.b64encode(mi_bs).decode()
#rsa每次加密后的结果可能不一样
print(base_64)

# 如果用了没有填充的算法,那每一次算出来的结果固定的
# 如果同一个明文反复计算结果是一样的那么考虑用js来完成逆向工作
2.1 第二中加密方法
// JSEncrypt
// setPublicKey

// 用的是固定的第三方库,库的名字叫jsencrypt
// 但是这个库只能在浏览器环境使用
// 我们用的是Node环境,所以不能直接是哟和


// 我们需要换一个库, 名字叫 node-jsencrypt
// 安装 npm install node-jsencrypt

var {JSEncrypto} = require('node-jsencrypt')
var o = new JSEncrypt  
o.setPublicKey('xxxxxxxxxxxxxxxxxxxxxxxx')
r = o.encrypt("加密的内容")

// 可以直接把网站的内容拿过来


3,RSA特殊解密

3.1 第一特征

1,如果用了没有填充的算法,那每一次算出来的结果固定的

2,如果同一个明文反复计算结果是一样的那么考虑用js来完成逆向工作

// 特征:
// "010001" -> 这是16进制 -> 65537
// setMaxDigits
// RSAKeyPair
// encryptedString

const {
	setMaxDigits,
	RSAKeyPair,
	encryptedString
} = require("./Ras加密");

function c(a,b,c){
    var d,e;
    return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d,a)
}

3,用RSA.js文件解决问题

例如:网易云案例

const CryptoJS = require("crypto-js")
var window = this

const {
	setMaxDigits,
	RSAKeyPair,
	encryptedString
} = require("./Ras加密");
!function() {
    function a(a) {
        var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
        for (d = 0; a > d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
        return c
    }
    function b(a, b) {
        var c = CryptoJS.enc.Utf8.parse(b)
          , d = CryptoJS.enc.Utf8.parse("0102030405060708")
          , e = CryptoJS.enc.Utf8.parse(a)
          , f = CryptoJS.AES.encrypt(e, c, {
            iv: d,
            mode: CryptoJS.mode.CBC
        });
        return f.toString()
    }
    function c(a, b, c) {
        var d, e;
        return setMaxDigits(131),
        d = new RSAKeyPair(b,"",c),
        e = encryptedString(d, a)
    }
    function d(d, e, f, g) {
        var h = {}
          , i = a(16);
        return h.encText = b(d, g),
        h.encText = b(h.encText, i),
        h.encSecKey = c(i, e, f),
        h
    }
    function e(a, b, d, e) {
        var f = {};
        return f.encText = c(a + e, b, d),
        f
    }
    window.asrsea = d,
    window.ecnonasr = e
}();

    var params = {
        csrf_token: "",
        encodeType: "aac",
        ids: "[1325905146]",
        level: "standard"
    }
    var second = '010001'
    var third = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    var forth = '0CoJUm6Qyw8W8jud'


function fn(params) {
       return window.asrsea(JSON.stringify(params),second,third,forth)

4, RSA普通解密

#加密解密
from Crypto.Cipher import PKCS1_v1_5
#加载key
from Crypto.PublicKey import RSA

import base64

f = open('./rsa_miyao.pem',mode='r',encoding='utf-8')
# 拿到已经生成好的秘钥
pri_key = f.read()
f.close()
# 拿到秘钥
rsakey = RSA.importKey(pri_key)
# 生成解密器对象
rsa = PKCS1_v1_5.new(key=rsakey)
miwen = 'mmf28CJEtFU2Y6C/qx10xoaRmsiY2at3LBjHR5DFdnG9V+5sGPFaMGDGM4OBVWKKJNuSFZgGL9Y409mbh32IKRL4TZYnc0RvJH/0t38d7AmnqnHAyTRUvpKlPzzJg559md6BcTA/ZpYZ4WAtXRuysMvuPTdlRvog2ceGJDXURajU3KyzHXFA9Hc+AamVL75D+YKrOB6n9YeV7n4+DK5mqouNlLp6Plee39vYBzN0IKkzyD6RatmVVUIxJCsUJmeJgIdnBGEuRA9bGNOG3VQa7NF/syWjiRNbKYz+KZHx+RtQ9GuzmPhtJbjh8anPeR2kzNwgfD1HiKhIBDQKVQH/eA=='
#对密文进行base64解码转换成字节
base64_2bs = base64.b64decode(miwen)
#对拿到的字节进行rsa解密得到明文字节
# 解密第二个参数给个None,意思是解密时出错了返回None
mingwen_bs = rsa.decrypt(base64_2bs,None)
#对明文字节进行解码得到明文
mingwen = mingwen_bs.decode('utf-8')
print(mingwen)

11 异步框架特征

## 异步框架 ---- 固定逻辑
## next 表示下一步是哪里
## return 语句表示给下一步传递的消息
## sent是接受上一步return返回的东西
## abrupt 第一个参数是return 表示该异步逻辑,彻底结束,返回值为第二个参数;
## stop 表示彻底终结该异步框架


return d().wrap((function(e) {
                    for (; ; )  // 死循环
                        switch (e.prev = e.next) {
                        case 0: // 第一次一定执行case 0
                            return e.next = 2, // 表示下一步执行哪里(表示下一步执行case 2,返回的内容是传递给case 2)
                            me.search.getSearchSalaryList(pe(pe({}, y), {}, {
                                pageNum: f.current,
                                limit: 15
                            }));
                        case 2:
                            t = e.sent,  // 上一步return的东西,上一步返回的内容100%是promise对象
                            a = t.resdata,
                            1 == t.rescode && a && (n = a.salarys,
                            r = a.pageCount,
                            c = a.totalCountStr,
                            l = a.company,
                            s = a.recCompany,
                            x((function(e) {
                                return (0,
                                z.JO)(f.current, e, n)
                            }
                            )),
                            F(+r || 0),
                            K(c || ""),
                            Z(l || null),
                            D(s || []),
                            J(!1));
                        case 6:
                        case "end":
                            return e.stop() // 彻底停止
                        }
                }
                ), e)

二,网页解析模块二

1,re正则

1、匹配单个字符与数字

匹配 说明
. 匹配除换行符以外的任意字符,当flags被设置为re.S时,可以匹配包含换行符以内的所有字符
[] 里面是字符集合,匹配[]里任意一个字符
[0123456789] 匹配任意一个数字字符
[0-9] 匹配任意一个数字字符
[a-z] 匹配任意一个小写英文字母字符
[A-Z] 匹配任意一个大写英文字母字符
[A-Za-z] 匹配任意一个英文字母字符
[A-Za-z0-9] 匹配任意一个数字或英文字母字符
[^lucky] []里的^称为脱字符,表示非,匹配不在[]内的任意一个字符
1 以[]中内的某一个字符作为开头
\d 匹配任意一个数字字符,相当于[0-9]
\D 匹配任意一个非数字字符,相当于[^0-9]
\w 匹配字母、下划线、数字中的任意一个字符,相当于[0-9A-Za-z_]
\W 匹配非字母、下划线、数字中的任意一个字符,相当于[^0-9A-Za-z_]
\s 匹配空白符(空格、换页、换行、回车、制表),相当于[ \f\n\r\t]
\S 匹配非空白符(空格、换页、换行、回车、制表),相当于[^ \f\n\r\t]

2、匹配锚字符

锚字符:用来判定是否按照规定开始或者结尾

匹配 说明
^ 行首匹配,和[]里的^不是一个意思
$ 行尾匹配

3、限定符

限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。

匹配 说明
(xyz) 匹配括号内的xyz,作为一个整体去匹配 一个单元 子存储
x? 匹配0个或者1个x,非贪婪匹配
x* 匹配0个或任意多个x
x+ 匹配至少一个x
x{n} 确定匹配n个x,n是非负数
x{n,} 至少匹配n个x
x{n,m} 匹配至少n个最多m个x
x|y |表示或的意思,匹配x或y

通用flags(修正符)

说明
re.I 是匹配对大小写不敏感
re.S 使.匹配包括换行符在内的所有字符

^a 匹配a开头的

2 匹配一个小写字母a并且a作为开头,等同于^a

[^a] 匹配一个小写字母a以外的任意字符

4,匹配符总结

1  []原子表
[a]  匹配一个小写字母a
[1]  匹配一个数字1
[ab] 匹配一个小写字母a或者b
[a1] 匹配一个小写字母a或者数字1
[123] 匹配一个数字1或者2或者3
[a-z] 匹配任意一个小写字母
[A-Z] 匹配任意一个大写字母
[a-zA-Z]  匹配任意一个字母
[0-9] 匹配任意一个数字 0-9
[a-zA-Z0-9]  匹配任意一个数字 0-9或者任意一个字母
以上不管[]中有多少内容 只匹配一个

2 {m}  限定符 不能单独使用  限定前面那个正则的m次
a     匹配一个小写字母a
ab    匹配小写字母ab
aa     匹配2个小写字母a
a{2}     匹配2个小写字母a
ab{2}    匹配小写字母abb
a{2}b{2}    匹配小写字母aabb
[a-zA-Z]{5}  匹配任意5个字母

3 {m, n}  限定符 不能单独使用  限定前面那个正则的m-n次
[a-zA-Z]{3,5}  匹配任意3-5个字母

4 {m,}  限定符 不能单独使用  限定前面那个正则的m次
[a-zA-Z]{3,}  至少匹配任意3个字母

5 ^...开始
abc    匹配abc三个字母
^abc    匹配以abc开头的三个字母
^[a]  匹配一个小写字母a并且a作为开头  等同于  ^a
[^a]  匹配一个小写字母a以外的任意字符


6 $   以...结尾
abc    匹配abc三个字母并且作为结尾
^abc     匹配以abc开头的三个字母
abc$    匹配abc三个字母并且作为结尾

7 ^$ 一般组合使用  (完全匹配)
匹配手机号
1[3-9][0-9]{9}
^1[3-9][0-9]{9}$

8 ?  匹配前面的正则0次或1次 相当于 {0,1}
-?[1-9] 匹配正负1-9

9 .  匹配换行符以外的任意字符

10 *  匹配任意次 {0,}

11  .*  匹配除换行符以外任意字符任意次 贪婪模式

12 .*?   匹配除换行符以外任意字符任意次 拒绝贪婪模式   (用的多)

13  +   匹配至少一次  相当于{1, }

14 .+    匹配除换行符以外任意字符至少1次 贪婪模式

15 .+?   匹配除换行符以外任意字符至少1次 拒绝贪婪模式

16  ()   子存储(会把括号里的值单独的保存下来)  一个单元 (ab)|(cd)

17  \w  匹配一位数字,字母,下划线  [a-zA-Z0-9_]

18  \W

19  \d  匹配一位数字   [0-9]

20  \D 和上面相反   [^0-9]


21  \s  匹配空白符

22  \S  和上面相反

5、贪婪与非贪婪

#贪婪模式  

#贪婪概念:匹配尽可能多的字符

# + .+  匹配换行符以外的字符至少一次
# + .*  匹配换行符以外的字符任意次

res = re.search('<b>.+</b>', '<b></b><b>b标签</b>')
res = re.search('<b>.*</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>')

# .+?  匹配换行符以外的字符至少一次  拒绝贪婪
# + .*?   匹配换行符以外的字符任意次      拒绝贪婪

res = re.search('<b>.+?</b>', '<b>b标签</b><b>b标签</b>')
res = re.search('<b>.*?</b>', '<b>b标签</b><b>b标签</b><b>b标签</b><b>b标签</b>')

6,正则用到的方法

1,re.search
# re.search
# 返回第一个匹配的结果
res = re.search('a','abcdef343')    # 返回一个对象 <re.Match object; span=(0, 1), match='a'>
# 匹配上才可以用group拿结果,不然的话匹配不上返回None,不能用group,要不然会报错
print(res.group())  # a
2,re.match
res = re.match('\d{2}','123')
print(res.group())

#match函数 
# match 必须第一位就开始匹配  否则匹配失败
# 给当前匹配到的结果起别名
s = '3G4HFD567'
x = re.match("(?P<value>\d+)",s)
print(x.group(0)) # 3
print(x.group('value')) # 3
3,re.findall
# findall
str = '<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>'
res = re.findall('<br>.*?</br>',str)   # ['<br>加粗1</br>', '<br>加粗2</br>', '<br>加粗3</br>', '<br></br>']
res = re.findall('<br>.*</br>',str)   # ['<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>']
res = re.findall('<br>.+?</br>',str)   # ['<br>加粗1</br>', '<br>加粗2</br>', '<br>加粗3</br>']
res = re.findall('<br>.+</br>',str)   # ['<br>加粗1</br><br>加粗2</br><br>加粗3</br><br></br>']

Str = '''
<a href="http://www.baidu.com">百度</a>
<A href="https://www.taobao.com">淘宝</A>
<a href="https://www.sina.com">新
浪</a>
'''

# 1,匹配出所有小写a的超链接
print(re.findall('<a href=".*?">.*?</a>',Str))
# ['<a href="http://www.baidu.com">百度</a>']

# .*? 匹配任意字符任意次,拒绝贪婪
print(re.findall('<a href=".*?">.*?</a>',Str,flags=re.S))
# ['<a href="http://www.baidu.com">百度</a>', '<a href="https://www.sina.com">新\n浪</a>']

# 2,匹配所有小写a或者大写A的超链接
print(re.findall('<[aA] href=".*?">.*?</[aA]>',Str,flags=re.S))
# ['<a href="http://www.baidu.com">百度</a>', '<A href="https://www.taobao.com">淘宝</A>', '<a href="https://www.sina.com">新\n浪</a>']

# 用 re.I 匹配大小写字符  re.S 可以匹配换行符
print(re.findall('<a href=".*?">.*?</a>',Str,flags=re.S | re.I))
# ['<a href="http://www.baidu.com">百度</a>', '<A href="https://www.taobao.com">淘宝</A>', '<a href="https://www.sina.com">新\n浪</a>']

# 3,获取网址和名称  () 给谁加括号,谁就返回
print(re.findall('(<a href="(.*?)">(.*?)</a>)',Str,flags=re.S | re.I))
# [('<a href="http://www.baidu.com">百度</a>', 'http://www.baidu.com', '百度'), ('<A href="https://www.taobao.com">淘宝</A>', 'https://www.taobao.com', '淘宝'), ('<a href="https://www.sina.com">新\n浪</a>', 'https://www.sina.com', '新\n浪')]
4,finditer
res = re.finditer('[a-z]','asdjgedksa43g')
print(res)  # 返回一个迭代器  <callable_iterator object at 0x0000015D8995F790>
print(next(res)) # <re.Match object; span=(0, 1), match='a'> 是一个对象,用group来取值
print(next(res).group()) # s

for i in res:
	print(i) # 返回一个迭代器  <callable_iterator object at 0x0000015D8995F790>
	print(i.group()) # 返回结果

5,group 和 groups 区别
# group 取第一个匹配的
print(re.search("<b>.*?</b>","<b>加粗</b>").group())  # <b>加粗</b>
print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group())  # <b>加粗</b>
print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(0))  # <b>加粗</b>
print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(1))  # 加粗
# print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group(2))  # 报错
print(re.search("<b>(?P<val>.*?)</b>","<b>加粗</b>").group('val'))  # 加粗

# goups 返回所有括号中的值
print(re.search("<a href='(.*?)'>(.*?)</a>","<a href='www.baidu.com'>百度</a>").groups()) # ('www.baidu.com', '百度')
6,re.split 正则拆分
print(re.split('\d','fdas3fedsa5fd45fdsa34fg4')) # ['fdas', 'fedsa', 'fd', '', 'fdsa', '', 'fg', '']
7,re.sub 正则替换
print(re.sub('\d','---','ab1fdsa456fdsa34fds65as35')) # ab---fdsa---------fdsa------fds------as------
8,re.compile
# compile函数
re_phone = re.compile(r"(0\d{2,3}-\d{7,8})")

s1 = "lucky's phone is 010-88888888"
s2 = "kaige's phone is 010-99999999"
ret1 = re_phone.search(s1)
ret2 = re_phone.search(s2)

2,xpath解析

2-1 导入使用
#导入和使用
from lxml import etree
html_tree = etree.HTML(html字符串)
html_tree.xpath()
# 使用xpath路径查询信息,返回一个列表

from lxml import etree
# 第一种方式
parse = etree.HTMLParser(encoding='UTF-8')
tree = etree.parse('./素材/豆瓣.html', parser=parse)
print(tree)

# 第二种 推荐
data = open('./素材/豆瓣.html', 'r', encoding='UTF-8').read()
tree = etree.HTML(data)
print(tree)

2-2 xpath 的使用
2-2-1 特定路径匹配
# 找登陆 获取当前路径下的所有匹配的a
a_list = tree.xpath('/html/body/div/div/div/a')


for a in a_list:
    print(a)  # <Element a at 0x2bdd868bc00>
    print(a.text)  # 登录 (获取文本)
    ## 想要把节点转换成看得懂的字符串标签数据
    print(etree.tostring(a, encoding='UTF-8').decode('UTF-8')) 
    # <a href="https://www.douban.com/accounts/login?source=book" class="nav-login" rel="nofollow">登录</a>
    

a_list = tree.xpath('/html/body/div/div/div/a')
a_list = tree.xpath('/html/body/div/div/div/div/p/text()')
a_list = tree.xpath('/html/body/div/div/div[1]/a[1]/text()')
a_list = tree.xpath('/html/body/div/div/div//a/text()')
2-2-2 获取当前路径下的文本
# 第一种
a_list = tree.xpath('/html/body/div/div/div/a/text()')

# 第二种
a_list = tree.xpath('/html/body/div/div/div/a')
for a in a_list:
    print(a.text)  #(获取文本)
2-2-3 //
# 不考虑当前所在位置

# 我想获取当前对象里所有的a
a_list = tree.xpath('//a') 
a_list = tree.xpath('//a/text()')
2-2-4 获取属性
# 获取img的src属性值
img_src = tree.xpath('//ul/li/a/img/@src')
img_src = tree.xpath('//ul/li//a/img/@src')
2-2-5 添加条件
# 添加class条件
img_src = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')
img_src = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div[@class="detail-frame"]/h2/a/text()')
2-2-6 位置查找
# 获取第一个li
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[1]/div/h2/a/text()')

# 获取第二个li
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[2]/div/h2/a/text()')

# 获取最后一个li
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[last()]/div/h2/a/text()')

# 获取倒数第二个li
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[last()-1]/div/h2/a/text()')

# 获取前俩个li
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li[position()<3]/div/h2/a/text()')

# 可以使用列表切片解决啊
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')[0]
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')[0:2]
2-2-7 ./ .//
# 一个完整的xpath路径,但是可以拆分
li = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li/div/h2/a/text()')

# 先匹配到li 再继续往下匹配
li_list = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li')
for li in li_list:
    print(li) # xpath 对象
    # 从当前位置向下匹配
    print(li.xpath('./div/h2/a/text()'))
    print(li.xpath('.//div/h2/a/text()'))
2-2-8 属性(一般用不到)
# 获取ul的class属性为cover-col-4 clearfix的ul下面的儿子li
li_list = tree.xpath('//ul[@class="cover-col-4 clearfix"]/li')

# 选取所有ul具有class属性的节点
li_list = tree.xpath('//ul[@class]')

# 获取所有ul具有aa属性的节点
li_list = tree.xpath('//ul[@aa]')

2-2-9 多条件 and or |
# 多个条件  and  or
print(tree.xpath('//div[@id="db-global-nav"]'))
print(tree.xpath('//div[@class="global-nav"]'))

# 获取同时满足id为db-global-nav class为global-nav 的ul
print(tree.xpath('//div[@class="global-nav" and @id="db-global-nav"]'))

# 获取满足id为db-global-nav 或 class为global-nav 的ul
print(tree.xpath('//div[@class="global-nav" or @id="db-global-nav"]'))

# |
print(tree.xpath('//div[@id="db-global-nav"] | //div[@class="global-nav"]'))
2-3 xpath语法
2-3-1 路径表达式
路径表达式 结果
/ul/li[1] 选取属于 ul子元素的第一个 li元素。
/ul/li[last()] 选取属于 ul子元素的最后一个 li元素。
/ul/li[last()-1] 选取属于 ul子元素的倒数第二个 li元素。
//ul/li[position()❤️] 选取最前面的两个属于 ul元素的子元素的 li元素。
//a[@title] 选取所有拥有名为 title的属性的 a元素。
//a[@title=‘xx’] 选取所有 a元素,且这些元素拥有值为 xx的 title属性。
2-3-2 选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。 一般用于浏览器copy xpath会出现
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

实例

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/ul/* 选取 ul元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。
//node() 获取所有节点

选取未知节点

路径表达式 结果
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。
2-3-3 逻辑运算
  • 查找所有id属性等于head并且class属性等于s_down的div标签

    //div[@id="head" and @class="s_down"]
    
  • 选取文档中的所有 title 和 price 元素。

    //title | //price
    

    注意: “|”两边必须是完整的xpath路径

2-3-4 属性查询
  • 查找所有包含id属性的div节点

    //div[@id]
    
  • 查找所有id属性等于maincontent的div标签

    //div[@id="maincontent"]
    
  • 查找所有的class属性

    //@class
    
  • //@attrName

    //li[@name="xx"]//text()  # 获取li标签name为xx的里面的文本内容
    
  • 获取第几个标签 索引从1开始

    tree.xpath('//li[1]/a/text()')  # 获取第一个
    tree.xpath('//li[last()]/a/text()')  # 获取最后一个
    tree.xpath('//li[last()-1]/a/text()')  # 获取倒数第二个
    
2-3-5 内容查询
  • 查找所有div标签下的直接子节点h1的内容

    //div/h1/text()
    
  • 属性值获取

    //div/a/@href   获取a里面的href属性值
    
  • 获取所有

    //*  #获取所有
    //*[@class="xx"]  #获取所有class为xx的标签
    
  • 获取节点内容转换成字符串

    c = tree.xpath('//li/a')[0]
    result=etree.tostring(c, encoding='utf-8')
    print(result.decode('UTF-8'))
    

3,bs4解析

1,导入和使用

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
# html进行美化
print(soup.prettify())

# 可以传入一段字符串或一个文件句柄.
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html>data</html>", 'lxml')

# beautifulsoup  lxml
from bs4 import BeautifulSoup

f = open('./素材/豆瓣.html', 'r', encoding='UTF-8')
data = f.read()
# 第一种方式  建议使用这种
soup = BeautifulSoup(data, 'lxml')

# 第二种方式()
soup = BeautifulSoup(open('./素材/豆瓣.html', 'r', encoding='UTF-8'), 'lxml')
print(soup)
print(type(soup))

2,浏览器结构化数据

2-1 .语法 soup对象.标签名
## .标签和.find 只获取第一个

soup.title  # 获取标签title
# <title>The Dormouse's story</title>

soup.title.name   # 获取标签名称
# 'title'

soup.title.string   # 获取标签title内的内容
# 'The Dormouse's story'

soup.title.parent  # 获取父级标签

soup.title.parent.name  # 获取父级标签名称
# 'head'


print(soup.title)
print(soup.div)
print(soup.a)
print(soup.img)
print(soup.abc)  # 不存在则为None

2-2 soup.find
# find  上面的.标签名 是当前find的简写   find可以给条件   .标签和.find 只获取第一个
print(soup.find('title'))
print(soup.find('div'))
# 获取soup对象中的第一个img标签
print(soup.img)
print(soup.find('img'))
2-3 获取属性
.语法或者find都只获取第一个

print(soup.div.attrs) # {'id': 'db-global-nav', 'class': ['global-nav']}
print(soup.div.attrs['id'])  # db-global-nav
print(soup.div.attrs['class']) # ['global-nav']
print(soup.div['id']) # db-global-nav
print(soup.div['class']) # ['global-nav']
2-4 find 条件查找
print(soup.find('a', class_="cover")) # 查找第一个class为cover的a标签
print(soup.find('p', class_="rating"))
print(soup.find('div', id="wrapper"))
print(soup.find('div', id="db-global-nav", class_="global-nav"))

## 可以用字典的形式查找满足多个属性的标签
print(soup.find('div', attrs={'id': "db-global-nav", 'class': "global-nav"}))

# class为多个的中间空格隔开就行
print(soup.find('ul', attrs={'class': "cover-col-4 clearfix"}))
print(soup.find('ul', attrs={'class': "caover-col-4 clearfix"}))
2-5 find 和 .语法组合使用
print(soup.find('a', class_="cover"))
print(type(soup.find('a', class_="cover")))  # <class 'bs4.element.Tag'> bs4对象才可以用组合使用
print(soup.find('a', class_="cover").find('img')) # 获取第一个a标签里的第一个img
print(soup.find('a', class_="cover").img) # 获取第一个a标签里的第一个img
print(soup.find('a', class_="cover").img.attrs) # {'src': 'https://img3.doubanio.com/mpic/s29535271.jpg'}
print(soup.find('a', class_="cover").img.attrs['src']) # https://img3.doubanio.com/mpic/s29535271.jpg
print(soup.find('a', class_="cover").img['src']) # https://img3.doubanio.com/mpic/s29535271.jpg
2-6 写入到文件
# 写入本地需要注意的点
with open('img.html', 'w', encoding='UTF-8') as f:
    f.write(str(soup.find('a', class_="cover").img))  ## 写入本地需要先转换为字符串,要不然会报错
    # f.write(soup.title.string)
2-7 获取文本
print(soup.title) # <title>新书速递</title>
print(soup.title.string) # 新书速递
print(type(soup.title.string)) # <class 'bs4.element.NavigableString'>
print(soup.title.strings)  # generator 对象
print(list(soup.title.strings))  # ['新书速递']

print(soup.title.text) # 新书速递
print(soup.title.get_text()) # 新书速递
print(soup.title.stripped_strings) # generator 对象
print(list(soup.title.stripped_strings)) # ['新书速递']
2-8 多层嵌套标签
## string 和 strings 区别
print(soup.find('div', class_="detail-frame"))
print(soup.find('div', class_="detail-frame").string)  # None
print(soup.find('div', class_="detail-frame").strings) # generator 对象
print(list(soup.find('div', class_="detail-frame").strings))  # 获取子子孙孙的文本 生成器返回

# text , get_text() , stripped_strings 区别
print(soup.find('div', class_="detail-frame").text)  # 返回所有文本字符串 包含非打印字符
print(soup.find('div', class_="detail-frame").get_text())  #和text一样 返回所有文本字符串 包含非打印字符
print(list(soup.find('div', class_="detail-frame").stripped_strings))  # 返回所有去除空白字符后的文本
2-9 prettify 美化
print(soup.find('div', class_="detail-frame"))
print(soup.find('div', class_="detail-frame").prettify())
2-10 find_all
# 查找所有 和find区别就是  查找所有 参数一样使用  find返回一个  find_all返回列表
print(soup.find_all('img'))   # 返回列表
print(soup.find_all('div', id="db-global-nav", class_="global-nav"))
print(soup.find_all('div', attrs={'id': "db-global-nav", 'class': "global-nav"}))
print(soup.find_all('div', limit=2))  # 取几个值  没啥用(我们用切片就完事了)
print(soup.find_all(['h2', 'img']))  # 获取h2和img标签
2-11 select
# select 查找所有  条件是选择器
print(soup.select('img'))
print(soup.select('.cover'))
print(soup.select('#db-global-nav'))
print(soup.select('.cover-col-4.clearfix'))
print(soup.select('.cover-col-4.clearfix#abc'))
print(soup.select('ul[class="cover-col-4 clearfix"]'))
print(soup.select('.cover-col-4.clearfix > li img'))

三,常用工具模块

1,os模块

import os
# 判断文件是否存在
os.path.exists()  #  判断文件或者文件夹是否存在,返回布尔值

os.path.join()    # 路径拼接
os.path.join(path1,path2,path3)

os.makedirs()     # 创建文件夹




os.getcwd()  # 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname")  # 改变当前脚本工作目录;相当于shell下cd
os.curdir  # 返回当前目录: ('.')
os.pardir  # 获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2')    # 可生成多层递归目录
os.removedirs('dirname1')    # 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname')    # 生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname')    # 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname')    # 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove()  # 删除一个文件
os.rename("oldname","newname")  # 重命名文件/目录
os.stat('path/filename')  # 获取文件/目录信息
os.sep    # 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep    # 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep    # 输出用于分割文件路径的字符串 win下为;,Linux下为:
os.name    # 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command")  # 运行shell命令,直接显示
os.environ  # 获取系统环境变量
os.path.abspath(path)  # 返回path规范化的绝对路径
os.path.split(path)  # 将path分割成目录和文件名二元组返回
os.path.dirname(path)  # 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path)  # 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素

os.path.exists(path)  # 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path)  # 如果path是绝对路径,返回True
os.path.isfile(path)  # 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path)  #如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]])  # 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path)  # 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path)  # 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) # 返回path的大小

2,json模块

## JSON格式兼容的是所有语言通用的数据类型,不能支持单一数据类型

# JSON ---------字典
dic = json.loads(s)

# 字典-----------JSON
s = json.dumps(dic)

import json
## 有时保存下来的中文数据打开后发现变成ASCII码,这是需要将ensure_ascii参数设置成False
    data = {
        'name' : 'name',
        'age' : 20,
    }
    json_str = json.dumps(data,ensure_ascii=False)

# josn.dump
    data = {
        'name':'name',
        'age':20,
    }
    #讲python编码成json放在那个文件里
    filename = 'a.txt'
    with open (filename,'w') as f:
        json.dump(data ,f)

## json.load    
    data  = {
        'name':'name',
        'age':20
    }
    filename = 'a.txt'
    with open (filename,'w') as f:
        json.dump(data,f)
    with open (filename) as f_:
        print(json.load(f_))


image-20230629122312231

2.1 猴子补丁S

### 在入扣文件处进行猴子补丁
import json
import ujson

def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads
    
monkey_patch_json()

3,random模块

a = random.choice('abcdefghijklmn')  # 参数也可以是个列表

a = "abcdefghijklmnop1234567890"
b = random.sample(a,3)   # 随机取三个值,返回一个列表

num = random.randint(1,100)


1,random.random()   # 得到的是 0----1 之间的小数 -------------- 0.6400374661599008
2,random.randint(1,3) # 范围是  [1,3]  包头包尾
3,random.randrange(1,2) # 范围是 [1,3)  顾头不顾尾
4,random.chioce('abcdefghijklmn')  # 参数也可以是个列表
5,random.sample(['a','b','c','d'],3)  # 随机取三个值,返回一个列表

6,random.uniform(1,3)  # 得到 1-------3 之间的浮点数

item = [1,2,3,4,5,6,7,8,9]
7,random.shuffle(item) # 洗牌,打乱顺序  [4, 1, 2, 9, 7, 5, 6, 3, 8]

4,string模块

string.ascii_letters  # 返回小写字母大写字母字符串
# 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

string.ascii_uppercase # 返回大写字母的字符串
# 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

string.ascii_lowercase # 返回小写字母的字符串
# 'abcdefghijklmnopqrstuvwxyz'

string.punctuation # 打印特殊字符
# '!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~'

string.digits  # 打印数字
# '0123456789'

5,异常处理

5.1 错误类型

## 语法错误 SyntaxError
## 逻辑错误 NameError  IndexError  ZeroDivisionError ValueError


## 一种是语法上的错误SyntaxError,这种错误应该在程序运行前就修改正确
 if  
  File "<stdin>", line 1
    if
     ^
SyntaxError: invalid syntax

# -------------------------------------------------------------------------------------------
# TypeError:数字类型无法与字符串类型相加
1+2# ValueError:当字符串包含有非数字的值时,无法转成int类型
    num=input(">>: ") #输入hello
    int(num)

# NameError:引用了一个不存在的名字x
	x

# IndexError:索引超出列表的限制
    l=['egon','aa']
    l[3]

# KeyError:引用了一个不存在的key
    dic={'name':'egon'}
    dic['age']

# AttributeError:引用的属性不存在
    class Foo:
        pass
    Foo.x

# ZeroDivisionError:除数不能为0
	1/0

5.1 逻辑错误两种处理方式

5.1.1 错误时可以预知的
age = input(">>:").strip()

if age.isdigit():  ## 可以用if 判断避免错误出现
    age = int(age)  ## age必须是数字,才能转换为int类型
    if age > 18:
        print("猜大了")
    else:
        print('猜小了')
5.1.2 错误时不可预知的
## 只要抛出异常同级别的代码不会往下运行

try:
    ##有可能抛出异常的子代码块
 
except 异常类型1 as e:
    pass
except 异常类型2 as e:
    pass

....
else:
    ## 如果被检测的子代码块没有异常发生则运行else
finally:
    ## 无论有没有异常发生都会运行此代码

## --------------------------------------------------------------------------------------------

## 用法一
try:
	print('11111111111')
	l = ['aaa','bbbb']
	l[3]  ## 抛出异常IndexError,该码块同级别的后续代码不会运行
	print('222222222222222')
	xxx
	print('3333333333333333333')
	dic = {'a':1}
	dic['a']
	print('end')
except IndexError as e:
	print('异常处理了')
	print(e)
except NameError as e:
	print('异常处理了')
	print(e)
  ## --------------------------------------------------------------------------------------
# 用法二

print('start')
try:
	print('11111111111')
	l = ['aaa','bbbb']
	l[3]  ## 抛出异常IndexError,该码块同级别的后续代码不会运行
	print('222222222222222')
	# xxx
	print('3333333333333333333')
	dic = {'a':1}
	dic['a']
	print('end')
except (IndexError,NameError) as e:
	print('异常处理了')
except KeyError as e:
	print('字典的key不存在',e)

    ## ------------------------------------------------------------------------------------------
## 用法三
## 万能异常
    
print('start')
try:
	print('11111111111')
	l = ['aaa','bbbb']
	l[3]  ## 抛出异常IndexError,该码块同级别的后续代码不会运行
	print('222222222222222')
	# xxx
	print('3333333333333333333')
	dic = {'a':1}
	dic['a']
	print('end')
except Exception as e:  ## 万能异常,都能匹配上
	print('万能异常')
## ----------------------------------------------------------------------------------------

## 方法四
##tyr 不能跟 else 连用

try:
	print('11111111111111')
	print('33333333333')
	print('2222222222222222222')
except Exception as e:
	print('所有异常都能匹配到')
else:
	print('==============>')

print('end...........')

## ------------------------------------------------------------------------------------------

## 方法五
## finally 可以单独与try配合使用

print('start')
try:
	print('11111111111')
	l = ['aaa','bbbb']
	l[3]  ## 抛出异常IndexError,该码块同级别的后续代码不会运行
	print('222222222222222')
	xxx
	print('3333333333333333333')
	dic = {'a':1}
	dic['a']
	print('end')
finally:  
    ## 应该把被检测代码中,回收系统化资源的代码放这里
	print('我不处理异常,无论是否发生异常我都会运行')

6,打码平台使用

import base64
import json
import requests
def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ''


if __name__ == "__main__":
    img_path = "./code.png"
    result = base64_api(uname='xxxxx', pwd='xxxxx', img=img_path, typeid=3)
    print(result)
        
import base64
import json
import requests
# 一、图片文字类型(默认 3 数英混合):
# 1 : 纯数字
# 1001:纯数字2
# 2 : 纯英文
# 1002:纯英文2
# 3 : 数英混合
# 1003:数英混合2
#  4 : 闪动GIF
# 7 : 无感学习(独家)
# 11 : 计算题
# 1005:  快速计算题
# 16 : 汉字
# 32 : 通用文字识别(证件、单据)
# 66:  问答题
# 49 :recaptcha图片识别
# 二、图片旋转角度类型:
# 29 :  旋转类型
#
# 三、图片坐标点选类型:
# 19 :  1个坐标
# 20 :  3个坐标
# 21 :  3 ~ 5个坐标
# 22 :  5 ~ 8个坐标
# 27 :  1 ~ 4个坐标
# 48 : 轨迹类型
#
# 四、缺口识别
# 18 : 缺口识别(需要2张图 一张目标图一张缺口图)
# 33 : 单缺口识别(返回X轴坐标 只需要1张图)
# 五、拼图识别
# 53:拼图识别
def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ""


if __name__ == "__main__":
    img_path = "C:/Users/Administrator/Desktop/file.jpg"
    result = base64_api(uname='你的账号', pwd='你的密码', img=img_path, typeid=3)
    print(result)

7,时间模块

7.1 time 模块

import time

# 时间戳 : 从1970年到现在经过的秒数
time.time()     # 时间戳---------用于计算

# 按照某种格式显示时间: 2020-03-30 11:11:11 AM || PM
time.strftime('%Y-%m-%d %H:%M:%S %p')  # 2023-06-27 14:24:38 PM
time.strftime('%Y-%m-%d %X')    # 2023-06-27 14:24:38


#结构化时间
res = time.localtime()  ## --------------获取年月日
print(res)   ## time.struct_time(tm_year=2023, tm_mon=6, tm_mday=27, tm_hour=14, tm_min=26, tm_sec=17, tm_wday=1, 				tm_yday=178, tm_isdst=0)

print(res.tm_year)  ## 年
print(res.tm_mon)   ## 月
print(res.tm_mday)  ## 日
print(res.tm_hour)  ## 小时
print(res.tm_min)   ## 分钟
print(res.tm_sec)   ## 秒
print(res.tm_wday)
print(res.tm_yday)
print(res.tm_isdst)

7.2 datetime 模块

import datetime

datetime.datetime.now() ##  2023-06-27 14:38:31.929938

datetime.datetime.now() + datetime.timedelta(days = 3)  ## 三天后的时间  2023-06-30 14:40:55.794329
# 参数有  days || secondes || weeks || hours || minutes
# days = 3 || -3   参数可以 为负数

7.3 时间格式的转换

import time


1,时间戳 <-----------------> 结构化时间
    # 结构化时间 -------------------------> 时间戳
        s_time = time.localtime()  # 结构化时间
        res = time.mktime(s_time)
        print(res)   # 1687848357.0

    # 时间戳 ---------------------------------> 结构化时间
        tp_time = time.time()
        res = time.localtime(tp_time)
        print(res) # time.struct_time(tm_year=2023, tm_mon=6, tm_mday=27, tm_hour=14, tm_min=48, tm_sec=36, 						tm_wday=1,tm_yday=178, tm_isdst=0)	
    # 时间戳 --------------------------------> 世界标准时间 --------- 跟本地时间差8小时
        tp_time = time.time()
        res = time.gmtime(tp_time)
        print(res)  # time.struct_time(tm_year=2023, tm_mon=6, tm_mday=27, tm_hour=6, tm_min=50, tm_sec=35, 						tm_wday=1,tm_yday=178, tm_isdst=0)
        
 2, 结构化 <-------------------------> 格式化时间
	## time.strptime('%Y-%m-%d %H:%M:%S %p',time.localtime())
	res =  time.strptime('1988-03-03 11:11:11','%Y-%m-%d %H:%M:%S')
    print(res)
    ## time.struct_time(tm_year=1988, tm_mon=3, tm_mday=3, tm_hour=11, tm_min=11, tm_sec=11, tm_wday=3, tm_yday=63, 		tm_isdst=-1)
    
    '1988-03-03 11:11:11' + 7  -----------------------> 结构化时间
    s_time = time.strptime('1988-03-03 11:11:11','%Y-%m-%d %H:%M:%S') # 结构化时间
 	miao = time.mktime(s_time) + 7 * 86400  ## 得到时间戳
    
    struct_time = time.localtime(miao) ##  得到结构化时间
    res = time.strftime('%Y-%m-%d %X',time.localtime(miao)) # 格式化时间
    print(res)   # 1988-03-10 11:11:11
    
    

7.4 ,了解

import time
## linix 操作系统上常见
print(time.asctime())  # Tue Jun 27 15:26:23 2023

8, sys模块

1 sys.argv           # 命令行参数List,第一个元素是程序本身路径,用于获取终端里的参数
2 sys.exit(n)        # 退出程序,正常退出时exit(0)
3 sys.version        # 获取Python解释程序的版本信息
4 sys.maxint         # 最大的Int值
5 sys.path           # 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
6 sys.platform       # 返回操作系统平台名称

8.1 打印进度条

import time

def process():
	recv_size = 0
	total_size = 333333
	
	while recv_size < total_size:
		# 下载了1024个字节数据
		time.sleep(0.05)
		recv_size += 1024
        if recv_size > total_size:
			recv_size = total_size
		percent = recv_size / total_size
		res = int(50 * percent) * "#"
		# 打印进度条
		print('\r[%-50s] %d%%' % (res,100 * percent) ,end='')


process()

## [##################################################] 100%

9,shutii 模块

import shutill

# 将文件内容拷贝到另一个文件中
shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))
    
# 仅拷贝权限。内容、组、用户均不变
shutil.copymode('f1.log', 'f2.log')   #目标文件必须存在

# 拷贝文件
shutil.copyfile('f1.log', 'f2.log') #目标文件无需存在

# 仅拷贝状态的信息,包括:mode bits, atime, mtime, flags
shutil.copystat('f1.log', 'f2.log') #目标文件必须存在

# 拷贝文件和权限
shutil.copy('f1.log', 'f2.log')

# 拷贝文件和状态信息
shutil.copy2('f1.log', 'f2.log')

# 递归的去拷贝文件夹

shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*')) 
# 目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除

shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
'''
通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
'''

#递归的去删除文件
shutil.rmtree('folder1')

#递归的去移动文件,它类似mv命令,其实就是重命名。
shutil.move('folder1', 'folder3')

# 创建压缩包并返回文件路径,例如:zip、tar
# 创建压缩包并返回文件路径,例如:zip、tar

base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
    # 如 data_bak  =>保存至当前路径
    # 如:/tmp/data_bak =>保存至/tmp/
format: 压缩包种类,“zip, “tar”, “bztar”,“gztar”
root_dir: 要压缩的文件夹路径(默认当前目录)
owner: 用户,默认当前用户
group: 组,默认当前组
logger: 用于记录日志,通常是logging.Logger对象


#将 /data 下的文件打包放置当前程序目录
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data')

#将 /data下的文件打包放置 /tmp/目录
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data') 


#shutil 对压缩包的处理是调用 ZipFile 和 TarFile 两个模块来进行的,详细:
import zipfile
    # 压缩
        z = zipfile.ZipFile('laxi.zip', 'w')
        z.write('a.log')
        z.write('data.data')
        z.close()

    # 解压
        z = zipfile.ZipFile('laxi.zip', 'r')
        z.extractall(path='.')
        z.close()


import tarfile
   # 压缩
       t=tarfile.open('/tmp/egon.tar','w')
       t.add('/test1/a.py',arcname='a.bak')
       t.add('/test1/b.py',arcname='b.bak')
       t.close()


    # 解压
        t=tarfile.open('/tmp/egon.tar','r')
        t.extractall('/egon')
        t.close()

10,pickle模块(有兼容性问题,了解就行)

import pickle

res = pickle.dumps({1,2,3,4,5})
print(res)
# b'\x80\x04\x95\x0f\x00\x00\x00\x00\x00\x00\x00\x8f\x94(K\x01K\x02K\x03K\x04K\x05\x90.'

res = pickle.loads(res)
print(res)
# {1, 2, 3, 4, 5}

# coding:utf-8
import pickle

with open('a.pkl',mode='wb') as f:
    # 一:在python3中执行的序列化操作如何兼容python2
    # python2不支持protocol>2,默认python3中protocol=4
    # 所以在python3中dump操作应该指定protocol=2
    pickle.dump('你好啊',f,protocol=2)

with open('a.pkl', mode='rb') as f:
    # 二:python2中反序列化才能正常使用
    res=pickle.load(f)
    print(res)

11,xml模块

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank updated="yes">5</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank updated="yes">69</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>


xml协议在各个语言里的都 是支持的,在python中可以用以下模块操作xml:
# print(root.iter('year')) #全文搜索
# print(root.find('country')) #在root的子节点找,只找一个
# print(root.findall('country')) #在root的子节点找,找所有

import xml.etree.ElementTree as ET
 
tree = ET.parse("xmltest.xml")
root = tree.getroot()
print(root.tag)
 
#遍历xml文档
    for child in root:
        print('========>',child.tag,child.attrib,child.attrib['name'])
        for i in child:
            print(i.tag,i.attrib,i.text)

    #只遍历year 节点
    for node in root.iter('year'):
        print(node.tag,node.text)
#---------------------------------------

import xml.etree.ElementTree as ET
 
tree = ET.parse("xmltest.xml")
root = tree.getroot()
 
#修改
    for node in root.iter('year'):
        new_year=int(node.text)+1
        node.text=str(new_year)
        node.set('updated','yes')
        node.set('version','1.0')
    tree.write('test.xml')
 
 
#删除node
    for country in root.findall('country'):
       rank = int(country.find('rank').text)
       if rank > 50:
         root.remove(country)

    tree.write('output.xml')

#在country内添加(append)节点year2
    import xml.etree.ElementTree as ET
    tree = ET.parse("a.xml")
    root=tree.getroot()
    for country in root.findall('country'):
        for year in country.findall('year'):
            if int(year.text) > 2000:
                year2=ET.Element('year2')
                year2.text='新年'
                year2.attrib={'update':'yes'}
                country.append(year2) #往country节点下添加子节点

    tree.write('a.xml.swap')



自己创建xml文档:
    import xml.etree.ElementTree as ET

    new_xml = ET.Element("namelist")
    name = ET.SubElement(new_xml,"name",attrib={"enrolled":"yes"})
    age = ET.SubElement(name,"age",attrib={"checked":"no"})
    sex = ET.SubElement(name,"sex")
    sex.text = '33'
    name2 = ET.SubElement(new_xml,"name",attrib={"enrolled":"no"})
    age = ET.SubElement(name2,"age")
    age.text = '19'

    et = ET.ElementTree(new_xml) #生成文档对象
    et.write("test.xml", encoding="utf-8",xml_declaration=True)

    ET.dump(new_xml) #打印生成的格式

12,configparser模块(导入某种格式的配置文件)

## 配置文件内容

[section1]
k1 = v1
k2:v2
user=egon
age=18
is_admin=true
salary=31

[section2]
k1 = v1

12.1 读取

import configparser

config=configparser.ConfigParser()
config.read('a.cfg') # 读取配置文件

#查看所有的标题
res=config.sections() #['section1', 'section2']
print(res)

#查看标题section1下所有key=value的key
options=config.options('section1')
print(options) #['k1', 'k2', 'user', 'age', 'is_admin', 'salary']

#查看标题section1下所有key=value的(key,value)格式
item_list=config.items('section1')
print(item_list) 
#[('k1', 'v1'), ('k2', 'v2'), ('user', 'egon'), ('age', '18'), ('is_admin', 'true'), ('salary', '31')]

#查看标题section1下user的值=>字符串格式
val=config.get('section1','user')
print(val) #egon

#查看标题section1下age的值=>整数格式
val1=config.getint('section1','age')
print(val1) #18

#查看标题section1下is_admin的值=>布尔值格式
val2=config.getboolean('section1','is_admin')
print(val2) #True

#查看标题section1下salary的值=>浮点型格式
val3=config.getfloat('section1','salary')
print(val3) #31.0

12.2 改写

import configparser

config=configparser.ConfigParser()
config.read('a.cfg',encoding='utf-8')


#删除整个标题section2
config.remove_section('section2')

#删除标题section1下的某个k1和k2
config.remove_option('section1','k1')
config.remove_option('section1','k2')

#判断是否存在某个标题
print(config.has_section('section1'))

#判断标题section1下是否有user
print(config.has_option('section1',''))


#添加一个标题
config.add_section('egon')

#在标题egon下添加name=egon,age=18的配置
config.set('egon','name','egon')
config.set('egon','age',18) #报错,必须是字符串


#最后将修改的内容写入文件,完成最终的修改
config.write(open('a.cfg','w'))

13 hashlib 模块

# hash是一类算法,该算法根据传入的内容,经过运算得到一串哈希值

# hash值的特单
	1,传入的内容一样,则得到的结果一样
    2,无论传多大内容,得到的hash值长度一样
    3,不能反向破解

14 subprocess模块

import subprocess 
 '''
 sh-3.2# ls /Users/egon/Desktop |grep txt$
 mysql.txt
 tt.txt
 事物.txt
 '''
## 查看 /Users/jieli/Desktop 下的文件列表
res1=subprocess.Popen('ls /Users/jieli/Desktop',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# shell = True 意思是调一个终端  stdout 是正确结果的输出管道   stderr 是接受错误结果的输出管道
# res1 是对象
print(res2.stdout.read()) # 打印正确的结果,得到的格式是字节,解码用的是系统的编码格式,mac为utf-8
print(res1.stderr.read()) # 打印错误的结果,得到的是字节格式,解码用的是系统的编码格式,windows为gbk

res=subprocess.Popen('grep txt$',shell=True,stdin=res1.stdout,stdout=subprocess.PIPE)
print(res.stdout.read().decode('utf-8'))


#等同于上面,但是上面的优势在于,一个数据流可以和另外一个数据流交互,可以通过爬虫得到结果然后交给grep
res1=subprocess.Popen('ls /Users/jieli/Desktop |grep txt$',shell=True,stdout=subprocess.PIPE)
print(res1.stdout.read().decode('utf-8'))

#windows下:
# dir | findstr 'test*'
# dir | findstr 'txt$'

import subprocess
res1=subprocess.Popen(r'dir C:\Users\Administrator\PycharmProjects\test\函数备课',shell=True,stdout=subprocess.PIPE)
res=subprocess.Popen('findstr test*',shell=True,stdin=res1.stdout,
                 stdout=subprocess.PIPE)

print(res.stdout.read().decode('gbk')) #subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码

15,日志模块(logging)

14.1 日志级别

import logging

CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置

14.2 默认级别为warning,默认打印到终端

import logging

logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')  ## WARNING:root:警告warn
logging.error('错误error')  ## ERROR:root:错误error
logging.critical('严重critical')  ## CRITICAL:root:严重critical

'''
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical
'''

14.3 为logging模块指定全局配置,针对所有logger有效,控制打印到文件中

'''
可在logging.basicConfig()函数中可通过具体参数来更改logging模块默认行为,可用参数有

    filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
    filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
    format:指定handler使用的日志显示格式。
    datefmt:指定日期时间格式。
    level:设置rootlogger(后边会讲解具体概念)的日志级别
    stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了			filename和stream两个参数,则stream参数会被忽略。
'''
## 例如:
logging.basicConfig(
	format = '%(asctime)s -  %(name)s - %(levelname)s - %(module)s' # 就这样自定义格式
)
format参数中可能用到的格式化串:
%(name)s # Logger的名字
%(levelno)s # 数字形式的日志级别
%(levelname)s # 文本形式的日志级别
%(pathname)s # 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s # 调用日志输出函数的模块的文件名
%(module)s # 调用日志输出函数的模块名
%(funcName)s # 调用日志输出函数的函数名
%(lineno)d # 调用日志输出函数的语句所在的代码行
%(created)f # 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d # 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s # 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d # 线程ID。可能没有
%(threadName)s # 线程名。可能没有
%(process)d # 进程ID。可能没有
%(message)s # 用户输出的消息

14.4 使用例子

#========使用

import logging
logging.basicConfig(
    ## 写到文件里的编码格式以系统编码格式为准,Windows为gbk
    filename='access.log',  ## 日志输出的位置
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s', ## 一个日志输出的格式
    datefmt='%Y-%m-%d %H:%M:%S %p', ## 输出里的时间格式
    level=10  ## 日志错误级别
)

logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')

#========结果
access.log内容:
2017-07-28 20:32:17 PM - root - DEBUG -test:  调试debug
2017-07-28 20:32:17 PM - root - INFO -test:  消息info
2017-07-28 20:32:17 PM - root - WARNING -test:  警告warn
2017-07-28 20:32:17 PM - root - ERROR -test:  错误error
2017-07-28 20:32:17 PM - root - CRITICAL -test:  严重critical

14.5 logging模块的Formatter,Handler,Logger,Filter对象

#logger:产生日志的对象
#Filter:过滤日志的对象
#Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端
#Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式

'''
critical=50
error =40
warning =30
info = 20
debug =10
'''


import logging

#1、logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
logger=logging.getLogger(__file__)

#2、Filter对象:不常用,略

#3、Handler对象:接收logger传来的日志,然后控制输出
h1=logging.FileHandler('t1.log') #打印到文件
h2=logging.FileHandler('t2.log') #打印到文件
h3=logging.StreamHandler() #打印到终端

#4、Formatter对象:日志格式
formmater1=logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
)

formmater2=logging.Formatter(
    '%(asctime)s :  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
)

formmater3=logging.Formatter('%(name)s %(message)s',)


#5、为Handler对象绑定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)

#6、将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)

#7、测试
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')

14.6 Logger与Handler的级别

### logger是第一级过滤,然后才能到handler,我们可以给logger和handler同时设置level


#验证
import logging

form=logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S %p',
)

ch=logging.StreamHandler()

ch.setFormatter(form)
# ch.setLevel(10)
ch.setLevel(20)

l1=logging.getLogger('root')
# l1.setLevel(20)
l1.setLevel(10)
l1.addHandler(ch)

l1.debug('l1 debug')

14.7 Logger的继承(了解)

import logging

formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

ch=logging.StreamHandler()
ch.setFormatter(formatter)


logger1=logging.getLogger('root')
logger2=logging.getLogger('root.child1')
logger3=logging.getLogger('root.child1.child2')


logger1.addHandler(ch)
logger2.addHandler(ch)
logger3.addHandler(ch)
logger1.setLevel(10)
logger2.setLevel(10)
logger3.setLevel(10)

logger1.debug('log1 debug')
logger2.debug('log2 debug')
logger3.debug('log3 debug')
'''
2017-07-28 22:22:05 PM - root - DEBUG -test:  log1 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test:  log2 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test:  log2 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test:  log3 debug
'''

14.8 应用

14.8.1 logging配置
"""
logging配置
"""

import os
import logging.config

# 定义三种日志输出格式 开始

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# 定义日志输出格式 结束

logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目录

logfile_name = 'all2.log'  # log文件名

# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)传递
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)  # 生成一个log实例
    logger.info('It works!')  # 记录该文件的运行状态

if __name__ == '__main__':
    load_my_logging_cfg()
14.8.2 使用
"""
MyLogging Test
"""

import time
import logging
import my_logging  # 导入自定义的logging配置

logger = logging.getLogger(__name__)  # 生成logger实例


def demo():
    logger.debug("start range... time:{}".format(time.time()))
    logger.info("中文测试开始。。。")
    for i in range(10):
        logger.debug("i:{}".format(i))
        time.sleep(0.2)
    else:
        logger.debug("over range... time:{}".format(time.time()))
    logger.info("中文测试结束。。。")

if __name__ == "__main__":
    my_logging.load_my_logging_cfg()  # 在你程序文件的入口加载自定义logging配置
    demo()
14.8.3 注意注意注意
"""
MyLogging Test
"""

import time
import logging
import my_logging  # 导入自定义的logging配置

logger = logging.getLogger(__name__)  # 生成logger实例


def demo():
    logger.debug("start range... time:{}".format(time.time()))
    logger.info("中文测试开始。。。")
    for i in range(10):
        logger.debug("i:{}".format(i))
        time.sleep(0.2)
    else:
        logger.debug("over range... time:{}".format(time.time()))
    logger.info("中文测试结束。。。")

if __name__ == "__main__":
    my_logging.load_my_logging_cfg()  # 在你程序文件的入口加载自定义logging配置
    demo()
14.8.4 另外一个django的配置,瞄一眼就可以,跟上面的一样
#logging_config.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        #打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 3,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日志:收集错误及以上的日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日志
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自动切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console', 'error'],
            'level': 'DEBUG',
            'propagate': True,
        },
        #logging.getLogger('collect')拿到的logger配置
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}


# -----------
# 用法:拿到俩个logger

logger = logging.getLogger(__name__) #线上正常的日志
collect_logger = logging.getLogger("collect") #领导说,需要为领导们单独定制领导们看的日志

14.9 直奔主题,常规使用

14.9.1 日志级别与配置
import logging
# 在
# 一:日志配置
logging.basicConfig(
    # 1、日志输出位置:1、终端 2、文件
    # filename='access.log', # 不指定,默认打印到终端

    # 2、日志格式
    format='%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',

    # 3、时间格式
    datefmt='%Y-%m-%d %H:%M:%S %p',

    # 4、日志级别
    # critical => 50
    # error => 40
    # warning => 30
    # info => 20
    # debug => 10
    level=30,
)

# 二:输出日志
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')

'''
# 注意下面的root是默认的日志名字
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical
'''
14.9.2 日志配置字典(setting.py)
"""
logging配置
在 setting.py中定义
"""

import os

# 1、定义三种日志输出格式,日志中可能用到的格式化串如下
# %(name)s Logger的名字
# %(levelno)s 数字形式的日志级别
# %(levelname)s 文本形式的日志级别
# %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
# %(filename)s 调用日志输出函数的模块的文件名
# %(module)s 调用日志输出函数的模块名
# %(funcName)s 调用日志输出函数的函数名
# %(lineno)d 调用日志输出函数的语句所在的代码行
# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
# %(thread)d 线程ID。可能没有
# %(threadName)s 线程名。可能没有
# %(process)d 进程ID。可能没有
# %(message)s用户输出的消息

# 2、强调:其中的%(name)s为getlogger时指定的名字
## 这些是预先定义好的自定义格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

test_format = '%(asctime)s] %(message)s'

# 3、日志配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    
    'formatters': {
        # 自己自定义的日志格式,可以自己改
        'standard': {
            # 自己定义的自定义格式
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
        'test': {
            'format': test_format
        },
    },
    
    'filters': {},
    
    ## 日志的接受者,不同的handle可以使日志输出到不同位置
    'handlers': {
        #打印到终端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            ## 指定输出格式
            'formatter': 'simple'
        },
        
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转
            'formatter': 'standard',
            # 可以定制日志文件路径
            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
            # LOG_PATH = os.path.join(BASE_DIR,'a1.log')
            'filename': 'a1.log',  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
        
        ## 测试用的日志格式
        'other': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',  # 保存到文件
            'formatter': 'test',
            'filename': 'a2.log',##拿到项目的跟文件夹 os.path.dirname(os.path.dirname(__file__))
            'encoding': 'utf-8',
        },
    },
    
    # 负责产生日志,产生的日志传递给handler负责处理
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        'kkk': {
            #  kkk 产生的日志传给谁
            'handlers': ['default', 'console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
            'propagate': False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层#  传递
        },
        
        'bbb': {
            #  kkk 产生的日志传给谁
            'handlers': ['console'],  # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG', # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
            'propagate': False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层#  传递
        },
        '专门的采集': {
            'handlers': ['other',],
            'level': 'DEBUG',
            'propagate': False,
        },
    },
}
14.9.3 使用
import settings

# !!!强调!!!
# 1、logging是一个包,需要使用其下的config、getLogger,可以如下导入
# 可能不能正常使用
# import logging.config 
# import logging.getLogger  

# 2、也可以使用如下导入
# from logging import config,getLogger 

from logging import config # 这样连同logging.getLogger都一起导入了,然后使用前缀logging.config.
from logging import getLogger # 用于获取配置文件里的日志生产者

# 3、加载配置
# 把配置好的配置字典扔进去
logging.config.dictConfig(settings.LOGGING_DIC)

logger1 = getLogger("kkk")  ## kkk 是可以同时向终端和文件里输出日志的
logger2 = getLogger('bbb')  ### bbb 只向终端里输出日志

# 4、输出日志
logger1=logging.getLogger('用户交易')
logger1.info('egon儿子alex转账3亿冥币')

# logger2=logging.getLogger('专门的采集') # 名字传入的必须是'专门的采集',与LOGGING_DIC中的配置唯一对应
# logger2.debug('专门采集的日志')
14.9.4 日志轮换
   ## 'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转
   ## 'maxBytes': 1024*1024*5,  # 日志大小 5M
   ##  'backupCount': 5,  最多保运几份

    #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转
            'formatter': 'standard',
            # 可以定制日志文件路径
            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
            # LOG_PATH = os.path.join(BASE_DIR,'a1.log')
            'filename': 'a1.log',  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },

15,struct模块

## 该模块可以把一个类型,如数字,转成固定长度的bytes
import struct
bytes = struct.pack('i',1000) ## 拿到长度固定的四个字节
num = struct.unpack('i',bytes)[0]   ## 结果是元祖,元祖里拿到数字拿到数字

image-20230706121238062

四,常用API模块

1,字符串

1.1 字符串查找方法

    startswith() #以指定字符串开头;
   	Endswith() # 以指定字符串结尾;
    find() # 返回字符串第一次出现的位置;
    Rfind() # 返回最后一次出现的位置;
    Count() # 返回某个字符总共出现的次数;
    Isallnum() # 判断所有字符是不是全是数字或者字母;    

2.2 去除首位信息

    Strip() # 去除字符串首位指定信息; 默认去除首位空格
    Lstrip() # 去除左边的指定信息;
    Rtrip() # 去除右边的指定信息;   

3.3 大小写转换

 	Capitalize() # 产生新的字符串,首字母大写;
    Title() # 每个单词首字母大写;
    Upper() # 所有字母转换成大写;
    Lower() # 所有字母转换成小写;
    Swapcase() # 所有字母大小写转换;

4.4 格式排版:

    # 1. Center() ljust() rjust() 用于实现排版;
    # 默认用空格填充
    # 2. 接受两个参数,第一个参数是要实现的长度,第二个字符是要填充的字符
    s = "aini"
    s.center(10,"*")  # 用*左右填充让s达到10的长度
    
    # 格式化
    "我是{0},我喜欢数字{1:*^8}".format("艾尼","666")
    # :后面是依次是 填充的字符 对齐方式(<^> 左中右) 格式化长度
    #  如:1:*^20 用*号居中对齐,长度为 20 个字符

	# 数字格式化
    

5.5 数字格式化

数字 格式 输出 描述
3.1415926 { :.2f } 3.14 保留小数点后两位
3.1415926 { :+.2f } 3.14 带符号的保留小数点后两位
2.71828 { :.0f } 3 不带小数
5 { :0>2d } 5 数字补零(填充左边,宽度为2)
5 { :x<4d } 5xxx 数字补x(填充右边,宽度为4)
10 { :x<4d } 10xx 数字补x(填充右边,宽度为4)
1000000 { :, } 1,000,000 以逗号分割的数字形式
0.25 { :.2% } 25.00% 百分比格式

6.6 其他方法:

    Isalnum() # 是否全为数字或字母;
    Isalpha() # 是不是都是字母或汉字组成;
    Isdigit() # 是不是都是由数字组成;
    Isspace() # 检测是否为空白字符;
    Isupper() # 检测是否为大写字母;
    Islower() # 检测是否为小写字母;

2,列表

方法 要点 描述
list.append(x) 增加元素 将元素x增加到列表list尾部
list.extend(aList) 增加元素 将列表aList素有元素加到列表list尾部
list.insert(index,x) 增加元素 将列表list指定index处插入元素x
list.remove(x)shan 删除元素 删除列表中首次出现的指定元素x
list.pop(index) 删除元素 删除index处的元素并返回,默认删除最后一个元素并返回
list.clear() 删除所有元素 删除原数,不会删除列表对象
list.index(x) 访问元素 返回第一个x的索引,若不存在则抛出异常
list.count(x) 计数 返回元素x在列表中出现的次数
len(list) 返回列表长度 返回类表中总元素的个数
方法 要点 描述
list.reverse() 翻转列表 所有元素原地翻转
list.sort() 排序 所有元素原地排序
list.copy() 浅拷贝 返回列表对象的浅拷贝

3,字典

# update(),把第二个字典加到第一个字典里面
a.update(b) # 把字典b加到a里面

# 可以用 del 删除键值对
 # Del a[‘name’] del a[‘sex’]
    
# 可以用 clear()方法删除所有键值对;
# Pop()方法可以删除键值对,并将值返回
a.pop("name")

# Popitem()方法:随机删除键值对,并将其返回

# 序列解包
a.values()
a.items()

4,Python常用内置函数

4.1 round() 函数

round()是一个处理数值的内置函数,它返回浮点数x的四舍五入值

4.2 all() 和 any()

all()和any(),用于判断可迭代对象中的元素是否为True。它们返回布尔值True或False

numbers = [1, 2, 3, 4, 5]
if all(num > 0 for num in numbers):
    print("All numbers are positive")
else:
    print("There are some non-positive numbers")

if any(num > 4 for num in numbers):
    print("At least one number is greater than 4")
else:
    print("No number is greater than 4")

4.3 lambda函数

lambda x: x + 5
这个Lambda函数可以像下面这样使用:
add_five = lambda x: x + 5
result = add_five(10)
print(result)   # 输出 15

4.4 sorted()函数

sorted()是一个内置函数,它用于对可迭代对象进行排序。
sorted()函数接受一个可迭代对象作为参数,并返回一个新的已排序的列表。

sorted(iterable, key=None, reverse=False)
# iterable: 需要排序的可迭代对象,例如列表、元组、集合、字典等。
#key(可选参数): 用于指定排序的关键字。key是一个函数,它将作用于iterable中的每个元素,并返回一个用于排序的关键字。默认为None,表示按照元素的大小进行排序。
# reverse(可选参数): 用于指定排序的顺序。如果设置为True,则按照逆序排序。默认为False,表示按照正序排序。

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # 输出结果为 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

words = ["apple", "banana", "cherry", "date"]
sorted_words = sorted(words, key=len)
print(sorted_words)  # 输出结果为 ["date", "apple", "banana", "cherry"]

numbers = [(1, 2), (3, 4), (2, 1), (4, 3)]
sorted_numbers = sorted(numbers, key=lambda x: x[1])
print(sorted_numbers)  # 输出结果为 [(2, 1), (1, 2), (4, 3), (3, 4)]
# 在上面的示例中,第一个示例对一个整数列表进行排序,第二个示例对一个字符串列表按照字符串长度进行排序,第三个示例对一个元组列表按照元组中第二个元素进行排序,其中使用了lambda表达式作为key参数来指定排序方式。

4.5 map()函数

map(函数名,可迭代对象) 
map函数是一种高阶函数,它接受一个函数和一个可迭代对象作为参数,返回一个新的可迭代对象,
map得到的是一个迭代器
# 其中每个元素都是将原可迭代对象中的元素应用给定函数后的结果。
# 可以简单理解为对可迭代对象中的每个元素都执行同一个操作,返回一个新的结果集合。
# 需要注意的是,map函数返回的是一个迭代器对象,因此如果要使用它的结果,需要将它转换为一个列表list()、元组tuple()或集合set()和其他可迭代对象。

map函数的一些应用
1.用来批量接收变量
n,m = map(int,input().split())

2.对可迭代对象进行批量处理返回列表map
m = map("".join,[["a","b","c"],["d","e","f"]])
print(m) -> ["abc","def"]

3.配合lambda函数达到自己想要的效果
numbers = [1, 2, 3, 4, 5]
doubled_numbers = map(lambda x: x * 2, numbers)
print(list(doubled_numbers))  -> [2, 4, 6, 8, 10]

4.5 filter()函数

filter() 函数是 Python 内置函数之一,它用于过滤序列中的元素,返回一个满足条件的新序列。

filter() 函数的语法如下:

my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = filter(lambda x: x % 2 == 0, my_list)
print(list(result))  # 输出 [2, 4, 6, 8, 10]
在这个例子中,lambda x: x % 2 == 0 是一个 lambda 函数,用于判断一个数是否为偶数。filter() 函数将这个 lambda 函数作为参数,对列表 my_list 进行过滤,最后返回一个新列表,其中包含 my_list 中所有的偶数。

4.6 ASCII码的函数

ord() # 接收字符转换ASCII码
chr() # 接收ASCII码转换字符

4.7 转进制函数

# 其它进制通用转10进制
int("x",y) # x是你要转的数,而y是这个数是由什么进制表示的,
# 当y为0时就按x的前缀来看如 0bx 二进制,0b 0o 0x,分别是二,八,十六

#  10进制转其他进制
hex() # 转16
oct() # 转8

# 有一个函数很适合10进制转其他各种进制
divmod(x,y)
#其作用是同时返回两个数的商和余数。
# 所以要这样接收它的值 a,b = divmod(x,y)

4.8 列表

Python中的list是一个非常重要的数据类型,可以用来存储多个值,包括数字、字符串、对象等等。
以下是一些常见的list函数及其示例:

append() - 将一个元素添加到list的末尾
fruits = ['apple', 'banana', 'cherry']
fruits.append('orange')
print(fruits) # ['apple', 'banana', 'cherry', 'orange']

extend() - 将一个list的所有元素添加到另一个list的末尾
fruits = ['apple', 'banana', 'cherry']
more_fruits = ['orange', 'mango', 'grape']
fruits.extend(more_fruits)
print(fruits) # ['apple', 'banana', 'cherry', 'orange', 'mango', 'grape']

insert() - 在指定的位置插入一个元素
fruits = ['apple', 'banana', 'cherry']
fruits.insert(1, 'orange')
print(fruits) # ['apple', 'orange', 'banana', 'cherry']

remove() - 删除指定的元素
fruits = ['apple', 'banana', 'cherry']
fruits.remove('banana')
print(fruits) # ['apple', 'cherry']

pop() - 删除指定位置的元素(默认为最后一个元素),并返回该元素的值
fruits = ['apple', 'banana', 'cherry']
last_fruit = fruits.pop()
print(last_fruit) # 'cherry'
print(fruits) # ['apple', 'banana']

index() - 返回指定元素在list中的索引位置
fruits = ['apple', 'banana', 'cherry']
banana_index = fruits.index('banana')
print(banana_index) # 1

count() - 返回指定元素在list中出现的次数
fruits = ['apple', 'banana', 'cherry', 'banana', 'banana']
banana_count = fruits.count('banana')
print(banana_count) # 3

sort() -list中的元素进行排序
fruits = ['apple', 'banana', 'cherry']
fruits.sort()
print(fruits) # ['apple', 'banana', 'cherry']

reverse() -list中的元素翻转
fruits = ['apple', 'banana', 'cherry']
fruits.reverse()
print(fruits) # ['cherry', 'banana', 'apple']

len() - 返回list中元素的数量
fruits = ['apple', 'banana', 'cherry']
num_fruits = len(fruits)
print(num_fruits) # 3

copy() - 返回一个list的副本
fruits = ['apple', 'banana', 'cherry']
fruits_copy = fruits.copy()
print(fruits_copy) # ['apple', 'banana', 'cherry']

clear() - 删除list中的所有元素
fruits = ['apple', 'banana', 'cherry']
fruits.clear()
print(fruits) # []

max() - 返回list中最大的元素
numbers = [5, 10, 3, 8, 15]
max_num = max(numbers)
print(max_num) # 15

min() - 返回list中最小的元素
numbers = [5, 10, 3, 8, 15]
min_num = min(numbers)
print(min_num) # 3

sum() - 返回list中所有元素的和(仅适用于数字类型的list)
numbers = [5, 10, 3, 8, 15]
sum_nums = sum(numbers)
print(sum_nums) # 41

any() - 如果list中至少有一个元素为True,则返回True
bool_list = [False, True, False]
has_true = any(bool_list)
print(has_true) # True

all() - 如果list中的所有元素都为True,则返回True
bool_list = [True, True, True]
all_true = all(bool_list)
print(all_true) # True

enumerate() - 返回一个枚举对象,其中包含list中每个元素的索引和值
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
    print(index, fruit)
# 0 apple
# 1 banana
# 2 cherry


map() -list中的每个元素应用函数,并返回结果的list
numbers = [1, 2, 3, 4]
squares = list(map(lambda x: x ** 2, numbers))
print(squares) # [1, 4, 9, 16]

filter() - 返回list中符合条件的元素的子集
numbers = [1, 2, 3, 4, 5, 6]
even_nums = list(filter(lambda x: x % 2 == 0, numbers))
print(even_nums) # [2, 4, 6]

reduce() -list中的元素应用函数,将其归约为单个值
from functools import reduce
numbers = [1, 2, 3, 4]
sum_nums = reduce(lambda x, y: x + y, numbers)
print(sum_nums) # 10

zip() - 将多个list的元素配对,返回一个元组的list
fruits = ['apple', 'banana', 'cherry']
colors = ['red', 'yellow', 'red']
fruit_colors = list(zip(fruits, colors))
print(fruit_colors) # [('apple', 'red'), ('banana', 'yellow'), ('cherry', 'red')]

sorted() - 返回一个新的已排序的list
numbers = [3, 2, 1, 5, 4]
sorted_nums = sorted(numbers)
print(sorted_nums) # [1, 2, 3, 4, 5]

join() -list中的字符串连接成一个字符串
fruits = ['apple', 'banana', 'cherry']
fruit_string = ', '.join(fruits)
print(fruit_string) # 'apple, banana, cherry'

slice() - 返回一个list的子集,根据索引的起始和结束位置
fruits = ['apple', 'banana', 'cherry', 'orange', 'grape']
subset = fruits[1:4]
print(subset) # ['banana', 'cherry', 'orange']

希望这些函数能够帮助你更好地使用Python的list类型。

4.9 元祖

Python元组是不可变序列,它不支持在原地修改元素。因此,Python元组的函数相对较少。
以下是Python元组的所有函数:
count
count方法返回元组中指定元素的数量。
my_tuple = ('apple', 'banana', 'apple', 'orange', 'banana', 'apple')
count = my_tuple.count('apple')
print(count)  # 输出:3

index
index方法返回元组中指定元素第一次出现的索引。
my_tuple = ('apple', 'banana', 'apple', 'orange', 'banana', 'apple')
index = my_tuple.index('orange')
print(index)  # 输出:3

len
len方法返回元组中元素的数量。
my_tuple = ('apple', 'banana', 'orange')
length = len(my_tuple)
print(length)  # 输出:3

tuple
tuple函数用于将一个可迭代对象转换为元组。
my_list = [1, 2, 3]
my_tuple = tuple(my_list)
print(my_tuple)  # 输出:(1, 2, 3)

zip
zip函数将多个可迭代对象的对应元素打包成元组,返回一个包含这些元组的迭代器。
fruits = ('apple', 'banana', 'orange')
quantities = (5, 2, 3)
prices = (1.2, 1.5, 0.8)
# 将三个元组打包成一个迭代器
inventory = zip(fruits, quantities, prices)
# 遍历迭代器中的元素
for item in inventory:
    print(item)
# 输出:
# ('apple', 5, 1.2)
# ('banana', 2, 1.5)
# ('orange', 3, 0.8)

sorted
sorted函数返回一个按指定顺序排序后的可迭代对象,可以接收一个关键字参数key来指定排序的关键字。
my_tuple = (3, 2, 1)
sorted_tuple = sorted(my_tuple)
print(sorted_tuple)  # 输出:[1, 2, 3]
# 按绝对值大小排序
my_tuple = (-3, 2, -1)
sorted_tuple = sorted(my_tuple, key=abs)
print(sorted_tuple)  # 输出:[-1, 2, -3]

reversed
reversed函数返回一个迭代器,包含按相反顺序排列的可迭代对象中的元素。
my_tuple = ('apple', 'banana', 'orange')
reversed_tuple = reversed(my_tuple)
for item in reversed_tuple:
    print(item)
# 输出:
# orange
# banana
# apple

继续
max
max函数返回可迭代对象中最大的元素,可以接收一个关键字参数key来指定比较的关键字。
my_tuple = (3, 2, 1)
max_element = max(my_tuple)
print(max_element)  # 输出:3
# 按绝对值大小比较
my_tuple = (-3, 2, -1)
max_element = max(my_tuple, key=abs)
print(max_element)  # 输出:-3

min
min函数返回可迭代对象中最小的元素,可以接收一个关键字参数key来指定比较的关键字。
my_tuple = (3, 2, 1)
min_element = min(my_tuple)
print(min_element)  # 输出:1
# 按绝对值大小比较
my_tuple = (-3, 2, -1)
min_element = min(my_tuple, key=abs)
print(min_element)  # 输出:-1

sum
sum函数返回可迭代对象中所有元素的和,可以接收一个可选参数start指定求和的起始值。
my_tuple = (3, 2, 1)
sum_value = sum(my_tuple)
print(sum_value)  # 输出:6
# 指定求和的起始值为5
my_tuple = (3, 2, 1)
sum_value = sum(my_tuple, 5)
print(sum_value)  # 输出:11

all
all函数返回可迭代对象中所有元素都为真值(即不为False、0、None等)时返回True,否则返回False。
my_tuple = (1, 2, 3)
result = all(my_tuple)
print(result)  # 输出:True
my_tuple = (1, 2, 0)
result = all(my_tuple)
print(result)  # 输出:False

any
any函数返回可迭代对象中至少有一个元素为真值(即不为False、0、None等)时返回True,否则返回False。
my_tuple = (0, False, None)
result = any(my_tuple)
print(result)  # 输出:False
my_tuple = (0, False, 1)
result = any(my_tuple)
print(result)  # 输出:True

4.10 字典

Python字典(dictionary)是一个无序的键值对集合。Python中有许多内置函数可以操作字典。
以下是一些常用的函数及其示例:

创建字典
# 创建一个空字典
my_dict = {}

# 创建一个非空字典
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}

访问字典
# 获取字典中指定键对应的值
value = my_dict['apple']
print(value)  # 输出:1

# 使用get()方法获取字典中指定键对应的值
value = my_dict.get('banana')
print(value)  # 输出:2

# 获取字典中所有键的列表
keys = list(my_dict.keys())
print(keys)  # 输出:['apple', 'banana', 'orange']

# 获取字典中所有值的列表
values = list(my_dict.values())
print(values)  # 输出:[1, 2, 3]

修改字典
# 修改字典中指定键对应的值
my_dict['apple'] = 4
print(my_dict)  # 输出:{'apple': 4, 'banana': 2, 'orange': 3}

# 使用update()方法修改字典中的值
my_dict.update({'apple': 5, 'orange': 6})
print(my_dict)  # 输出:{'apple': 5, 'banana': 2, 'orange': 6}

删除字典
# 删除字典中指定键值对
del my_dict['apple']
print(my_dict)  # 输出:{'banana': 2, 'orange': 6}

# 删除字典中所有键值对
my_dict.clear()
print(my_dict)  # 输出:{}

其他函数
# 获取字典中键值对的数量
num_items = len(my_dict)
print(num_items)  # 输出:0

# 检查字典中是否存在指定键
if 'apple' in my_dict:
    print('Yes')  # 输出:No

# 复制字典
new_dict = my_dict.copy()
print(new_dict)  # 输出:{}


遍历字典
# 遍历字典中所有键值对
for key, value in my_dict.items():
    print(key, value)

# 遍历字典中所有键
for key in my_dict.keys():
    print(key)

# 遍历字典中所有值
for value in my_dict.values():
    print(value)

设置默认值
# 使用setdefault()方法设置默认值
my_dict.setdefault('apple', 0)
print(my_dict)  # 输出:{'banana': 2, 'orange': 6, 'apple': 0}

合并字典
# 使用update()方法合并字典
dict1 = {'apple': 1, 'banana': 2}
dict2 = {'orange': 3, 'pear': 4}
dict1.update(dict2)
print(dict1)  # 输出:{'apple': 1, 'banana': 2, 'orange': 3, 'pear': 4}

# 使用**运算符合并字典
dict1 = {'apple': 1, 'banana': 2}
dict2 = {'orange': 3, 'pear': 4}
dict3 = {**dict1, **dict2}
print(dict3)  # 输出:{'apple': 1, 'banana': 2, 'orange': 3, 'pear': 4}

字典推导式
# 创建字典推导式
my_dict = {i: i*2 for i in range(5)}
print(my_dict)  # 输出:{0: 0, 1: 2, 2: 4, 3: 6, 4: 8}

反转字典
# 反转字典中的键值对
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}
reversed_dict = {value: key for key, value in my_dict.items()}
print(reversed_dict)  # 输出:{1: 'apple', 2: 'banana', 3: 'orange'}

排序字典
# 按键排序
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}
sorted_dict = {key: my_dict[key] for key in sorted(my_dict)}
print(sorted_dict)  # 输出:{'apple': 1, 'banana': 2, 'orange': 3}

# 按值排序
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}
sorted_dict = {key: value for key, value in sorted(my_dict.items(), key=lambda item: item[1])}
print(sorted_dict)  # 输出:{'apple': 1, 'banana': 2, 'orange': 3}

过滤字典
# 过滤字典中满足条件的键值对
my_dict = {'apple': 1, 'banana': 2, 'orange': 3}
filtered_dict = {key: value for key, value in my_dict.items() if value > 1}
print(filtered_dict)  # 输出:{'banana': 2, 'orange': 3}

计数器
# 使用collections模块中的Counter类创建计数器
from collections import Counter
my_list = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
my_counter = Counter(my_list)
print(my_counter)  # 输出:Counter({'apple': 3, 'banana': 2, 'orange': 1})

# 获取计数器中指定元素的数量
count = my_counter['apple']
print(count)  # 输出:3

# 获取计数器中出现次数最多的元素和出现次数
most_common = my_counter.most_common(1)
print(most_common)  # 输出:[('apple', 3)]

4.11 集合

以下是Python set对象支持的一些常用方法:

add(): 将一个元素添加到set中,如果元素已经存在,什么都不会发生。
fruits = {'apple', 'banana', 'orange'}
fruits.add('kiwi')
print(fruits) # {'orange', 'banana', 'kiwi', 'apple'}

clear(): 移除set中的所有元素。
fruits = {'apple', 'banana', 'orange'}
fruits.clear()
print(fruits) # set()

copy(): 返回set的一个副本。
fruits = {'apple', 'banana', 'orange'}
fruits_copy = fruits.copy()
print(fruits_copy) # {'orange', 'banana', 'apple'}

difference(): 返回一个包含set和另一个set或iterable中不同元素的新set。也可以直接减 eg:fruits - more_fruits
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
diff_fruits = fruits.difference(more_fruits)
print(diff_fruits) # {'orange', 'apple'}

discard():set中移除一个元素,如果元素不存在,什么都不会发生。
fruits = {'apple', 'banana', 'orange'}
fruits.discard('banana')
print(fruits) # {'orange', 'apple'}

intersection(): 返回一个包含set和另一个set或iterable中共同元素的新set。也可以直接交 eg:fruits & more_fruits
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
common_fruits = fruits.intersection(more_fruits)
print(common_fruits) # {'banana'}

isdisjoint(): 如果set和另一个set或iterable没有共同元素,返回True,否则返回False。也可以直接交然后判断 eg:return fruits & more_fruits == set()
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'kiwi', 'pineapple'}
print(fruits.isdisjoint(more_fruits)) # True

issubset(): 如果set是另一个set的子集,返回True,否则返回False。
也可以直接交然后判断是不是等于自身 eg:return fruits & more_fruits == fruits
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'orange', 'kiwi', 'pineapple'}
print(fruits.issubset(more_fruits)) # False

issuperset(): 如果set是另一个set的超集,返回True,否则返回False。
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'orange'}
print(fruits.issuperset(more_fruits)) # True

pop(): 移除并返回set中的一个元素,如果set为空,抛出KeyError异常。
fruits = {'apple', 'banana', 'orange'}
print(fruits.pop()) # 'orange'
print(fruits) # {'apple', 'banana'}

remove():set中移除一个元素,如果元素不存在,抛出KeyError异常。
fruits = {'apple', 'banana', 'orange'}
fruits.remove('banana')
print(fruits) # {'orange', 'apple'}

symmetric_difference(): 返回一个包含set和另一个set或iterable中不重复元素的新set

symmetric_difference_update():set和另一个set或iterable中不重复的元素更新到set中。
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
fruits.symmetric_difference_update(more_fruits)
print(fruits) # {'orange', 'kiwi', 'pineapple', 'apple'}

union(): 返回一个包含set和另一个set或iterable中所有元素的新set。
不可以+,除了 union() 方法,我们还可以使用 | 运算符来实现两个 set 的并集
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
all_fruits = fruits.union(more_fruits)
print(all_fruits) # {'kiwi', 'apple', 'pineapple', 'orange', 'banana'}

update():set和另一个set或iterable中所有元素更新到set中。
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
fruits.update(more_fruits)
print(fruits) # {'kiwi', 'apple', 'pineapple', 'orange', 'banana'}

difference_update():set和另一个set或iterable中不同的元素更新到set中。
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
fruits.difference_update(more_fruits)
print(fruits) # {'orange', 'apple'}

intersection_update():set和另一个set或iterable中共同的元素更新到set中。
fruits = {'apple', 'banana', 'orange'}
more_fruits = {'banana', 'kiwi', 'pineapple'}
fruits.intersection_update(more_fruits)
print(fruits) # {'banana'}


4.12 字符串处理函数

大小写处理
s,s1 = "aaaBBBccc", "123456"
s.upper() # 将字符串全部大写 AAABBBCCC
s.lower() # 将字符串全部小写 aaabbbccc
s.swapcase() # 将s大小写反转 AAAbbbCCC

字符判断
isdigit() , isnumeric # 判断字符串中是否全是数字字符
print(list(map(lambda x:x.isdigit(),[Z,Z2]))) # [False, True]
isdigit:是否为数字字符,包括Unicode数字,单字节数字,双字节全角数字,不包括汉字数字,罗马数字、小数
isnumeric:是否所有字符均为数值字符,包括Unicode数字、双字节全角数字、罗马数字、汉字数字,不包括小数。

s.isalpha() # 判断字符串中是否全为字母
s.isalnum() # 判断字符串中是否全为字母或者数字

4.13 callable()

## 判断一个对象能不能调用
def func():
    pass

class Foo:
    pass
print(callable(Foo))  ## true
print(callable(func)) ## true

4.14 dir()----查看属性

class Foo:
    pass
obj = Foo()

print(dir(obj)) ## 查看obj的属性
'''
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
'''

4.15 enumerate()

## 既能拿到索引,又能拿到值
lis = ['a','b','c','d','f']

for i,v in enumerate(lis):
	print(i,v)
# 0 a
# 1 b
# 2 c
# 3 d
# 4 f

4.16 eval()

## 执行字符串里的表达式
res = eval('1 + 2')

res = eval('{'name':'aini','age':22}')
print(res) 
## {'name':'aini','age':22}
## 拿到的就是字典类型

4.17 frozenset()

s = frozenset({1,2,3,4,5,6})  ## 得到不可变集合

4.18 hash()

## 传入不可变类型,得到一个hash值
res = hash('aini')
print(res) ## -3947223962220906649

4.19 help()

## 查看文档注释

4.20 isinstance()

## 判断一个对象是不是一个类的实例

class Foo:
    pass

obj = Foo()

## 判断obj是不是Foo的实例化
isinstance(obj,Foo)

## 判断列表,字典都可以用
isinstance([],list)
isinstance({{'name':'aini','age':22}},dict)

五,request网络请求

1,request模块

1-1,response的常用属性:

1,response.text  #响应体 str类型response.encoding  #从HTTP header中猜测的响应内容的编码方式
2,respones.content #响应体 bytes类型
3,response.status_code #响应状态码,
4,response.request.headers #响应对应的请求头
5,response.headers 响应头,
6,response.cookies #响应的cookie(经过了set-cookie动作)
7,response.url  #获取访问的url,
8,response.json() #获取json数据 得到内容为字典 (如果接口响应体的格式是json格式时)
9,response.ok

  # 如果status_code小于等于200,response.ok返回True。
  # 如果status_code大于200,response.ok返回False。

# 先安装 reqeuests
import requests
# 给定抓取的网址的url
url = 'http://www.baidu.com'
# 发起get请求
response = requests.get(url)

# 看一下响应的状态码
print(response.status_code)
# 看一下请求的url
print('response.url', response.url)
# 获取编码
print(response.encoding)
# 设定编码
# response.encoding = 'GBK'
# response.encoding = 'UTF-8'
# response.encoding = response.apparent_encoding

# 看一下返回的数据  以字符串形式返回的(一般情况下会乱码 需要给定编码)
print(response.text)

# 获取bytes
print( response.content)
print(response.content.decode()) # 使用默认解码方式
print(response.content.decode('UTF-8')) # 使用默认解码方式
print(response.content.decode('GBK')) # 使用默认解码方式

# 获取响应头
print(response.headers)

# 获取响应对应的请求头
print(response.request.headers)

print(response.ok)

# 获取JSON数据
print(response.json())

1-2, response.text 和response.content的区别

## response.text

# 类型:str
# 解码类型: requests模块自动根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码
# 如何修改编码方式:`response.encoding="gbk/UTF-8"`
## response.content

## 类型:bytes
## 解码类型: 没有指定
## 如何修改编码方式:`response.content.deocde("utf8")`

获取网页源码的通用方式:

response.content.decode()
response.content.decode("UTF-8")
response.text

## 以上三种方法从前往后尝试,能够100%的解决所有网页解码的问题
## 所以:更推荐使用`response.content.deocde()`的方式获取响应的html页面

1-3,下载图片实例

import requests

# 图片的URL
url = 'https://ww4.sinaimg.cn/mw690/0076vsZ6ly1hiw5krf9wdj31401hcb2a.jpg'
# 发起请求
response = requests.get(url)
# 图片使用wb
with open('dlrb.jpg', 'wb') as f:
    f.write(response.content)

1-4,添加请求头

import requests

url = 'http://www.baidu.com'

## 添加请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 携带请求头
response = requests.get(url, headers=headers)
# print(response.content.decode())
print(response.request.headers)

1-5,get传参数

1-5-1 第一种方式
import requests

## url  
## get参数在URL里以查询字符串形式传递
url = 'http://www.zishazx.com/product?page=1&size_id=0&volum_id=0&price_id=2&category_id=1001&prize_id=0&pug_id=25#views'

# 设置请求头
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}

# 发起get请求
response = requests.get(url, headers=headers)

# 获取返回数据
data = response.content.decode()
print(data)
# 写入
with open('zsh.html', 'w', encoding='UTF-8') as f:
    f.write(data)
1-5-2 第二种方式
import requests

url = 'http://www.zishazx.com/product'
# 设置请求头
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 鞋带的参数 get传参
params = {
    'page': 1,
    'size_id': 0,
    'volum_id': 0,
    'price_id': 2,
    'category_id': 1001,
    'prize_id': 0,
    'pug_id': 25
}

# 发起get请求  携带了 get传参 携带了headers请求头
response = requests.get(url, headers=headers, params=params)
# 获取返回数据
data = response.content.decode()
print(response.url)
# print(data)
# 写入
with open('zsh.html', 'w', encoding='UTF-8') as f:
    f.write(data)

1-6 实战案例

1-6-1 获取图片案例
import requests
from lxml import etree

url = 'https://app.mi.com/subject/115150'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}

response = requests.get(url, headers=headers)
data = response.content.decode()

tree = etree.HTML(data)
li_list = tree.xpath('//ul[@class="applist"]/li')
for li in li_list:
    # 获取图片地址
    img_src = li.xpath('./a/img/@data-src')[0]
    # 获取名称
    name = li.xpath('./h5/a/text()')[0]
    # 获取简介
    decr = li.xpath('./p/a/text()')[0]
    print(img_src, name, decr)
1-6-2 xpath紫砂之星抓取单页数据
import requests
from lxml import etree

# 要抓取的url
url = 'http://www.zishazx.com/product'
# 给请求头 ua
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 发起get请求
res = requests.get(url, headers=headers)
# 获取响应的页面内容
data = res.content.decode()
# 实例化匹配对象
tree = etree.HTML(data)
# print(data)
# 获取到了所有的li
li_list = tree.xpath('//ul[@class="list clearfix"]/li')
for li in li_list:
    # 获取到图片的src地址
    img = li.xpath('./p[@class="img"]/a/img/@src')[0]
    name = li.xpath('./p[@class="name"]/text()')[0]
    p_no = li.xpath('./p[@class="p_no"]/text()')[0]
    print(img, name, p_no)
1-6-3 bs4紫砂之星抓取单页数据
import requests
from bs4 import BeautifulSoup

# 要抓取的url
url = 'http://www.zishazx.com/product'
# 给请求头 ua
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 发起get请求
res = requests.get(url, headers=headers)
# 获取响应的页面内容
data = res.content.decode()
# 实例化匹配对象
soup = BeautifulSoup(data, 'lxml')
# print(data)
# 获取到了所有的li
# li_list = tree.xpath('//ul[@class="list clearfix"]/li')
li_list = soup.find('ul', class_="list clearfix").findAll('li')
for li in li_list:
    # 获取到图片的src地址
    # img = li.xpath('./p[@class="img"]/a/img/@src')[0]
    img = li.find('p', class_="img").a.img['src']
    # name = li.xpath('./p[@class="name"]/text()')[0]
    name = li.find('p', class_="name").string
    # p_no = li.xpath('./p[@class="p_no"]/text()')[0]
    p_no = li.find('p', class_="p_no").string
    print(img, name, p_no)
1-6-4 xpath小说阅读网
from lxml import etree
import requests

url = 'https://www.readnovel.com/category'
# 给定请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 发起请求
res = requests.get(url, headers=headers)
data = res.content.decode()
# print(data)
# 实例化tree对象
tree = etree.HTML(data)
# 获取所有图书的li
li_list = tree.xpath('//div[@class="right-book-list"]/ul/li')
for li in li_list:
    # 获取封面
    img = li.xpath('./div[@class="book-img"]/a/img/@src')[0]
    # 获取标题
    title = li.xpath('./div[@class="book-info"]/h3/a/text()')[0]
    # 获取简介
    intro = li.xpath('.//p[@class="intro"]/text()')[0]
    print(img, title, intro)
1-6-5 bs4小说阅读网
from bs4 import BeautifulSoup
import requests

url = 'https://www.readnovel.com/category'
# 给定请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 发起请求
res = requests.get(url, headers=headers)
data = res.content.decode()
# print(data)
# 实例化tree对象
soup = BeautifulSoup(data, 'lxml')
# 获取所有图书的li
# li_list = tree.xpath('//div[@class="right-book-list"]/ul/li')
li_list = soup.find('div', class_="right-book-list").ul.findAll('li')
for li in li_list:
    # 获取封面
    # img = li.xpath('./div[@class="book-img"]/a/img/@src')[0]
    img = li.find('div', class_="book-img").a.img['src']
    # 获取标题
    # title = li.xpath('./div[@class="book-info"]/h3/a/text()')[0]
    title = li.find('div', class_="book-info").h3.a.string
    # 获取简介
    # intro = li.xpath('.//p[@class="intro"]/text()')[0]
    intro = li.find('p', class_="intro").string
    print(img, title, intro)
1-6-6 xpath抓取优美图库
import os
import random
import time

from lxml import etree
import requests

url = 'http://www.umeituku.com/bizhitupian/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}

res = requests.get(url, headers=headers)
data = res.content.decode()
tree = etree.HTML(data)
# 获取所有二级标签页的url地址
a_list = tree.xpath('//div[@class="TypeList"]/ul/li/a/@href')
# 创建图片目录 存储当前的图片
path= 'img'
if not os.path.exists(path):
    os.mkdir(path)
for url in a_list:
    # 开始访问二级标签页内容
    res = requests.get(url, headers=headers)
    data = res.content.decode()
    tree = etree.HTML(data)
    try:
        # 找二级标签页当前大图的src地址和图片名称
        src = tree.xpath('//p[@align="center"]/a/img/@src')[0]
        alt = tree.xpath('//p[@align="center"]/a/img/@alt')[0]
        print(src, alt, '图片下载中======')
        # 图片存入本地
        with open(os.path.join(path, alt+'.jpg'), 'wb') as f:
            f.write(requests.get(src, headers=headers).content)
        print(src, alt, '图片下载完成......')
        # 给个时间自省
    except Exception as e:
        print(f'在抓取网址:{url}的过程中出现了问题, 问题为:', e)
    time.sleep(random.randint(1, 4))
print('OVER  下载结束!')
1-6-7 抓取冶金信息
from lxml import etree
import requests

url = 'http://www.metalinfo.cn/mi.html'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
"""
pageSize: 20
current: 2
resourceType: r_news
facetFilter: {}
order: desc
sort: sort_time
"""
res = requests.get(url, headers=headers)
data = res.content.decode()
tree = etree.HTML(data)
li_list = tree.xpath('//ul[@id="searchLists"]/li')
print(li_list)
for li in li_list:
    list_title = ''.join(li.xpath('//div[@class="list-title "]//text()'))
    list_intro = li.xpath('//div[@class="list-intro"]/text()')[0]
    list_keys = ''.join(li.xpath('//div[@class="list-keys"]/text()'))
    print(list_title, list_intro, list_keys)

1-6-8 异步请求
from lxml import etree
import requests

url = 'http://www.metalinfo.cn/json/search/list'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
# 传递post数据
data = {
    'pageSize': 200,
    'current': 1,
    'resourceType': 'r_news',
    'facetFilter': {},
    'order': 'desc',
    'sort': 'sort_time',
}
# 发起post请求
res = requests.post(url, headers=headers, data=data)
# 获取json数据
json_data = res.json()['result']['records']
i = 0
for j in json_data:
    # 获取标题
    title = j['title']
    # 获取简介
    r_abstract = j['r_abstract']
    print(title)
    print(r_abstract)
    i += 1
print(i)
1-6-9 中信证券抓取单页
import requests
from lxml import etree

url = 'http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}

res = requests.get(url, headers=headers)
html = res.content.decode()
tree = etree.HTML(html)
li_list = tree.xpath('//ul[@class="list-con"]/li')
# print(li_list)
for li in li_list:
    con = li.xpath('./span/text()')
    th1 = con[0]
    th2 = con[1]
    th3 = con[2]
    th4 = con[3]
    th5 = con[4]
    print(th1, th2, th3, th4, th5)

2,反扒措施

2-1 代理的使用

# 用到的库
import requests
# 写入获取到的ip地址到proxy

# 一个ip地址
proxy = {
    'http':'http://221.178.232.130:8080'
}

res = requests.get(url, proxies=proxy)
print(res.content.decode())

### ----------------------------------------------------------------------------

# 多个ip地址
proxy = [
  {'http':'http://221.178.232.130:8080'},
  {'http':'http://221.178.232.130:8080'}
]

import random
proxy = random.choice(proxy) # 随机获取ip


res = requests.get(url, proxies=proxy)
print(res.content.decode())

2-2 处理cookie

2-2-1 带cookie的请求
import requests

url = 'https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=554225&size=15'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://xueqiu.com/',
    ## 携带cookie信息
    'Cookie':'cookiesu=121697804017738; device_id=2cb32776fe1f32adba3aefc52173bcdc; xq_a_token=e2f0876e8fd368a0be2b6d38a49ed2dd5eec7557; xqat=e2f0876e8fd368a0be2b6d38a49ed2dd5eec7557; xq_r_token=2a5b753b2db675b4ac36c938d20120660651116d; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOi0xLCJpc3MiOiJ1YyIsImV4cCI6MTcwMDY5OTg3NSwiY3RtIjoxNjk4MjM4NzI4MTU4LCJjaWQiOiJkOWQwbjRBWnVwIn0.RtY0JREVs0R4s9sgP2RsybzTrLY7UD5dDElnpf16r7-F02lOLkU7mdgAm0HjvKvbcAYYeRyP6Ke6rdy3WfbFI-RlJwzxIo5wZ4ScGzy0Vj3VYKqsh7-Wx8MnzyRjVcJPtVUfBlN_Plj5nmxnQPykmZwKSRjKT02YBy2XH4OHNaN0sG1Rst37mAj2f42lTogbHdfZBsRUkweP-UezUkEyvSncUYIe9IAMZmHf7d5AQ94BK5h3nhSqy01KyyTf2aonnwWG7rNrOeuo7F28S50Wz-1JBKtbQYhRbOEZL2FVpizmpC_h98pYl3RtDBVvbiUEJPxx1-bRN6J78h3bduYu0w; u=121697804017738; Hm_lvt_1db88642e346389874251b5a1eded6e3=1697804019,1698238782;'
}

# 再发请求。拿数据
res = requests.get(url, headers=headers)
print(res.json())
2-2-2, coocie的字典形式
import requests

# 携带cookie登录雪球网  抓取完善个人资料页面
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
    'Referer': 'https://xueqiu.com/u/1990923459',
    'Host': 'xueqiu.com',
}
url = 'https://xueqiu.com/users/connectnew?redirect=/setting/user'

cookie_dict = {
    'u': '1990923459',
    'bid': '1f110dfd43538f4b8362dfcd21ffbb64_l27g4lfl',
    'xq_is_login': '1',
    'xq_r_token': '5dcbe83944f0b75325f91246061d4a2a01999367'
}
res = requests.get(url, headers=headers, cookies=cookie_dict)
with open('雪球网.html', 'w') as f:
    f.write(res.content.decode('UTF-8'))
    print(res.content.decode('UTF-8'))
2-2-3 获取服务端返回的cookie
import requests

url = 'https://xueqiu.com/'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}
res = requests.get(url, headers=headers)
# 获取cookie
cookies = res.cookies
print(cookies)
# 获取字典格式的cookie
print(dict(cookies))
2-2-4 携带首页服务器响应的cookie
import requests
# 就是为了获取cookie
index_url = 'https://xueqiu.com/'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://xueqiu.com/',
}
res = requests.get(index_url, headers=headers)
# 获取cookie
cookies = dict(res.cookies)

url = 'https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=554225&size=15'
# 携带cookie进行请求
res = requests.get(url, headers=headers, cookies=cookies)
print(res.json())
2-2-5, 使用session处理cookie
import requests
# 就是为了获取cookie
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://xueqiu.com/',
}
# 使用这个session对象进行维护
session = requests.Session()
# session = requests.session()
# 请求首页  获取返回的cookie
index_url = 'https://xueqiu.com/'
session.get(index_url, headers=headers)

# 获取数据
url = 'https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=554225&size=15'
# 携带cookie进行请求
res = session.get(url, headers=headers)
print(res.json())
2-2-6 模拟登录
6-1 手动处理cookie
import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}
# 登录的url地址
login_url = 'https://passport.17k.com/ck/user/login'
data = {
    'loginName': '17346570232',
    'password': 'xlg17346570232',
}
# 发起登录请求
res = requests.post(login_url, headers=headers, data=data)
cookies = dict(res.cookies)  # 获取登录后的cookie

# 获取登录后的数据
url = 'https://user.17k.com/ck/user/myInfo/96139098?bindInfo=1&appKey=2406394919'
res = requests.get(url, headers=headers, cookies=cookies)
print(res.content.decode())
6-2 自动维护cookie
import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}
# 登录的url地址
login_url = 'https://passport.17k.com/ck/user/login'
data = {
    'loginName': '17346570232',
    'password': 'xlg17346570232',
}
# 发起登录请求
session = requests.Session()
session.post(login_url, headers=headers, data=data)

# 获取登录后的数据
url = 'https://user.17k.com/ck/user/myInfo/96139098?bindInfo=1&appKey=2406394919'
res = session.get(url, headers=headers)
print(res.content.decode())

2-3 案例

2-3-1 处理验证码
1-1 下载验证码
## 首先找到发放验证码的URL地址,获取验证码保存到本地

import requests
# 验证码地址
url = 'https://so.gushiwen.cn/RandCode.ashx'
res = requests.get(url)

with open('yzm.jpg', 'wb') as f:
    f.write(res.content)
1-2 识别验证码
## 用验证码识别模块识别验证码

# 终端用pip安装ddddoce包
pip install ddddocr

# ddddocr可能会出现的问题
# 1 在运行过程中 报错最底部出现dll的问题  安装一下c++环境
# 2 运行顶部会出现PIL的问题
#         可能没安装pillow模块  pip install pillow
#         安装了(版本高了 卸载原有pillow 安装9.5.0的pillow模块)
#          pip uninstall  pillow
#          pip install   pillow==9.5.0
import ddddocr
ocr = ddddocr.DdddOcr()
with open('yzm.jpg', 'rb') as f:
    data = f.read()
result = ocr.classification(data)
print(result)
1-3 登录
import requests

url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx',
}
email = '793390457@qq.com',
pwd = 'xlg17346570232',
data = {
    '__VIEWSTATE': 'AaJHgmeyf8Le6GErv1HkNyY9sDsTvgOx5w95HI82SkYSEWCpd9gSo2mvsYno9ZIc1D/tjgrPiujAhdRcKtnUjN5RdyvONf3MAk83da/5zRoc2WtYcqhyh1iEk9hVU6e7jmM8I07Z3dNPLNcAouMrW4mUaGk=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': email,
    'pwd': pwd,
    'code': 'k04c', 
    'denglu': '登录',
}
res = requests.post(url, headers=headers, data=data)
# print(res.content.decode())
with open('gsw.html', 'w', encoding='UTF-8') as f:
    f.write(res.content.decode())
1-4 上面三步整合
import requests
import ddddocr

url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx',
}

# 验证码处理
img_url = 'https://so.gushiwen.cn/RandCode.ashx'
res = requests.get(img_url)
img = res.content
with open('yzm.jpg', 'wb') as f:
    f.write(img)
ocr = ddddocr.DdddOcr()
result = ocr.classification(img)

email = '793390457@qq.com',
pwd = 'xlg17346570232',
print(result)
data = {
    '__VIEWSTATE': 'AaJHgmeyf8Le6GErv1HkNyY9sDsTvgOx5w95HI82SkYSEWCpd9gSo2mvsYno9ZIc1D/tjgrPiujAhdRcKtnUjN5RdyvONf3MAk83da/5zRoc2WtYcqhyh1iEk9hVU6e7jmM8I07Z3dNPLNcAouMrW4mUaGk=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': email,
    'pwd': pwd,
    'code': result,
    'denglu': '登录',
}
res = requests.post(url, headers=headers, data=data)
# print(res.content.decode())
with open('gsw.html', 'w', encoding='UTF-8') as f:
    f.write(res.content.decode())

但是还是提示验证码不对?? 问题出现在哪里??

1-5 最终版本

需要添加cookie数据

import requests
import ddddocr
session = requests.Session()
url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
    'Referer': 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx',
}
# 验证码处理
img_url = 'https://so.gushiwen.cn/RandCode.ashx'
res = session.get(img_url)
img = res.content
with open('yzm.jpg', 'wb') as f:
    f.write(img)
ocr = ddddocr.DdddOcr()
result = ocr.classification(img)

email = '793390457@qq.com',
pwd = 'xlg17346570232',
print(result)
data = {
    '__VIEWSTATE': 'AaJHgmeyf8Le6GErv1HkNyY9sDsTvgOx5w95HI82SkYSEWCpd9gSo2mvsYno9ZIc1D/tjgrPiujAhdRcKtnUjN5RdyvONf3MAk83da/5zRoc2WtYcqhyh1iEk9hVU6e7jmM8I07Z3dNPLNcAouMrW4mUaGk=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': email,
    'pwd': pwd,
    'code': result,
    'denglu': '登录',
}
res = session.post(url, headers=headers, data=data)
# print(res.content.decode())
with open('gsw.html', 'w', encoding='UTF-8') as f:
    f.write(res.content.decode())
1-6 打码平台使用
import base64
import json
import requests
def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ''


if __name__ == "__main__":
    img_path = "./code.png"
    result = base64_api(uname='xxxxx', pwd='xxxxx', img=img_path, typeid=3)
    print(result)
        
import base64
import json
import requests
# 一、图片文字类型(默认 3 数英混合):
# 1 : 纯数字
# 1001:纯数字2
# 2 : 纯英文
# 1002:纯英文2
# 3 : 数英混合
# 1003:数英混合2
#  4 : 闪动GIF
# 7 : 无感学习(独家)
# 11 : 计算题
# 1005:  快速计算题
# 16 : 汉字
# 32 : 通用文字识别(证件、单据)
# 66:  问答题
# 49 :recaptcha图片识别
# 二、图片旋转角度类型:
# 29 :  旋转类型
#
# 三、图片坐标点选类型:
# 19 :  1个坐标
# 20 :  3个坐标
# 21 :  3 ~ 5个坐标
# 22 :  5 ~ 8个坐标
# 27 :  1 ~ 4个坐标
# 48 : 轨迹类型
#
# 四、缺口识别
# 18 : 缺口识别(需要2张图 一张目标图一张缺口图)
# 33 : 单缺口识别(返回X轴坐标 只需要1张图)
# 五、拼图识别
# 53:拼图识别
def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ""


if __name__ == "__main__":
    img_path = "C:/Users/Administrator/Desktop/file.jpg"
    result = base64_api(uname='你的账号', pwd='你的密码', img=img_path, typeid=3)
    print(result)

3,抓取多页数据

3-1 xpath紫砂之星抓取多页数据

import random
import time

import requests
from lxml import etree

for i in range(1, 11):
    url = f'http://www.zishazx.com/product?page={i}&volum_start=0&volum_end=0&volum_id=0&price_start=0&price_end=0&category_id=0&pug_id=0&size_id=0&price_id=0&prize_id=0&search=&sflag=#views'
    print(f'正在抓取第{i}页数据======》')
    # 给请求头 ua
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
    }
    # 发起get请求
    res = requests.get(url, headers=headers)
    # 获取响应的页面内容
    data = res.content.decode()
    # 实例化匹配对象
    tree = etree.HTML(data)
    # print(data)
    # 获取到了所有的li
    li_list = tree.xpath('//ul[@class="list clearfix"]/li')
    for li in li_list:
        # 获取到图片的src地址
        img = li.xpath('./p[@class="img"]/a/img/@src')[0]
        name = li.xpath('./p[@class="name"]/text()')[0]
        p_no = li.xpath('./p[@class="p_no"]/text()')[0]
        print(img, name, p_no)
    time.sleep(random.randint(1,4))


3-2 抓取多页数据案例

"""
http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html   1
http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index_1.html  2
http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index_4.html   5
"""

import requests
import re
from lxml import etree

url = 'http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}

res = requests.get(url, headers=headers)
html = res.content.decode()
# print(html)
page = re.search('var countPage = (?P<page>\d+)//共多少页', html).group('page')
for i in range(int(page)+1):
    url = f'http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index_{i}.html'
    if not i:
        url = f'http://www.cs.ecitic.com/newsite/cpzx/jrcpxxgs/zgcp/index.html'
    print(url)

3-3 登录处理

import requests
index_url = 'https://v3pro.houjiemeishi.com/PC/index.html'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}
session = requests.Session()
session.get(index_url, headers=headers)

# 登录的url地址
login_url = 'https://v3pro.houjiemeishi.com//index.php?store_id=1&store_type=6'
# 登录携带的表单数据
data = {
    'module': 'app_pc',
    'action': 'login',
    'm': 'login',
    'phone': 'luckyboy',
    'password': 'bHVja3lib3k=',
    'imgCode': 'flms',
    'language': '',
}
headers['Referer'] = 'https://v3pro.houjiemeishi.com/PC/pages/login/login.html'
res = session.post(login_url, data=data, headers=headers)
data = res.json()
print('登录后返回的', data)
access_id = data['data']['access_id']
# 抓取个人资料
own = 'https://v3pro.houjiemeishi.com//index.php?store_id=1&store_type=6'
data = {
    'module': 'app_pc',
    'action': 'user',
    'm': 'personal_resources',
    'access_id': access_id,
    'language': '',
}
headers['Referer'] = 'https://v3pro.houjiemeishi.com/PC/index.html?module=my&action=my&m=my&a=myinfor'
res = session.post(login_url, data=data, headers=headers)
print('获取个人资料的', res.json()

4,requests处理证书错误

12306ssl错误

import requests

url = "https://www.12306.cn/mormhweb/"
response = requests.get(url)

5、超时参数的使用

response = requests.get(url,timeout=3)

6,请求参数类型

 1. Query String Parameters
 	# 最终是要被添加在url上面的.
	# 此时, 你可以有两个选择.
	1. 把参数直接怼在url上面
		url = https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20
		requests.get(url)
	2. 把参数弄出来. 弄成字典. 然后通过params传递给服务器
		url = "https://movie.douban.com/j/chart/top_list"
        params = {
        type: 13
        interval_id: 100:90
        action:
        start: 0
        limit: 20
        }
		requests.get(url, params=params)
	3. 还可以混合搭配.
        url = "https://movie.douban.com/j/chart/top_list?type=13"
        params = {
        interval_id: 100:90
        action:
        start: 0
        limit: 20
        }

		requests.get(url, params=params)

# 上述三种方案. 最终在服务器是一样的..原因是, 请求是要遵守http协议的
# 在http协议中. get请求的所有参数都要怼在url上面

	2. Form Data
        # 首先Form Data一定是post请求
	    # 它的参数. 需要处理成字典. 然后再requests模块里.
            url = "xxxxx"
            data = {
            数据
            }

		requests.post(url, data=data)

	3. Request Payload 表示的就是挂载.
		# 挂载的数据类型不一定是json. 最多的是json...
		# 在请求头里. 会有一个content-type属性. 来描述该挂载内容的格式
		# 我们处理的时候. 一般是按照浏览器上面的提示. 来组装参数以及请求头

            url = "xxxxx"
            data = {
            字典
            }
		解决方案:
            1. 直接用json参数传递
            requests.post(url, json=data)

            # 把你传递的字典. 处理成json然后发送给服务器
            # 隐藏的逻辑:
            # 自动在请求头里帮你添加content-type: json....
            # 上述逻辑是自动的

            2. 手动把字典处理成json. 然后再请求头里增加content-type
            把处理好的json用data传递出去
            s = json.dumps(data, separators=(',', ':')) # json字符串

            requests.post(url, data=s, headers={
            "Content-Type": "application/json"
            })

			# 上述逻辑是自己手动来...
            4. 三种参数可以混搭....
            # Query String 和 Form Data
            # Query String 和 Requests payload

            # 用上面的三种方案. 混合处理即可.

    4. 请求头.
        User-Agent: 表示的是你的网络设备...
        Referer: 防盗链.
        # a页面 发请求到b页面. 在b页面的请求头上面就会有referer, 是a页面的地址.
        Cookie:
        # 客户端和服务器的会话状态.
        # 应该如何处理cookie
        1. 服务器在返回的http数据包的时候. 在响应头上会有Set-Cookie
        # Set-Cookie: xxxxx=xxxxx
        # 告诉浏览器. 把该信息保存下来.以后使用我的网站的时候. 带着该cookie信息

        # 保持cookie状态: session
        #session表示会话. 你再session中保存的请求头信息. cookie信息
        # 可以重复使用...
        # 可以帮我们保持住cookie的状态.

        # 它只能帮你保持住, 响应头回来的cookie信息.

    2. 在js中可以动态的修改cookie
    # 有些网站不是通过`响应头`加载的cookie..... 这时候session就不行了....该信息是`js动态`加载的

    # 此时. 我们只能想办法手工去维护cookie的改变策略. 还是要用session....

    # 综上. 我们发请求的时候. 一定要用session

六,selenium

1,下载配置

## 安装:
pip install selenium

## 它与其他库不同的地方是他要启动你电脑上的浏览器, 这就需要一个驱动程序来辅助. 

## 这里推荐用chrome浏览器

## chrome驱动地址:

http://chromedriver.storage.googleapis.com/index.html
https://googlechromelabs.github.io/chrome-for-testing/#stable

image-20240122193411031

## 先查看自己谷歌浏览器的版本,我的是120.0.6099.255

然后打开这个驱动地址

https://googlechromelabs.github.io/chrome-for-testing/#stable

image-20240122193642372

选stable 稳定版

然后在网页上搜索我们的版本,只要前三个部分对应上就行,也就是120.0.6099

如下图,就这样我们找到了我们想要的版本

image-20240122193954470

把URL地址复制下载去浏览器下载驱动

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下完以后解压,发现里面是个exe文件(往后浏览器更新了,驱动也需要重新下载对应版本的)

image-20240122194328843

然后关键的来了. 把你下载的浏览器驱动放在python解释器所在的文件夹

Windwos: py -0p 查看Python路径

Mac: open + 路径

image-20240122195003848

到此为止配置就结束了

2,selenium导入使用

from selenium.webdriver import Chrome  # 导入谷歌浏览器的类

# 创建浏览器对象
web = Chrome()  # 如果你的浏览器驱动放在了解释器文件夹

web.get("http://www.baidu.com")  # 输入网址

2、selenium的基本使用

2.1 加载网页:

selenium通过控制浏览器,所以对应的获取的数据都是elements中的内容

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
# 访问百度
driver.get("http://www.baidu.com/")
# 截图
driver.save_screenshot("baidu.png")

2.2 定位和操作:

# 搜索关键字 杜卡迪
driver.find_element(By.ID, "kw").send_keys("杜卡迪")
# 点击id为su的搜索按钮
driver.find_element(By.ID, "su").click()

3.3 查看请求信息:

driver.page_source   # 获取页面内容
driver.get_cookies()
driver.current_url

3.4 退出

driver.close()  # 退出当前页面
driver.quit()   # 退出浏览器

3.5 小结

1. selenium的导包:
    from selenium import webdriver
    
2. selenium创建driver对象:
    driver = webdriver.Chrome()
    
3. selenium请求数据:
    driver.get("http://www.baidu.com/")
    
4. selenium查看数据: 
    driver.page_source
    
5. 关闭浏览器: 
    driver.quit()
    
6. 根据id定位元素: 
    driver.find_element_by_id("kw")/driver.find_element(By.ID, "kw")
    
7. 操作点击事件: 
    click()
    
8. 给输入框赋值:
    send_keys()
    
9,获取cookie:
	driver.get_cookies()
    
10,刷新页面
	driver.refresh()
    
11,执行js代码
	driver.execute_script(f'window.scrollBy(0, {step_length})')

3-6 小案例

3-6-1 简单案例

找到搜索框,输入内容,找到搜索按钮进行点击

import time

from selenium.webdriver import Chrome  # 导入谷歌浏览器的类

# 创建浏览器对象
from selenium.webdriver.common.by import By

web = Chrome()  # 如果你的浏览器驱动放在了解释器文件夹

web.get("https://www.gushiwen.cn/")  # 输入网址

# 查找搜索框
txtKey = web.find_element(By.ID,'txtKey')

txtKey.send_keys('唐诗')

# 找到点击按钮
search = web.find_element(By.XPATH,'//*[@id="search"]/form/input[3]')
search.click()
print(search)
time.sleep(5)
web.quit()
3-6-2 解决登录问题
2-1 基本代码
import time

from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By

driver = Chrome()
# 访问的网址
driver.get('https://www.gushiwen.cn/')
"""
1  点击我的  到登录页面
2  获取账号节点  输入值
3  获取密码节点  输入值
4  获取验证码节点  输入值
5  点击登录
"""
# 点我的
driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click()

# 获取账号节点
email = driver.find_element(By.ID, 'email')
email.send_keys('793390457@qq.com')
# 获取密码节点
password = driver.find_element(By.ID, 'pwd')
password.send_keys('xlg17346570232')
# 获取验证码节点
yzm = driver.find_element(By.ID, 'code')
yzm.send_keys('1234')
time.sleep(5)

# 点击登录
driver.find_element(By.ID, 'denglu').click()

time.sleep(5)
2-2 打码平台
import base64
import json
import requests
import time

from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By


def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ""


if __name__ == "__main__":
    driver = Chrome()
    # 访问的网址
    driver.get('https://www.gushiwen.cn/')
    """
    1  点击我的  到登录页面
    2  获取账号节点  输入值
    3  获取密码节点  输入值
    4  获取验证码节点  输入值
    5  点击登录
    """
    # 点我的
    driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click()

    # 获取账号节点
    email = driver.find_element(By.ID, 'email')
    email.send_keys('793390457@qq.com')
    # 获取密码节点
    password = driver.find_element(By.ID, 'pwd')
    password.send_keys('xlg17346570232')
    # 验证码图片的节点
    img_path = "yzm.jpg"
    # screenshot截图并保存保存
    driver.find_element(By.ID, 'imgCode').screenshot(img_path)
    # 识别验证码
    result = base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3)
    print(result)
    # 获取验证码节点
    yzm = driver.find_element(By.ID, 'code')
    yzm.send_keys(result)  # 输入识别后的值
    time.sleep(8)
    # 点击登录
    driver.find_element(By.ID, 'denglu').click()
    time.sleep(50)


2-3 保存登录后的cookie
import base64
import json
import requests
import time

from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By


def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        #!!!!!!!注意:返回 人工不足等 错误情况 请加逻辑处理防止脚本卡死 继续重新 识别
        return result["message"]
    return ""


if __name__ == "__main__":
    driver = Chrome()
    # 访问的网址
    driver.get('https://www.gushiwen.cn/')
    """
    1  点击我的  到登录页面
    2  获取账号节点  输入值
    3  获取密码节点  输入值
    4  获取验证码节点  输入值
    5  点击登录
    """
    # 点我的
    driver.find_element(By.XPATH, '/html/body/div[1]/div[1]/div/div[2]/div/a[6]').click()

    # 获取账号节点
    email = driver.find_element(By.ID, 'email')
    email.send_keys('793390457@qq.com')
    # 获取密码节点
    password = driver.find_element(By.ID, 'pwd')
    password.send_keys('xlg17346570232')
    # 验证码图片的节点
    img_path = "yzm.jpg"
    # screenshot截图并保存保存
    driver.find_element(By.ID, 'imgCode').screenshot(img_path)
    # 识别验证码
    result = base64_api(uname='luckyboyxlg', pwd='17346570232', img=img_path, typeid=3)
    print(result)
    # 获取验证码节点
    yzm = driver.find_element(By.ID, 'code')
    yzm.send_keys(result)  # 输入识别后的值
    time.sleep(8)
    # 点击登录
    driver.find_element(By.ID, 'denglu').click()
    time.sleep(4)
    # 获取cookie保存到本地
    cookies = driver.get_cookies()
    print(cookies)
    with open('cookies.txt', 'w', encoding='UTF-8') as f:
        f.write(json.dumps(cookies))


2-4 携带cookie进行访问
import time
from selenium.webdriver.common.by import By
from selenium.webdriver import Chrome
import json

driver = Chrome()
# 访问登录
driver.get('https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx')
# 本地cookie加载
with open('cookies.txt', 'r', encoding='UTF-8') as f:
    cookies = json.loads(f.read())

# cookie加载到selenium中
for cookie in cookies:
    driver.add_cookie(cookie)

# 刷新一下
driver.refresh()

driver.get('https://so.gushiwen.cn/user/collect.aspx')

time.sleep(10)
3-6-3 抓取网易
import time
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By


def window_scroll(driver, stop_length, step_length):
    '''
    向下滚动方法封装
    :param driver: selenium对象
    :param stop_length: 滚动终止值
    :param step_length: 每次滚动步长
    :return:
    '''
    while True:
        # 终止不滚的条件
        if stop_length - step_length <= 0:
            driver.execute_script(f'window.scrollBy(0, {stop_length})')
            break
        # 执行js代码 向下滚动
        driver.execute_script(f'window.scrollBy(0, {step_length})')
        stop_length -= step_length
        time.sleep(1)  # 1秒滚一下
    # driver.execute_script('window.scrollBy(0, 30000)')
if __name__ == '__main__':
    driver = Chrome()
    driver.get('https://news.163.com/')
    stop_length = 30000  # 终止值
    step_length = 2000  # 每次滚动的值
    # 循环5次点击加载更多
    for i in range(1, 6):
        window_scroll(driver, stop_length, step_length)
        # 点击加载更多
        more = driver.find_element(By.XPATH, '//*[@id="index2016_wrap"]/div[3]/div[2]/div[3]/div[2]/div[5]/div/a[3]/div[1]/span')
        # more.click()  # 点击
        driver.execute_script('arguments[0].click()', more)
        print(f'第:{i}次 点击加载更多')
    time.sleep(5)
    # 获取页面所有源代码
    page = driver.page_source
    print(page)

3、selenium的定位操作

1,元素定位的两种写法:

  • 直接调用型

     el = driver.find_element_by_xxx(value)
     # xxx是定位方式,后面我们会讲,value为该方式对应的值
    
  • 使用By类型(需要导入By) 建议使用这种方式

     # 直接掉用的方式会在底层翻译成这种方式
    from selenium.webdriver.common.by import By
    driver.find_element(By.xxx,value)
    

2,元素定位的两种方式:

  • 精确定位一个元素,返回结果为一个element对象,定位不到则报错

    driver.find_element(By.xx, value)  # 建议使用
    driver.find_element_by_xxx(value)
    
  • 定位一组元素,返回结果为element对象列表,定位不到返回空列表

    driver.find_elements(By.xx, value)  # 建议使用
    driver.find_elements_by_xxx(value)
    

3,元素定位的八种方法:

以下方法在element之后添加s就变成能够获取一组元素的方法

  • By.ID 使用id值定位

    el = driver.find_element(By.ID, '')
    el = driver.find_element_by_id()            
    
  • By.XPATH 使用xpath定位

    el = driver.find_element(By.XPATH, '')
    el = driver.find_element_by_xpath()         
    
  • By.TAG_NAME. 使用标签名定位

    el = driver.find_element(By.TAG_NAME, '')
    el = driver.find_element_by_tag_name()     
    
  • By.LINK_TEXT使用超链接文本定位

    el = driver.find_element(By.LINK_TEXT, '')
    el = driver.find_element_by_link_text() 
    
  • By.PARTIAL_LINK_TEXT 使用部分超链接文本定位

    el = driver.find_element(By.PARTIAL_LINK_TEXT  , '')
    el = driver.find_element_by_partial_link_text()
    
  • By.NAME 使用name属性值定位

    el = driver.find_element(By.NAME, '')
    el = driver.find_element_by_name()
    
  • By.CLASS_NAME 使用class属性值定位

    el = driver.find_element(By.CLASS_NAME, '')   
    el = driver.find_element_by_class_name()
    
  • By.CSS_SELECTOR 使用css选择器定位

    el = driver.find_element(By.CSS_SELECTOR, '')  
    el = driver.find_element_by_css_selector()
    

注意:

find_element与find_elements区别

1. 只查找一个元素的时候:可以使用find_element(),find_elements()
   find_element()会返回一个WebElement节点对象,但是没找到会报错,而find_elements()不会,之后返回一个空列表
2. 查找多个元素的时候:只能用find_elements(),返回一个列表,列表里的元素全是WebElement节点对象
3. 找到都是节点(标签)
4. 如果想要获取相关内容(只对find_element()有效,列表对象没有这个属性)  使用  .text
5. 如果想要获取相关属性的值(如href对应的链接等,只对find_element()有效,列表对象没有这个属性):使用   .get_attribute("href")      

4、元素的操作

find_element_by_xxx方法仅仅能够获取元素对象,接下来就可以对元素执行以下操作 从定位到的元素中提取数据的方法

4.1 从定位到的元素中获取数据
el.get_attribute(key)           # 获取key属性名对应的属性值
el.text                        	# 获取开闭标签之间的文本内容
4.2 对定位到的元素的操作
el.click()                      # 对元素执行点击操作

el.submit()                     # 对元素执行提交操作

el.clear()                      # 清空可输入元素中的数据

el.send_keys(data)              # 向可输入元素输入数据

5,小结

## 1. 根据xpath定位元素:
	driver.find_elements(By.XPATH,"//*[@id='s']/h1/a")
    
## 2. 根据class定位元素:
	driver.find_elements(By.CLASS_NAME, "box")
    
## 3. 根据link_text定位元素:
	driver.find_elements(By.LINK_TEXT, "下载豆瓣 App")
    
## 4. 根据tag_name定位元素:
	driver.find_elements(By.TAG_NAME, "h1")
    
## 5. 获取元素文本内容:
	element.text
    
## 6. 获取元素标签属性: 
	element.get_attribute("href")
    
## 7. 向输入框输入数据: 
	element.send_keys(data)

4、无头浏览器

我们已经基本了解了selenium的基本使用了. 但是呢, 不知各位有没有发现, 每次打开浏览器的时间都比较长. 这就比较耗时了. 我们写的是爬虫程序. 目的是数据. 并不是想看网页. 那能不能让浏览器在后台跑呢? 答案是可以的

from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options

opt = Options()
opt.add_argument("--headless")
opt.add_argument('--disable-gpu')
opt.add_argument("--window-size=4000,1600")  # 设置窗口大小

web = Chrome(options=opt)
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options

opt = Options()
opt.add_argument("--headless")
opt.add_argument('--disable-gpu')

web = Chrome(options=opt)
web.get('https://www.baidu.com')
print(web.title)

5、selenium 处理cookie

通过driver.get_cookies()能够获取所有的cookie

  • 获取cookie

    dictCookies = driver.get_cookies()
    
  • 设置cookie

    driver.add_cookie(dictCookies)
    
  • 删除cookie

    #删除一条cookie
    driver.delete_cookie("CookieName")
    # 删除所有的cookie
    driver.delete_all_cookies()
    

6,其他知识

6.1 当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:

alert = driver.switch_to_alert()

6.2 页面前进和后退

driver.forward()     # 前进
driver.back()        # 后退
driver.refresh() 		 # 刷新
driver.close()       # 关闭当前窗口

6.3 设置浏览器最大窗口

driver.maximize_window()  #最大化浏览器窗口

七,数据库操作

1,MySQL

1.1 基础知识

# DB:数据库,他保存了一系列有组织的数据;
# DBMS:数据库管理系统;
# SQL:结构化查询语言,用来与数据库进行沟通;
1.1.1 MySQL服务的登录和退出
# 1,通过 Windows 自带的客户端
    # 登录:MySQL -h 主机名 -p 端口号 -u 用户名 -p 密码(-p 和密码之间不能有空格)
    # 退出:exit
1.1.2 查看数据库的版本号
## 1,Windows 客户端进入 MySQL,写 SQL 命令 
	select version();
## 2,Windows 客户端写 
	MySQL --version*(结尾没有分号)
1.1.3 MySQL 语法规范
## 1. 不区分大小写,建议关键字大写,表名,列名小写
## 2. 每条命令最好分号结尾;
## 3. 每条命令根据需要进行缩进,换行;
## 4. 注释
    1,单行注释:# 注释内容;
    2,单行注释:-- 注释内容(-- 后面必须有空格)
    3,多行注释:/* 注释内容 */
1.1.4 SQL语言
## 1. DQL:数据查询语言;select
## 2. DML:数据操作语言 增删改
## 3. DDL:数据定义语言
## 4. TCL:事务控制语言

1.2 DQL 基础查询语言

1.2.1 进阶1:DQL基础查询
1-1 代码格式化
## F12 可以对代码进行格式化;
1-2 简单查询
# 查询单个字段
select last_name from employees;

# 查询表中的多个字段
select last_name , salary,email from employees;

# 查询表中的所有字段
select * from employees;

# 查询常量表达式
select 100;
select 'aini';
select 100*98;
select version();
1-3 起别名
## as 关键字

    ## 方式一
        select 98 * 100 as 运算结果
        select last_name as, first_name asfrom employees;

    ## 方式二 
        select last_name 姓,first_name 名 from employees;

    ## 注意:特殊字符如#,关键字 建议加上双引号;
        select salaty as "out put" from employess;
1-4 去重
# 去重 distinct

## 查询员工表中涉及到的所有部门编号
select distinct department_id from employees;
1-5 +的作用
## 两个操作数是数值,则做加法运算;
## 其中一个为字符串时,试图将其转换为数值,转换成功继续做数值的加法运算;如果转换失败,则将此字符串转换为 0;如果其中一方为 	null,则结果已经为 null;

select "aini" + 20   # 20
select 50 + 30       # 80
select "a" + "b"     # 0
1-6 concat函数
## Null 与任何字段,任何数据拼接结果都是 null

select concat('a','b','c') as 字符连接;
select concat(last_name,first_name) as 姓名 from employees;
1-7 IFNULL 函数
## 1,IFNULL(x,y) x 为判断的值,y 为返回的值 ,意思就是假如 x 是 null
select ifnull(commission_puc,0) as 奖金率,commission-puc from employees;

## null 的地方会返回函数里面设置的值,不是null 的地方会返回本身的值
1.2.2 进阶2:条件查询
2-1 条件查询分类
# 按条件表达式进行筛选
	>  < = !=  <>  >=   <=
	
# 按逻辑表达式进行筛选
	&& || !  and  not or
	
# 模糊查询
	like ,  between  and ,  in  , null
2-2 条件表达式查询
# 查询工资>12000的员工信息;
	select * from employees where salaty > 12000;

# 查询部门编号不等于90号的员工名和部门编号
	select last_anme ,department_id from employees where department_id <> 90;
2-3 按逻辑表达式筛选
# 查询员工工资在10000 到 20000 之间的员工名,工资,奖金
	select last_name,salary commistion_put from employees where salary >= 10000 and salary <= 20000;
	
# 查询部门编号不是在90 到 110 之间的,或者工资高于15000的员工信息;
	select * from employees where department_id < 90 or department_id > 110 or salary > 15000;
	select * from employees where not  (department_id >= 90 and department_id <= 110) or salary > 15000;
2-4 模糊查询
4.1 通配符
# %表示任意多个字符,也包含零个字符;
# _表示任意单个字符
4-2 like
# 查询员工名中包含字符a的员工信息
	select * from employees where last_name like '%a%';

# 查询员工名中第三个字符为e的,第五个字符为a的员工名
	select last_name from employees where last_name like '__e_a%'

# 查询员工名中第二字符为下划线的员工名
	select last_name from employees where last_name like '_\_%';  # 需要对下划线进项转义

# 自定义转义符号
	select last_name from employees where last_name like '_$_%' escape '$';
	select last_name from employees where last_name like '_z_%' escape 'z';
4-3 between and
# 包含临界值,临界值不能颠倒

# 查询员工编号在100到120之间的员工信息
	select * from employees where employee_id between 100 and 120;
4-4 in
## 判断值是否等于in列表中的某一项
## 不支持括号里使用通配符
## in 列表的值类型必须统一或兼容

# 查询员工的工种编号是IT_PROG,AD_VP,AD_PRES 中的一个工种名和工种编号;
	select last_name ,job_id from employees where job_id in ('IT_PROG','AD_VP','AD_PRES')
4-5 is null
## 不能用来判断字段值是否为空

## 查询没有奖金的员工和奖金率
	select last_name,commistion_pct from employees where commisstion_pct is null
	## = 不能判断字段值是否为Null,所以使用is null

## 查询有奖金的员工和奖金率
	select last_name,commistion_pct from employees where commission-pct is not null
4-6 安全等于
#案例1:查询没有奖金的员工名和奖金率
SELECT
	last_name,
	commission_pct
FROM
	employees
WHERE
	commission_pct <=>NULL;	
	
#案例2:查询工资为12000的员工信息
SELECT
	last_name,
	salary
FROM
	employees

WHERE 
	salary <=> 12000;
4-7 Isnull函数
## 功能:判断一个字段值是否为 null;
## Isnull(字段名),就一个参数,值为 null 返回 1,值不是为空则返回 0;

# IS NULL:仅仅可以判断NULL值,可读性较高,建议使用
## <=>   :既可以判断NULL值,又可以判断普通的数值,可读性较低
1.2.3 进阶3:排序
/*
特点:
1、asc代表的是升序,可以省略   desc代表的是降序
2、order by子句可以支持 单个字段、别名、表达式、函数、多个字段
3、order by子句在查询语句的最后面,除了limit子句
*/

#1、按单个字段排序
SELECT * FROM employees ORDER BY salary DESC;

#2、添加筛选条件再排序
#案例:查询部门编号>=90的员工信息,并按员工编号降序
SELECT *
FROM employees
WHERE department_id>=90
ORDER BY employee_id DESC;

#3、按表达式排序
#案例:查询员工信息 按年薪降序
SELECT *,salary*12*(1+IFNULL(commission_pct,0))
FROM employees
ORDER BY salary*12*(1+IFNULL(commission_pct,0)) DESC;

#4、按别名排序
#案例:查询员工信息 按年薪升序
SELECT *,salary*12*(1+IFNULL(commission_pct,0)) 年薪
FROM employees
ORDER BY 年薪 ASC;

#5、按函数排序
#案例:查询员工名,并且按名字的长度降序

SELECT LENGTH(last_name),last_name 
FROM employees
ORDER BY LENGTH(last_name) DESC;

#6、按多个字段排序
#案例:查询员工信息,要求先按工资降序,再按employee_id升序
SELECT *
FROM employees
ORDER BY salary DESC,employee_id ASC;

img

## Order by 语句一般放在最后
1.2.4 进阶4:常见函数介绍
4-1 分类
## 分类:
	## 单行函数:
		## Concat() ; length() ; ifnull() ; isnull() 等;
	## 分组函数:
		## 功能:做统计使用,统计函数;
			
##单行函数:
    ## 1. 字符函数:
    ## 2. 数学函数:
    ## 3. 日期函数:
    ## 4. 其他函数:
    ## 5. 流程控制函数:
4-2 字符函数
2-1 length(str)
## 注一个汉字占三个字节

SELECT LENGTH('john');
SELECT LENGTH('张三丰hahaha');

SHOW VARIABLES LIKE '%char%'
2-2 concat
SELECT CONCAT(last_name,'_',first_name) 姓名 FROM employees;
2-3 upper,lower
SELECT UPPER('john');
SELECT LOWER('joHn');
#示例:将姓变大写,名变小写,然后拼接
SELECT CONCAT(UPPER(last_name),LOWER(first_name))  姓名 FROM employees;
2-4 substr,substring
# 注意:索引从1开始
#截取从指定索引处后面所有字符
SELECT SUBSTR('李莫愁爱上了陆展元',7)  out_put;

#截取从指定索引处指定字符长度的字符
SELECT SUBSTR('李莫愁爱上了陆展元',1,3) out_put;

#案例:姓名中首字符大写,其他字符小写然后用_拼接,显示出来

SELECT CONCAT(UPPER(SUBSTR(last_name,1,1)),'_',LOWER(SUBSTR(last_name,2)))  out_put FROM employees;
## 可以放三个参数,第一个为字段或要截取的字符串,第二个是开始位置的索引,第三个是长度
2-5 instr
## Instr 返回一个小字符串在大字符串里面的第一次出现的起始索引找不到就返回 0
SELECT INSTR('杨不殷六侠悔爱上了殷六侠','殷八侠') AS out_put;
2-6 trim
# 只去掉前后的空格或者前后的某个指定字符
SELECT LENGTH(TRIM('    张翠山    ')) AS out_put;

SELECT TRIM('aa' FROM 'aaaaaaaaa张aaaaaaaaaaaa翠山aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')  AS out_put;
2-7 lpad
## lpad 在左边填充指定字符,到指定长度(指定长度指的是总长度)
SELECT LPAD('殷素素',2,'*') AS out_put;
2-8 rpad
## 8,rpad 右填充,跟lpad同理
SELECT RPAD('殷素素',12,'ab') AS out_put;
2-9 replace
## replace 替换 ,可以把所有被替换的内容都替换掉
## 三个参数(字符串,’被替换的内容’,’替换的新内容’)
SELECT REPLACE('周芷若周芷若周芷若周芷若张无忌爱上了周芷若','周芷若','赵敏') AS out_put;
4-3 数学函数
3-1 round
## 一个参数,直接四舍五入到整数,第二个参数可以设置保留的小数位数
SELECT ROUND(1.567,2);
SELECT ROUND(-1.55);
3-2 floor
SELECT FLOOR(-9.99);
3-3 ceil
SELECT CEIL(-1.02);
3-4 truncate截断
SELECT TRUNCATE(1.69999,1);
## 结果是 1.6
3-5 mod
SELECT MOD(10,-3);
SELECT 10%3;
4-3 日期函数
3-1 now
#now 返回当前系统日期+时间
SELECT NOW();
3-2 curdate
#curdate 返回当前系统日期,不包含时间
SELECT CURDATE();
3-3 curtime
#curtime 返回当前时间,不包含日期
SELECT CURTIME();
3-4 获取指定部分
#可以获取指定的部分,年、月、日、小时、分钟、秒
SELECT YEAR(NOW());
SELECT YEAR('1998-1-1');

SELECT  YEAR(hiredate)FROM employees;

SELECT MONTH(NOW());
SELECT MONTHNAME(NOW());


#str_to_date 将字符通过指定的格式转换成日期
SELECT STR_TO_DATE('1998-3-2','%Y-%c-%d') AS out_put;

#查询入职日期为1992--4-3的员工信息
SELECT * FROM employees WHERE hiredate = '1992-4-3';

SELECT * FROM employees WHERE hiredate = STR_TO_DATE('4-3 1992','%c-%d %Y');


#date_format 将日期转换成字符
SELECT DATE_FORMAT(NOW(),'%y年%m月%d日') AS out_put;

#查询有奖金的员工名和入职日期(xx月/xx日 xx年)
SELECT last_name,DATE_FORMAT(hiredate,'%m月/%d日 %y年') 入职日期
FROM employees
WHERE commission_pct IS NOT NULL;

4-4 其他函数
SELECT VERSION();
SELECT DATABASE();
SELECT USER();
4-5 流程控制函数
5-1 if函数
#1.if函数: if else 的效果

SELECT IF(10<5,'大','小');

SELECT last_name,commission_pct,IF(commission_pct IS NULL,'没奖金,呵呵','有奖金,嘻嘻') 备注
FROM employees;
5-2 case函数的使用
/*
case 要判断的字段或表达式
when 常量1 then 要显示的值1或语句1;
when 常量2 then 要显示的值2或语句2;
...
else 要显示的值n或语句n;
end
*/

/*案例:查询员工的工资,要求

部门号=30,显示的工资为1.1倍
部门号=40,显示的工资为1.2倍
部门号=50,显示的工资为1.3倍
其他部门,显示的工资为原工资

*/

SELECT salary 原始工资,department_id,
CASE department_id
WHEN 30 THEN salary*1.1
WHEN 40 THEN salary*1.2
WHEN 50 THEN salary*1.3
ELSE salary
END AS 新工资
FROM employees;



#3.case 函数的使用二:类似于 多重if
/*
case 
when 条件1 then 要显示的值1或语句1
when 条件2 then 要显示的值2或语句2
。。。
else 要显示的值n或语句n
end
*/

#案例:查询员工的工资的情况
/*
如果工资>20000,显示A级别
如果工资>15000,显示B级别
如果工资>10000,显示C级别
否则,显示D级别
*/

SELECT salary,
CASE 
WHEN salary>20000 THEN 'A'
WHEN salary>15000 THEN 'B'
WHEN salary>10000 THEN 'C'
ELSE 'D'
END AS 工资级别
FROM employees;
4-6 分组函数
/*
功能:用作统计使用,又称为聚合函数或统计函数或组函数
分类:
sum 求和、avg 平均值、max 最大值 、min 最小值 、count 计算个数

特点:
1、sum、avg一般用于处理数值型
   max、min、count可以处理任何类型
2、以上分组函数都忽略null值
3、可以和distinct搭配实现去重的运算
4、count函数的单独介绍
一般使用count(*)用作统计行数
5、和分组函数一同查询的字段要求是group by后的字段
*/
6-1 简单的使用
SELECT SUM(salary) FROM employees;
SELECT AVG(salary) FROM employees;
SELECT MIN(salary) FROM employees;
SELECT MAX(salary) FROM employees;
SELECT COUNT(salary) FROM employees;

SELECT SUM(salary),AVG(salary) 平均,MAX(salary) 最高,MIN(salary) 最低,COUNT(salary) 个数
FROM employees;

SELECT SUM(salary),ROUND(AVG(salary),2) 平均,MAX(salary) 最高,MIN(salary),COUNT(salary) 个数
FROM employees;

6-2 参数支持那些类型
SELECT SUM(last_name) ,AVG(last_name) FROM employees;
SELECT SUM(hiredate) ,AVG(hiredate) FROM employees;

SELECT MAX(last_name),MIN(last_name) FROM employees;

SELECT MAX(hiredate),MIN(hiredate) FROM employees;

SELECT COUNT(commission_pct) FROM employees;
SELECT COUNT(last_name) FROM employees;
6-3 能否忽略null
SELECT 
SUM(commission_pct),
AVG(commission_pct),
SUM(commission_pct)/35,
SUM(commission_pct)/107 
FROM employees;

SELECT MAX(commission_pct) ,MIN(commission_pct) FROM employees;

SELECT COUNT(commission_pct) FROM employees;
SELECT commission_pct FROM employees;
6-3 和distinct搭配
SELECT SUM(DISTINCT salary),SUM(salary) FROM employees;

SELECT COUNT(DISTINCT salary),COUNT(salary) FROM employees;
6-5 count函数的详细介绍
SELECT COUNT(salary) FROM employees;

SELECT COUNT(*) FROM employees;

SELECT COUNT(1) FROM employees;

效率:
## MYISAM存储引擎下  ,COUNT(*)的效率高
## INNODB 存储引擎下,COUNT(*)和COUNT(1)的效率差不多,比COUNT(字段)要高一些
6-6 和分组函数一同查询的字段有限制
SELECT AVG(salary),employee_id  FROM employees;
6-7 datediff函数
## 返回两个日期相差的天数
select datediff('2017-10-1','2017-05-18');
select datediff(max(hiredate),min(hiredate)) from employees;
1.2.5 进阶5:分组查询
/*
语法:
select 查询列表
from 表
【where 筛选条件】
group by 分组的字段
【order by 排序的字段】;

特点:
1、和分组函数一同查询的字段必须是group by后出现的字段
2、筛选分为两类:分组前筛选和分组后筛选
		针对的表			位置		连接的关键字
分组前筛选	原始表				group by前	where
	
分组后筛选	group by后的结果集    		group by后	having

问题1:分组函数做筛选能不能放在where后面
答:不能

问题2:where——group by——having

一般来讲,能用分组前筛选的,尽量使用分组前筛选,提高效率

3、分组可以按单个字段也可以按多个字段
4、可以搭配着排序使用
*/
5-1 简单的分组
#案例1:查询每个工种的员工平均工资
SELECT AVG(salary),job_id
FROM employees
GROUP BY job_id;

#案例2:查询每个位置的部门个数
SELECT COUNT(*),location_id
FROM departments
GROUP BY location_id;
5-2 分组前筛选
#案例1:查询邮箱中包含a字符的 每个部门的最高工资
SELECT MAX(salary),department_id
FROM employees
WHERE email LIKE '%a%'
GROUP BY department_id;

#案例2:查询有奖金的每个领导手下员工的平均工资
SELECT AVG(salary),manager_id
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY manager_id;
5-3 分组后筛选
#案例:查询哪个部门的员工个数>5
# ①查询每个部门的员工个数
SELECT COUNT(*),department_id
FROM employees
GROUP BY department_id;
#② 筛选刚才①结果
SELECT COUNT(*),department_id
FROM employees
GROUP BY department_id
HAVING COUNT(*)>5;


#案例2:每个工种有奖金的员工的最高工资>12000的工种编号和最高工资
SELECT job_id,MAX(salary)
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY job_id
HAVING MAX(salary)>12000;

#案例3:领导编号>102的每个领导手下的最低工资大于5000的领导编号和最低工资

manager_id>102

SELECT manager_id,MIN(salary)
FROM employees
GROUP BY manager_id
HAVING MIN(salary)>5000;
5-4 添加排序
#案例:每个工种有奖金的员工的最高工资>6000的工种编号和最高工资,按最高工资升序

SELECT job_id,MAX(salary) m
FROM employees
WHERE commission_pct IS NOT NULL
GROUP BY job_id
HAVING m>6000
ORDER BY m ;

5-5 按多个字段分组
#案例:查询每个工种每个部门的最低工资,并按最低工资降序

SELECT MIN(salary),job_id,department_id
FROM employees
GROUP BY department_id,job_id
ORDER BY MIN(salary) DESC;
1.2.6 进阶6:连接查询
/*
含义:又称多表查询,当查询的字段来自于多个表时,就会用到连接查询

笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行

发生原因:没有有效的连接条件
如何避免:添加有效的连接条件

分类:
	按年代分类:
	sql92标准:仅仅支持内连接
	sql99标准【推荐】:支持内连接+外连接(左外和右外)+交叉连接
	
	按功能分类:
		内连接:
			等值连接
			非等值连接
			自连接
		外连接:
			左外连接
			右外连接
			全外连接
		交叉连接
*/
6-1 sql92标准
1-1 等值连接
/*
① 多表等值连接的结果为多表的交集部分
② n表连接,至少需要n-1个连接条件
③ 多表的顺序没有要求
④ 一般需要为表起别名
⑤ 可以搭配前面介绍的所有子句使用,比如排序、分组、筛选

*/

#案例1:查询女神名和对应的男神名
SELECT name,boyName 
FROM boys,beauty
WHERE beauty.boyfriend_id= boys.id;

#案例2:查询员工名和对应的部门名
SELECT last_name,department_name
FROM employees,departments
WHERE employees.`department_id`=departments.`department_id`;
1-2 给表起别名
/*
①提高语句的简洁度
②区分多个重名的字段
注意:
    如果为表起了别名,则查询的字段就不能使用原来的表名去限定,
    两个表的顺序可以改变
*/
#查询员工名、工种号、工种名

SELECT e.last_name,e.job_id,j.job_title
FROM employees  e,jobs j
WHERE e.`job_id`=j.`job_id`;
1-3 两个表顺序调换
#查询员工名、工种号、工种名

SELECT e.last_name,e.job_id,j.job_title
FROM jobs j,employees e
WHERE e.`job_id`=j.`job_id`;
1-4 可以加筛选
#案例:查询有奖金的员工名、部门名
SELECT last_name,department_name,commission_pct
FROM employees e,departments d
WHERE e.`department_id`=d.`department_id`
AND e.`commission_pct` IS NOT NULL;

#案例2:查询城市名中第二个字符为o的部门名和城市名
SELECT department_name,city
FROM departments d,locations l
WHERE d.`location_id` = l.`location_id`
AND city LIKE '_o%';
1-5 可以加分组
#案例1:查询每个城市的部门个数
SELECT COUNT(*) 个数,city
FROM departments d,locations l
WHERE d.`location_id`=l.`location_id`
GROUP BY city;


#案例2:查询有奖金的每个部门的部门名和部门的领导编号和该部门的最低工资
SELECT department_name,d.`manager_id`,MIN(salary)
FROM departments d,employees e
WHERE d.`department_id`=e.`department_id`
AND commission_pct IS NOT NULL
GROUP BY department_name,d.`manager_id`;
1-6 可以加排序
#案例:查询每个工种的工种名和员工的个数,并且按员工个数降序
SELECT job_title,COUNT(*)
FROM employees e,jobs j
WHERE e.`job_id`=j.`job_id`
GROUP BY job_title
ORDER BY COUNT(*) DESC;

1-7 三表连接
#案例:查询员工名、部门名和所在的城市
SELECT last_name,department_name,city
FROM employees e,departments d,locations l
WHERE e.`department_id`=d.`department_id`
AND d.`location_id`=l.`location_id`
AND city LIKE 's%'
ORDER BY department_name DESC;

1-8 非等值连接
#案例1:查询员工的工资和工资级别


SELECT salary,grade_level
FROM employees e,job_grades g
WHERE salary BETWEEN g.`lowest_sal` AND g.`highest_sal`
AND g.`grade_level`='A';

/*
select salary,employee_id from employees;
select * from job_grades;
CREATE TABLE job_grades
(grade_level VARCHAR(3),
 lowest_sal  int,
 highest_sal int);

INSERT INTO job_grades
VALUES ('A', 1000, 2999);

INSERT INTO job_grades
VALUES ('B', 3000, 5999);

INSERT INTO job_grades
VALUES('C', 6000, 9999);

INSERT INTO job_grades
VALUES('D', 10000, 14999);

INSERT INTO job_grades
VALUES('E', 15000, 24999);

INSERT INTO job_grades
VALUES('F', 25000, 40000);

*/
1-9 自连接
#案例:查询 员工名和上级的名称
SELECT e.employee_id,e.last_name,m.employee_id,m.last_name
FROM employees e,employees m
WHERE e.`manager_id`=m.`employee_id`;

## 自连接就是把一张表用两次来查询数据
1-10 案例讲解
## 单行函数
    #1.	显示系统时间(注:日期+时间)
    SELECT NOW();

    #2.	查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary)
    SELECT employee_id,last_name,salary,salary*1.2 "new salary"
    FROM employees;
    #3.	将员工的姓名按首字母排序,并写出姓名的长度(length)
    SELECT LENGTH(last_name) 长度,SUBSTR(last_name,1,1) 首字符,last_name
    FROM employees
    ORDER BY 首字符;

    #4.	做一个查询,产生下面的结果
    <last_name> earns <salary> monthly but wants <salary*3>
    Dream Salary
    King earns 24000 monthly but wants 72000

    SELECT CONCAT(last_name,' earns ',salary,' monthly but wants ',salary*3) AS "Dream 			Salary"
    FROM employees
    WHERE salary=24000;

    #5.	使用case-when,按照下面的条件:
    job                  grade
    AD_PRES            A
    ST_MAN             B
    IT_PROG             C
    SA_REP              D
    ST_CLERK           E
    产生下面的结果
    Last_name	Job_id	Grade
    king	AD_PRES	A

    SELECT last_name,job_id AS  job,
    CASE job_id
    WHEN 'AD_PRES' THEN 'A' 
    WHEN 'ST_MAN' THEN 'B' 
    WHEN 'IT_PROG' THEN 'C' 
    WHEN 'SA_PRE' THEN 'D'
    WHEN 'ST_CLERK' THEN 'E'
    END AS Grade
    FROM employees
    WHERE job_id = 'AD_PRES';

#分组查询
    #1.查询各job_id的员工工资的最大值,最小值,平均值,总和,并按job_id升序
    SELECT MAX(salary),MIN(salary),AVG(salary),SUM(salary),job_id
    FROM employees
    GROUP BY job_id
    ORDER BY job_id;


    #2.查询员工最高工资和最低工资的差距(DIFFERENCE)
    SELECT MAX(salary)-MIN(salary) DIFFRENCE
    FROM employees;

    #3.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内
    SELECT MIN(salary),manager_id
    FROM employees
    WHERE manager_id IS NOT NULL
    GROUP BY manager_id
    HAVING MIN(salary)>=6000;

    #4.查询所有部门的编号,员工数量和工资平均值,并按平均工资降序
    SELECT department_id,COUNT(*),AVG(salary) a
    FROM employees
    GROUP BY department_id
    ORDER BY a DESC;

    #5.选择具有各个job_id的员工人数
    SELECT COUNT(*) 个数,job_id
    FROM employees
    GROUP BY job_id;
    
## 排序查询
    #1.查询员工的姓名和部门号和年薪,按年薪降序 按姓名升序
    SELECT last_name,department_id,salary*12*(1+IFNULL(commission_pct,0)) 年薪
    FROM employees
    ORDER BY 年薪 DESC,last_name ASC;


    #2.选择工资不在8000到17000的员工的姓名和工资,按工资降序
    SELECT last_name,salary
    FROM employees
    WHERE salary NOT BETWEEN 8000 AND 17000
    ORDER BY salary DESC;

    #3.查询邮箱中包含e的员工信息,并先按邮箱的字节数降序,再按部门号升序
    SELECT *,LENGTH(email)
    FROM employees
    WHERE email LIKE '%e%'
    ORDER BY LENGTH(email) DESC,department_id ASC;
    
    
# 分组函数
    #1.查询公司员工工资的最大值,最小值,平均值,总和
    SELECT MAX(salary) 最大值,MIN(salary) 最小值,AVG(salary) 平均值,SUM(salary)FROM employees;

    #2.查询员工表中的最大入职时间和最小入职时间的相差天数 (DIFFRENCE)
    SELECT MAX(hiredate) 最大,MIN(hiredate) 最小,(MAX(hiredate)-MIN(hiredate))/1000/3600/24 DIFFRENCE
    FROM employees;
    SELECT DATEDIFF(MAX(hiredate),MIN(hiredate)) DIFFRENCE
    FROM employees;
    SELECT DATEDIFF('1995-2-7','1995-2-6');


    #3.查询部门编号为90的员工个数
    SELECT COUNT(*) FROM employees WHERE department_id = 90;
6-2 sql99语法
/*
语法:
	select 查询列表
	from 表1 别名 【连接类型】
	join 表2 别名 
	on 连接条件
	【where 筛选条件】
	【group by 分组】
	【having 筛选条件】
	【order by 排序列表】
	
分类:
内连接(★):inner
外连接
	左外(★):left 【outer】
	右外(★):right 【outer】
	全外:full【outer】
交叉连接:cross 
*/

2-1 内连接
/*
语法:
select 查询列表
from 表1 别名
inner join 表2 别名
on 连接条件;

分类:
等值
非等值
自连接

特点:
①添加排序、分组、筛选
②inner可以省略
③ 筛选条件放在where后面,连接条件放在on后面,提高分离性,便于阅读
④inner join连接和sql92语法中的等值连接效果是一样的,都是查询多表的交集
*/
2-2 等值连接
#案例1.查询员工名、部门名
SELECT last_name,department_name
FROM departments d
 JOIN  employees e
ON e.`department_id` = d.`department_id`;



#案例2.查询名字中包含e的员工名和工种名(添加筛选)
    SELECT last_name,job_title
    FROM employees e
    INNER JOIN jobs j
    ON e.`job_id`=  j.`job_id`
    WHERE e.`last_name` LIKE '%e%';



#3. 查询部门个数>3的城市名和部门个数,(添加分组+筛选)
    #①查询每个城市的部门个数
    #②在①结果上筛选满足条件的
    SELECT city,COUNT(*) 部门个数
    FROM departments d
    INNER JOIN locations l
    ON d.`location_id`=l.`location_id`
    GROUP BY city
    HAVING COUNT(*)>3;




#案例4.查询哪个部门的员工个数>3的部门名和员工个数,并按个数降序(添加排序)
    #①查询每个部门的员工个数
    SELECT COUNT(*),department_name
    FROM employees e
    INNER JOIN departments d
    ON e.`department_id`=d.`department_id`
    GROUP BY department_name

    #② 在①结果上筛选员工个数>3的记录,并排序
    SELECT COUNT(*) 个数,department_name
    FROM employees e
    INNER JOIN departments d
    ON e.`department_id`=d.`department_id`
    GROUP BY department_name
    HAVING COUNT(*)>3
    ORDER BY COUNT(*) DESC;

#5.查询员工名、部门名、工种名,并按部门名降序(添加三表连接)
SELECT last_name,department_name,job_title
FROM employees e
INNER JOIN departments d ON e.`department_id`=d.`department_id`
INNER JOIN jobs j ON e.`job_id` = j.`job_id`
ORDER BY department_name DESC;

2-3 非等值连接
#查询员工的工资级别
SELECT salary,grade_level
FROM employees e
 JOIN job_grades g
 ON e.`salary` BETWEEN g.`lowest_sal` AND g.`highest_sal`;
 
 
 #查询工资级别的个数>20的个数,并且按工资级别降序
 SELECT COUNT(*),grade_level
FROM employees e
 JOIN job_grades g
 ON e.`salary` BETWEEN g.`lowest_sal` AND g.`highest_sal`
 GROUP BY grade_level
 HAVING COUNT(*)>20
 ORDER BY grade_level DESC;
2-4 自连接
 #查询员工的名字、上级的名字
 SELECT e.last_name,m.last_name
 FROM employees e
 JOIN employees m
 ON e.`manager_id`= m.`employee_id`;
 
  #查询姓名中包含字符k的员工的名字、上级的名字
 SELECT e.last_name,m.last_name
 FROM employees e
 JOIN employees m
 ON e.`manager_id`= m.`employee_id`
 WHERE e.`last_name` LIKE '%k%';
2-5 外连接
 /*
 应用场景:用于查询一个表中有,另一个表没有的记录
 
 特点:
 1、外连接的查询结果为主表中的所有记录
	如果从表中有和它匹配的,则显示匹配的值
	如果从表中没有和它匹配的,则显示null
	外连接查询结果=内连接结果+主表中有而从表没有的记录
 2、左外连接,left join左边的是主表
    右外连接,right join右边的是主表
 3、左外和右外交换两个表的顺序,可以实现同样的效果 
 4、全外连接=内连接的结果+表1中有但表2没有的+表2中有但表1没有的
 */
 #引入:查询男朋友 不在男神表的的女神名
 
 SELECT * FROM beauty;
 SELECT * FROM boys;
2-6 左外连接
 SELECT b.*,bo.*
 FROM boys bo
 LEFT OUTER JOIN beauty b
 ON b.`boyfriend_id` = bo.`id`
 WHERE b.`id` IS NULL;
 
 
 #案例1:查询哪个部门没有员工
 #左外
 SELECT d.*,e.employee_id
 FROM departments d
 LEFT OUTER JOIN employees e
 ON d.`department_id` = e.`department_id`
 WHERE e.`employee_id` IS NULL;
2-7 右外连接
  SELECT d.*,e.employee_id
 FROM employees e
 RIGHT OUTER JOIN departments d
 ON d.`department_id` = e.`department_id`
 WHERE e.`employee_id` IS NULL;
2-8 全连接
 USE girls;
 SELECT b.*,bo.*
 FROM beauty b
 FULL OUTER JOIN boys bo
 ON b.`boyfriend_id` = bo.id;
2-9 交叉连接
 SELECT b.*,bo.*
 FROM beauty b
 CROSS JOIN boys bo;
2-10 sql92和slq99pk
 #sql92和 sql99pk
 /*
 功能:sql99支持的较多
 可读性:sql99实现连接条件和筛选条件的分离,可读性较高
 */
2-11 案例练习
## 连接查询
#1.显示所有员工的姓名,部门号和部门名称。
USE myemployees;

SELECT last_name,d.department_id,department_name
FROM employees e,departments d
WHERE e.`department_id` = d.`department_id`;

#2.查询90号部门员工的job_id和90号部门的location_id
SELECT job_id,location_id
FROM employees e,departments d
WHERE e.`department_id`=d.`department_id`
AND e.`department_id`=90;

#3.	选择所有有奖金的员工的
last_name , department_name , location_id , city

SELECT last_name , department_name , l.location_id , city
FROM employees e,departments d,locations l
WHERE e.department_id = d.department_id
AND d.location_id=l.location_id
AND e.commission_pct IS NOT NULL;
#4.选择city在Toronto工作的员工的
last_name , job_id , department_id , department_name 

SELECT last_name , job_id , d.department_id , department_name 
FROM employees e,departments d ,locations l
WHERE e.department_id = d.department_id
AND d.location_id=l.location_id
AND city = 'Toronto';

#5.查询每个工种、每个部门的部门名、工种名和最低工资
SELECT department_name,job_title,MIN(salary) 最低工资
FROM employees e,departments d,jobs j
WHERE e.`department_id`=d.`department_id`
AND e.`job_id`=j.`job_id`
GROUP BY department_name,job_title;

#6.查询每个国家下的部门个数大于2的国家编号
SELECT country_id,COUNT(*) 部门个数
FROM departments d,locations l
WHERE d.`location_id`=l.`location_id`
GROUP BY country_id
HAVING 部门个数>2;

#7、选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
employees	Emp#	manager	Mgr#
kochhar		101	king	100

SELECT e.last_name employees,e.employee_id "Emp#",m.last_name manager,m.employee_id "Mgr#"
FROM employees e,employees m
WHERE e.manager_id = m.employee_id
AND e.last_name='kochhar';


## 外连接


#一、查询编号>3的女神的男朋友信息,如果有则列出详细,如果没有,用null填充
SELECT b.id,b.name,bo.*
FROM beauty b
LEFT OUTER JOIN boys bo
ON b.`boyfriend_id` = bo.`id`
WHERE b.`id`>3;

#二、查询哪个城市没有部门
SELECT city
FROM departments d
RIGHT OUTER JOIN locations l 
ON d.`location_id`=l.`location_id`
WHERE  d.`department_id` IS NULL;

#三、查询部门名为SAL或IT的员工信息
SELECT e.*,d.department_name,d.`department_id`
FROM departments  d
LEFT JOIN employees e
ON d.`department_id` = e.`department_id`
WHERE d.`department_name` IN('SAL','IT');

SELECT * FROM departments
WHERE `department_name` IN('SAL','IT');

## 子查询
#1.	查询和Zlotkey相同部门的员工姓名和工资
    #①查询Zlotkey的部门
    SELECT department_id
    FROM employees
    WHERE last_name = 'Zlotkey'
    #②查询部门号=①的姓名和工资
    SELECT last_name,salary
    FROM employees
    WHERE department_id = (
        SELECT department_id
        FROM employees
        WHERE last_name = 'Zlotkey'
    )

#2.查询工资比公司平均工资高的员工的员工号,姓名和工资。
    #①查询平均工资
    SELECT AVG(salary)
    FROM employees
    #②查询工资>①的员工号,姓名和工资。
    SELECT last_name,employee_id,salary
    FROM employees
    WHERE salary>(
        SELECT AVG(salary)
        FROM employees
    );



#3.查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资
    #①查询各部门的平均工资
    SELECT AVG(salary),department_id
    FROM employees
    GROUP BY department_id

    #②连接①结果集和employees表,进行筛选
    SELECT employee_id,last_name,salary,e.department_id
    FROM employees e
    INNER JOIN (
        SELECT AVG(salary) ag,department_id
        FROM employees
        GROUP BY department_id
    ) ag_dep
    ON e.department_id = ag_dep.department_id
    WHERE salary>ag_dep.ag ;



#4.	查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
    #①查询姓名中包含字母u的员工的部门
    SELECT  DISTINCT department_id
    FROM employees
    WHERE last_name LIKE '%u%'
    #②查询部门号=①中的任意一个的员工号和姓名
    SELECT last_name,employee_id
    FROM employees
    WHERE department_id IN(
        SELECT  DISTINCT department_id
        FROM employees
        WHERE last_name LIKE '%u%'
    );


#5. 查询在部门的location_id为1700的部门工作的员工的员工号
    #①查询location_id为1700的部门
    SELECT DISTINCT department_id
    FROM departments 
    WHERE location_id  = 1700

    #②查询部门号=①中的任意一个的员工号
    SELECT employee_id
    FROM employees
    WHERE department_id =ANY(
        SELECT DISTINCT department_id
        FROM departments 
        WHERE location_id  = 1700
    );
#6.查询管理者是King的员工姓名和工资
    #①查询姓名为king的员工编号
    SELECT employee_id
    FROM employees
    WHERE last_name  = 'K_ing'

    #②查询哪个员工的manager_id = ①
    SELECT last_name,salary
    FROM employees
    WHERE manager_id IN(
        SELECT employee_id
        FROM employees
        WHERE last_name  = 'K_ing'
    );

#7.查询工资最高的员工的姓名,要求first_name和last_name显示为一列,列名为 姓.名
    #①查询最高工资
    SELECT MAX(salary)
    FROM employees
    #②查询工资=①的姓.名
    SELECT CONCAT(first_name,last_name) "姓.名"
    FROM employees
    WHERE salary=(
        SELECT MAX(salary)
        FROM employees
    );

1.2.7 进阶7:子查询
/*
含义:
出现在其他语句中的select语句,称为子查询或内查询
外部的查询语句,称为主查询或外查询

分类:
按子查询出现的位置:
	select后面:
		仅仅支持标量子查询
	
	from后面:
		支持表子查询
	where或having后面:★
		标量子查询(单行) √
		列子查询  (多行) √
		行子查询
	
	exists后面(相关子查询)
		表子查询
按结果集的行列数不同:
	标量子查询(结果集只有一行一列)
	列子查询(结果集只有一列多行)
	行子查询(结果集有一行多列)
	表子查询(结果集一般为多行多列)
*/
7-1 where或having后面
/*
1、标量子查询(单行子查询)
2、列子查询(多行子查询)
3、行子查询(多列多行)
特点:
①子查询放在小括号内
②子查询一般放在条件的右侧
③标量子查询,一般搭配着单行操作符使用
> < >= <= = <>

列子查询,一般搭配着多行操作符使用
in、any/some、all
④子查询的执行优先于主查询执行,主查询的条件用到了子查询的结果

*/
1-1 标量子查询
#案例1:谁的工资比 Abel 高?
    #①查询Abel的工资
    SELECT salary
    FROM employees
    WHERE last_name = 'Abel'
    #②查询员工的信息,满足 salary>①结果
    SELECT *
    FROM employees
    WHERE salary>(

        SELECT salary
        FROM employees
        WHERE last_name = 'Abel'
    );

#案例2:返回job_id与141号员工相同,salary比143号员工多的员工 姓名,job_id 和工资
    #①查询141号员工的job_id
    SELECT job_id
    FROM employees
    WHERE employee_id = 141
    #②查询143号员工的salary
    SELECT salary
    FROM employees
    WHERE employee_id = 143

    #③查询员工的姓名,job_id 和工资,要求job_id=①并且salary>②
    SELECT last_name,job_id,salary
    FROM employees
    WHERE job_id = (
        SELECT job_id
        FROM employees
        WHERE employee_id = 141
    ) AND salary>(
        SELECT salary
        FROM employees
        WHERE employee_id = 143
    );


#案例3:返回公司工资最少的员工的last_name,job_id和salary
    #①查询公司的 最低工资
    SELECT MIN(salary)
    FROM employees
    #②查询last_name,job_id和salary,要求salary=①
    SELECT last_name,job_id,salary
    FROM employees
    WHERE salary=(
        SELECT MIN(salary)
        FROM employees
    );


#案例4:查询最低工资大于50号部门最低工资的部门id和其最低工资
    #①查询50号部门的最低工资
    SELECT  MIN(salary)
    FROM employees
    WHERE department_id = 50
    #②查询每个部门的最低工资
    SELECT MIN(salary),department_id
    FROM employees
    GROUP BY department_id

#③ 在②基础上筛选,满足min(salary)>①
    SELECT MIN(salary),department_id
    FROM employees
    GROUP BY department_id
    HAVING MIN(salary)>(
        SELECT  MIN(salary)
        FROM employees
        WHERE department_id = 50
    );

#非法使用标量子查询
    SELECT MIN(salary),department_id
    FROM employees
    GROUP BY department_id
    HAVING MIN(salary)>(
        SELECT  salary
        FROM employees
        WHERE department_id = 250
    );
1-2 列子查询
#案例1:返回location_id是1400或1700的部门中的所有员工姓名
    #①查询location_id是1400或1700的部门编号
    SELECT DISTINCT department_id
    FROM departments
    WHERE location_id IN(1400,1700)
    #②查询员工姓名,要求部门号是①列表中的某一个

    SELECT last_name
    FROM employees
    WHERE department_id  <>ALL(
        SELECT DISTINCT department_id
        FROM departments
        WHERE location_id IN(1400,1700)
    );


#案例2:返回其它工种中比job_id为‘IT_PROG’工种任一工资低的员工的员工号、姓名、job_id 以及salary
    #①查询job_id为‘IT_PROG’部门任一工资
    SELECT DISTINCT salary
    FROM employees
    WHERE job_id = 'IT_PROG'
    #②查询员工号、姓名、job_id 以及salary,salary<(①)的任意一个
    SELECT last_name,employee_id,job_id,salary
    FROM employees
    WHERE salary<ANY(
        SELECT DISTINCT salary
        FROM employees
        WHERE job_id = 'IT_PROG'
    ) AND job_id<>'IT_PROG';

#或
    SELECT last_name,employee_id,job_id,salary
    FROM employees
    WHERE salary<(
        SELECT MAX(salary)
        FROM employees
        WHERE job_id = 'IT_PROG'

    ) AND job_id<>'IT_PROG';


#案例3:返回其它部门中比job_id为‘IT_PROG’部门所有工资都低的员工   的员工号、姓名、job_id 以及salary
    SELECT last_name,employee_id,job_id,salary
    FROM employees
    WHERE salary<ALL(
        SELECT DISTINCT salary
        FROM employees
        WHERE job_id = 'IT_PROG'

    ) AND job_id<>'IT_PROG';

#或

    SELECT last_name,employee_id,job_id,salary
    FROM employees
    WHERE salary<(
        SELECT MIN( salary)
        FROM employees
        WHERE job_id = 'IT_PROG'

    ) AND job_id<>'IT_PROG';

1-3 行子查询
##行子查询(结果集一行多列或多行多列)

#案例:查询员工编号最小并且工资最高的员工信息
SELECT * 
FROM employees
WHERE (employee_id,salary)=(
	SELECT MIN(employee_id),MAX(salary)
	FROM employees
);

    #①查询最小的员工编号
    SELECT MIN(employee_id)
    FROM employees
    #②查询最高工资
    SELECT MAX(salary)
    FROM employees
    #③查询员工信息
    SELECT *
    FROM employees
    WHERE employee_id=(
        SELECT MIN(employee_id)
        FROM employees
    )AND salary=(
        SELECT MAX(salary)
        FROM employees
    );
7-2 select后面
/*
仅仅支持标量子查询
*/

#案例:查询每个部门的员工个数
SELECT d.*,(

	SELECT COUNT(*)
	FROM employees e
	WHERE e.department_id = d.`department_id`
 ) 个数
 FROM departments d;
 
 
 #案例2:查询员工号=102的部门名
SELECT (
	SELECT department_name,e.department_id
	FROM departments d
	INNER JOIN employees e
	ON d.department_id=e.department_id
	WHERE e.employee_id=102	
) 部门名;

7-3 from后面
/*
将子查询结果充当一张表,要求必须起别名
*/

#案例:查询每个部门的平均工资的工资等级
#①查询每个部门的平均工资
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
SELECT * FROM job_grades;

#②连接①的结果集和job_grades表,筛选条件平均工资 between lowest_sal and highest_sal

SELECT  ag_dep.*,g.`grade_level`
FROM (
	SELECT AVG(salary) ag,department_id
	FROM employees
	GROUP BY department_id
) ag_dep
INNER JOIN job_grades g
ON ag_dep.ag BETWEEN lowest_sal AND highest_sal;

7-4 exists后面
#四、exists后面(相关子查询)

/*
语法:
exists(完整的查询语句)
结果:
1或0
*/

SELECT EXISTS(SELECT employee_id FROM employees WHERE salary=300000);

#案例1:查询有员工的部门名
#in
SELECT department_name
FROM departments d
WHERE d.`department_id` IN(
	SELECT department_id
	FROM employees
)

#exists
SELECT department_name
FROM departments d
WHERE EXISTS(
	SELECT *
	FROM employees e
	WHERE d.`department_id`=e.`department_id`
);


#案例2:查询没有女朋友的男神信息
#in
SELECT bo.*
FROM boys bo
WHERE bo.id NOT IN(
	SELECT boyfriend_id
	FROM beauty
)

#exists
SELECT bo.*
FROM boys bo
WHERE NOT EXISTS(
	SELECT boyfriend_id
	FROM beauty b
	WHERE bo.`id`=b.`boyfriend_id`
);

1.2.8 进阶8:分页查询
/*
应用场景:当要显示的数据,一页显示不全,需要分页提交sql请求
语法:
	select 查询列表
	from 表
	【join type join 表2
	on 连接条件
	where 筛选条件
	group by 分组字段
	having 分组后的筛选
	order by 排序的字段】
	limit 【offset,】size;
	
	offset要显示条目的起始索引(起始索引从0开始)
	size 要显示的条目个数
特点:
	①limit语句放在查询语句的最后
	②公式
	要显示的页数 page,每页的条目数size
	
	select 查询列表
	from 表
	limit (page-1)*size,size;
	size=10
	page  
	1	0
	2  	10
	3	20
	
*/
#案例1:查询前五条员工信息
SELECT * FROM  employees LIMIT 0,5;
SELECT * FROM  employees LIMIT 5;

#案例2:查询第11条——第25条
SELECT * FROM  employees LIMIT 10,15;

#案例3:有奖金的员工信息,并且工资较高的前10名显示出来
SELECT 
    * 
FROM
    employees 
WHERE commission_pct IS NOT NULL 
ORDER BY salary DESC 
LIMIT 10 ;

1.2.9 进阶9:联合查询
/*
union 联合 合并:将多条查询语句的结果合并成一个结果
语法:
查询语句1
union
查询语句2
union
...

应用场景:
要查询的结果来自于多个表,且多个表没有直接的连接关系,但查询的信息一致时
特点:★
1、要求多条查询语句的查询列数是一致的!
2、要求多条查询语句的查询的每一列的类型和顺序最好一致
3、union关键字默认去重,如果使用union all 可以包含重复项
*/


#引入的案例:查询部门编号>90或邮箱包含a的员工信息
SELECT * FROM employees WHERE email LIKE '%a%' OR department_id>90;;
SELECT * FROM employees  WHERE email LIKE '%a%'
UNION
SELECT * FROM employees  WHERE department_id>90;


#案例:查询中国用户中男性的信息以及外国用户中年男性的用户信息
SELECT id,cname FROM t_ca WHERE csex='男'
UNION ALL
SELECT t_id,tname FROM t_ua WHERE tGender='male';
1.2.10 子查询经典案例
# 1. 查询工资最低的员工信息: last_name, salary

#①查询最低的工资
SELECT MIN(salary)
FROM employees

#②查询last_name,salary,要求salary=①
SELECT last_name,salary
FROM employees
WHERE salary=(
	SELECT MIN(salary)
	FROM employees
);

# 2. 查询平均工资最低的部门信息

#方式一:
#①各部门的平均工资
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
#②查询①结果上的最低平均工资
SELECT MIN(ag)
FROM (
	SELECT AVG(salary) ag,department_id
	FROM employees
	GROUP BY department_id
) ag_dep

#③查询哪个部门的平均工资=②

SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary)=(
	SELECT MIN(ag)
	FROM (
		SELECT AVG(salary) ag,department_id
		FROM employees
		GROUP BY department_id
	) ag_dep

);

#④查询部门信息

SELECT d.*
FROM departments d
WHERE d.`department_id`=(
	SELECT department_id
	FROM employees
	GROUP BY department_id
	HAVING AVG(salary)=(
		SELECT MIN(ag)
		FROM (
			SELECT AVG(salary) ag,department_id
			FROM employees
			GROUP BY department_id
		) ag_dep

	)

);

#方式二:
#①各部门的平均工资
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id

#②求出最低平均工资的部门编号
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) 
LIMIT 1;

#③查询部门信息
SELECT *
FROM departments
WHERE department_id=(
	SELECT department_id
	FROM employees
	GROUP BY department_id
	ORDER BY AVG(salary) 
	LIMIT 1
);




# 3. 查询平均工资最低的部门信息和该部门的平均工资
#①各部门的平均工资
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
#②求出最低平均工资的部门编号
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) 
LIMIT 1;
#③查询部门信息
SELECT d.*,ag
FROM departments d
JOIN (
	SELECT AVG(salary) ag,department_id
	FROM employees
	GROUP BY department_id
	ORDER BY AVG(salary) 
	LIMIT 1

) ag_dep
ON d.`department_id`=ag_dep.department_id;



# 4. 查询平均工资最高的 job 信息
#①查询最高的job的平均工资
SELECT AVG(salary),job_id
FROM employees
GROUP BY job_id
ORDER BY AVG(salary) DESC
LIMIT 1

#②查询job信息
SELECT * 
FROM jobs
WHERE job_id=(
	SELECT job_id
	FROM employees
	GROUP BY job_id
	ORDER BY AVG(salary) DESC
	LIMIT 1

);
# 5. 查询平均工资高于公司平均工资的部门有哪些?

#①查询平均工资
SELECT AVG(salary)
FROM employees

#②查询每个部门的平均工资
SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id

#③筛选②结果集,满足平均工资>①

SELECT AVG(salary),department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary)>(
	SELECT AVG(salary)
	FROM employees

);

# 6. 查询出公司中所有 manager 的详细信息.
#①查询所有manager的员工编号
SELECT DISTINCT manager_id
FROM employees

#②查询详细信息,满足employee_id=①
SELECT *
FROM employees
WHERE employee_id =ANY(
	SELECT DISTINCT manager_id
	FROM employees

);

# 7. 各个部门中 最高工资中最低的那个部门的 最低工资是多少

#①查询各部门的最高工资中最低的部门编号
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY MAX(salary)
LIMIT 1


#②查询①结果的那个部门的最低工资

SELECT MIN(salary) ,department_id
FROM employees
WHERE department_id=(
	SELECT department_id
	FROM employees
	GROUP BY department_id
	ORDER BY MAX(salary)
	LIMIT 1


);
# 8. 查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary
#①查询平均工资最高的部门编号
SELECT 
    department_id 
FROM
    employees 
GROUP BY department_id 
ORDER BY AVG(salary) DESC 
LIMIT 1 

#②将employees和departments连接查询,筛选条件是①
    SELECT 
        last_name, d.department_id, email, salary 
    FROM
        employees e 
        INNER JOIN departments d 
            ON d.manager_id = e.employee_id 
    WHERE d.department_id = 
        (SELECT 
            department_id 
        FROM
            employees 
        GROUP BY department_id 
        ORDER BY AVG(salary) DESC 
        LIMIT 1) ;

1.3 DD 数据定义语言

#DDL
/*
    数据定义语言
    库和表的管理

    一、库的管理
    创建、修改、删除
    二、表的管理
    创建、修改、删除

    创建: create
    修改: alter
    删除: drop
*/
1.3.1 库的管理
1-1 库的创建
/*
语法:
create database  [if not exists]库名;
*/

#案例:创建库Books
CREATE DATABASE IF NOT EXISTS books ;
1-2 库的修改
RENAME DATABASE books TO 新库名;

#更改库的字符集
ALTER DATABASE books CHARACTER SET gbk;
1-3 库的删除
DROP DATABASE IF EXISTS books;
1.3.2 表的管理
2-1 表的创建
/*
    语法:
    create table 表名(
        列名 列的类型【(长度) 约束】,
        列名 列的类型【(长度) 约束】,
        列名 列的类型【(长度) 约束】,
        ...
        列名 列的类型【(长度) 约束】
    )
*/
#案例:创建表Book
CREATE TABLE book(
	id INT,#编号
	bName VARCHAR(20),#图书名
	price DOUBLE,#价格
	authorId  INT,#作者编号
	publishDate DATETIME#出版日期
);


DESC book;

#案例:创建表author
CREATE TABLE IF NOT EXISTS author(
	id INT,
	au_name VARCHAR(20),
	nation VARCHAR(10)
)
DESC author;

2-2 表的修改
/*
语法
alter table 表名 add|drop|modify|change column 列名 【列类型 约束】;
*/
2-1 修改列名
#①修改列名
ALTER TABLE book CHANGE COLUMN publishdate pubDate DATETIME;
2-2 修改列的类型或约束
#②修改列的类型或约束
ALTER TABLE book MODIFY COLUMN pubdate TIMESTAMP;
2-3 添加新列
#③添加新列
ALTER TABLE author ADD COLUMN annual DOUBLE; 
2-4 删除列
#④删除列
ALTER TABLE book_author DROP COLUMN  annual;
2-5 修改表名
#⑤修改表名
ALTER TABLE author RENAME TO book_author;
2-3 表的删除
DROP TABLE IF EXISTS book_author;

SHOW TABLES;

#通用的写法:
DROP DATABASE IF EXISTS 旧库名;
CREATE DATABASE 新库名;

DROP TABLE IF EXISTS 旧表名;
CREATE TABLE  表名();
2-4 表的复制
INSERT INTO author VALUES
(1,'村上春树','日本'),
(2,'莫言','中国'),
(3,'冯唐','中国'),
(4,'金庸','中国');

SELECT * FROM Author;
SELECT * FROM copy2;
4-1 仅仅复制表结构
CREATE TABLE copy LIKE author;
4-2 复制表的结构+数据
CREATE TABLE copy2 
SELECT * FROM author;
4-3 只复制部分数据
CREATE TABLE copy3
SELECT id,au_name
FROM author 
WHERE nation='中国';
4-4 仅仅复制某些字段
CREATE TABLE copy4 
SELECT id,au_name
FROM author
WHERE 0;

1.3.3 常见的数据类型



其他:
    1,binary 和 varbinary 用于保存较短的二进制;
    2,Enum 用于保存枚举
    3,Set 用于保存集合
日期型
    1,date (4 字节) 1000-01-01 ~ 9999-12-31;
    2,datetime (8 字节)
    1000-01-01 00:00:00 ~ 9999-12-31 23:59:59;
    3,timestamp (4 字节) 1970~2038 年的某个时刻
    (受时区的影响)
    4,time 只有时间
    5,year 只有年份 1901 ~ 2155

*/
3-1 整形
/*
1,Tinyint(1 字节)
    有符号 -128~127
    无符号 0~255
    
2,Smallint(2 字节)
    有符号 -32768~32767
    无符号 0~65535
    
3,Mediumint(3 字节)
    有符号 -8388608~8388607
    无符号 0~1677215
    
4,Int,Integer(4 字节)
    有符号 -2147483648~2147483647
    无符号 0~4294967295
    
5,Bigint (8 字节)
反正非常非常大

特点:
① 如果不设置无符号还是有符号,默认是有符号,如果想设置无符号,需要添加unsigned关键字
② 如果插入的数值超出了整型的范围,会报out of range异常,并且插入临界值
③ 如果不设置长度,会有默认的长度
长度代表了显示的最大宽度,如果不够会用0在左边填充,但必须搭配zerofill使用!
*/
1-1 设置无符号和有符号
DROP TABLE IF EXISTS tab_int;
CREATE TABLE tab_int(
	t1 INT(7) ZEROFILL,
	t2 INT(7) ZEROFILL 

);

DESC tab_int;

INSERT INTO tab_int VALUES(-123456);
INSERT INTO tab_int VALUES(-123456,-123456);
INSERT INTO tab_int VALUES(2147483648,4294967296);
INSERT INTO tab_int VALUES(123,123);

SELECT * FROM tab_int;
3-2 小数
/*
浮点型
    1,float(m,d) 4 字节
    2,Double 8 字节
    
定点类型
    1,Dec(m,d) m+2 字节
    2,Decimal(m,d)
 (dec,decimal 两者一样。Dec 是简写)
特点:
    1,m 是总的位数,d 是小数点后面保留的位数
    2,如果超过范围,插入的也是临界值
    3,M,d 都能省略;
    4,如果 decimal 的话 m 默认是 10,d 默认是 0;
    5,如果是 float 和 double 随着插入的数值的精度来确定精度;
    6,定点型精确度较高,如果要求插入的数值要求较高,如货币运算,则可以选择定点型;
    
原则:选择的类型越简单越好
*/

CREATE TABLE tab_char(
	c1 ENUM('a','b','c')


);

INSERT INTO tab_char VALUES('a');
INSERT INTO tab_char VALUES('b');
INSERT INTO tab_char VALUES('c');
INSERT INTO tab_char VALUES('m');
INSERT INTO tab_char VALUES('A');

SELECT * FROM tab_set;

CREATE TABLE tab_set(
	s1 SET('a','b','c','d')
);
INSERT INTO tab_set VALUES('a');
INSERT INTO tab_set VALUES('A,B');
INSERT INTO tab_set VALUES('a,c,d');

2-1 测试M和D
DROP TABLE tab_float;
CREATE TABLE tab_float(
	f1 FLOAT,
	f2 DOUBLE,
	f3 DECIMAL
);
SELECT * FROM tab_float;
DESC tab_float;

INSERT INTO tab_float VALUES(123.4523,123.4523,123.4523);
INSERT INTO tab_float VALUES(123.456,123.456,123.456);
INSERT INTO tab_float VALUES(123.4,123.4,123.4);
INSERT INTO tab_float VALUES(1523.4,1523.4,1523.4);

#原则:
/*
所选择的类型越简单越好,能保存数值的类型越小越好
*/
3-3 字符型
/*
字符型
    1.Char(m) m 是字符数
    2.Varcahr(m)
    3.Text
    4.Blob(较大的二进制)
    
Char()是固定长度的字符,varcahr(m)是固定长度的字符
*/

/*
较短的文本:
char
varchar

其他:
binary和varbinary用于保存较短的二进制
enum用于保存枚举
set用于保存集合

较长的文本:
text
blob(较大的二进制)
特点:

          写法		M的意思					       特点			  空间的耗费	  效率
char	char(M)		最大的字符数,可以省略,默认为1		固定长度的字符		比较耗费	高

varchar varchar(M)	 最大的字符数,不可以省略		       可变长度的字符		比较节省	低
*/

CREATE TABLE tab_char(
	c1 ENUM('a','b','c')
);

INSERT INTO tab_char VALUES('a');
INSERT INTO tab_char VALUES('b');
INSERT INTO tab_char VALUES('c');
INSERT INTO tab_char VALUES('m');
INSERT INTO tab_char VALUES('A');

SELECT * FROM tab_set;

CREATE TABLE tab_set(
	s1 SET('a','b','c','d')
);

INSERT INTO tab_set VALUES('a');
INSERT INTO tab_set VALUES('A,B');
INSERT INTO tab_set VALUES('a,c,d');
3-4 日期型
/*
分类:
    date只保存日期
    time 只保存时间
    year只保存年

    datetime保存日期+时间
    timestamp保存日期+时间

特点:

		  字节		   范围		时区等的影响
datetime	               8		1000——9999	                  不受
timestamp	4	                    1970-2038	                    受

*/

CREATE TABLE tab_date(
	t1 DATETIME,
	t2 TIMESTAMP

);
INSERT INTO tab_date VALUES(NOW(),NOW());

SELECT * FROM tab_date;

SHOW VARIABLES LIKE 'time_zone';

SET time_zone='+9:00';


1.3.4 库和表的管理练习
#1.	创建表dept1
    NAME	NULL?	TYPE
    id		INT(7)
    NAME		VARCHAR(25)

    USE test;
    CREATE TABLE dept1(
        id INT(7),
        NAME VARCHAR(25)
    );

#2.	将表departments中的数据插入新表dept2中
    CREATE TABLE dept2
    SELECT department_id,department_name
    FROM myemployees.departments;

#3.	创建表emp5
    NAME	NULL?	TYPE
    id		INT(7)
    First_name	VARCHAR (25)
    Last_name	VARCHAR(25)
    Dept_id		INT(7)

    CREATE TABLE emp5(
    id INT(7),
    first_name VARCHAR(25),
    last_name VARCHAR(25),
    dept_id INT(7)
    );


#4.	将列Last_name的长度增加到50
	ALTER TABLE emp5 MODIFY COLUMN last_name VARCHAR(50);
	
#5.	根据表employees创建employees2
	CREATE TABLE employees2 LIKE myemployees.employees;

#6.	删除表emp5
	DROP TABLE IF EXISTS emp5;

#7.	将表employees2重命名为emp5
	ALTER TABLE employees2 RENAME TO emp5;

#8.在表dept和emp5中添加新列test_column,并检查所作的操作
	ALTER TABLE emp5 ADD COLUMN test_column INT;
	
#9.直接删除表emp5中的列 dept_id
    DESC emp5;
    ALTER TABLE emp5 DROP COLUMN test_column;
1.3.5 常见的约束
5-1 分类
/*
1. 非空约束: not null;
2. 默认约束: default;
3. 主键约束: primary key 保证字段值得唯一性,非空;
4. 为以约束: unique 保证字段值得唯一性,可以为空;
5. 检查约束: check (在 MySQL 中不支持,但不报错);
6. 外键约束:foreign key 用于限制两个表的关系,用于保证该字段的值必须来自主表的关联列的值;在从表添加外键约束,用于引用主表中某列的值;

*/
5-2 约束添加的分类
## 1,列级约束:6 大约束都支持,但外键约束没有效果;
## 2,表级约束:除了非空,默认值约束,其余的都支持;
5-3 添加约束的时机、
## 1.创建表时
## 2.修改表时
5-4 主键和唯一性对比
/*
主键和唯一的大对比:

		保证唯一性  是否允许为空    一个表中可以有多少个   是否允许组合
主键	   √		 ×		       至多有1个             √,但不推荐
唯一	   √		 √		       可以有多个            √,但不推荐
	
外键:
	1、要求在从表设置外键关系
	2、从表的外键列的类型和主表的关联列的类型要求一致或兼容,名称无要求
	3、主表的关联列必须是一个key(一般是主键或唯一)
	4、插入数据时,先插入主表,再插入从表
	删除数据时,先删除从表,再删除主表
*/
5-5 添加表时添加约束
5-1 添加列级约束
#1.添加列级约束
/*
语法:

直接在字段名和类型后面追加 约束类型即可。

只支持:默认、非空、主键、唯一
*/

USE students;
DROP TABLE stuinfo;
CREATE TABLE stuinfo(
	id INT PRIMARY KEY,#主键
	stuName VARCHAR(20) NOT NULL UNIQUE,#非空
	gender CHAR(1) CHECK(gender='男' OR gender ='女'),#检查
	seat INT UNIQUE,#唯一
	age INT DEFAULT  18,#默认约束
	majorId INT REFERENCES major(id)#外键
);

CREATE TABLE major(
	id INT PRIMARY KEY,
	majorName VARCHAR(20)
);

#查看stuinfo中的所有索引,包括主键、外键、唯一
SHOW INDEX FROM stuinfo;
5-2 添加表级约束
/*

语法:在各个字段的最下面
 【constraint 约束名】 约束类型(字段名) 
*/

DROP TABLE IF EXISTS stuinfo;
CREATE TABLE stuinfo(
	id INT,
	stuname VARCHAR(20),
	gender CHAR(1),
	seat INT,
	age INT,
	majorid INT,
	
	CONSTRAINT pk PRIMARY KEY(id),#主键
	CONSTRAINT uq UNIQUE(seat),#唯一键
	CONSTRAINT ck CHECK(gender ='男' OR gender  = '女'),#检查
	CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id)#外键	
);

SHOW INDEX FROM stuinfo;

#通用的写法:★
CREATE TABLE IF NOT EXISTS stuinfo(
	id INT PRIMARY KEY,
	stuname VARCHAR(20),
	sex CHAR(1),
	age INT DEFAULT 18,
	seat INT UNIQUE,
	majorid INT,
	CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id)
);

5-6 修改表时添加约束
6-1 添加列级约束
alter table 表名 modify column 字段名 字段类型 新约束;
6-2 添加表级约束
alter table 表名 add 【constraint 约束名】 约束类型(字段名) 【外键的引用】;
DROP TABLE IF EXISTS stuinfo;
CREATE TABLE stuinfo(
	id INT,
	stuname VARCHAR(20),
	gender CHAR(1),
	seat INT,
	age INT,
	majorid INT
)
DESC stuinfo;

6-3 添加非空约束
#1.添加非空约束
ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20)  NOT NULL;
6-4 添加默认约束
#2.添加默认约束
ALTER TABLE stuinfo MODIFY COLUMN age INT DEFAULT 18;
6-5 添加主键
#①列级约束
	ALTER TABLE stuinfo MODIFY COLUMN id INT PRIMARY KEY;

#②表级约束
	ALTER TABLE stuinfo ADD PRIMARY KEY(id);
6-6 添加唯一
#①列级约束
	ALTER TABLE stuinfo MODIFY COLUMN seat INT UNIQUE;

#②表级约束
	ALTER TABLE stuinfo ADD UNIQUE(seat);
6-7 添加外键
ALTER TABLE stuinfo ADD CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id); 
5-6 修改表时删除约束
6-1 删除非空约束
ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NULL;
6-2 删除默认约束
ALTER TABLE stuinfo MODIFY COLUMN age INT ;
6-3 删除主键
ALTER TABLE stuinfo DROP PRIMARY KEY;
6-4 删除唯一
ALTER TABLE stuinfo DROP INDEX seat;
6-5 删除外键
ALTER TABLE stuinfo DROP FOREIGN KEY fk_stuinfo_major;
1.3.6 标识列
/*
又称为自增长列
含义:可以不用手动的插入值,系统提供默认的序列值


特点:
1、标识列必须和主键搭配吗?不一定,但要求是一个key
2、一个表可以有几个标识列?至多一个!
3、标识列的类型只能是数值型
4、标识列可以通过 SET auto_increment_increment=3;设置步长
可以通过 手动插入值,设置起始值
*/

6-1 创建表时创建标识列
DROP TABLE IF EXISTS tab_identity;
CREATE TABLE tab_identity(
	id INT  ,
	NAME FLOAT UNIQUE AUTO_INCREMENT,
	seat INT 
);

TRUNCATE TABLE tab_identity;

INSERT INTO tab_identity(id,NAME) VALUES(NULL,'john');
INSERT INTO tab_identity(NAME) VALUES('lucy');
SELECT * FROM tab_identity;

SHOW VARIABLES LIKE '%auto_increment%';

SET auto_increment_increment=3;
1.3.7 常见约束 案例讲解
#1.向表emp2的id列中添加PRIMARY KEY约束(my_emp_id_pk)

ALTER TABLE emp2 MODIFY COLUMN id INT PRIMARY KEY;
ALTER TABLE emp2 ADD CONSTRAINT my_emp_id_pk PRIMARY KEY(id);

#2.	向表dept2的id列中添加PRIMARY KEY约束(my_dept_id_pk)

#3.	向表emp2中添加列dept_id,并在其中定义FOREIGN KEY约束,与之相关联的列是dept2表中的id列。
ALTER TABLE emp2 ADD COLUMN dept_id INT;
ALTER TABLE emp2 ADD CONSTRAINT fk_emp2_dept2 FOREIGN KEY(dept_id) REFERENCES dept2(id);

		    位置		  支持的约束类型			      是否可以起约束名
列级约束:	列的后面	 语法都支持,但外键没有效果	     不可以
表级约束:	所有列的下面	默认和非空不支持,其他支持	    可以(主键没有效果)

1.4 DML 数据操纵语言

/*
    数据操作语言:
    插入:insert
    修改:update
    删除:delete
*/
1.4.1 插入
1-1 经典插入
*
语法:
insert into 表名(列名,...) values(值1,...);

*/
SELECT * FROM beauty;
#1.插入的值的类型要与列的类型一致或兼容
INSERT INTO beauty(id,NAME,sex,borndate,phone,photo,boyfriend_id)
VALUES(13,'唐艺昕','女','1990-4-23','1898888888',NULL,2);

#2.不可以为null的列必须插入值。可以为null的列如何插入值?
#方式一:
INSERT INTO beauty(id,NAME,sex,borndate,phone,photo,boyfriend_id)
VALUES(13,'唐艺昕','女','1990-4-23','1898888888',NULL,2);

#方式二:
INSERT INTO beauty(id,NAME,sex,phone)
VALUES(15,'娜扎','女','1388888888');

#3.列的顺序是否可以调换
INSERT INTO beauty(NAME,sex,id,phone)
VALUES('蒋欣','女',16,'110');


#4.列数和值的个数必须一致
INSERT INTO beauty(NAME,sex,id,phone)
VALUES('关晓彤','女',17,'110');

#5.可以省略列名,默认所有列,而且列的顺序和表中列的顺序一致
INSERT INTO beauty
VALUES(18,'张飞','男',NULL,'119',NULL,NULL);

1-2 插入方式2
/*

语法:
insert into 表名
set 列名=值,列名=值,...
*/

INSERT INTO beauty
SET id=19,NAME='刘涛',phone='999';

1-3 两种方式比较
#1、方式一支持插入多行,方式二不支持

INSERT INTO beauty
VALUES(23,'唐艺昕1','女','1990-4-23','1898888888',NULL,2)
,(24,'唐艺昕2','女','1990-4-23','1898888888',NULL,2)
,(25,'唐艺昕3','女','1990-4-23','1898888888',NULL,2);

#2、方式一支持子查询,方式二不支持
INSERT INTO beauty(id,NAME,phone)
SELECT 26,'宋茜','11809866';

INSERT INTO beauty(id,NAME,phone)
SELECT id,boyname,'1234567'
FROM boys WHERE id<3;
1.4.2 修改
/*

1.修改单表的记录★

语法:
    update 表名
    set 列=新值,列=新值,...
    where 筛选条件;

2.修改多表的记录【补充】

    语法:
    sql92语法:
    update 表1 别名,表2 别名
    set 列=值,...
    where 连接条件
    and 筛选条件;

sql99语法:
    update 表1 别名
    inner|left|right join 表2 别名
    on 连接条件
    set 列=值,...
    where 筛选条件;

*/
2-1 修改单表记录
#案例1:修改beauty表中姓唐的女神的电话为13899888899

UPDATE beauty SET phone = '13899888899'
WHERE NAME LIKE '唐%';

#案例2:修改boys表中id好为2的名称为张飞,魅力值 10
UPDATE boys SET boyname='张飞',usercp=10
WHERE id=2;
2-2 修改多表记录
#案例 1:修改张无忌的女朋友的手机号为114
UPDATE boys bo
INNER JOIN beauty b ON bo.`id`=b.`boyfriend_id`
SET b.`phone`='119',bo.`userCP`=1000
WHERE bo.`boyName`='张无忌';

#案例2:修改没有男朋友的女神的男朋友编号都为2号
UPDATE boys bo
RIGHT JOIN beauty b ON bo.`id`=b.`boyfriend_id`
SET b.`boyfriend_id`=2
WHERE bo.`id` IS NULL;

SELECT * FROM boys;
1.4.3 删除语句
/*

方式一:delete
语法:

1、单表的删除【★】
	delete from 表名 where 筛选条件

2、多表的删除【补充】
sql92语法:
    delete 表1的别名,表2的别名
    from 表1 别名,表2 别名
    where 连接条件
    and 筛选条件;

sql99语法:
    delete 表1的别名,表2的别名
    from 表1 别名
    inner|left|right join 表2 别名 on 连接条件
    where 筛选条件;

方式二:truncate
语法:truncate table 表名;

*/

3-1 delete
1-1 单表删除
#案例:删除手机号以9结尾的女神信息
DELETE FROM beauty WHERE phone LIKE '%9';
SELECT * FROM beauty;

1-2 多表删除
#案例:删除张无忌的女朋友的信息
DELETE b
FROM beauty b
INNER JOIN boys bo ON b.`boyfriend_id` = bo.`id`
WHERE bo.`boyName`='张无忌';

#案例:删除黄晓明的信息以及他女朋友的信息
DELETE b,bo
FROM beauty b
INNER JOIN boys bo ON b.`boyfriend_id`=bo.`id`
WHERE bo.`boyName`='黄晓明';
3-2 truncate语句
#案例:将魅力值>100的男神信息删除
TRUNCATE TABLE boys 
3-3 delete pk truncate
/*

1.delete 可以加where 条件,truncate不能加

2.truncate删除,效率高一丢丢
3.假如要删除的表中有自增长列,
如果用delete删除后,再插入数据,自增长列的值从断点开始,而truncate删除后,再插入数据,自增长列的值从1开始。
4.truncate删除没有返回值,delete删除有返回值

5.truncate删除不能回滚,delete删除可以回滚.
*/

SELECT * FROM boys;

DELETE FROM boys;
TRUNCATE TABLE boys;
INSERT INTO boys (boyname,usercp)
VALUES('张飞',100),('刘备',100),('关云长',100);
1.4.4 案例讲解
#1.	运行以下脚本创建表my_employees

USE myemployees;
CREATE TABLE my_employees(
	Id INT(10),
	First_name VARCHAR(10),
	Last_name VARCHAR(10),
	Userid VARCHAR(10),
	Salary DOUBLE(10,2)
);
CREATE TABLE users(
	id INT,
	userid VARCHAR(10),
	department_id INT

);
#2.	显示表my_employees的结构
	DESC my_employees;

#3.	向my_employees表中插入下列数据
    ID	FIRST_NAME	LAST_NAME	USERID	SALARY
    1	patel		Ralph		Rpatel	895
    2	Dancs		Betty		Bdancs	860
    3	Biri		Ben		Bbiri	1100
    4	Newman		Chad		Cnewman	750
    5	Ropeburn	Audrey		Aropebur	1550

#方式一:
    INSERT INTO my_employees
    VALUES(1,'patel','Ralph','Rpatel',895),
    (2,'Dancs','Betty','Bdancs',860),
    (3,'Biri','Ben','Bbiri',1100),
    (4,'Newman','Chad','Cnewman',750),
    (5,'Ropeburn','Audrey','Aropebur',1550);
    
    DELETE FROM my_employees;
    
#方式二:
    INSERT INTO my_employees
    SELECT 1,'patel','Ralph','Rpatel',895 UNION
    SELECT 2,'Dancs','Betty','Bdancs',860 UNION
    SELECT 3,'Biri','Ben','Bbiri',1100 UNION
    SELECT 4,'Newman','Chad','Cnewman',750 UNION
    SELECT 5,'Ropeburn','Audrey','Aropebur',1550;
				
#4.	 向users表中插入数据
    1	Rpatel	10
    2	Bdancs	10
    3	Bbiri	20
    4	Cnewman	30
    5	Aropebur	40

    INSERT INTO users
    VALUES(1,'Rpatel',10),
    (2,'Bdancs',10),
    (3,'Bbiri',20);

#5.将3号员工的last_name修改为“drelxer”
	UPDATE my_employees SET last_name='drelxer' WHERE id = 3;

#6.将所有工资少于900的员工的工资修改为1000
	UPDATE my_employees SET salary=1000 WHERE salary<900;

#7.将userid 为Bbiri的user表和my_employees表的记录全部删除
    DELETE u,e
    FROM users u
    JOIN my_employees e ON u.`userid`=e.`Userid`
    WHERE u.`userid`='Bbiri';

#8.删除所有数据
    DELETE FROM my_employees;
    DELETE FROM users;
    
#9.检查所作的修正
    SELECT * FROM my_employees;
    SELECT * FROM users;

#10.清空表my_employees
	TRUNCATE TABLE my_employees;

1.5 TCL 事务控制语言

1.5.1 事务的属性
1. 原子性
## 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
2. 一致性
## 事务必须使数据库从一个形状状态变换到另外一个一致性状态
3. 隔离性
## 一个事务不能被其他事务干扰,事务之间是隔离的,互不干扰
4. 持久性
## 事务一旦被提交,它对数据库中数据的改变就是永久性的
1.5.2 事务的创建
## 隐式事务:事务没有明显的开启和结束标记,比如 insert,update,delete 语句;
## 显示事务:具有明显的开启和结束标志。先设置自动提交功能为禁用,SET autocommit = 0;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

## 步骤一:开启事务
    1,Set autocommit = 0;
    2,Start transaction;(可选的,可以不写);
    
## 步骤二:编写事务中的 SQL 语句
    3,语句 1;(select,insert,update,delete)
    4,语句 2;
    
## 步骤三:结束事务(提交事务或回滚事务)commit or rollback
1.5.3 演示事务的使用步骤
#开启事务
    SET autocommit=0;
    START TRANSACTION;
    
#编写一组事务的语句
    UPDATE account SET balance = 1000 WHERE username='张无忌';
    UPDATE account SET balance = 1000 WHERE username='赵敏';

#结束事务
    ROLLBACK;
    #commit;

1.5.4 隔离机制
## 对于同时运行的多个事务,当这些事务访问数据库中的相同数据时,如果没有采取必要的隔离机制,就会导致各种并发问题;

## 脏读:对于两个事务 T1,T2,T1 读取了已经被 T2 更新但还没被提交的字段之后,若 T2 回滚,T1 读取的内容就是临时无效的;

## 不可重复读:对于两个事务 T1,T2,T1 读取了一个字段,然后 T2 更新了该字段之后,T1 再次读取该字段,值就不一样了;

## 幻读:对于两个事务 T1,T2,T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行之后,如果 T1 再次读取同一个表,就会多出几行;
1.5.5 四种隔离级别
## MySQL 支持 4 种事务隔离级别。MySQL 默认的事务隔离级别为:REPEATABLE READ

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.5.6 注意
## 1,5.7.2 之前 使用 show variables like 'tx_isoation' 或者 select @@tx_isolation;
## 2 , 5.7.2 之 后 使 用 show variables like 'transaction_isoation' 或 者 select @@transaction_isoation;

Savepoint:节点名,设置保存点

image-20230924175359617

SET autocommit=0;
START TRANSACTION;
DELETE FROM account WHERE id=25;
SAVEPOINT a;#设置保存点
DELETE FROM account WHERE id=28;
ROLLBACK TO a;#回滚到保存点


SELECT * FROM account;
1.5.7 演示事务对于delete和truncate的处理的区别
SET autocommit=0;
START TRANSACTION;

DELETE FROM account;
ROLLBACK;

1.5.8 视图
/*
含义:虚拟表,和普通表一样使用
mysql5.1版本出现的新特性,是通过表动态生成的数据

比如:舞蹈班和普通班级的对比
	创建语法的关键字	是否实际占用物理空间	使用

视图	create view		 只是保存了sql逻辑	   增删改查,只是一般不能增删改

表	 create table	  保存了数据		     增删改查
*/
8-1 视图创建
/*
语法:
create view 视图名
as
查询语句;

*/
USE myemployees;

#1.查询姓名中包含a字符的员工名、部门名和工种信息
#①创建
CREATE VIEW myv1
AS

SELECT last_name,department_name,job_title
FROM employees e
JOIN departments d ON e.department_id  = d.department_id
JOIN jobs j ON j.job_id  = e.job_id;


#②使用
SELECT * FROM myv1 WHERE last_name LIKE '%a%';

#2.查询各部门的平均工资级别

    #①创建视图查看每个部门的平均工资
        CREATE VIEW myv2
        AS
        SELECT AVG(salary) ag,department_id
        FROM employees
        GROUP BY department_id;

    #②使用
        SELECT myv2.`ag`,g.grade_level
        FROM myv2
        JOIN job_grades g
        ON myv2.`ag` BETWEEN g.`lowest_sal` AND g.`highest_sal`;



#3.查询平均工资最低的部门信息
SELECT * FROM myv2 ORDER BY ag LIMIT 1;

#4.查询平均工资最低的部门名和工资
CREATE VIEW myv3
AS
SELECT * FROM myv2 ORDER BY ag LIMIT 1;

SELECT d.*,m.ag
FROM myv3 m
JOIN departments d
ON m.`department_id`=d.`department_id`;

8-2 视图的修改
2-1 方式1:
#方式一:
/*
create or replace view  视图名
as
查询语句;
*/
SELECT * FROM myv3 

CREATE OR REPLACE VIEW myv3
AS
SELECT AVG(salary),job_id
FROM employees
GROUP BY job_id;
2-2 方式2:
/*
语法:
alter view 视图名
as 
查询语句;
*/
ALTER VIEW myv3
AS
SELECT * FROM employees;
8-3 视图的删除
/*
语法:drop view 视图名,视图名,...;
*/

DROP VIEW emp_v1,emp_v2,myv3;
8-4 查看视图
DESC myv3;

SHOW CREATE VIEW myv3;
8-5 视图的更新
CREATE OR REPLACE VIEW myv1
AS
SELECT last_name,email,salary*12*(1+IFNULL(commission_pct,0)) "annual salary"
FROM employees;

CREATE OR REPLACE VIEW myv1
AS
SELECT last_name,email
FROM employees;

SELECT * FROM myv1;
SELECT * FROM employees;
5-1 插入
INSERT INTO myv1 VALUES('张飞','zf@qq.com');
5-2 修改
UPDATE myv1 SET last_name = '张无忌' WHERE last_name='张飞';
5-3 删除
DELETE FROM myv1 WHERE last_name = '张无忌';
5-4 注意
#具备以下特点的视图不允许更新
#①包含以下关键字的sql语句:分组函数、distinct、group  by、having、union或者union all

CREATE OR REPLACE VIEW myv1
AS
SELECT MAX(salary) m,department_id
FROM employees
GROUP BY department_id;

SELECT * FROM myv1;

#更新
UPDATE myv1 SET m=9000 WHERE department_id=10;

#②常量视图
CREATE OR REPLACE VIEW myv2
AS
SELECT 'john' NAME;

SELECT * FROM myv2;

#更新
UPDATE myv2 SET NAME='lucy';


#③Select中包含子查询
CREATE OR REPLACE VIEW myv3
AS

SELECT department_id,(SELECT MAX(salary) FROM employees) 最高工资
FROM departments;

#更新
SELECT * FROM myv3;
UPDATE myv3 SET 最高工资=100000;

#④join
CREATE OR REPLACE VIEW myv4
AS

SELECT last_name,department_name
FROM employees e
JOIN departments d
ON e.department_id  = d.department_id;

#更新

SELECT * FROM myv4;
UPDATE myv4 SET last_name  = '张飞' WHERE last_name='Whalen';
INSERT INTO myv4 VALUES('陈真','xxxx');

#⑤from一个不能更新的视图
CREATE OR REPLACE VIEW myv5
AS

SELECT * FROM myv3;

#更新
SELECT * FROM myv5;

UPDATE myv5 SET 最高工资=10000 WHERE department_id=60;

#⑥where子句的子查询引用了from子句中的表
CREATE OR REPLACE VIEW myv6
AS

SELECT last_name,email,salary
FROM employees
WHERE employee_id IN(
	SELECT  manager_id
	FROM employees
	WHERE manager_id IS NOT NULL
);

#更新
SELECT * FROM myv6;
UPDATE myv6 SET salary=10000 WHERE last_name = 'k_ing';
1.5.9 视图案例讲解
#一、创建视图emp_v1,要求查询电话号码以‘011’开头的员工姓名和工资、邮箱
CREATE OR REPLACE VIEW emp_v1
AS
SELECT last_name,salary,email
FROM employees
WHERE phone_number LIKE '011%';

#二、创建视图emp_v2,要求查询部门的最高工资高于12000的部门信息
CREATE OR REPLACE VIEW emp_v2
AS
SELECT MAX(salary) mx_dep,department_id
FROM employees
GROUP BY department_id
HAVING MAX(salary)>12000;

SELECT d.*,m.mx_dep
FROM departments d
JOIN emp_v2 m
ON m.department_id = d.`department_id`;
1.5.10 变量
10-1 系统变量
## 系统变量:变量由系统提供,不是用户定义,属于服务器层面

## 1,查看所有的系统变量 
	SHOW VARIABLES;
 
## 2,查看满足条件的部分系统变量;
	SHOW VARIABLES LIKE '%char%';
	
## 3,查看指定的某个系统的变量值;
	SELECT @@character_set_client;
	@@global.变量名 @@session.变量名 @@变量名
	
## 4,为系统变量名赋值
    1,set 变量名 = 值(global.变量名||session.变量名)
    2,Set @@global.变量名 = 值 (session.变量名)
    
## 如果是全局级别加 global,如果是会话级别加 session 关键字
1-1 全局变量
## 全局变量 
SHOW GLOBAL VARIABLES;
    (1) 查看部分全局变量:
    	SHOW GLOBAL VARIABLES LIKE '%char%';
    	
    (2) 查看指定的全局变量的值:
    	SELECT @@global.autocommit;
    (3) 为某个指定的全局变量赋值 
        SET @@global.autocommit=0;
        
## 作用域:服务器每次启动将为所有的全局变量赋初始值,针对于所有的会话(连接有效),但不能跨重启
1-2 会话变量
SHOW SESSION VARIABLES;
## 作用域:仅仅针对于当前的会话(连接有效)

(1) 查看部分会话变量:
	SHOW SESSION VARIABLES LIKE '%char%';
	
(2) 查看指定的全局变量的值:
	SELECT @@SESSION.autocommit;
	
(3) 为某个指定的全局变量赋值 
	SET @@SESSION.变量名=值;
10-2 自定义变量
## 自定义变量: 用户自定义的变量,不是系统提供的
2-1 用户变量
## 作用域:仅仅针对于当前的会话(连接有效)可以应用在任何地方

1 声明并初始化
    ## (1)SET @用户变量名:=值;(为了不跟等号起冲突);
    ## (2)SET @用户变量名=值;
    ## (3)SELECT @用户变量名:=值;
    
2 更新用户变量的值(跟声明初始化一样)
    ## (1) SET @用户变量名:=值;(为了不跟等号起冲突);
    ## (2) SET @用户变量名=值;
    ## (3) SELECT @用户变量名:=值;
    ## (4) SELECT 字段 INTO @变量名 FROM 表(查询出来的值必须是一个值才能赋给这个变量)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3 查看用户变量的值
	## (1)SELECT @用户变量名
2-2 局部变量
## 作用域:仅仅在定义它的 begin end 中有效;
1) 声明:DECLARE 变量名 类型 || DECLARE 变量名 类型 DEFAULT 值;

2) 赋值:
    ## (1)SET 用户变量名:=值;(为了不跟等号起冲突);
    ## (2)SET 用户变量名=值;
    ## (3)SELECT 用户变量名:=值;
    ## (4)SELECT 字段 INTO 变量名 FROM 表

3) 使用:SELECT 局部变量名;
2-3 案例
#案例:声明两个变量,求和并打印

#用户变量
SET @m=1;
SET @n=1;
SET @sum=@m+@n;
SELECT @sum;

#局部变量
DECLARE m INT DEFAULT 1;
DECLARE n INT DEFAULT 1;
DECLARE SUM INT;
SET SUM=m+n;
SELECT SUM;


#用户变量和局部变量的对比

		   作用域			    定义位置		     语法
用户变量	当前会话		    会话的任何地方		   加@符号,不用指定类型
局部变量	定义它的BEGIN END中 	BEGIN END的第一句话	  一般不用加@,需要指定类型
			
1.5.11 存储过程函数
/*
含义:一组预先编译好的 SQL 语句的集合,理解成批处理语句
    (1)提高代码的重用性;
    (2)简化操作;
    (3)减少了编译次数和数据库服务器的连接次数,提高了效率;

A.创建语法:CREATE PROCEDURE 存储过程名字(参数列表) BEGIN 存储过程体 END;
    (1)参数列表包含三部分:参数模式 参数名 参数类型: In stuname varchar(20);
    (2)参数模式:(三种模式)
        a.In:该参数可以作为输入,需要调用方传入值;  
        b.Out:该参数可以作为输出,也就是说该参数可以作为返回值;  
        c.Inout:该参数既可以作为输入,又可以作为输出;
    (3)如果存储过程仅仅只有一句话则begin end可以省略;
    (4)存储过程中的每条SQL语句结尾要求必须加分号,存储过程结尾可以使用delimiter 结束标记
    (5)调用语法:call 存储过程名(实参列表);

*/
11-1 创建语法
CREATE PROCEDURE 存储过程名(参数列表)
BEGIN

	存储过程体(一组合法的SQL语句)
END

#注意:
/*
1、参数列表包含三部分
参数模式  参数名  参数类型
举例:
in stuname varchar(20)

参数模式:
in:该参数可以作为输入,也就是该参数需要调用方传入值
out:该参数可以作为输出,也就是该参数可以作为返回值
inout:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值

2、如果存储过程体仅仅只有一句话,begin end可以省略
存储过程体中的每条sql语句的结尾要求必须加分号。
存储过程的结尾可以使用 delimiter 重新设置
语法:
delimiter 结束标记
案例:
delimiter $
*/
11-2 调用语法
CALL 存储过程名(实参列表);
2-1 空参列表
#案例:插入到admin表中五条记录

SELECT * FROM admin;

DELIMITER $
CREATE PROCEDURE myp1()
BEGIN
	INSERT INTO admin(username,`password`) 
	VALUES('john1','0000'),('lily','0000'),('rose','0000'),('jack','0000'),('tom','0000');
END $

#调用
CALL myp1()$
2-2 in模式
#案例1:创建存储过程实现 根据女神名,查询对应的男神信息

CREATE PROCEDURE myp2(IN beautyName VARCHAR(20))
BEGIN
	SELECT bo.*
	FROM boys bo
	RIGHT JOIN beauty b ON bo.id = b.boyfriend_id
	WHERE b.name=beautyName;
	

END $

#调用
CALL myp2('柳岩')$

#案例2 :创建存储过程实现,用户是否登录成功

CREATE PROCEDURE myp4(IN username VARCHAR(20),IN PASSWORD VARCHAR(20))
BEGIN
	DECLARE result INT DEFAULT 0;#声明并初始化
	
	SELECT COUNT(*) INTO result#赋值
	FROM admin
	WHERE admin.username = username
	AND admin.password = PASSWORD;
	
	SELECT IF(result>0,'成功','失败');#使用
END $

#调用
CALL myp3('张飞','8888')$

2-3 out模式
#案例1:根据输入的女神名,返回对应的男神名
CREATE PROCEDURE myp6(IN beautyName VARCHAR(20),OUT boyName VARCHAR(20))
BEGIN
	SELECT bo.boyname INTO boyname
	FROM boys bo
	RIGHT JOIN
	beauty b ON b.boyfriend_id = bo.id
	WHERE b.name=beautyName ;
	
END $


#案例2:根据输入的女神名,返回对应的男神名和魅力值
CREATE PROCEDURE myp7(IN beautyName VARCHAR(20),OUT boyName VARCHAR(20),OUT usercp INT) 
BEGIN
	SELECT boys.boyname ,boys.usercp INTO boyname,usercp
	FROM boys 
	RIGHT JOIN
	beauty b ON b.boyfriend_id = boys.id
	WHERE b.name=beautyName ;	
END $

#调用
CALL myp7('小昭',@name,@cp)$
SELECT @name,@cp$
2-4 in-out模式
#案例1:传入a和b两个值,最终a和b都翻倍并返回

CREATE PROCEDURE myp8(INOUT a INT ,INOUT b INT)
BEGIN
	SET a=a*2;
	SET b=b*2;
END $

#调用
SET @m=10$
SET @n=20$
CALL myp8(@m,@n)$
SELECT @m,@n$

11-3 删除存储过程
#语法:drop procedure 存储过程名
DROP PROCEDURE p1;
DROP PROCEDURE p2,p3;#×
11-4 查看存储过程
DESC myp2;
SHOW CREATE PROCEDURE  myp2;
11-5 存储过程案例讲解
#一、创建存储过程实现传入用户名和密码,插入到admin表中

CREATE PROCEDURE test_pro1(IN username VARCHAR(20),IN loginPwd VARCHAR(20))
BEGIN
	INSERT INTO admin(admin.username,PASSWORD)
	VALUES(username,loginpwd);
END $

#二、创建存储过程实现传入女神编号,返回女神名称和女神电话

CREATE PROCEDURE test_pro2(IN id INT,OUT NAME VARCHAR(20),OUT phone VARCHAR(20))

BEGIN
	SELECT b.name ,b.phone INTO NAME,phone
	FROM beauty b
	WHERE b.id = id;

END $
#三、创建存储存储过程或函数实现传入两个女神生日,返回大小

CREATE PROCEDURE test_pro3(IN birth1 DATETIME,IN birth2 DATETIME,OUT result INT)
BEGIN
	SELECT DATEDIFF(birth1,birth2) INTO result;
END $
#四、创建存储过程或函数实现传入一个日期,格式化成xx年xx月xx日并返回
CREATE PROCEDURE test_pro4(IN mydate DATETIME,OUT strDate VARCHAR(50))
BEGIN
	SELECT DATE_FORMAT(mydate,'%y年%m月%d日') INTO strDate;
END $

CALL test_pro4(NOW(),@str)$
SELECT @str $

#五、创建存储过程或函数实现传入女神名称,返回:女神 and 男神  格式的字符串
如 传入 :小昭
返回: 小昭 AND 张无忌
DROP PROCEDURE test_pro5 $
CREATE PROCEDURE test_pro5(IN beautyName VARCHAR(20),OUT str VARCHAR(50))
BEGIN
	SELECT CONCAT(beautyName,' and ',IFNULL(boyName,'null')) INTO str
	FROM boys bo
	RIGHT JOIN beauty b ON b.boyfriend_id = bo.id
	WHERE b.name=beautyName;
	SET str=
END $

CALL test_pro5('柳岩',@str)$
SELECT @str $

#六、创建存储过程或函数,根据传入的条目数和起始索引,查询beauty表的记录
DROP PROCEDURE test_pro6$
CREATE PROCEDURE test_pro6(IN startIndex INT,IN size INT)
BEGIN
	SELECT * FROM beauty LIMIT startIndex,size;
END $

CALL test_pro6(3,5)$
1.5.12 函数
12-1 创建语法
create function 函数名(函数列表) returns 返回类型 begin 函数体 end

/*
含义:一组预先编译好的SQL语句的集合,理解成批处理语句
1、提高代码的重用性
2、简化操作
3、减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率

区别:
存储过程:可以有0个返回,也可以有多个返回,适合做批量插入、批量更新
函数:有且仅有1 个返回,适合做处理数据后返回一个结果

*/
12-2 注意
1.参数列表包含两个部分,参数名和参数类型;
2.函数体肯定会有return语句,如果没有会报错;如果return语句没有放在函数体的最后也不报错但不建议
3.函数体中仅有一句话则可以省略begin end;
4.使用delimiter语句设置结束标记;
12-3 调用语法
SELECT 函数名(参数列表)
3-1 无参有返回
#1.无参有返回
#案例:返回公司的员工个数
CREATE FUNCTION myf1() RETURNS INT
BEGIN
	DECLARE c INT DEFAULT 0;#定义局部变量
	SELECT COUNT(*) INTO c#赋值
	FROM employees;
	RETURN c;	
END $

SELECT myf1()$
3-2 有参有返回
#案例1:根据员工名,返回它的工资
CREATE FUNCTION myf2(empName VARCHAR(20)) RETURNS DOUBLE
BEGIN
	SET @sal=0;#定义用户变量 
	SELECT salary INTO @sal   #赋值
	FROM employees
	WHERE last_name = empName;
	
	RETURN @sal;
END $

SELECT myf2('k_ing') $

#案例2:根据部门名,返回该部门的平均工资
CREATE FUNCTION myf3(deptName VARCHAR(20)) RETURNS DOUBLE
BEGIN
	DECLARE sal DOUBLE ;
	SELECT AVG(salary) INTO sal
	FROM employees e
	JOIN departments d ON e.department_id = d.department_id
	WHERE d.department_name=deptName;
	RETURN sal;
END $

SELECT myf3('IT')$
12-4 查看函数
SHOW CREATE FUNCTION myf3;
12 -5 删除函数
DROP FUNCTION myf3;
12-6 案例
#一、创建函数,实现传入两个float,返回二者之和
CREATE FUNCTION test_fun1(num1 FLOAT,num2 FLOAT) RETURNS FLOAT
BEGIN
	DECLARE SUM FLOAT DEFAULT 0;
	SET SUM=num1+num2;
	RETURN SUM;
END $

SELECT test_fun1(1,2)$

1.5.13 流程控制机构
13-1 分支结构
1-1 if函数
## 语法:
    ## IF(表达式1,表达式2,表达式3);
    ## 如果表达式1成立,则返回表达式2的值,不成立则返回表达式3的值;
1-2 case 结构
① 一般用于实现等值判断;
② 一般用于实现区间判断

语法1: 
	CASE 变量|表达式|字段 
	WHEN 要判断的值1 THEN 返回的值1
	WHEN 要判断的值2 THEN 返回的值2
	WHEN 要判断的值3 THEN 返回的值3
	...
	ELSE 要返回的值n
	END CASE;
语法2: 
	CASE 
	WHEN 要判断的条件1 THEN 返回的语句1或值;
	WHEN 要判断的条件2 THEN 返回的值2或语句;
	WHEN 要判断的条件3 THEN 返回的值3或语句;
	...
	ELSE 要返回的条件n
	END CASE;
## 特点:可以作为表达式,嵌套在其他语句中使用;也可以作为独立的语句使用(可以放在任何地方)如果作为独立的语句只用,只能放在begin end中


#案例1:创建函数,实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
CREATE FUNCTION test_case(score FLOAT) RETURNS CHAR
BEGIN 
	DECLARE ch CHAR DEFAULT 'A';
	CASE 
	WHEN score>90 THEN SET ch='A';
	WHEN score>80 THEN SET ch='B';
	WHEN score>60 THEN SET ch='C';
	ELSE SET ch='D';
	END CASE;
	RETURN ch;
END $

SELECT test_case(56)$
1-3 if 结构
## 语法:
		IF 条件1 THEN 语句1;
		ELSEIF 条件2 THEN 语句2;
		,,,
		ELSE 条件n;
		END IF;
		
## 应用在begin end 中;

#案例1:创建函数,实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
CREATE FUNCTION test_if(score FLOAT) RETURNS CHAR
BEGIN
	DECLARE ch CHAR DEFAULT 'A';
	IF score>90 THEN SET ch='A';
	ELSEIF score>80 THEN SET ch='B';
	ELSEIF score>60 THEN SET ch='C';
	ELSE SET ch='D';
	END IF;
	RETURN ch;	
END $

SELECT test_if(87)$


#案例2:创建存储过程,如果工资<2000,则删除,如果5000>工资>2000,则涨工资1000,否则涨工资500
CREATE PROCEDURE test_if_pro(IN sal DOUBLE)
BEGIN
	IF sal<2000 THEN DELETE FROM employees WHERE employees.salary=sal;
	ELSEIF sal>=2000 AND sal<5000 THEN UPDATE employees SET salary=salary+1000 WHERE employees.`salary`=sal;
	ELSE UPDATE employees SET salary=salary+500 WHERE employees.`salary`=sal;
	END IF;
	
END $

CALL test_if_pro(2100)$
13-2 循环结构
2-1 分类
WHILE, LOOP, REPEAT,
1.ITERATE 类似于continue,结束本次循环,继续下次循环;
2.LEAVE 类似于 break,跳出,结束当前所在的循环;
2-2 语法
#1.while
/*
语法:

【标签:】while 循环条件 do
	循环体;
end while【 标签】;


while(循环条件){
	循环体;
}
*/

#2.loop
/*
语法:
【标签:】loop
	循环体;
end loop 【标签】;

可以用来模拟简单的死循环
*/

#3.repeat
/*
语法:
【标签:】repeat
	循环体;
until 结束循环的条件
end repeat 【标签】;

*/
2-3 案例
#1.没有添加循环控制语句
#案例:批量插入,根据次数插入到admin表中多条记录
DROP PROCEDURE pro_while1$
CREATE PROCEDURE pro_while1(IN insertCount INT)
BEGIN
	DECLARE i INT DEFAULT 1;
	WHILE i<=insertCount DO
		INSERT INTO admin(username,`password`) VALUES(CONCAT('Rose',i),'666');
		SET i=i+1;
	END WHILE;
	
END $

CALL pro_while1(100)$


/*

int i=1;
while(i<=insertcount){

	//插入
	
	i++;

}

*/


#2.添加leave语句

#案例:批量插入,根据次数插入到admin表中多条记录,如果次数>20则停止
TRUNCATE TABLE admin$
DROP PROCEDURE test_while1$
CREATE PROCEDURE test_while1(IN insertCount INT)
BEGIN
	DECLARE i INT DEFAULT 1;
	a:WHILE i<=insertCount DO
		INSERT INTO admin(username,`password`) VALUES(CONCAT('xiaohua',i),'0000');
		IF i>=20 THEN LEAVE a;
		END IF;
		SET i=i+1;
	END WHILE a;
END $


CALL test_while1(100)$


#3.添加iterate语句

#案例:批量插入,根据次数插入到admin表中多条记录,只插入偶数次
TRUNCATE TABLE admin$
DROP PROCEDURE test_while1$
CREATE PROCEDURE test_while1(IN insertCount INT)
BEGIN
	DECLARE i INT DEFAULT 0;
	a:WHILE i<=insertCount DO
		SET i=i+1;
		IF MOD(i,2)!=0 THEN ITERATE a;
		END IF;
		
		INSERT INTO admin(username,`password`) VALUES(CONCAT('xiaohua',i),'0000');
		
	END WHILE a;
END $

CALL test_while1(100)$

/*

int i=0;
while(i<=insertCount){
	i++;
	if(i%2==0){
		continue;
	}
	插入	
}
*/

13-3 流程控制案例讲解
/*一、已知表stringcontent
其中字段:
id 自增长
content varchar(20)

向该表插入指定个数的,随机的字符串
*/
DROP TABLE IF EXISTS stringcontent;
CREATE TABLE stringcontent(
	id INT PRIMARY KEY AUTO_INCREMENT,
	content VARCHAR(20)
	
);
DELIMITER $
CREATE PROCEDURE test_randstr_insert(IN insertCount INT)
BEGIN
	DECLARE i INT DEFAULT 1;
	DECLARE str VARCHAR(26) DEFAULT 'abcdefghijklmnopqrstuvwxyz';
	DECLARE startIndex INT;#代表初始索引
	DECLARE len INT;#代表截取的字符长度
	WHILE i<=insertcount DO
		SET startIndex=FLOOR(RAND()*26+1);#代表初始索引,随机范围1-26
		SET len=FLOOR(RAND()*(20-startIndex+1)+1);#代表截取长度,随机范围1-(20-startIndex+1)
		INSERT INTO stringcontent(content) VALUES(SUBSTR(str,startIndex,len));
		SET i=i+1;
	END WHILE;

END $

CALL test_randstr_insert(10)$

1.6 ,python连接MySQL

1.6.1 连接数据库
import pymysql

# 连接MySQL数据库
db = pymysql.connect(host='127.0.0.1', user='root', password='123456', database='test',port=3306)

# 设置字符集  防止乱码
db.set_charset('utf8')

# 创建游标对象
cursor = db.cursor()

# 准备SQL
sql = 'select * from user'
# 执行SQL语句
cursor.execute(sql)

# 获取所有
print(cursor.fetchall())
print(cursor.fetchone())


# 关闭数据库连接
db.close()
1.6.2 事件回滚
import pymysql


# 连接MySQL数据库
db = pymysql.connect(host='127.0.0.1', user='root', password='123456', database='test',port=3306)

# 设置字符集  防止乱码
db.set_charset('utf8')

# 创建游标对象
cursor = db.cursor()

try:
    # 准备插入SQL语句
    sql = 'insert into user values(4,"刘强东", 50, "男")'
    # 执行SQL语句
    cursor.execute(sql)
    # 提交事务 保存到数据库中
    db.commit()
except Exception as e:
    print(e)
    # 回滚
    db.rollback()
# 对于插入获取受影响的行数
print(cursor.rowcount)

# 关闭数据库连接
db.close()

2,Redis

举例(普通连接):

import redis

# decode_responses=True  自动解码
r = redis.Redis(host='127.0.0.1',port=6379,password='123456',db=0,decode_responses=True) #默认数据库为0 

r = redis.StrictRedis(host='10.10.2.14',port=6379,password='123456',decode_responses=True)

连接池:connection pool

管理对一个redis server的所有连接,避免每次建立,释放连接的开销。默认,每个redis实例都会维护一个自己的连接池,可以直接建立一个连接池,作为参数传给redis,这样可以实现多个redis实例共享一个连接池。

举例(连接池):

pool = redis.ConnectionPool(host='127.0.0.1',port=6379,db=0,password='123456',decode_responses=True)

r = redis.Redis(connection_pool=pool)

八,Python进阶知识

1,编码相关

1.1 指定默认的读文件的解码格式保证不乱码

这不是注释,第一行是固定格式 #coding:用什么编码格式读文件
# coding:utg-8 (如果写代码时指定则就是用什么方式编码,如果读文件时指定,则以什么格式解码)
# 代码内容

#Python3里的str类型默认直接存成Unicode所以不存在乱码
#·若要保证Python2的str类型也不乱码
x = u"艾尼你好"  # 前面加上u,意思就是Unicode编码

注:Python3默认用utf-8解码; Python2用ASCII码解码

2, 读写文件

2.1 控制文件读写内容的模式:t和b

# 强调:读写不能单独使用,必须跟r/w/a连用

open()方法,with 语法
1,t模式(默认的模式) 
	# 读写都以str(Unicode)为单位
    # 必须指定encoding="utf-8"
    # 必须是文本文件才可以指定编码
2,b模式
	# 是对字节进行操作
    # 不用指定编码
    
#文件操作基本流程
1,打开文件
	# window系统路径分割问题
    # 解决方案一:推荐
		f = open(r'C:\a\b\c\aini.txt')
    # 解决方案二:open这函数已经解决好了,右斜杠也可以
    	f = open('C:/a/b/c/aini.txt)
2,操作文件
    f = open('./aini.txt',mode='r',encoding='utf-8')
    res = f.read() 
    # 指针会停在最后,所以第二次读的时候没内容,需要重新打开文件,重新读取
    # 会读取所有内容
3,关闭文件
   f.close()  #回收操作系统资源
                 

2.2 文件操作的模式

# 文件操作模式
    # r  w  a  默认都是t模式,对文本进行操作(rt,wt,at)
    # rb wb ab 对字节进行操作
    # a 是追加模式,会往文件末尾开始写,w会把源文件清空掉
    # rt+ 可读可写,文件不存在直接报错
    # wt+ 可读可写,
                 
# 指针移动
    # 指针移动的单位都是bytes字节为单位
    # 只有一种特殊情况
         # t模式下的read(n),n代表的是字符个数
    with open('./aini.txt',mode='rt',encoding='utf-8') as f:
	f.read(4)  # 四个字符
     
     ### 注意: 只有0模式在t模式下使用
     f.seek(n,模式)   # n值得是指针移动的字节个数,n可以是负数,可以倒着移动
                 # 模式  
                 	# 0 参照的是文件开头位置
                 	# 1 参照的是当前指针的所造位置
                 	# 2 参照物是文件末尾
    f.tell  ## 获取指针当前位置

3,函数参数详解

3.1 位置参数--------关键字参数---------混合使用

1,位置实参:在函数调用阶段, 按照从左到有的顺序依次传入的值
# 特点:按照顺序与形参一一对应

2 关键字参数
# 关键字实参:在函数调用阶段,按照key=value的形式传入的值
# 特点:指名道姓给某个形参传值,可以完全不参照顺序
def func(x,y):
	print(x,y)

func(y=2,x=1) # 关键字参数
func(1,2)  # 位置参数

3,混合使用,强调
    # 1、位置实参必须放在关键字实参前
        def func(x,y):
            print(x,y)
        func(1,y=2)
        func(y=2,1)

    # 2、不能能为同一个形参重复传值
        def func(x,y):
            print(x,y)
        func(1,y=2,x=3)
        func(1,2,x=3,y=4)

3.2 默认参数------位置参数与默认参数混用

4,默认参数
    # 默认形参:在定义函数阶段,就已经被赋值的形参,称之为默认参数
    # 特点:在定义阶段就已经被赋值,意味着在调用阶段可以不用为其赋值
        def func(x,y=3):
            print(x,y)

        func(x=1)
        func(x=1,y=44444)


        def register(name,age,gender='男'):
             print(name,age,gender)

        register('三炮',18)
        register('二炮',19)	
        register('大炮',19)
        register('没炮',19,'女')


5,位置形参与默认形参混用,强调:
    # 1、位置形参必须在默认形参的左边
          def func(y=2,x):  # 错误写法
                pass

    # 2、默认参数的值是在函数定义阶段被赋值的,准确地说被赋予的是值的内存地址
    # 示范1:
        m=2
        def func(x,y=m): # y=>2的内存地址
            print(x,y)
        m=3333333333333333333
        func(1)

    # 3、虽然默认值可以被指定为任意数据类型,但是不推荐使用可变类型
    # 函数最理想的状态:函数的调用只跟函数本身有关系,不外界代码的影响
        m = [111111, ]

        def func(x, y=m):
        print(x, y)

        m.append(3333333)
        m.append(444444)
        m.append(5555)

        func(1)
        func(2)
        func(3)

       def func(x,y,z,l=None):
           if l is None:
               l=[]
               l.append(x)
               l.append(y)
               l.append(z)
            print(l)

       func(1,2,3)
       func(4,5,6)

       new_l=[111,222]
       func(1,2,3,new_l)

3.3 可变长度的参数

6,可变长度的参数(*与**的用法)
    # 可变长度指的是在调用函数时,传入的值(实参)的个数不固定
    # 而实参是用来为形参赋值的,所以对应着,针对溢出的实参必须有对应的形参来接收

6.1 可变长度的位置参数
    # I:*形参名:用来接收溢出的位置实参,溢出的位置实参会被*保存成元组的格式然后赋值紧跟其后的形参名
        # *后跟的可以是任意名字,但是约定俗成应该是args

        def func(x,y,*z): # z =(3,4,5,6)
            print(x,y,z)

        func(1,2,3,4,5,6)

        def my_sum(*args):
               res=0
               for item in args:
                    res+=item
                return res

            res=my_sum(1,2,3,4,)
            print(res)

    # II: *可以用在实参中,实参中带*,先*后的值打散成位置实参
        def func(x,y,z):
            print(x,y,z)
   
        func(*[11,22,33]) # func(11,22,33)
        func(*[11,22]) # func(11,22)
       
        l=[11,22,33]
        func(*l)

    # III: 形参与实参中都带*
        def func(x,y,*args): # args=(3,4,5,6)
            print(x,y,args)

        func(1,2,[3,4,5,6])
        func(1,2,*[3,4,5,6]) # func(1,2,3,4,5,6)
        func(*'hello') # func('h','e','l','l','o')


6.2 可变长度的关键字参数
    # I:**形参名:用来接收溢出的关键字实参,**会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名
        # **后跟的可以是任意名字,但是约定俗成应该是kwargs
        def func(x,y,**kwargs):
            print(x,y,kwargs)
       
        func(1,y=2,a=1,b=2,c=3)

    # II: **可以用在实参中(**后跟的只能是字典),实参中带**,先**后的值打散成关键字实参
        def func(x,y,z):
            print(x,y,z)

        func(*{'x':1,'y':2,'z':3}) # func('x','y','z')
        func(**{'x':1,'y':2,'z':3}) # func(x=1,y=2,z=3)

    # 错误
        func(**{'x':1,'y':2,}) # func(x=1,y=2)
        func(**{'x':1,'a':2,'z':3}) # func(x=1,a=2,z=3)


    # III: 形参与实参中都带**
        def func(x,y,**kwargs):
           print(x,y,kwargs)

        func(y=222,x=111,a=333,b=444)
        func(**{'y':222,'x':111,'a':333,'b':4444})

    # 混用*与**:*args必须在**kwargs之前
        def func(x,*args,**kwargs):
            print(args)
            print(kwargs)
       
        func(1,2,3,4,5,6,7,8,x=1,y=2,z=3)


    def index(x,y,z):
        print('index=>>> ',x,y,z)

    def wrapper(*args,**kwargs): #args=(1,) kwargs={'z':3,'y':2}
        index(*args,**kwargs)
        # index(*(1,),**{'z':3,'y':2})
        # index(1,z=3,y=2)

    wrapper(1,z=3,y=2) # 为wrapper传递的参数是给index用的

3.4 函数的类型提示

## : 后面是提示信息,可以随意写
def regidter(name:"不能写艾尼",age:"至少18岁")print(name)
    print(age)
    
def register(name:str,age:int,hobbies:tuple)->int:  #  返回值类型为 int
    print(name)
    print(age)
    print(hobbies)
 
# 添加提示功能的同时,再添加默认值
def register(name:str = 'aini',age:int = 18 ,hobbies:tuple)->int:  #  返回值类型为 int
    print(name)
    print(age)
    print(hobbies)

4,装饰器

4.1 装饰器的一步步实现

## 装饰器:装饰器定义一个函数,该函数是用来为其他函数添加额外的工能
## 装饰器就是不修改源代码以及调用方式的基础上增加新功能

## 开放封闭原则
	# 开放:指的是对拓展工能是开放的
	# 封闭: 指的是对修改源代码是封闭的
    
## 添加一个计算代码运行时间的工能(修改了源代码)    
import time
def index(name,age):
	start = time.time()
	time.sleep(3)
	print('我叫%s,今年%s岁'%(name,age))
	end = time.time()
	print(end - start)
index(age = 18,name = 'aini')

# --------------------------------------------------------------------------

def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))

def wrapper():
	start = time.time()
	index1(name="aini", age=18)
	time.sleep(3)
	end = time.time()
	print(end - start)
    
wrapper()
# 解决了修改原函数,但是也改变了函数调用方式

# --------------------------------------------------------------------------------------
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))

def wrapper(name,age):
	start = time.time()
	index1(name, age)
	time.sleep(3)
	end = time.time()
	print(end - start)
    
wrapper('aini',18)

# -----------------------------------------------------------------------------------
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))

def wrapper(*args,**kwargs):
	start = time.time()
	index1(*args,**kwargs)
	time.sleep(3)
	end = time.time()
	print(end - start)
    
wrapper('aini',age = 18)

# ------------------------------------------------------------------------------------
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))
    
def outer():
    func = index
    def wrapper(*args,**kwargs):
        start = time.time()
        fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
    return wrapper
    
f = outer()  # f本质就是wrapper函数

### 继续改进
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))
    
def outer(fun):
    def wrapper(*args,**kwargs):
        start = time.time()
        fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
    return wrapper
    
f = outer(index1)  # f本质就是wrapper函数
f(name='aini',age=22)


# 继续改进,偷梁换柱
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))
    
def outer(fun):
    def wrapper(*args,**kwargs):
        start = time.time()
        fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
    return wrapper
    
index1 = outer(index1)  # f本质就是wrapper函数
index1(name='aini',age=22)  # 新功能加上了,也没有修改函数的调用方式

# ---------------------------------------------------------

4.2 装饰器最终版本

# 被装饰函数有返回值
########### 装饰器最终版本

def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))
    return [name,age]  # 有返回值
    
def outer(fun):
    def wrapper(*args,**kwargs):
        start = time.time()
        arg = fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
        return arg # 返回index1 的返回值
    return wrapper
    
index1 = outer(index1)  # f本质就是wrapper函数
res = index1(name='aini',age=22)  # 新功能加上了,也没有修改函数的调用方式,把原函数的返回值也拿到了
# --------------------------------------------------------------------------------------------------

4.3 装饰器语法糖

def outer(fun):
    def wrapper(*args,**kwargs):
        start = time.time()
        arg = fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
        return arg # 返回index1 的返回值
    return wrapper   
@outer
def index1(name,age):
	print('我叫%s,今年%s岁' % (name, age))
    return [name,age]  # 有返回值


# -----------------------------------------------
# 与原函数伪装的更像一点

from functools import wraps  # 用于把原函数的属性特征赋值给另一个函数
def outer(fun):
    @wraps(fun) # 可以把fun函数的所有属性特征加到wrapper函数身上
    def wrapper(*args,**kwargs):
    
        # wrapper.__name__ = fun.__name__
        # wrapper.__doc__ = fun.__doc__
        # 手动赋值麻烦
        
        start = time.time()
        arg = fun(*args,**kwargs)
        time.sleep(3)
        end = time.time()
        print(end - start)
        return arg # 返回index1 的返回值
    return wrapper

@outer
def index1(name,age):
    '''我是index1'''   # 通过help(index)函数来查看 文档信息,可以通过index.__doc__ = 'xxxxx' 来给某个函数赋值文档信息
    # 通过 index__name__ 可以获得函数的名字,也可以对其进行赋值
	print('我叫%s,今年%s岁' % (name, age))
    return [name,age]  # 有返回值

4.4 有参装饰器

4.4.1 不用语法糖
### 不用语法糖
def auth(func,db_type):
	def wrapper(*args,**kwargs):
		name = input('your name:').strip()
		pwd = input('your password:').strip()

		if db_type == 'file':
			print('基于文件验证')
			if name == 'aini' and pwd == 'aini123':
				print('login success')
				res = func(*args,**kwargs)
				return res
			else:
				print('用户名或者密码错误!!')

		elif db_type == 'mysql':
			print('基于mysql验证')
		elif db_type == 'ldap':
			print('基于ldap验证')
		else:
			print('基于其他途径验证')
	return wrapper


def index(x,y):
	print('index->>%s:%s'%(x,y))

index = auth(index,'file')
index('aini',22)
4.4.2 语法糖01
#---------------------------------------------------------------------
# 语法糖01
def auth(db_type = "file"):
	def deco(func):
		def wrapper(*args,**kwargs):
			name = input('your name:').strip()
			pwd = input('your password:').strip()

			if db_type == 'file':
				print('基于文件验证')
				if name == 'aini' and pwd == 'aini123':
					print('login success')
					res = func(*args,**kwargs)
					return res
				else:
					print('用户名或者密码错误!!')

			elif db_type == 'mysql':
				print('基于mysql验证')
			elif db_type == 'ldap':
				print('基于ldap验证')
			else:
				print('基于其他途径验证')
		return wrapper
	return deco

deco = auth(db_type = 'file')
@deco
def index(x,y):
	print('index->>%s:%s'%(x,y))
index('aini',22)

deco = auth(db_type = 'mysql')
@deco
def index(x,y):
	print('index->>%s:%s'%(x,y))
index('aini',22)
4.4.3 标准语法糖

# ---------------------------------------------------------------------------------
# 标准语法糖模板
def auth(外界传递的参数):
	def deco(func):
		def wrapper(*args,**kwargs):
              '''自己扩展的功能'''  
             res = func(*args,**kwargs)
             return res
         return wrapper
	return deco
@auth(外界传递的参数)
def index(x,y):
    print(x,y)
    return(x,y)


# 标准语法糖02(例子)
def auth(db_type = "file"):
	def deco(func):
		def wrapper(*args,**kwargs):
			name = input('your name:').strip()
			pwd = input('your password:').strip()

			if db_type == 'file':
				print('基于文件验证')
				if name == 'aini' and pwd == 'aini123':
					print('login success')
					res = func(*args,**kwargs)
					return res
				else:
					print('用户名或者密码错误!!')

			elif db_type == 'mysql':
				print('基于mysql验证')
			elif db_type == 'ldap':
				print('基于ldap验证')
			else:
				print('基于其他途径验证')
		return wrapper
	return deco


@auth(db_type = 'file')
def index(x,y):
	print('index->>%s:%s'%(x,y))
index('aini',22)

@auth(db_type = 'file')
def index(x,y):
	print('index->>%s:%s'%(x,y))
index('aini',22)

5, 迭代器

5.1 基础知识

1,迭代器:迭代取值的工具,迭代是重复的过程,每一次重复都是基于上次的结果而继续的,单纯的重复不是迭代

# 可迭代对象: 但凡内置有__iter__()方法的都称之为可迭代对象
# 字符串---列表---元祖---字典---集合---文件操作  都是可迭代对象

# 调用可迭代对象下的__iter__方法将其转换为可迭代对象 
d = {'a':1, 'b':2, 'c':3}

d_iter = d.__iter__() # 把字典d转换成了可迭代对象

#   d_iter.__next__()     # 通过__next__()方法可以取值

print(d_iter.__next__()) # a
print(d_iter.__next__()) # b
print(d_iter.__next__()) # c

# 没值了以后就会报错, 抛出异常StopIteration
#-----------------------------------------------
d = {'a':1, 'b':2, 'c':3}
d_iter = d.__iter__()
while True:
	try:
		print(d_iter.__next__())
	except StopIteration:
		break
# 对同一个迭代器对象,取值取干净的情况下第二次取值的时候去不了,没值,只能造新的迭代器

5.2 迭代器与for循环工作原理

#可迭代对象与迭代器详解
    #可迭代对象:内置有__iter__() 方法对象
        # 可迭代对象.__iter__(): 得到可迭代对象
        
    #迭代器对象:内置有__next__() 方法
    	# 迭代器对象.__next__():得到迭代器的下一个值
        # 迭代器对象.__iter__(): 得到的值迭代器对象的本身(调跟没调一个样)-----------> 为了保证for循环的工作
        
# for循环工作原理
    d = {'a':1, 'b':2, 'c':3}
    d_iter = d.__iter__()

    # 1,d.__iter__() 方法得到一个跌倒器对象
    # 2,迭代器对象的__next__()方法拿到返回值,将该返回值赋值给k
    # 3,循环往复步骤2,直到抛出异常,for循环会捕捉异常并结束循坏

    for k in d:
        print(k)
        
    # 可迭代器对象不一定是迭代器对象------------迭代器对象一定是可迭代对象
    # 字符串---列表---元祖---字典---集合只是可迭代对象,不是迭代器对象、
    # 文件操作时迭代器对象也是可迭代对象

6,生成器(本质就是迭代器)

# 函数里包含yield,并且调用函数以后就能得到一个可迭代对象
def test():
	print('第一次')
	yield 1
	print('第二次')
	yield 2
	print('第三次')
	yield 3
	print('第四次')

g = test()
print(g) # <generator object test at 0x0000014C809A27A0>
g_iter = g.__iter__()
res1 = g_iter.__next__() # 第一次
print(res1) # 1
res2 = g_iter.__next__() # 第二次
print(res2) # 2
res3 = g_iter.__next__() # 第三次
print(res3) # 3  

# 补充
len(s) -------> s.__len__()
next(s) ------> s.__next__()
iter(d) -------> d.__iter__()

1,yield 表达式

def person(name):
	print("%s吃东西啦!!"%name)
	while True:
		x = yield None
		print('%s吃东西啦---%s'%(name,x))


g = person('aini')
# next(g) =============== g.send(None)
next(g)
next(g)
# send()方法可以给yield传值
# 不能在第一次运行时用g.send()来传值,需要用g.send(None)或者next(g) 来初始化,第二次开始可以用g.send("值")来传值
g.send("雪糕")  # aini吃东西啦---雪糕
g.send("西瓜")  # aini吃东西啦---西瓜

2, 三元表达式

x = 10
y = 20
res = x if x > y else y
# 格式
条件成立时返回的值 if 条件 else 条件不成立时返回的值

3,列表生成式

l = ['aini_aaa','dilnur_aaa','donghua_aaa','egon']
res = [name for name in l if name.endswith('aaa')]
print(res)

# 语法: [结果 for 元素 in 可迭代对象 if 条件]


l = ['aini_aaa','dilnur_aaa','donghua_aaa','egon']
l = [name.upper() for name in l]
print(l)

l = ['aini_aaa','dilnur_aaa','donghua_aaa','egon']
l = [name.replace('_aaa','') for name in l if name.endswith('_aaa')]
print(l)

4,其他生成器(——没有元祖生成式——)

### 字典生成器
keys = ['name','age','gender']
res = {key: None for key in keys}
print(res)  # {'name': None, 'age': None, 'gender': None}

items = [('name','aini'),('age',22),('gender','man')]
res = {k:v for k,v in items}
print(res)

## 集合生成器
keys = ['name','age','gender']
set1 = {key for key in keys}

## 没有元祖生成器
g = (i for i in range(10) if i % 4 == 0 )  ## 得到的是一个迭代器


#### 统计文件字符个数
with open('aini.txt', mode='rt', encoding= 'utf-8') as f:
    res = sum(len(line) for line in f)
    print(res)

5,二分法

l = [-10,-6,-3,0,1,10,56,134,222,234,532,642,743,852,1431]

def search_num(num,list):
	mid_index = len(list) // 2
	if len(list) == 0:
		print("没找到")
		return False
	if num > list[mid_index]:
		list = list[mid_index + 1 :]
		search_num(num,list)
	elif num < list[mid_index]:
		list = list[:mid_index]
		search_num(num, list)
	else:
		print('找到了' , list[mid_index])

search_num(743,l)

6,匿名函数与lambdaj

## 定义
res = lambda x,y : x+y
## 调用
(lambda x,y : x+y)(10,20)  # 第一种方法
res(10,20)    ## 第二种方法

##应用场景
salary = {
    'aini':20000,
    'aili':50000,
    'dilnur':15000,
    'hahhaha':42568,
    'fdafdaf':7854
}

res = max(salary ,key= lambda x : salary[x])
print(res)

7,模块

## 内置模块
## 第三方模块
## 自定义模块

## 模块的四种形式
1, 使用Python编写的py文件
2, 已被编译为共享库或DLL的C或C++扩展
3, 把一系列模块组织到一起的文件夹(文件夹下面有个__init__.py 该文件夹称为包)
3, 使用C编写并链接到Python解释器的内置模块

import foo
## 首次导入模块会发生什么?
1,执行foo.py
2, 产生foo.py的命名空间
3,在当前文件中产生的有一个名字foo,改名字指向2中产生的命名空间

## 无论是调用还是修改与源模块为准,与调用位置无关

## 导入模块规范
1 Python内置模块
2,Python第三方模块
3,自定义模块

## 起别名

import foo as f

## 自定义模块命名应该纯小写+下划线

## 可以在函数内导入模块

7.1 写模块时测试

# 每个Python文件内置了__name__,指向Python文件名

# 当foo.py 被运行时, 
__name__  =  "__main__"

# 当foo.py 被当做模块导入时,
__name__ != "__main__"

##### 测试时可以if判断,在foo.py文件中写以下判断
if __name__ == "__main__" :
    ##  你的测试代码

7.2 from xxx import xxx

# from foo import x 发生什么事情
1, 产生一个模块的命名空间
2, 运行foo.py 产生,将运行过程中产生的名字都丢到命名空间去
3, 在当前命名空间拿到一个名字,改名字指向模块命名空间

7.3 从一个模块导入所有

#不太推荐使用
form foo import *  
# 被导入模块有个 __all__ = []
__all__ = []   # 存放导入模块里的所有变量和函数, 默认放所有的变量和函数,也可以手动修改


foo.py
    __all__ = ['x','change']
    x = 10
    def change():
        global x
        x = 20
    a = 20
    b = 30
    
run.py    
    from foo import *  ## * 导入的是foo.py里的 __all__ 列表里的变量和函数
    print(x)
    change()
    print(a)  # 会报错,因为foo.py 里的 __all__ 列表里没有a变量

7.4 sys.path 模块搜索路径优先级

1, 内存(内置模块)
2, 从硬盘查找

import sys
# 值为一个列表,存放了一系列的文件夹
# 其中第一个文件夹是当前执行所在的文件夹
# 第二个文件夹当不存在,因为这不是解释器存放的,是pycharm添加的
print(sys.path)
# sys.path 里放的就是模块的存放路径查找顺序
[
'E:\\Desktop\\python全栈\\模块', 'E:\\Desktop\\python全栈', 'D:\\软件\\pycharm\\PyCharm 2021.3.1\\plugins\\python\\helpers\\pycharm_display', 'D:\\软件\\python\\python310.zip', 'D:\\软件\\python\\DLLs', 'D:\\软件\\python\\lib', 'D:\\软件\\python', 'C:\\Users\\艾尼-aini\\AppData\\Roaming\\Python\\Python310\\site-packages', 'D:\\软件\\python\\lib\\site-packages', 'D:\\软件\\python\\lib\\site-packages\\win32', 'D:\\软件\\python\\lib\\site-packages\\win32\\lib', 'D:\\软件\\python\\lib\\site-packages\\Pythonwin', 'D:\\软件\\pycharm\\PyCharm 2021.3.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend'
]

7.5 sys.modules 查看内存中的模块

import sys
print(sys.module)   # 是一个字典,存放导入的模块

## 可以判断一个模块是否已经在内存中
print('foo' in sys.module)

7.6 编写规范的模块

"this module is used to ......"    # 第一行文档注释
import sys  # 导入需要用到的包
x = 1  # 定义全局变量
class foo:    # 定义类
 	pass
def test():  #定义函数
    pass

if __name__ == "__main__":
    pass

8,包(包本身就是模块)

###  包就是一个包含__init__.py的文件夹,包的本质是模块的一种形式,包用来被当做模块导入

### 导入包运行时运行__inti__.py文件里的代码
 
### 环境变量是以执行文件为准备的,所有的被导入的模块或者说后续的其他的sys.path都是参照执行文件的sys.path

9, 软件开发的目录规范

ATM  --------------------------------- # 项目跟目录
	bin
    	start.py ---------------------# 启动程序
    config  ------------------------- # 项目配置文件
    	setting.py
    db ------------------------------- # 数据库相关的文件夹
    	db_handle.py
    lib ------------------------------ # 共享库(包)
    	common.py
    core ------------------------------# 核心代码逻辑
    	src.py
    api -------------------------------# API有关的文件夹
    	api.py
    log -------------------------------# 记录日志的文件夹
    	user.log
    README --------------------------- # 对软件的解释说明
    
    
    
    __file__  #  当前文件的绝对路径
    
    # 在start.py中运行 print(__file__) ---------------------- E:\Desktop\python全栈\ATM\bin\start.py
    
    import os
    import sys
    BASE_DIR = os.path.dirname(os.path.dirname(__file__))  ## 这样可以动态拿到根目录
    sys.path.append(BASE_DIR) # 把项目根目录加到环境变量了,这样可以很好的导包了
    
    
    ## 如果把运行文件 start.py 直接放在跟文件的目录下,就不需要处理环境变量了

    
    

10,反射

10.1 什么是反射

## 反射---------------> 程序运行过程当中,动态的获取对象的信息。

10.2 如何实现反射

# 通过dir:查看某一个对象可以.出来那些属性来
# 可以通过字符串反射到真正的属性上,得到熟悉值

## 四个内置函数的使用
hasattr() ## 判断属性是否存在
getattr() ## 得到属性
setattr() ## 设置属性
delattr() ## 删除属性

hasattr(obj,'name') ## 判断对象 obj 有没有 name 属性
getattr(obj,;'name',None) ## 得到对象 obj 的 name 属性,如果没有返回 None
setattr(obj,'name','aini') ## 设置对象 obj 的 name 属性为 "aini"
delattr(obj,'name') ## 删除对象 obj 的 name 属性

九,面向对象编程

1,类的定义

## 类名驼峰命名 
## 类体中可以写任意Python代码,类体代码在定义时就运行
## __dic__ 可以查看类的命名空间
'''
{'__module__': '__main__', 'school': 'donghua', 'adress': 'shanghai', 'local': <classmethod(<function Student.local at 0x000001BCF418E9E0>)>, 'say_hello': <staticmethod(<function Student.say_hello at 0x000001BCF418EA70>)>, '__init__': <function Student.__init__ at 0x000001BCF418EB00>, 'say_score': <function Student.say_score at 0x000001BCF418EB90>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
'''


class Student:
	# 类属性
	# 可以被所有的实例对象所共享
	school = 'donghua'
	adress = 'shanghai'
    stu_count = 0 # 统计注册的实例个数
	
	# 类方法
	@classmethod
	def local(cls):
		print(cls.adress)

	# 静态方法
	# 可以调用类的属性和方法
	@staticmethod
	def say_hello(str):
		print(str)
		Student.local()
	
	# 通过构造函数__init__创建对象的属性
	def __init__(self,name,age,score):
		self.name = name
		self.age = age
		self.score = score
        Student.stu_count += 1
		
	# 创建实例对象方法
	def say_score(self):
		print(f'{self.name}的分数是{self.score}')

print(Student.say_score)  ## <function Student.say_score at 0x00000255F6DDEB90>


s1 = Student('aini',22,80)  ## 实例化
Student.say_score(s1) ## aini的分数是80
s1.say_score() ----- ## 本质是 Student.say_score(s1)
## 通过类名可以调用实例方法,需要传递实例进去


## 实例化发生的三件事情
1,先产生一个空对象
2,Python会自动调用 __init__方法
3,返回初始化完的对象

print(s1.__dict__) ------ ## ## {'name': 'aini', 'age': 22, 'score': 80}

2,封装

2.1 私有属性

## 在属性或方法前加__前缀,可以对外进行隐藏
## 这种隐藏对外不对内,因为__开头的属性会在类定义阶段检查语法时统一变形

class Foo:
	__x = 1

	def __test(self):
		print('from test')
     
    def f2(self):
		print(self.__x)  # 1
		print(self.__test) ## <bound method Foo.__test of <__main__.Foo object at 0x000002063304B7F0>>

## 隐藏属性的访问 
## Python不推荐此方法
print(Foo._Foo__x)  ## 1
print(Foo._Foo__test) ##  <function Foo.__test at 0x000001C42976E320>

## 这种变形操作只在检查类语法的时候发生一次,之后定义__定义的属性都不会变形

Foo.__y = 3
print(Foo.__y) 

2.2 property使用

## 第一种类型
## 把函数像普通属性一样调用
class Person:

	def __init__(self,name):
		self.__name = name

	@property
	def get_name(self):
		return self.__name

aini = Person('aini')
print(aini.get_name)  ## 'aini'


## 第二种类型
class Person:

	def __init__(self,name):
		self.__name = name


	def get_name(self):
		return self.__name


	def set_name(self,val):
		if type(val) is not str:
			print('必须传入str类型')
			return
		self.__name = val
	
    ## 伪装成数据接口的属性
	name = property(get_name,set_name)

aini = Person('aini')
print(aini.name)  ## 'aini'
aini.name = 'norah'
print(aini.name)  ## 'norah'


## 第三种方法
## 起一个一样的函数名,用不同功能的property装饰
class Person:

	def __init__(self,name):
		self.__name = name

	@property   ## name = property(name)
	def name(self):
		return self.__name

	@name.setter 
	def name(self,val):
		if type(val) is not str:
			print('必须传入str类型')
			return
		self.__name = val
        
    @ name.deleter  
    def name(self):
        print("不能删除")

3,继承

Python里支持多继承

python3里没有继承任何类的类都继承了Object类

Python2 里有经典类和新式类

经典类:没有继承Object ------------------ 新式类:继承了Object

class Parent1:
    pass

class Parent2:
    pass

class Sub1(Parent1):  ## 单继承
    pass

class Sub2(Parent1,Parent2):  ## 多继承
    pass

print(Sub1.__bases__)  ## (<class '__main__.Parent1'>,)
print(Sub2.__bases__)  ## (<class '__main__.Parent1'>, <class '__main__.Parent2'>)

3.1 继承的实现

class OldBoyPeople:
    school = 'OLDBOY'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

class Student(OldBoyPeople):

    def choose_course(self):
        print(f'学生 {self.name}正在选课')


class Teacher(OldBoyPeople):

    def __init__(self,name,age,sex,salary,level):

        # 调父类的属性就行
        OldBoyPeople.__init__(self,name,age,sex)
        self.salary = salary
        self.level = level

    def score(self):
        print('老师 %s 正在给学生打分' %self.name)

t = Teacher('agen',25,'man',50000,'一级')
print(t.__dict__)  ## {'name': 'agen', 'age': 25, 'sex': 'man', 'salary': 50000, 'level': '一级'}

stu_1 = Student('aini',22,'man')
print(stu_1.name,stu_1.age,stu_1.sex) ## aini 22 man
print(stu_1.school) ## OLDBOY
stu_1.choose_course() ## 学生 aini正在选课

3.2 单继承背景下的属性查找

class Foo:
	def f1(self):
		print('Foo.f1')

	def f2(self):
		print('Foo.f2')
		self.f1() ## z这里如何调用自己的f1函数
        # 第一种方法 Foo.f1(self)
        # 第二种方法,把f1函数改为次有属性 self.__f1() 

class Bar(Foo):
	def f1(self):
		print('Bar.f1')
        
obj = Bar()
obj.f2()  ## 到父类调f2,也会把自己传进来,随意 self.f1() == obj.f1()
## Foo.f2
## Bar.f1

3.3 菱形问题

image-20230701134915382

'''
大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻
'''


class A(object):
    def test(self):
        print('from A')


class B(A):
    def test(self):
        print('from B')


class C(A):
    def test(self):
        print('from C')


class D(B,C):
    pass


obj = D()
obj.test() # 结果为:from B

3.4 继承原理

## python2 和 Python3 里算出来的mro不一样的

## python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下

D.mro() 
## [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

B.mro()
## [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

## 1.子类会先于父类被检查
## 2.多个父类会根据它们在列表中的顺序被检查
## 3.如果对下一个类存在两个合法的选择,选择第一个父类

3.5 深度优先和广度优先

image-20230701135247948

## 参照下述代码,多继承结构为非菱形结构,此时,会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

class E:
    def test(self):
        print('from E')


class F:
    def test(self):
        print('from F')


class B(E):
    def test(self):
        print('from B')


class C(F):
    def test(self):
        print('from C')


class D:
    def test(self):
        print('from D')


class A(B, C, D):
    # def test(self):
    #     print('from A')
    pass
print(A.mro())
'''
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
'''

image-20230701135004435

## 如果继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先
####################  这是经典类:深度优先查找

class G: # 在python2中,未继承object的类及其子类,都是经典类
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

####################  这是新式类:广度优先查找

class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    def test(self):
        print('from F')

class B(E):
    def test(self):
        print('from B')

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证

3.6 Mixins机制(解决多继承问题)

## Mixins机制核心:多继承背景下,尽可能地提升多继承的可读性
## 让多继承满足人类的思维习惯

## 民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的

class Vehicle:  # 交通工具
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")

class CivilAircraft(Vehicle):  # 民航飞机
    pass

class Helicopter(Vehicle):  # 直升飞机
    pass

class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
    pass
## -------------------------------------------------------------------------------------------------------------
## 解决方法
class Vehicle:  # 交通工具
    pass

class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")

class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass

class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass

class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
## -------------------------------------------------------------------------------------------------------------
## 使用Minin
class Vehicle:  # 交通工具
    pass

class FlyableMixin:
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")

class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass

class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass

class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
## --------------------------------------------------------------------------------------------------------

3.7 使用minin注意事项

## 使用Mixin类实现多重继承要非常小心

## 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
## 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个	标识其归属含义的父类
## 然后,它不依赖于子类的实现
## 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

3.8 派生与方法重用

# 子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的

class OldBoyPeople:
	def __init__(self,name,age,sex):
		self.name = name
		self.age = age
		self.sex = sex
		
	def f1(self):
		print('%s say hello' %self.name)
		
class Teacher(OldBoyPeople):
	def __int__(self,name,age,sex,level,salary):
        
		## 第一种方法(不依赖于继承)
		## OldBoyPeople.__init__(self,name,age,sex)
		
		## 第二种方法(严格依赖继承,只能用于新式类)
            ## Python2中需要传入类和本身
            ## super(Teacher, self).__init__(name.age, sex)
            ## Python3中什么也不需要传
		super().__init__(name,age,sex)
        
        ## super 根据当前类的mro去访问父类里面去找
		
		self.level = level
		self.salary = salary
        
## super 严格遵守 mor 去找父类,而不是我们肉眼看到的

#A没有继承B
class A:
   def test(self):
		super().test()

class B:
    def test(self):
        print('from B')

class C(A,B):
    pass

C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
 ## [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>]
obj=C()
obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro()
## from B

3.9 组合

'''
在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例
'''

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def tell_birth(self):
       print('<%s-%s-%s>' %(self.year,self.mon,self.day))

class People:
    school='清华大学'
    def __init__(self,name,sex,age):
        self.name=name
        self.sex=sex
        self.age=age

#Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
class Teacher(People): #老师是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老师有生日
        self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)

# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date类的功能
teacher1.birth.tell_birth()

# 重用Course类的功能
for obj in teacher1.courses: 
    obj.tell_info()

4,多态

4.1 多态的一种方式

## 多态:同一种事务的多种状态
## 多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象

class Animal:
	def say(self):
		print('动物基本的发声')

class Person(Animal):
	def say(self):
		super().say()
		print('啊啊啊啊啊啊啊啊')

class Dog(Animal):
	def say(self):
		super().say()
		print('汪汪汪')

class Pig(Animal):
	def say(self):
		super().say()
		print('哼哼哼')

person = Person()
dog = Dog()
pig = Pig()

## 定义统一的接口,实现多态
def animal_say(animal):
	animal.say()

animal_say(person)
animal_say(dog)
animal_say(pig)

## 多态的例子
def my_len(val):
    return val.__len__()

my_len('aini')
my_len([1,12,3,4,5,'hhh'])
my_len({'name':'aini','age':22})

4.2 Python推崇的多态

## 鸭子类型,不用继承

class Cpu:
	def read(self):
		print('cpu read')
		
	def write(self):
		print('cpu write')
		
class Meu:
	def read(self):
		print('meu read')

	def write(self):
		print('meu write') 
		
class Txt:
	def read(self):
		print('txt read')

	def write(self):
		print('txt write') 


cpu = Cpu()
meu = Meu()
txt = Txt()

5,classmethod

import setting

class Mysql:

	def __init__(self,ip,port):
		self.ip = ip
		self.port = port

	def func(self):
		print('%s %s' %(self.ip,self.port))

	@classmethod  ## 提供一种初始化对象的方法
	def from_conf(cls):
		return cls(setting.IP,setting.PORT)  ## 返回的就是一个实例化的对象,不需要我自己一个个创建

obj = Mysql.from_conf()
print(obj.__dict__)  ## {'ip': '127.0.0.1', 'port': 3306}

6,staticmethod

class Mysql:

	def __init__(self,ip,port):
		self.ip = ip
		self.port = port
	@staticmethod
	def create_id():
		import uuid
		return uuid.uuid4()

obj = Mysql('127.0.0.1','3306')

## 像普通函数一样调用就可以,不会自动传参,需要人工传参
print(Mysql.create_id())  ## 57c42038-b169-4f25-9057-d83795d097cc
print(obj.create_id()) ## 485372bc-efca-4da8-a446-b11c7bbf3c9b

7,内置方法

7.1 什么是内置方法

## 定义在类内部,以__开头和__结尾的方法称之为内置方法

## 会在满足某种情况下回自动触发执行
## 为什么用: 为了定制化我们的类或者对象

7.2 如何使用内置方法

# __str__
# __del__

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def say(self):
        print('<%s:%s>'%(self.name,self.age))

obj = People('aini',22)
print(obj)  ## <__main__.People object at 0x00000276F6B8B730>

## ----------------------------------------------------------------------

## __str__ 来完成定制化操作
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def say(self):
        print('<%s:%s>'%(self.name,self.age))
    
    def __str__(self):
        print('这是xxxxx对象')   ## 值起到提示作用
        return '<%s:%s>' % (self.name, self.age)  ## 必须要有return,而且返回字符串

obj = People('aini',22)
print(obj)  ##  <aini:22>

## ----------------------------------------------------------------------------------------
# __del__ :在清理对象时触发,会先执行该方法

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def say(self):
        print('<%s:%s>'%(self.name,self.age))
        
    def __del__(self):
        print('running......')

obj = People('aini',22)
print('=======================')

'''
 == == == == == == == == == == == =    ## 程序运行完了,要清理对象
running......  ## 清理对象时云运行
'''

## ---------------------------------------------------------------------------------------
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def say(self):
        print('<%s:%s>'%(self.name,self.age))
        
    def __del__(self):
        print('running......')

obj = People('aini',22)
del obj
print('=======================')

'''
running......  ## 清理对象时云运行
 == == == == == == == == == == == =    ## 程序运行完了
'''

## ---------------------------------------------------------------------------------------------
#####  对象本身占得是应用程序的内存空间,所以没有多大用处

##### 但是如果对象某个属性x 比如 obj.x 占得是操作系统内存空间,对象运行完了以后Python回收的是程序中的内存空间
### 操作系统不会被回收

class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        self.x = open('aini.txt','w',encoding="utf-8")
        
    def say(self):
        print('<%s:%s>'%(self.name,self.age))
        
    def __del__(self):
        print('running......')
        ## 发起系统调用,告诉系统回收操作系统资源,比如如下:
        self.x.close()

obj = People('aini',22)
print('=======================')

8,元类介绍

## 元类----------------> 用来实例化产生类的那个类
## 关系 : 元类---------------实例化 --------------->类----------------------> 对象

class People:
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))

## 查看内置元类
print(type(People))  # <class 'type'>
print(type(int))     # <class 'type'>

## class关键字定义的类和内置的类都是由type产生的

8.1 class关键字创建类的步骤

# 类三大特征:类名 class_name || 类的基类  clas_bases = (Object) || 类体本身 --> 一对字符串(执行类体代码,拿到运行空间)

class People:
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
class_body = '''
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
'''
class_dic = {} # 定义类的命名空间

# 类名 
	class_name = "People"
# 类的基类  
	clas_bases = (object,)
# 执行类体代码,拿到运行空间
    exec(class_body,{},class_dic)  # 空字典指的是全局命名空间  class_dic是类的命名空间 
    ## 运行拿到exec以后可以拿到类体代码的运行空间,放在class_dic 里

    print(class_dic) 
    ## {'__init__': <function __init__ at 0x0000016A0BDC9900>, 'say': <function say at 0x0000016A0BE2E320>}

# 调用元类
	People = type(class_name,class_basis,class_dic)
    print(People)  ## <class '__main__.People'>

8.2 定制元类,控制类的产生

## 定制元类
class Mymeta(type): ## 只有继承了type类的类才可以称之为元
    ## 运行__init__方法的时候,空对象和这些class_name,class_basis,class_dic一共四个参数一起传进来了
    ## 所以需要四个参数接受
    
    ## 重写了造对象的方法,不写__new__方法的话自动创建空对象
    ## 参数为: 类本身,调用类时所传入的参数
    def __new__(xls,*args,**kwargs):
        ##第一种方法 ----------------> 调父类的__new__()方法造对象 
        return super().__new__(cls,*args,**kwargs)
    	## 第二种方法 -----------------> 调用元类的内置方法
        return type.__new__(cls,*args,**kwargs)

	## 可以控制类的产生
    def __init__(self,class_name,class_basis,class_dic):
        ## 类的首字母大写
        if not x.capitalize():
            raise NameError('类名的首字母必须大写啊!!!!')     
     


class People(object ,metaclass = Mymeta): 
    # class产生类的话会自动继承object
    # 底层的话需要明确之指定继承object类
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
        
class_body = '''
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
'''
class_dic = {} # 定义类的命名空间

# 类名 
	class_name = "People"
# 类的基类  
	clas_bases = (object,)
# 执行类体代码,拿到运行空间
    exec(class_body,{},class_dic) 
# 调用元类
	People = Mymeta(class_name,class_basis,class_dic)  ## 调用 type.__call__(方法)
    ## 将参数先传给 __new__方法,造空对象
    ## 然后参数传递给 __init__方法初始化类
    
    ## 调用Mymeta发生的事儿,调用Mymeta 就是type.__call__()
    	# 先造一个空对象 ==> People 调用__new__方法
        #  调用Mymeta这个类的__inti__方法,完成初始化对象的操作(这个过程中可以控制类的产生)
        # 返回初始化好的对象
      
## 总结
	# 控制造空对象过程   重写 __new__()方法
    # 控制类的产生  重写 __init__()方法

8.3 new(方法)

## 具体看 8.2 控制造空对象的过程
##  __new__() 放下造对象时,早于 __init__() 方法运行  

8.4 call(方法)

## 8.1 中
# 调用元类时
	People = Mymeta(class_name,class_basis,class_dic)  ## 本质就是调用 type的__call__()方法
   

class Foo:
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __call__(self,name,age):
        print(name,age)
        print('我运行了obj下面的__call__方法')
        
obj = Foo(111,222)
obj("aini",'22') 
# 'aini' 22
# '我运行了obj下面的__call__方法'

## 对象的类里定义__call__方法的话,实例对象可以调用

### -------------------------------------------------------------------
## 如果想要控制类的调用, 那就重写__call__()方法
## 定制元类
class Mymeta(type):  
    def __call__(self,*args,**kwargs):
        ## Mymeta.__call__函数内会调用People.__new__()方法
        people_obj = self__new__(self)
        
        ## 可以对类进行定制化
        obj.__dict__['xxxx'] ='所有的obj产生的类我新加了一个属性'
        
        ## Mymeta.__call__函数内调用People.__inti__()方法
        self.__init__(people_obj,*args,**kwargs)
        
    ## 重写了造对象的方法,不写__new__方法的话自动创建空对象
    ## 参数为: 类本身,调用类时所传入的参数
    def __new__(xls,*args,**kwargs):
        ##第一种方法 ----------------> 调父类的__new__()方法造对象 
        return super().__new__(cls,*args,**kwargs)
    	## 第二种方法 -----------------> 调用元类的内置方法
        return type.__new__(cls,*args,**kwargs)

	## 可以控制类的产生
    def __init__(self,class_name,class_basis,class_dic):
        ## 类的首字母大写
        if not x.capitalize():
            raise NameError('类名的首字母必须大写啊!!!!')     
     

        
class People(object ,metaclass = Mymeta): 
    # class产生类的话会自动继承object
    # 底层的话需要明确之指定继承object类
	def __init__(self, name, age):
		self.name = name
		self.age = age
        
    def __new__(cls,*args,**kwargs):
        # 造对象
        return object.__new__(cls,*args,**kwargs)
        

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
 ## ------------------------------------------------------------        
 class_body = '''
	def __init__(self, name, age):
		self.name = name
		self.age = age

	def say(self):
		print('<%s:%s>' % (self.name, self.age))
'''
class_dic = {} # 定义类的命名空间
class_name = "People" 
clas_bases = (object,)
exec(class_body,{},class_dic) 
 People = Mymeta(class_name,class_basis,class_dic)  ## 调用 type.__call__(方法)     
    
  ## 调用Mymeta发生的事儿,调用Mymeta 就是type.__call__()
    	# 先造一个空对象 ==> People 调用__new__方法
        #  调用Mymeta这个类的__inti__方法,完成初始化对象的操作(这个过程中可以控制类的产生)
        # 返回初始化好的对象
        
             
  obj = People('aini',22)
	# 实例化People发生的三件事
    	# 调用Mymeta.__call__(self,*args,**kwargs)方法
        #  Mymeta.__call__函数内会调用People.__new__()方法
        #  Mymeta.__call__函数内调用People.__inti__()方法

8.5 属性查找的原则

image-20230704003043593

## 属性查找的原则:对象 ----> 类 ----------->父类
## 切记:父类不是元类,不会去从元类里找

十,网络编程

10.1 cs架构与bs架构

## cs架构
    ## Client ----------------------------------------- Server

    # 客服端软件 <==============================> 服务端软件
    # 操作系统<===================================> 操作系统
    # 计算机硬件 <================================> 计算机硬件

## bs架构
	## Brower ----------------------------------------------- Server

10.2 网络通信

## 网络存在的意义就是跨地域数据传输---------》 称之为通信
## 网络 = 物理链接介质  + 互联网通信协议

10.3 OSI七层协议

## 五层协议
	#  应用层
    # 传输层
    # 网络层
    # 数据链路层
    # 物理层

## 协议:规定数据的组织格式   格式:头部 + 数据部分

10.4 网络协议

## 计算机1 ----------------------------计算机2、
## 应用层 ------------------------------应用层
## 传输层 ----------------------------- 传输层
## 网络层 ----------------------------- 网络层 ------> (源IP地址,目标IP地址) 数据1
## 数据链路层 --------------------------数据链路层 --->(源mac地址,目标mac地址) 数据2((源IP地址,目标IP地址) 数据1)
## 物理层1 ------------二层交互机------------物理层2

# 二层交互机将从 物理层1接受的 二进制数据接收到以后可以解析到数据链路层(源mac地址,目标mac地址) 数据2 再转换成二进制 发给物理层2
10.4.1 物理层
## 物理层由来:上面提到,孤立的计算机之间要想一起玩,就必须接入internet,言外之意就是计算机之间必须完成组网

## 物理层功能:主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0

## 物理层负责发送电信号
	## 单纯的电信号毫无意义,必须对其进行分组
    

image-20230704134226026

10.4.2 数据链路层(帧)
## 数据链路层: Ethernet以太网协议
	## 规定一:一组数据称之为一个数据帧
    ## 规定二:数据帧分成两部分 头 + 数据两部分
    		## 头: 源地址与目标地址,该地址是Mac地址
        	## 数据:包含的是网络层整体的内容
    ## 规定三:但凡介入互联网的主机,必须有网卡,每一块网卡在出场时标志好一个全世界独一无二的地址该地址称之为-----> mac地址
    
## mac地址:(了解)
'''
    head中包含的源和目标地址由来:ethernet规定接入internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即mac地址

    mac地址:每块网卡出厂时都被烧制上一个世界唯一的mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号)  
'''
                
## head包含:(固定18个字节) ----------------- 源地址与目标地址,该地址是Mac地址
'''
    发送者/源地址,6个字节
    接收者/目标地址,6个字节
    数据类型,6个字节
    data包含:(最短46字节,最长1500字节)
'''
## 数据包的具体内容
## head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送

## 注意:计算机通信基本靠吼,既以太网协议的工作方式是广播
10.4.3 网络层(包)

## 网络层:IP协议
    ## 划分IP协议
    ## 每一个广播域但凡要接通外部,一定要有一个网关帮内部的计算机转发包到公网网关与外界通信走的是路由协议
    
## 规定1:一组数据称之为一个数据包
## 规定2: 数据帧分成两个部分----> 头 + 数据
	## 头包含:源地址与目标地址,该地址是IP地址
    ## 数据包含:传输层整体的内容
    
3-1 ip协议
# IP协议:
    '''
        1,规定网络地址的协议叫ip协议,它定义的地址称之为ip地址,广泛采用的v4版本即ipv4,它规定网络地址由32位2进制表示
        2,范围0.0.0.0-255.255.255.255
        3,一个ip地址通常写成四段十进制数,例:172.16.10.1

    '''
    ## ip地址分成两部分
        # 网络部分:标识子网
        # 主机部分:标识主机
    # 注意:单纯的ip地址段只是标识了ip地址的种类,从网络部分或主机部分都无法辨识一个ip所处的子网

    # 例:172.16.10.1与172.16.10.2并不能确定二者处于同一子网

    #ipv4地址:
        # 8bit.8bit.8bit.8bit
        0.0.0.0 ~ 255.255.255.255
    ## ipv6
        ## 目前在逐渐普及
3-2 子网掩码
   ## 子网掩码
		# 8bit.8bit.8bit.8bit
    ## 一个合法的IPv4地址组成部分=ip地址/子网掩码 ---------------------> 区分广播域
    	## 172.16.10.1/255.255.255.0
        ## ## 172.16.10.1/24  -----------------> 表示24位二进制数
'''
    知道”子网掩码”,我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都	为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。
'''

## 同为1结果为1,有0结果为0
    ## 计算机1的
        ## IP地址
        172.16.10.1:	 		10101100.00010000.00001010.000000001
        ## 子网掩码地址
        255255.255.255.0: 		 11111111.11111111.11111111.00000000
        ## 网络地址
                                10110101100.00010000.00001010.000000001->172.16.10.0
    ## 计算机2的
        ## IP地址
        172.16.10.2:			10101100.00010000.00001010.000000010
        ## 子网掩码地址
        255255.255.255.0:		11111111.11111111.11111111.00000000
        ## 网络地址
                                10101100.00010000.00001010.000000001->172.16.10.0
     ## 两个计算机网络地址一样,所以属于一个局域网内
3-3 APR协议
    
## 事先知道的是对方的IP地址
## 但是计算机的底层通信是基于ethernet以太网协议的mac地址通信

##API协议 -----------> 能够将IP地址解析成mac地址

## 两台计算机再同一个局域网内,直接发包就可以
计算计1             直接                       计算机2
ARP:
自己的IP,对方的IP  
#-1 计算二者的网络地址,如果一样,那ARP协议拿到计算机2的mac地址就可以
#-2 发送广播包

## 两台计算机不在同一个局域网内
计算计1            网关                       计算机2
ARP:
自己的IP,对方的IP  
# 计算二者的网络地址,如果不一样,应该拿到网关的mac地址

## FF:FF:FF:FF:FF:FF  ---------------------->意思就是要对方的Mac地址
##-1 如果在同一个局域网内,那就拿到了对方的Mac地址
##-2 发送广播包

![image-20230704153508853](htt


  1. lucky ↩︎

  2. a ↩︎

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐