day17-25序列化、python操作CSV/EXCEL/PDF/WORD/PPT文件、爬虫、正则表达式

❤ day 17-20 -----------------------

❤对象序列化和反序列化

# 热身练习:将100以内的素数输出到一个文件中

# 素数判断函数
def is_prime(num: int) -> bool:  
    '''
    判断一个正整数是不是质数
    :param num: 正整数
    :return: 质数返回True,否则返回False
    '''
    for i in range(2, int(num ** 0.5) + 1):  # 需取到num**0.5
        if num % i == 0:
            return False
    return True


with open('prime.txt', 'w', encoding='utf-8') as file:
    for n in range(2, 100):
        if is_prime(n):
            # 方法一:
            print(n, file=file)  # 将结果打印到文件,而不是打印到控制台
            # 方法二:
            file.write(str(n))
            file.write('\n')  # 换行符
            # 方法三:
            file.write(f'{n}\n')  # 格式化字符串

1. 将字典和列表写入文件

对象的序列化(serialization)和反序列化(deserialization)

  • 序列化:把一个对象(字典、列表等)变成字符串(str)或者字节串(bytes)

  • 反序列化:从字节串或字符串中还原出一个对象(字典、列表等)

Python的标准库有**json(字符串) / pickle(字节串)**模块,可以支持我们做序列化和反序列化操作

JSON —> JavaScript Object Notation —> JavaScript语言创建对象的字面量语法 (字面量:常数量)

例如:

let person = {
    name: "骆骆",
    age: 41,
    sex: True
}

这种数据格式也非常适合在两个系统(尤其是异构的系统)传输数据(因为它是纯文本),所以今天当我们说到JSON,更多的时候是把它当成一种数据交换格式。

Python中的字典跟JSON格式非常的像,所以我们可以通过将字典转成JSON格式的字符串,就可以写入文件中实现持久化。

文件保存数据的方式:

  • 二进制文件,字节串

  • 文本文件,字符串

json序列化,其他编程语言能读出来

pickle序列化,是python的私有化语言,其他编程语言不能读出来

2 序列化和反序列化

2.1 json模块

Python3 中可以使用 json 模块来对 JSON(文本文件) 数据进行编解码,它包含了两个函数:

  1. 编码序列化 - 把一个对象(字典、列表等)变成字符串(str)或者字节串(bytes))
  • json.dumps(): 对数据进行编码。
  • json.dump(操作对象, fp = 文件对象): 对数据进行编码。

2)解码反序列化 - 从字节串或字符串中还原出一个对象(字典、列表等))

  • json.loads(): 对数据进行解码。

  • **json.load(fp=文件对象)?*对数据进行解码。

2.1.1 序列化(字典和列表写入到文件 - dumps)

将字典和列表写入到文件中

import json

person = {
    'name': '骆骆',
    'age': 41,
    'sex': True,
    'friends': ['赵云', '马超', '辛弃疾'],
    'car': {
        'brand': 'QQ',
        'max_speed': 120
    }
}
with open('person.txt', 'w') as file:
    # 序列化
    # 方法一:(分解写法)
    # content = json.dumps(person)
    # file.write(content)
    # 方法一:(合并写法)
    # print(json.dumps(person), file=file)
    
    # 方法二:
    json.dump(person, fp=file)

2.1.2 反序列化(字符串还原成(字典)对象)

读取文件中的JSON格式数据还原成字典对象

反序列化:将字符串还原成(字典)对象

import json

with open('person.txt') as file:
    # 反序列化:将字符串还原成(字典)对象
    # content = file.read()
    # obj = json.loads(content)
    # 从文件中读取JSON字符串还原成字典对象
    obj = json.load(fp=file)
    print(obj)
    print(type(obj))

2.2 pickle模块

Python3 中可以使用 pickle 模块来对 二进制文件 数据进行编解码,它包含了两个函数:

  • pickle.dumps(): 对数据进行编码。
  • pickle.dump(操作对象, file=文件对象):对数据进行编码。
  • pickle.loads(): 对数据进行解码。

2.3.1 序列化

将字典和列表写入到文件中

import pickle

person = {
    'name': '骆骆',
    'age': 41,
    'sex': True,
    'friends': ['赵云', '马超', '辛弃疾'],
    'car': {
        'brand': 'QQ',
        'max_speed': 120
    }
}
with open('person.dat', 'wb') as file:
    # 序列化
    # print(json.dumps(person), file=file)
    # content = json.dumps(person)
    # file.write(content)
    pickle.dump(person, file=file)

2.3.2 反序列化

读取文件中的Pickle格式数据还原成字典对象

反序列化:将字节串(二进制数据)还原成(字典)对象

import pickle

with open('person.dat', 'rb') as file:
    # 反序列化:将字节串(二进制数据)还原成(字典)对象
    obj = pickle.load(file)
    print(obj)
    print(type(obj))
    
    # 结果:
    # {'name': '张三', 'age': 18, 'gender': True, 'friends': ['小花', '小明'], 'car': {'brand': 'QQ', 'max_speed': 120}}
    # class 'dict'>

❤ 联网获取数据(通过API接口获取数据)

URL —> 网址 —> 统一资源定位符 —> 能够唯一标识一个(网络)资源的符号

URL —> Universal Resource Locator —>唯一标识一个(网络)资源

例如:百度首页

https://www.baidu.com/ 简写版

https://www.baidu.com:443/index.html 完整版

https://14.215.177.38:443/index.html IP地址版

https://www.baidu.com:443/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png

例如:豆瓣网

https://movie.douban.com:443/top250

https://movie.douban.com/top250?start=25&filter= ?后的不属于URL的内容,是请求参数

协议://用户名:口令@域名或者IP地址:端口/路径1/路径2/资源名称

URI —> 统一资源标识符 —> URL + URN

使用三方库 requests 可以非常方便的实现通过URL访问网络资源的操作
可以使用Python的包管理工具 pip 来安装和管理三方库以及三方工具

修改 pip 下载源为国内的镜像网站(推荐使用豆瓣网的镜像)
pip config set global.index-url https://pypi.doubanio.com/simple

检查有没有pip包:terminal(py文件下方位置) - 输入:pip --version

查找三方库:pip search requests
安装三方库:pip install requests
卸载三方库:pip uninstall requests
更新三方库:pip install -U requests

协议 —> 规范和标准 —> 网络协议 —> 通过网络进行通信的双方要遵守的规范和标准
HTTP —> 超文本传输协议 —> 请求响应式协议

HTTP -> 超文本传输协议
HTTP(S) -> 超文本传输协议(安全版本)

3.1 导入第三方库 requests

首先,安装第三方库requests,使用前需先导入.

import requests

3.1 requests 模块

requests.get(url=数据地址, params = 请求数据参数) - 通过指定的URL向Web服务器发起一个请求,该函数会返回一个相应对象

网页上获取数据:例如:从聚合数据平台获取天气数据:聚合数据 —> 天气预报(查看) —> 申请数据(得到key) —> 测试 —> 填入请求参数 -> 发送请求

3.2 通过python联网获取数据

通过python联网获取数据:例如:从聚合数据平台获取天气数据:聚合数据 —> 天气预报 —> 申请数据,获取key—> 查看天气预报中的API文档,获取接口地址(http://apis.juhe.cn/simpleWeather/query),获取请求参数

# 从聚合数据平台获取天气数据
# 聚合数据 ---> 天气预报 ---> 申请数据,获取`key`---> 查看天气预报中的`API文档`,获取`接口地址`(http://apis.juhe.cn/simpleWeather/query),获取`请求参数`

import json

import requests

# get函数会通过你指定的URL向Web服务器发起一个请求,该函数会返回一个响应对象
resp = requests.get(
    url='http://apis.juhe.cn/simpleWeather/query',
    params={
        'city': '上海',
        'key': 'e73ebce8dc3cb2f35510f4462f08430c'
    }
)
# 获取服务器响应的内容并将其反序列化成一个字典对象
weather_dict = json.loads(resp.text)
print(weather_dict['result']['realtime'])
# futures = weather_dict['result']['future']
# for future in futures:
#     print(future)
# 聚合数据 - 手机归属地

import json

import requests

url = 'http://apis.juhe.cn/mobile/get'
key = 'a85eb75a4850c5502be6ff253e70b5f2'
phone = '15882460234'

resp_phone_belong = requests.get(
    url = url,
    params={
        'key':key,
        'phone':phone
    }
)

print(resp_phone_belong.text)  # .text - 获取网页内容,得到网页内容的字符串
phone_belong_dict = json.loads(resp_phone_belong.text)  # .loads - 反序列化,将字符串转换为字典
print(phone_belong_dict)
# 天行数据 - 头条新闻获取
# 每页获取10条新闻
for page in range(1,6):
    resp_news = requests.get(
        url = 'http://api.tianapi.com/topnews/index',
        params={
            'key':'e8c5524dd2a365f20908ced735f8e480',
            'page':page,
            'num':10
        }
    )
    result = resp_news.json()  # 将resp_news转换为字典【待查证】
    for news_dict in result['newslist']:
        print(news_dict['title'])  # 新闻标题
        print(news_dict['url'])  # 新闻链接
        print(news_dict['source'])  # 新闻来源
        print('-'*30)

3.3 通过响应对象获取服务器返回的内容

  • 文本文件对象.text - 通过响应对象获取服务器返回的文本内容

  • 二进制文件对象.content - 通过响应对象获取服务器返回的二进制数据

# 通过响应对象获取服务器返回的内容

import requests

resp = requests.get('https://www.sohu.com/')
# 通过响应对象获取服务器返回的文本内容
print(resp.text)

resp = requests.get('http://29e5534ea20a8.cdn.sohucs.com/c_cut,x_47,y_0,w_588,h_392,c_zoom,h_103/os/news/d5d461792f7944f11e9ed9a2bd2ff7a5.jpg')
with open('test.jpg', 'wb') as file:
    # 通过响应对象获取服务器返回的二进制数据
    file.write(resp.content)

给文件标题添加时间

from datetime import datetime

import openpyxl
import requests

workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.append(('标题', '链接', '来源'))
for page in range(1, 6):
    resp = requests.get(
        url='http://api.tianapi.com/topnews/index',
        params={
            'key': 'e8c5524dd2a365f20908ced735f8e480',
            'page': page,
            'num': 20
        }
    )
    result = resp.json()
    for news_dict in result['newslist']:
        title, url, source = news_dict['title'], news_dict['url'], news_dict['source']
        sheet.append((title, url, source))
curr = datetime.now()
workbook.save(f'头条新闻数据_{curr.year}{curr.month:0>2d}{curr.day:0>2d}.xlsx')

❤ 用python操作Excel文件

4.1 导入第三方库 openpyxl

工作簿(workbook):一个Excel文件叫一个工作簿

工作表(sheet) :工作簿中的一个表格叫做一个工作表

单元格(cell):Excel文件表中表格的行列交汇点叫单元格

python操作Excel文件需借助第三方库openpyxl

首先,安装第三方库openpyxl,使用前需先导入

import openpyxl

4.2 创建工作簿

创建一个Excel工作簿

workbook = openpyxl.Workbook()

创建工作表

workbook.create_sheet('hello')

获取默认的工作表

sheet = workbook.active

4.3 将数据写入Excel文件

  • 工作表.append(数据)

    注意:想要把多个数据写在同一行,并分别填入多个单元格,就需要将数据以序列的方式传入

  • 工作表.cell(行索引,列索引,数据)

    • 注意:工作表中的行索引和列索引都是从1开始的
sheet.append(('姓名', '语文', '数学', '英语'))
# sheet.append(['姓名', '语文', '数学', '英语'])
sheet.append(('张三', 50, 60, 70))
sheet.append(('李四', 53, 66, 72))
sheet.append(('王五', 51, 67, 74))

sheet.cell(5, 1, '小明')  # 在5行4列单元格写入'小明'

4.4 保存工作簿

  • 工作对象.save(文件路径)
workbook.save('学生成绩表.xlsx')

练习:

# 练习:从天行数据获取头条新闻,并将数据保存至excel
news = openpyxl.Workbook()
# news.create_sheet('news')
sheet = news.active
# sheet.append(('title'))

for page in range(1, 6):
    resp_news = requests.get(
        url='http://api.tianapi.com/topnews/index',
        params={
            'key': 'e8c5524dd2a365f20908ced735f8e480',
            'page': page,
            'num': 10
        }
    )
    result = resp_news.json()  # 将resp_news转换为字典
    for news_dict in result['newslist']:
        title,url,source = news_dict['title'], news_dict['url'], news_dict['source']
        sheet.append((title, url, source))
        # sheet.append((news_dict['url']))
        # sheet.append((news_dict['source']))

        # print(news_dict['title'])  # 新闻标题
        # print(news_dict['url'])  # 新闻链接
        # print(news_dict['source'])  # 新闻来源
        # print('-'*30)
curr = datetime.now()  # 获取当前的结构体时间
news.save(f'新闻_{curr.year}{curr.month:0>2d}{curr.day:0>2d}.xlsx')  # 文件名中加日期

4.5 读取Excel文件

如何用Excel打开乱码文件:现将文件另存为UTF-8 with BOM utf8bom文件,再用Excel打开

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yeLAOAQy-1634975911748)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211012091006237.png)]

4.5.1 加载Excel文件

openpyxl.load_workbook(‘文件路径’)

#读取excel文件

import openpyxl

# 加载Excel文件 ---> Workbook
workbook = openpyxl.load_workbook('resources/口罩销售数据.xlsx')
# 获取所有工作表的名字
print(workbook.sheetnames)
# 获得第一个工作表 ---> Worksheet
sheet = workbook.worksheets[0]
# 获取单元格的数据
print(sheet.cell(3, 4).value)
print(sheet['D3'].value)
# 单元格的范围
print(sheet.dimensions)
# 获取表格的行数和列数
print(sheet.max_row, sheet.max_column)
# 循环遍历所有的数据
# for row in range(1, sheet.max_row + 1):
#     for col in range(1, sheet.max_column):
#         print(sheet.cell(row, col).value, end='\t')
#     print()
for row in range(2, sheet.max_row + 1):
    for col in 'ABCDEF':
        print(sheet[f'{col}{row}'].value, end='\t')
    print()

例如:

在当前⽂文件夹下有⼀一个名为“阿⾥里里巴巴2020年年股票数据.xlsx”的Excel⽂文件,如果想读取并显示该⽂文件的内容,
可以通过如下所示的代码来完成。

import datetime

import openpyxl

# 加载⼀一个⼯工作簿 ---> Workbook
wb = openpyxl.load_workbook('阿⾥里里巴巴2020年年股票数据.xlsx')
# 获取⼯工作表的名字
print(wb.sheetnames)
# 获取⼯工作表 ---> Worksheet
sheet = wb.worksheets[0]
# 获得单元格的范围
print(sheet.dimensions)
# 获得⾏行行数和列列数
print(sheet.max_row, sheet.max_column)

# 获取指定单元格的值
print(sheet.cell(3, 3).value) 	 #通过cell方法
print(sheet['C3'].value)	  # 通过单元格
print(sheet['G255'].value) 		 # 通过单元格

# 获取多个单元格(嵌套元组)	-	返回嵌套的元组
print(sheet['A2:C5']) 	 # 通过切片
# print(sheet['A2':'C5'])

# 读取所有单元格的数据
for row_ch in range(2, sheet.max_row + 1):
    for col_ch in 'ABCDEFG':
        value = sheet[f'{col_ch}{row_ch}'].value
        if type(value) == datetime.datetime:
            print(value.strftime('%Y年年%m⽉月%d⽇日'), end='\t')    # \t:表示空四个字符,或一个缩进/制表符/Tab键
        elif type(value) == int:
            print(f'{value:<10d}', end='\t')
            # X < Nd   -   将数据转换为长度为N的字符串,不够在后面用X(默认为空格)对应的字符来填充
        elif type(value) == float:
            print(f'{value:.4f}', end='\t')  
            # .Nf	  -   保留N位小数
        else:
            print(value, end='\t')
    print()

❤ 用python操作CSV文件

CSV(Comma Separated Values)全称逗号分隔值文件是⼀一种简单、通⽤用的⽂文件格式,被⼴广泛的应⽤用于应⽤用程序
(数据库、电⼦子表格等)数据的导⼊入和导出以及异构系统之间的数据交换。因为CSV是纯⽂文本⽂文件,不不管是什什么操
作系统和编程语⾔言都是可以处理理纯⽂文本的,⽽而且很多编程语⾔言中都提供了了对读写CSV⽂文件的⽀支持,因此CSV格式在
数据处理理和数据科学中被⼴广泛应⽤用。

使用方法参见 ‘第23课:⽤用Python读写CSV⽂文件.pdf’

1. 读取CSV文件

常用的字符编码为’utf - 8’

若文件存为了UTF-8 with BOM utf8bom文件,则相应的字符编码为’utf-8-sig’

1utf - 8 - sig 带签名的UFT-8 —> 有字节序标记

with open('./resources/2018年北京积分落户数据.csv', 'r', encoding='utf-8-sig') as file:
    # utf - 8 - sig  带签名的UFT-8 ---> 有字节序标记
    content = file.readline()  # 读取一行内容
    # file.readlines()  # 可一次性读完文件所有内容,但不推荐该做法(因为文件很大时,内存消耗太大)
    while content:
        print(content, end='')
        content = file.readline()
        

# 注意:如果读取UTF-8 with BOM utf8bom文件时指定encoding='utf-8',读出结果结果中可能会多出一点内容,例如:
with open('./resurces/2018年北京积分落户数据.csv', 'r', encoding='utf-8') as file:
# 结果:
# ['\ufeffid', 'name', 'birthday', 'company', 'score']
# 其中的feff是字节序标记,因为该文件为UTF-8 with BOM utf8bom文件,即为带签名的UFT-8的文件,encoding='utf-8-sig'
with open('./resources/2018年北京积分落户数据.csv', 'r', encoding='utf-8-sig') as file:
    reader = csv.reader(file, delimiter='#')  #
    # csv.reader()的参数还有:
    # delimiter(默认是逗号)
    # 该文件为逗号分隔的文件,若为其他符号作为分隔符的文件,则需要用参数delimeter(默认是逗号)进行指定(需与原文件的分隔符相同),否则会将整个一行作为同一个字符串读出(即一行只有一个元素)
    # quotechar='|'     -   去掉包围符|

    # 如果值带有包围符(双引号,或|),但不想读出|,就需要用参数quotechar='|'来去除包围
    for line in reader:
        print(line)

2. 将数据写入CSV文件

# newline=''    -   这样追加后就不会出现空行
with open('./resources/2018年北京积分落户数据.csv', 'a', encoding='utf-8-sig', newline='') as file:
    writer = csv.writer(file)  #
    # writer.writerow(['6020', '一一', '1981-06', '北京宝洁技术有限公司', '90.74'])
    # writer.writerows(嵌套列表)     -   一次写多行
    # ['6020', '一一', '1981-06', '北京宝洁技术有限公司', '90.74']
    writer.writerows([
        ['6021', '一一', '1981-06', '北京宝洁技术有限公司', '90.74'],
        ['6022', '一一', '1981-06', '北京宝洁技术有限公司', '90.74'],
        ['6023', '一一', '1981-06', '北京宝洁技术有限公司', '90.74']
    ])

3. 将CSV文件转换为Excel、修改Excel文件样式

方法一:直接另存为Excel文件

方法二:通过python将CSV文件转换为Excel格式

# 将CSV文件转换为Excel格式

import csv

import openpyxl
from openpyxl.styles import Font, Alignment


workbook = openpyxl.Workbook()
sheet = workbook.create_sheet('2018年北京积分落户数据')
# sheet = workbook.active
# sheet.title('2018年北京积分落户数据')

with open('resources/2018年北京积分落户数据.csv', 'r', encoding='utf-8-sig') as file:
    reader = csv.reader(file, delimiter='#', quotechar='|')
    for line in reader:
        sheet.append(line)
        
# 修改单元格样式

# 修改指定行的高度
sheet.row_dimensions[1].height = 40
# 修改指定列的宽度
cols_width = {
    'A': 30,
    'B': 50,
    'C': 60,
    'D': 180,
    'E': 50
}
for key in cols_width:
    sheet.column_dimensions[key].width = cols_width[key]  # 修改列宽
# 修改单元格的样式
for col in range(1, 6):  # 修改第一行的格式
    sheet.cell(1, col).font = Font(name='STKaitiSC-Regular', size=22, bold=True, color='0000ff')
    sheet.cell(1, col).alignment = Alignment(horizontal='center', vertical='center')
    
workbook.save('resources/2018年北京积分落户数据.xlsx')

❤ 类、面向对象(OOP)

用对象的概念将数据和操作数据的函数从逻辑上组织成一个整体。
在面向对象的世界里,我们要解决任何问题都是先创建对象,然后给对象发出消息。

对象 - 接收消息的实体 —> 具体概念

类 - 对象的蓝图和模板 —> 抽象概念

总结:

  1. 一切皆为对象
  2. 每个对象都是独一无二的
  3. 对象都有静态特征(属性)和动态特征(行为)
  4. 对象都属于某个类

面向对象编程的实施步骤:

  1. 定义类

    数据抽象:给出对象的静态特征 —> 属性

    行为抽象:给出对象的动态特征 —> 行为

  2. 创建对象

  3. 给对象发消息

1. 类和对象

1.1 什么是类、什么是对象

什么类:拥有相同功能相同属性的对象的集合 (抽象的概念)

什么对象:对象是类的实例(具体的事物)

从生活的角度理解类和对象:

  • 人是类,余婷就是对象、骆昊是另外一个对象

  • 杯子是类,我桌上这个杯子就是对象

1.2 创建类和创建对象

1.2.1 创建类

创建类 - 就是用代码描述清楚这个类是拥有哪些相同功能(函数)和哪些相同属性(变量)的对象的集合

语法:

​ class 类名:

​ 类的说明文档

​ 类的内容

说明:

  • class - 关键字;固定写法

  • 类名 - 程序员自己命名;

    ​ 要求:标识符、不是关键字

    ​ 规范:见名知义;

    ​ 驼峰式命名,并且第一个字母大写;

    ​ 不使用系统的函数名、类名、模块名

  • : - 固定写法

  • 类的说明文档 - 多行注释

  • 类的内容 - 包含属性和方法

    • 属性:类属性(类的字段)、对象属性

    • 方法:对象方法、类方法、静态方法

注意:方法是定义在类中的函数

命名规则:

- 变量、函数的命名:snake_case

- 类命名:CamelNotation(驼峰命名法)

1.2.2 创建对象

语法:

​ 类名()

# 创建类
class Person:
    """类的说明文档"""
    # 属性
    # 方法
    pass


# 创建对象
p1 = Person()
print(p1)

p2 = Person()
print(p2)

1.3 对象方法

1)怎么定义:

直接定义在类中函数就是对象方法

2)怎么调用:

通过 ‘对象.’ 的形式来调用

3)特点:

有默认参数self,这个参数在调用的时候不用传参,系统会自动将当前对象传给self。(谁调用就指向谁)

class Student:
    # study是一个对象方法
    def study(self):
        print(f'self:{self}')
        print('学习')

    def eat(self, food):
        print(f'吃{food}')


stu1 = Student()
stu2 = Student()
print(f'stu1:{stu1}')
print(f'stu2:{stu2}')
stu2.study()
stu1.eat('米饭')

1.4 init方法

__ init __方法 - 初始化方法

1)构造方法: 函数名和类名相同,用来创建对象的函数就是构造函数(构造方法)
python的构造函数,需要程序员自己写,创建类的时候系统会自动创建这个类的构造函数

2)初始化方法:

​ a. 当通过类创建对象的时候系统会自动调用 __ init __方法

​ b. 在调用调用构造函数创建对象的时候,需不需要参数需要几个参数看对应的 __ init __ 除了 self 以外有
没有额外的参数,有几个额外的参数。

​ c. 程序员在类中添加 __ init _ 方法的时候只需要保证方法名是 __ init __ 有默认参数self就可以。
形参和函数体可以根据情况随便添加

补充:前后都有两个下划线的方法又叫魔法方法,这类方法不需要程序员去调用,在特定情况下会被自动调用

class Dog:
    def __init__(self, x=9):
        print('初始化方法')


p1 = Dog(10)
p2 = Dog(x=20)
p3 = Dog()

1.5 属性

属性 - 定义在类中的变量

1)类属性

​ a.怎么定义: 直接定义类中的变量

​ b.怎么使用: 通过 ‘类.’ 的方式来使用

​ c.什么时候用: 属性值不会因为对象不同而不一样

2)对象属性

​ a.怎么定义:以 ‘self.属性名 = 值’ 的形式定义在 __ init __ 方法中

​ b.怎么使用:以 '对象.'的方式来使用

​ c.什么时候用:
属性值会因为对象不同而不一样的时候就使用对象属性

class Circle:
    # a 是类属性
    a = 10
    # pi 是类属性
    pi = 3.1415926

    # radius是对象属性
    def __init__(self):
        self.radius = 1


# 使用类属性
print(Circle.a)

c1 = Circle()
# 使用对象属性
print(c1.radius)

1.4.2 对象属性的初始值

class Person:
    def __init__(self, name, gender='男'):
        self.name = name
        self.gender = gender
        self.age = 0


p1 = Person('小明')
p2 = Person('张三')
p3 = Person('小花', '女')

print(p1.name, p1.gender)
print(p2.name, p2.gender)
print(p3.name, p3.gender)

1.4.3 在对象方法中对象属性的使用

class Circle:
    pi = 3.1415926

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        # 如果在实现对象方法的功能的时候需要用到对象属性,由self来提供这个对象属性
        # return 圆周率 x 圆半径的平方
        return Circle.pi * self.radius ** 2


c1 = Circle(2)
c2 = Circle(4)

print(c1.area())
print(c2.area())

1.5 方法

方法 - 定义在类中的函数

1)对象方法

​ a.怎么定义:直接定义在类中

​ b.怎么调用:对象.

​ c.特点:有个默认参数self,self不用传参,谁调用self就指向谁
d.什么时候用:如果实现函数的功能需要用到对象属性,就将定义成对象方法

2)类方法 @classmethod

​ a.怎么定义:定义函数前加装饰 @classmethod

​ b.怎么调用:类.

​ c.特点:有个默认参数cls,调用时候不用传参,系统自动将当前类传给cls

​ d.什么时候用:在不需要对象属性的前提下,需要类

3)静态方法 @staticmethod

​ a.怎么定义:定义函数前加装饰器 @staticmethod

​ b.怎么调用:类.

​ c.特点:没有默认参数

​ d.什么时候用:在不需要对象属性的前提下,也不需要类

class A:
    def func1(self):
        print('对象方法')
        print(A)

    @classmethod
    def func2(cls):
        print(f'cls:{cls}')
        print('类方法')
        b = cls()
        print(f'b:{b}')

    @staticmethod
    def func3():
        print('静态方法')


a = A()
a.func1()

print(f'A:{A}')
A.func2()

A.func3()

4)类方法和静态方法的区别【待核实是否正确】

@classmethod - 将返回值传给类,再得到通过类操作后的结果

@staticmethod - 返回传给类的值(即将什么值传给了类,直接返回这个值)

... 
   @classmethod
    def value_f(cls, value: float, base=1000):
        return cls(int(value*base), base)

    @staticmethod
    def value_ff(value: float, base=1000):
        return int(value * base), base


f1 = Fraction.value_f(0.5)
print(f1)
f2 = Fraction.value_ff(0.5)
print(f2)
# 结果:
# 1/2
# (500, 1000)

1.6 练习:

练习要求:使用类的方法描述:

矩形类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1U1bkW6-1634975911757)(file:///C:\Users\32873\Documents\Tencent Files\1090340335\Image\Group2\0S\N0\0SN0}0F[U7LU6W3DU@PR@`2.jpg)]

# 矩形类

# 第1步:定义类
class Rectangle:
    """矩形"""
    
    # 数据抽象
    def __init__(self, width, height):
        # self - 第一个参数都是self;self代表对象自己
        # __init__方法:在使用构造器语法创建对象时,方法__init__会被自动调用
        """
        初始化方法
        :param width: 矩形的宽度
        :param height: 矩形的高度
        """
        self.width = width
        self.height = height
        
    # def __init__(self, w, h):
    #     self.width = w
    #     self.height = h

    # 行为抽象
    def perimeter(self):
        """计算周长"""
        return (self.width + self.height) * 2

    def area(self):
        """计算面积"""
        return self.width * self.height

# 第2步:创建对象 ---> 构造器语法
# 构造器语法:对象名 = 类名()
rect = Rectangle(width=5, height=3)
# rect = Rectangle(5,3)

# 第3步:给对象发消息
print(f'矩形的周长:{rect.perimeter()}')
print(f'矩形的面积:{rect.area()}')

学生类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xvIVjU8M-1634975911758)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211012144554507.png)]

# 学生类
class Student():
    """学生"""

    def __init__(self, name, age):
        """
        初始化代码
        :param name: 姓名
        :param age: 年龄
        """
        self.name = name
        self.age = age

    def eat(self):
        """吃饭"""
        return f'{self.name} is eating.'

    def play(self):
        """玩耍"""
        return f'{self.name} is playing.'

    def study(self, course_name):
        """
        学习
        :param course_name: 课程名称
        :return:
        """
        return f'{self.name} is studing {course_name}.'


stu1 = Student('一一', 41)
stu2 = Student('二二', 32)

stu1.eat()
stu2.play()
print(stu1.study('Chinese'))

计算泳池造价

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVhu7pge-1634975911760)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211012153812149.png)]

# 泳池造价
class Circle:
    def __init__(self, radius):
        self.radius = radius

    def perimeter(self):
        return 2 * math.pi * self.radius

    def area(self):
        return math.pi * self.radius ** 2

r = float(input('请输入泳池半径:'))
c1, c2 = Circle(r), Circle(r + 3)
fence_price = c2.perimeter() * 38.2
aisle_price = (c2.area()-c1.area()) * 28.5
print(f'围墙的造价为{fence_price:.2f}')
print(f'过道的造价为{aisle_price:.2f}')

其中:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R36IHyBt-1634975911761)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211012154907611.png)]

数字时钟

属性:时分秒

行为:走字、显示时间

清屏

# 数字时钟
class Clock:

    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second

    def show(self, mode_12=False):
        """
        显示时间
        :param mode_12:
        :return:
        """
        if mode_12:
            if self.hour < 12:
                return f'{self.hour:0>2d}:{self.minute:0>2d}:{self.second:0>2d} AM'
            else:
                self.hour = self.hour - 12 if self.hour > 12 else self.hour
                return f'{self.hour:0>2d}:{self.minute:0>2d}:{self.second:0>2d} PM'

        return f'{self.hour:0>2d}:{self.minute:0>2d}:{self.second:0>2d}'

    def run(self):
        """走字"""
        self.second += 1
        if self.second == 60:
            self.second = 0
            self.minute += 1
            if self.minute == 60:
                self.hour += 1
                self.minute = 0
                if self.hour == 24:
                    self.hour = 0


clock = Clock()
while True:
    # 通过os模块的system函数
    # 清屏:Windows - --> cls
    #      macOS - --> clear
    # 即打印语句前写入代码:
    import os
    os.system('cls')  # windows系统
    os.system('clear')  # 苹果系统
    # 注意:直接运行并不会清屏,需要在终端运行才会实现清屏效果
    # 调用终端运行程序:Terminal ---> 调用python解释器执行该py文件(即在终端输入:python example08.py) ---> 即可实现清屏效果
    print(clock.show())
    time.sleep(1)
    clock.run()

倒计时器

import time


class CountdownTimer():

    def __init__(self, hour=24, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second

    def show(self):
        """显示时间"""
        return f'{self.hour:0>2d}:{self.minute:0>2d}:{self.second:0>2d}'

    def is_over(self):
        """判断倒计时是否结束"""
        return self.hour == 0 and self.minute == 0 and self.second == 0

    def run(self):
        """走字"""
        if not self.is_over():
            self.second -= 1
            if self.second < 0:
                self.minute -= 1
                self.second = 59
                if self.minute < 0:
                    self.hour -= 1
                    self.minute = 59


clock = CountdownTimer(0, 0, 3)
print(clock.show())
while not clock.is_over():
    clock.run()
    print(clock.show())
    time.sleep(1)
print('倒计时结束,时间到!')

狗类和人类

定义一个狗类和一个人类:

狗拥有属性:姓名、性别和品种 拥有方法:叫唤

人类拥有属性:姓名、年龄、狗 拥有方法:遛狗

class Dog:
    def __init__(self, name, breed, gender='母狗'):
        self.name = name
        self.gender = gender
        self.breed = breed
        
    def bark(self):
        print(f'{self.name}:汪汪汪!')


class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.age = age
        self.dog = None

    def walk_the_dog(self):
        if self.dog:
            print(f'{self.name}牵着{self.dog.name}散步!')
        else:
            print('没有狗!遛自己!')


p1 = Person('小明')
p1.dog = Dog('财财', '土狗')
p1.walk_the_dog()

二维点类

定义一个二维点类,拥有属性:x坐标、y坐标 拥有方法:求当前点到另外一个点的距离

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def distance(self, other):
        return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5


p1 = Point()
p2 = Point(10, 0)
print(p1.distance(p2))

圆类

定义一个圆类,拥有属性:半径、圆心 拥有方法:求圆的周长和面积、判断当前圆和另一个圆是否外切

class Circle:
    def __init__(self, r, center=Point()):
        self.r = r
        self.center = center

    def is_exterior_contact(self, other):
        return self.center.distance(other.center) == self.r + other.r

线段类

定义一个线段类,拥有属性:起点和终点, 拥有方法:获取线段的长度

class Line:
    def __init__(self, start=Point(), end=Point()):
        self.start = start
        self.end = end

    def length(self):
        return self.start.distance(self.end)

❤ 面向对象(进阶)——骆

class Student:
    def __init__(self, name, age=18, study_id='000'):
        self.name = name
        self.age = age
        self.study_id = study_id

    # 在当前类的对象被打印的时候自动调用,并且将这个方法的返回值作为打印结果(返回值必须是字符串)
    def __repr__(self):
        return f'<{str(self.__dict__)[1:-1]}>'


stu1 = Student('小明')
stu2 = Student('小花', 20, '0001')

练习:

1. 点、移动点、两点距离、线段、线段相交

  1. 定义类描述平面上的点,提供移动点、计算一个点到另一个点距离的方法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcha6Xtc-1634975911762)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013125611363.png)]

  2. 定义类描述平面上的线段,提供计算线段长度,判断一个线段与另一个线段是否相交的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCwfgtY6-1634975911763)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013140105261.png)]

import math

# 1.定义类描述平面上的点,提供移动点、计算一个点到另一个点距离的方法
class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def move_to(self, x, y):
        self.x = x
        self.y = y

    def move_by(self, dx, dy):
        self.x += dx
        self.y += dy

    def distance(self, other):
        return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)

    def __str__(self):
        return f'<{self.x}, {self.y}>'


p1 = Point(3, 5)
p2 = Point(6, 1)
print(p1, p2)
print(p1.distance(p2))
p1.move_to(0, 0)
p2.move_by(1, -1)
print(p1, p2)
print(p1.distance(p2))

# 2.定义类描述平面上的线段,提供计算线段长度,判断一个线段与另一个线段是否相交的方法
class Line():
    def __init__(self, start: Point, end: Point):
        self.start = start
        self.end = end

    def length(self):
        return self.start.distance(self.end)

    def intersect(self, other):
    # self就是接收消息的对象,就是下面的p1,other是另一个点的对象,就是下面的p2
        sx1, sy1, ex1, ey1 = self.start.x, self.start.y, self.end.x, self.end.y
        sx2, sy2, ex2, ey2 = other.start.x, other.start.y, other.end.x, other.end.y
        return not (max(sx1, ex1) > min(sx2, ex2)
                    or min(sx1, ex1) < max(sx2, ex2)
                    or max(sy1, ey1) > min(sy2, ey2)
                    or min(sy1, ey1) < max(sy2, ey2))


p1 = Point(1, 3)
p2 = Point(4, 7)
print(p1, p2)
print(p1.distance(p2))

p3 = Point(2, 3)
p4 = Point(5, 7)
line1 = Line(p1, p2)
line2 = Line(p3, p4)
print(line1.intersect(line2))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ro6mmYJQ-1634975911764)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013130908588.png)]

2. 三角形

类方法 @classmethod

类方法 - 发给类的消息(比静态方法多一个参数,这个参数(cls)代表接收消息的类) —> @classmethod

@classmethod
def is_valid_sides(cls, a, b, c):
    return a + b > c and b + c > a and a + c > b

静态方法 @staticmethod

静态方法 - 不是三角形对象的消息,而是发给三角形类的消息 —> @staticmethod

Triangle.check_sides(a, b, c)

@staticmethod
def check_sides(a, b, c):
    return a + b > c and b + c > a and a + c > b
  • 类方法和静态方法写一个即可

同时输入多个数据

a, b, c = map(float, input('请输入三条边的长度: ').split())

练习:通过类描述三角形,并计算三角形的周长和面积

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yaoRmkyU-1634975911764)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013131014828.png)]

# 通过类描述三角形,并计算三角形的周长和面积
import math


class Triangle:

    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    @classmethod
    def is_valid_sides(cls, a, b, c):
        return a + b > c and b + c > a and a + c > b
	
    @staticmethod
    def check_sides(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self): 
    	"""周长"""
        return self.a + self.b + self.c

    def area(self):
    	"""面积"""
        p = self.perimeter() / 2
        a, b, c = self.a, self.b, self.c
        return math.sqrt(p * (p - a) * (p - b) * (p - c))


def main():
    a, b, c = map(float, input('请输入三条边的长度: ').split())  # # 同时接收多数据
    if Triangle.check_sides(a, b, c):
        tri = Triangle(a, b, c)
        print(f'三角形的周长: {tri.perimeter()}')
        print(f'三角形的面积: {tri.area()}')
    else:
        print('不能构成三角形!')


if __name__ == '__main__':
    main()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8H25aXHV-1634975911765)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013131044797.png)]

3. 扑克游戏

思考:有哪些类对象?这些对象有哪些属性和方法?

  • 牌 - 花色、点数;显示牌面;比较大小

    一张张的

  • 扑克 - 装54张牌的列表;洗牌/发牌
    54张牌放一起叫做类

    洗牌/发牌改变的是扑克的状态,所以应该是扑克的属性

  • 玩家 - ID/昵称/手牌;摸牌/ 打牌/ 整理手牌

解决问题的套路:

  1. 根据问题描述来 圈名词、圈动词

    名词-要么是类,要么是类的属性

    动词 -方法

经验:

  • 符号常量总是优于字面常量
  • 例如:在扑克游戏中表示黑桃、红心、梅花、方片时,SPADE,HEART,CLUB,DIAMOND优于0,1,2,3

魔术方法缩写示意:

  • less than —> __ lt __ —> <
  • great than —> __ gt __ —> >
  • equal to —> __ eq __ —> =
  • less than or equal to—> __ xxx __ —> <=
import random

# 定义常量(变量名大写):黑桃、红桃、梅花、方片
SPADE, HEART, CLUB, DIAMOND = range(4)

class Card:
    """牌"""

    def __init__(self, suite, face):
        """初始化方法
        :param suite: 花色
        :param face: 点数
        """
        self.suite = suite
        self.face = face

    def __repr__(self):
        # 打印时只需打印 对象 便可自动打印 对象.show()
        return self.show()

    def __lt__(self, other):
        # self一张牌,other另一张牌
        # 魔术方法__lt__    -   less than 小于
        # 花色不同比花色,花色相同比点数
        if self.suite != other.suite:
            return self.suite < other.suite
        face1 = 14 if self.face == 1 else self.face
        face2 = 14 if other.face == 1 else other.face
        return face1 < face2

    def show(self):
        """显示牌面"""
        suites = ('♠︎', '♥︎', '♣︎', '♦︎')
        faces = ('', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K')
        return f'{suites[self.suite]}{faces[self.face]}'


class Poker:
    """扑克"""

    def __init__(self):
        """54张牌"""
        self.cards = [Card(suite, face)
                      for suite in range(4)
                      for face in range(1, 14)]
        self.index = 0

    def shuffle(self):
        """洗牌"""
        self.index = 0
        random.shuffle(self.cards)

    def has_more(self):
        """有没有牌可以发出"""
        return self.index < len(self.cards)

    def deal(self):
        """发一张牌"""
        card = self.cards[self.index]
        self.index += 1
        return card


class Player:
    """玩家"""

    def __init__(self, nickname):
        """初始化方法
        :param nickname: 昵称
        """
        self.nickname = nickname
        self.cards = []

    def get_card(self, card):
        """摸牌"""
        self.cards.append(card)

    def arrange(self):
        """整理手牌"""
        # # 方法一:不使用魔术方法__lt__
        # # sort方法可以传入key参数来指定根据什么比较元素的大小,从而自定义排序规则
        # self.cards.sort(key=lambda x: x.face)  # 按点数比大小
        # # self.cards.sort(key=lambda card: card.suite)  # 按花色比大小

        # 方法二:使用魔术方法__lt__
        self.cards.sort()


poker = Poker()
poker.shuffle()
names = ('妲己', '狄仁杰', '赵云', '孙悟空')
players = [Player(name) for name in names]

for _ in range(13):
    # 给每个玩家发13张牌
    for player in players:
        card = poker.deal()  # 扑克发牌
        player.get_card(card)  # 玩家得到牌

for player in players:
    player.arrange()  # 整理牌
    print(player.nickname, end=': ')
    print(player.cards)

# card1 = Card(HEART, 13)
# # # 无def __repr__(self):
# # print(card1.show())
# # 有def __repr__(self):
# print(card1)

# card2 = Card(DIAMOND, 5)
# card3 = Card(SPADE, 1)
# card4 = Card(CLUB, 10)
# print(card1, card2, card3, card4)

❤ 面向对象(进阶)

class Student:
    def __init__(self, name, age=18, study_id='000'):
        self.name = name
        self.age = age
        self.study_id = study_id

    # 在当前类的对象被打印的时候自动调用,并且将这个方法的返回值作为打印结果(返回值必须是字符串)
    def __repr__(self):
        return f'<{str(self.__dict__)[1:-1]}>'


stu1 = Student('小明')
stu2 = Student('小花', 20, '0001')

1. 查-获取属性值

  • 对象.属性 - 获取对象指定属性的值,如果属性不存在报错

  • getattr(对象, 属性名) - 获取对象指定属性的值,如果属性不存在报错

  • getattr(对象, 属性名, 默认值) - 获取对象指定属性的值,如果属性不存在返回默认值

print(stu1.name)
print(getattr(stu1, 'name'))
print(getattr(stu1, 'name', '无名氏'))

# value = input('请输入需要获取的属性:')
# print(getattr(stu1, value))

# print(stu1.gender)    # AttributeError: 'Student' object has no attribute 'gender'
# print(getattr(stu1, 'gender'))      # AttributeError: 'Student' object has no attribute 'gender'
print(getattr(stu1, 'gender', '男'))     # 男

2. 增、改

  • 对象.属性 = 值 - 当属性存在的时候修改属性的值,当属性不存在的时候添加属性
  • setattr(对象, 属性名, 值) - 当属性存在的时候修改属性的值,当属性不存在的时候添加属性
stu1.name = '张三'
# print(stu1.__dict__)    # {'name': '张三', 'age': 18, 'study_id': '000'}
print(stu1)     # "{'name': '张三', 'age': 18, 'study_id': '000'}"

stu1.gender = '女'
print(stu1)     # <'name': '张三', 'age': 18, 'study_id': '000', 'gender': '女'>
print(stu1.gender)

setattr(stu1, 'age', 30)
print(stu1.age)     # 30

setattr(stu1, 'score', 100)
print(stu1)     # <'name': '张三', 'age': 30, 'study_id': '000', 'gender': '女', 'score': 100>
print(stu1.score)       # 100

3. 删

  • del 对象.属性 - 删除指定对象的指定属性
  • delattr(对象, 属性名) - 删除指定对象的指定属性
del stu1.age
print(stu1)     # <'name': '张三', 'study_id': '000', 'gender': '女', 'score': 100>
# print(stu1.age)     # AttributeError: 'Student' object has no attribute 'age'

delattr(stu1, 'study_id')
print(stu1)         # <'name': '张三', 'gender': '女', 'score': 100>

stu1.neme = '李四'

4. 练习

class A:
    # __slots__属性的值就是当前类的对象最多能够拥有的对象属性,如果为空,那么这个类的对象就不能有对象属性
    # 注意:如果给类设置了__slots__,那么这个类的对象就不能再使用__dict__属性
    __slots__ = ('x', 'y', 'z')

    def __init__(self):
        self.x = 10


a = A()
a.y = 100
a.z = 200
# a.m = 300

☆ 魔术方法

__ repr __

在当前类的对象被打印的时候自动调用,并且将这个方法的返回值作为打印结果(返回值必须是字符串)

# 在当前类的对象被打印的时候自动调用,并且将这个方法的返回值作为打印结果(返回值必须是字符串)
def __repr__(self):
	return f'<{str(self.__dict__)[1:-1]}>'

__ slots __

属性的值就是当前类的对象最多能够拥有的对象属性,如果为空,那么这个类的对象就不能有对象属性

注意:如果给类设置了__ slots __ ,那么这个类的对象就不能再使用__ dict __ 属性

# __slots__属性的值就是当前类的对象最多能够拥有的对象属性,如果为空,那么这个类的对象就不能有对象属性
    # 注意:如果给类设置了__slots__,那么这个类的对象就不能再使用__dict__属性
class A:
	__slots__ = ('x', 'y', 'z')
	
    def __init__(self):
    	self.x = 10
    	
a = A()
a.y = 100
a.z = 200
# a.m = 300

__ doc __

  • 类的说明文档(类属性)
class A:
    """还是计算机是"""
    pass

print(int.__doc__)
print(int(10).bit_length())
# print(dict.__doc__)

__ module __

  • 获取类所在的模块(类属性)
print(int.__module__)
print(A.__module__)

__ class __

  • 获取对象的类型,功能和type() (对象属性)
a = A()
print(a.__class__)      # <class '__main__.A'>
print(type(a))          # <class '__main__.A'>

__ dict __

  • 获取类所有的类属性和对应的值,以字典的形式返回(类属性)

__ dict __

  • 获取对象所有的对象属性和对应的值,以字典的形式返回(对象属性)
print(a.__dict__)

__ name __

  • 获取类的名字(类属性)
print(A.__name__)       # 'A'

__ base __

  • 获取当前类的父类

__ bases __

  • 获取当前类的父类们
print(A.__base__)    # <class 'object'>  - 基类
print(A.__bases__)      # (<class 'object'>,)

6. 运算符重载

6.1 python中的运算符

python中每个运算符都对应一个固定的魔法方法,哪个类型中实现的对应的魔法方法,那个类型的数据就支持对应的运算符。

(python中某种数据是否支持某种运算符就看这个类中是否定义了运算符对应的魔法方法)

from copy import copy

print(10 + 29)
print('abc' + '34')
print([10, 34] + [239, 0, 'abc'])

print(10 - 9)
print({23, 89} - {23})

# 10 + 29   ==  10.__add__(29)
# 'abc' + '34'   ==  'abc'.__add__('34')
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # self + other
    def __add__(self, other):
        return self.age + other.age

    # self *  other
    def __mul__(self, other):
        return [copy(self) for _ in range(other)]

    # self > other
    def __gt__(self, other):
        return self.age > other.age

    def __repr__(self):
        return f'<{str(self.__dict__)[1:-1]}, id: {id(self)}>'
        # return str(self.__dict__)


stu1 = Student('小明', 18)
stu2 = Student('小花', 20)

a = stu1 + stu2     #  a = stu1.__add__(stu2)
print(a)
print(stu1 * 4)

# print(stu1 in stu2)

stu_list = [stu1, stu2, Student('张三', 12)]
print(stu_list)

# print(max(stu_list, key=lambda item: item.age))
print(max(stu_list))

__ add __:+

# self + other
def __add__(self, other):
    return self.age + other.age

__ sub __: -

# self - other
def __add__(self, other):
    return self.age - other.age

__ mul __:*

# self *  other
def __mul__(self, other):
    return [copy(self) for _ in range(other)]

__ truediv __ : /

# self /  other
def __truediv__(self, other): # /
    """除法"""
    num = self.num * other.den
    den = self.den * other.num
    return Fraction(num, den)

__ floordiv __ : //

# self //  other
def __floordiv__(self, other):  # //
    """除法"""
    num = self.num * other.den
    den = self.den * other.num
    return Fraction(num, den)

__ gt __:>

# self > other
def __gt__(self, other):
    return self.age > other.age

__ lt __: <

# self < other
def __gt__(self, other):
    return self.age > other.age

7. 继承

7.1 继承

继承就是让子类直接拥有父类的属性和方法。

子类 - 继承者

父类 - 被继承者,又叫超类、基类

7.2 继承的语法

class 类名(父类):

​ 说明文档

​ 类的内容

class 类名(父类1, 父类2,…):

​ 说明文档

​ 类的内容

注意:如果定义类的时候没有写继承关系,那么这个类默认继承基类object ,相当于:class 类名: == class 类名(object):

7.3 添加新属性和方法

1)添加方法和添加类属性

直接在子类中定义新的类属性和方法

2)添加对象属性

class Person:
    num = 61

    def __init__(self):
        self.name = '张三'
        self.age = 30
        self.gender = '男'

    def eat(self, food):
        print(f'在吃{food}')

    @staticmethod
    def func1():
        print('静态方法')


class Student(Person):
    x = '学生'

    def __init__(self):
        # 调用当前类的父类的__init__方法
        super().__init__()
        # 给学生类增加属性
        self.study_id = '0001'
        self.subject = 'Python'

    def study(self):
        print('好好学习,天天向上!')

    @classmethod
    def func2(cls):
        print('学生的类方法')


stu = Student()
print(stu.name, stu.age, stu.gender)
stu.eat('包子')

print(Student.num)
Student.func1()

print(Student.x)
stu.study()
Student.func2()
print(stu.study_id, stu.subject)
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y


class B(A):
    def __init__(self, m, n, x=10, y=20):
        super().__init__(x, y)
        self.m = m
        self.n = n


a = A(200, 300)

b = B(1, 2)
print(b.x, b.y)
# b.x = 3
# b.y = 4

8. 继承细节问题

8.1 子类和父类有相同的方法(重写)

class A:
    def func1(self):
        print('A的func1')


class B(A):
    def func1(self):
        print('B的func1')


B().func1()
A().func1()

8.2 super的用法

super(类, 对象).方法() - 调用指定类的父类的指定方法

注意:()中的对象必须是()里面类的对象

class A:
    def func1(self):
        print('A的func1')


class B(A):
    def func2(self):
        super(B, self).func1()
        print('B的func2')


B().func2()
class A:
    def func1(self):
        print('A的func1')

    def func2(self):
        print('A的func2')


class B(A):
    def func1(self):
        print('B的func1')


class C:
    def func1(self):
        print('C的func1')


class D(C):
    def func1(self):
        super(B, B()).func2()  # 指定类- B,对象 - B(),指定方法- func2()。 调用指定类的父类的指定方法,即调用A类的func2方法
        print('D的func1')


d = D()
d.func1()

8.3 多继承: 子类只能继承第一个父类的对象属性(所有父类的方法和类属性都可以继承)

class AA:
    num = 100

    def __init__(self):
        self.x = 100
        self.y = 200

    def func1(self):
        print('对象方法AA')


class BB:
    message = '你好'

    def __init__(self):
        self.m = 100
        self.n = 200

    def func2(self):
        print('对象方法BB')


class CC(AA, BB):
    pass


c = CC()

print(CC.num, CC.message)

c.func1()
c.func2()

print(c.x, c.y)
# print(c.m, c.n)   # AttributeError: 'CC' object has no attribute 'm'(多继承只能继承第一个父类的对象属性)

8.4 私有化

访问权限(属性和方法的权限):公开的、保护的、私有的

​ 公开的 - 在类的外部可以使用、类的内部可以使用、也可以被继承

​ 保护的 - 在类的外部不可以使用、类的内部可以使用、也可以被继承

​ 私有的 - 只能在类的内部使用,不能被继承,也不能在外部使用

python中类的内容的权限只有一种:公开的

python的私有化: 想让属性和方法变成私有的只需要在名字前加 __ (但是不能同时用 __ 结尾)

_python私有化的本质:就存储数据的时候在私有化名字前加 ’ _ 类名’

python中的保护:在名字前加 _

注意:

  • 不能直接调用类中带 __ 的属性或方法

  • 可以通过 对象._带 __ 的属性或方法 调用带 __ 的属性或方法

class A:
    m = 100
    __n = 200

    @staticmethod
    def func1():
        print(A.m, A.__n)
        A.__func2()

    @staticmethod
    def __func2():
        print('私有方法')

 
print(A.m)  # 100
A.func1()  
# 100
# 100 200
# 私有方法

# 不能直接调用类中带 __ 的属性或方法
# print(A.__n)  # AttributeError: type object 'A' has no attribute '__n'
# A.__func2()  # AttributeError: type object 'A' has no attribute '__func2'

print(A._A__n)  # 200.通过 **对象._带 __ 的属性或方法** 调用带 __ 的属性或方法
# python之禅

9. 拷贝

9.1 拷贝

1)直接赋值

直接将变量中的地址赋值给另外一个变量,赋值后两个变量指向同一块内存区域,并且相互影响

2)浅拷贝

列表切片、列表.copy()、字典.copy()等都是浅拷贝、copy()
复制原数据产生一个新的数据,将新的数据的地址返回。如果原数据中有子对象(有可变数据),不会复制子对象

3)深拷贝

deepcopy
复制原数据产生一个新的数据,将新的数据的地址返回。如果原数据中有子对象,子对象也会被复制

class Dog:
    def __init__(self, name, gender='公'):
        self.name = name
        self.gender = gender

    def __repr__(self):
        return str(self.__dict__)


class Person:
    def __init__(self, name, age=18, dog=None):
        self.name = name
        self.age = age
        self.dog = dog

    def __repr__(self):
        return str(self.__dict__)


p1 = Person('小明', dog=Dog('财财'))
p2 = p1
p3 = copy(p1)
p4 = deepcopy(p1)

print(f'p1:{p1}, id:{id(p1)}')
print(f'p2:{p2}, id:{id(p2)}')
print(f'p3:{p3}, id:{id(p3)}')
print(f'p4:{p4}, id:{id(p4)}')
print('-------------------------------------------------')
p1.name = '小花'
p1.dog.name = '大黄'
print(f'p1:{p1}, id:{id(p1)}')
print(f'p2:{p2}, id:{id(p2)}')
print(f'p3:{p3}, id:{id(p3)}')
print(f'p4:{p4}, id:{id(p4)}')

9.2 内存管理

1)内存的申请

定义变量保存数据的时候系统会自动申请。如果定义变量保存的时候可变数据,每次都会申请新的内存,如果是不可变的数据,会检查这个数据
是否已经保存过,如果已经存储就不会再重新申请内存。

2)释放(垃圾回收机制)

如果一个数据的引用计数(引用的个数)为0,那么这个数据就会被自动释放。
引用:保存数据地址的对象就是这个数据的引用

❤ 继承

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MmUAREZ5-1634975911766)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211013164049332.png)]

学生类、老师类

学生:姓名/年龄/年级;吃饭/玩耍/学习

老师:姓名/年龄/职称;吃饭/玩耍/授课

Martin Fowler - 代码有很多种很坏的味道,但重复是最坏的一种

class Person:

    def __init__(self, name, age):
        """初始化方法
        :param name: 姓名
        :param age: 年龄
        """
        self.name = name
        self.age = age

    def eat(self):
        """吃饭"""
        print(f'{self.name}正在吃饭.')

    def play(self):
        """玩耍"""
        print(f'{self.name}正在玩耍.')


class Student(Person):
    """学生"""

    def __init__(self, name, age, grade):
        """初始化方法
        :param grade: 年级
        """
        super().__init__(name, age)
        self.grade = grade

    def study(self, course_name):
        """学习
        :param course_name: 课程名称
        """
        print(f'{self.name}正在学习{course_name}.')


class Teacher(Person):
    """老师"""

    def __init__(self, name, age, title):
        """初始化方法
        :param title: 职称
        """
        super().__init__(name, age)
        self.title = title

    def teach(self, course_name):
        """授课"""
        print(f'{self.name}{self.title}正在讲{course_name}.')


student = Student('王大锤', 18, '大一')
teacher = Teacher('骆骆', 41, '叫兽')
student.play()
student.study('Python程序设计')
teacher.eat()
teacher.teach('数据库原理')

工资结算系统

公司有三类员工,结算工资的方式不同:

部门经理:15000
程序员:计时计薪:200元/小时
销售员:底薪+提成:1800元+销售额5%提成

给出员工的信息,自动结算月薪

小记:

  • employee 雇员
    姓名,职位,

  • employer 雇主

  • salary 薪资

  • programmer 程序员

  • manager 经理

  • salesman
    销售员

  • sale 销售额

class Employee:
    '''员工'''

    def __init__(self, name):
        '''
        初始类方法
        :param name: 名字
        :param position: 职位
        '''
        self.name = name


class Salesman(Employee):
    """销售员"""

    def __init__(self, name):
        """
        初始化方法
        :param salary: 销售额
        """
        super().__init__(name)

    def salary(self, sale):
        return 1800 + sale * 0.05


class Manager(Employee):
    """经理"""

    def __init__(self, name):
        """
        初始化方法
        """
        super().__init__(name)

    def salary(self):
        return 15000


class Programmer(Employee):
    """程序员"""

    def __init__(self, name):
        """
        初始化方法
        :param day: 工作时长(小时)
        """
        super().__init__(name)

    def salary(self, hour):
        return hour * 200


salesman = Salesman('张三')
print(salesman.salary(50))

manager = Manager('王大锤')
print(manager.salary())

programmer = Programmer('小王')
print(programmer.salary(1000))

❤ 函数式编程

❤ 面向对象

1. 分数类

求最大公约数

常规法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ul9Rw04w-1634975911766)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014095731605.png)]

辗转求余数法(欧几里得算法)

辗转求余数法(欧几里得算法):

已知两个数 x, y

y % x == 0 —> 成立:返回x

​ —> 不成立:找 y % x, x 的最大公约数

例如:

  • 15 27 —> 12 15 —> 3 12 —> 3
  • 99999999 99999998 —> 1 99999998 —> 1
def gcd(x, y):
    """求最大公约数"""
    while y % x != 0:
        x, y = y % x, x
    return x

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQlkth7S-1634975911767)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014100557429.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VImqwuf4-1634975911767)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014143928691.png)]

完整代码

def gcd(x, y):
    """求最大公约数"""
    # 辗转求余数法
    while y % x != 0:  # 不管分子分母的顺序如何,都适用
        x, y = y % x, x
    return x
    
# 通过继承Exception自定义异常类型    
class FractionException(Exception):
    pass


class Fraction:
    """分数类"""

    @classmethod 
    # 通过类方法创建一个静态方法,用于在创建对象前就进行的判断
    def from_value(cls, value: float, base=10000):
        """小数变分数"""
        return cls(int(value * base), base)

    @classmethod
    def from_string(cls, string: str):
        """字符串变分数"""
		# # 合并写法:
        # num, den = map(int, map(str.strip, string.split('/')))  
        # # # 分解写法:
        # items = string.split('/') # 通过'/'拆分
        # num,den = [int(item.strip()) for item in items]  # 去空格
        num, den = [int(item.strip()) for item in string.split('/')]
        return cls(num, den)

    def __init__(self, num, den):
        """初始化方法:分子(numerator)、分母(denominator)"""
        if den == 0:
            # 如果分母为0,直接引发异常让程序崩溃
            raise FractionException('分母不能为0')  
        self.num = num
        self.den = den
        self.normalize()  # 规范
        self.simplify()  # 化简

    def __str__(self): 
        """直接通过打印对象即可打印出对象的值,而不是打印对象的位置"""
        if self.den == 1:
            return f'{self.num}'
        return f'{self.num}/{self.den}'

    def __add__(self, other):  # 魔术方法:调用时:对象1 + 对象2
    # def add(self, other):  # 常规方法:调用时:对象1.add(对象2)
        """加法(add)"""
        num = self.num * other.den + other.num * self.den
        den = self.den * other.den
        return Fraction(num, den)

    def __sub__(self, other):  # def sub(self, other):
        """减法(substract)"""
        num = self.num * other.den - other.num * self.den
        den = self.den * other.den
        return Fraction(num, den)

    def __mul__(self, other):  # def mul(self, other):
        """乘法(multiply)"""
        num = self.num * other.num
        den = self.den * other.den
        return Fraction(num, den)

    def __truediv__(self, other):  # def div(self, other):
        """除法(divide)"""
        num = self.num * other.den
        den = self.den * other.num
        return Fraction(num, den)

    @property  # 将方法变为属性,通过“对象.value”调用;不用这个装饰器则需要通过“对象.value”调用;注意:只有该方法不用传参时才能使用该装饰器
    def value(self):
        """分数转换为小数"""
        return self.num / self.den

    def simplify(self):
        """化简
        - 分子分母如果有公约数,就除以最大公约数
        """
        if self.num != 0:
            factor = gcd(abs(self.num), abs(self.den))
            self.num, self.den = self.num // factor, self.den // factor
        return self  # 返回对象本身

    def normalize(self):
        """规范
        - 检查分子分母是否有负数:如果分母有负号,就把这个负号放在分子上
        - 分子为0  -   直接将分母赋值为1,且只显示分子
        - 分母为1  -   只显示分子
        """
        if self.num == 0:
            self.den = 1
        elif self.den < 0:
            self.num = -self.num
            self.den = -self.den
        return self  # 返回对象本身


# f1 = Fraction(-6, -9)
f1 = Fraction.from_string('-6 / -9')
print('f1', f1)
# f2 = Fraction(3, -4)
f2 = Fraction.from_value(-0.75)
print('f2', f2)
f3 = f1 * f2
print('f3', f3)
f4 = Fraction(11, 19)
print('f4', f4)
print(f1 + f2 - f3)
f5 = (f1 + f2) * f3 / f4
print(f5)
print(f5.num / f5.den)
print(f5.value)
   

2. 工资结算系统

公司有三类员工,结算月薪的方式是不一样的:

部门经理 —> 15000元 / 月

程序员 —> 计算工时 —> 200元 / 小时 * 本月工时

销售员 —> 底薪+提成 —> 1800元 + 本月销售额5%提成

要求:给出员工的信息,自动结算月薪。

完整代码:

from abc import ABCMeta, abstractmethod  # abc 中导入原类


class Employee(metaclass=ABCMeta):  # 将这个类创建为抽象类,抽象类不能创建对象
	'''员工'''

    def __init__(self, name):
        self.name = name

    @abstractmethod  # 将该方法通过子类来实现
    def get_salary(self):
        pass


class Manager(Employee):
	"""经理"""
    def get_salary(self):
        return 15000.0


class Programmer(Employee):
	"""程序员"""
    def __init__(self, name):
        super().__init__(name)  # 将父类的属性继承过来
        # self.name = name
        self.working_hour = 0  # 工作时长

    def get_salary(self):  # 改写get_salary方法
        return 200 * self.working_hour


class Salesman(Employee):
	"""销售员"""
    def __init__(self, name):
        super().__init__(name)
        self.sales = 0.0  # 销售额

    def get_salary(self):
        return 1800.0 + self.sales * 0.05

# 实例化
def main():
    emps = [
        Manager('曹操'), Programmer('荀彧'), Programmer('郭嘉'),
        Salesman('典韦'), Salesman('曹仁'), Programmer('李典')
    ]
    for emp in emps:
        if type(emp) == Programmer:
            emp.working_hour = int(input(f'请输入{emp.name}本月工作时间: '))
        elif type(emp) == Salesman:
            emp.sales = float(input(f'请输入{emp.name}本月销售额: '))
        print(f'{emp.name}本月工资为: {emp.get_salary():.2f}元')  # 多态:不同的对象接收相同的消息做了不同的事情


if __name__ == '__main__':
    main()

其中的知识点:

override - 重写 / 覆盖 / 置换

override —> 重写 / 覆盖 / 置换 —> 子类将父类已有的方法重新实现一遍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QSD6LBUB-1634975911768)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014160543750.png)]

抽象类

子类可以重写父类的方法,不同的子类可以对同一个方法给出不同的实现版本

专门用于继承的父类叫抽象类,不能通过抽象类创建对象

from abc import ABCMeta  # abc 中导入原类


class Employee(metaclass=ABCMeta):  # 将这个类创建为抽象类,抽象类不能创建对象

    def __init__(self, name):
        self.name = name

如果通过抽象类创建对象会报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYAlZznB-1634975911769)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014161310337.png)]

@abstractmethod - 方法让子类来实现

@abstractmethod - 让该方法让子类来实现

class Programmer(Employee):

    def __init__(self, name):
        super().__init__(name)  # 将父类的属性继承过来
        # self.name = name
        self.working_hour = 0

    def get_salary(self):
        return 200 * self.working_hour     

多态

多态 - 不同的对象(部门经理、销售员、程序员)接收到相同的消息(get_salary)做了不同的事情(每种员工结算月薪的方式都不一样)

实现多态:最重要的一步就是方法重写,所以这个方法在运行时就会表现出多态
行为(接收的消息是一样的,但做的事情是不一样的)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uYarjh9v-1634975911769)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014161630403.png)]

3. 自定义列表类

创建一个新的列表类,实现列表元素的 求和、平均值、方差、标准方差、标准差

平均数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEGphskr-1634975911770)(https://bkimg.cdn.bcebos.com/formula/6e931edc086948c3b206e5d224d667ff.svg)]

(n表示这组数据个数,x1、x2、x3……xn表示这组数据具体数值)

方差

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e9QmOBrT-1634975911771)(https://bkimg.cdn.bcebos.com/formula/8ddc32475dfe46aa38b43ddd37f5c85b.svg)]

标准方差

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eypky0Rk-1634975911771)(https://bkimg.cdn.bcebos.com/formula/14f8527c2b28800f906e1dcf5b6b5a1a.svg)]

标准差

=方差的算术平方根=s=sqrt(((x1-x)^2 +(x2-x)^2 +…(xn-x)^2)/(n-1))

字面量语法 和 构造器语法

# # 创建列表
# # 字面量语法
# list1 = [1, 2, 3]
# print(list1)  # [1, 2, 3]
# # 构造器语法
# list2 = list([1, 2, 3])
# print(list2)  # [1, 2, 3]
# list3 = list((1, 2, 3))
# print(list3)  # [1, 2, 3]

完整代码:

方法一:继承list类

继承list类,再添加我们需要用到的方法,通过继承来是实现对已有代码的复用

class MyList(list):
    """自定义列表"""
    
	# 重新实现list的初始化方法,使得通过MyList(1,2,3,4,5)就可以创建[1,2,3,4,5]
    def __init__(self, *args):
        super().__init__()
        for value in args:
            self.append(value)

    def append(self, value):
        if not isinstance(value, (int, float)):
            raise ValueError('只能追加int或float类型的元素')
        super().append(value)

    def insert(self, index, value):
        if not isinstance(value, (int, float)):
            raise ValueError('只能插入int或float类型的元素')
        super().insert(index, value)

    def mean(self):
        """均值"""
        return sum(self) / len(self)

    def median(self):
        """中位数"""
        length, sorted_list = len(self), sorted(self)
        # 注意:此处排序不要使用sort(),因为sort()会打乱原列表的数据
        if length % 2 != 0:
            return sorted_list[length // 2]
        return (sorted_list[length // 2 - 1] + sorted_list[length // 2]) / 2

    def std(self):
        """样本标准差(standard deviation)"""
        return self.var() ** 0.5

    def var(self):
        """样本方差(variance)"""
        x_bar = self.mean()
        return sum([(x - x_bar) ** 2 for x in self]) / (len(self) - 1)


nums = MyList(1, 2, 3, 4, 5)
print(nums)
nums.append(100.2)
nums.insert(0, 200.5)
nums.remove(4)
print(nums)
nums += [8, 12, 15]
print(nums)
print(sorted(nums))
print('均值:', nums.mean())
print('中位数:', nums.median())
print('方差:', nums.var())
print('标准差:', nums.std())
方法二:在自定义类中关联list对象

在自定义类中关联list对象,将各种操作委派给list对象来完成,通过委派关系(关联关系)实现对象代码的复用。

class ThyList:  # Thy:古英语中的your

    def __init__(self, *args):
        self.container = []
        for value in args:
            self.append(value)

    def append(self, value):
        if not isinstance(value, (int, float)):
            raise ValueError('ThyList追加int或float类型的元素')
        self.container.append(value)

    def insert(self, index, value):
        if not isinstance(value, (int, float)):
            raise ValueError('ThyList插入int或float类型的元素')
        self.container.insert(index, value)

    def mean(self):
        """均值"""
        return sum(self.container) / len(self.container)

    def median(self):
        """中位数"""
        length, sorted_list = len(self.container), sorted(self.container)
        if length % 2 != 0:
            return sorted_list[length // 2]
        return (sorted_list[length // 2 - 1] + sorted_list[length // 2]) / 2

    def std(self):
        """样本标准差(standard deviation)"""
        return self.var() ** 0.5

    def var(self):
        """样本方差(variance)"""
        x_bar = self.mean()
        return sum([(x - x_bar) ** 2 for x in self.container]) / (len(self.container) - 1)

    def __repr__(self):
        return self.container.__repr__()


nums = ThyList(1, 2, 3, 4, 5)
print(nums)
nums.append(100)
nums.insert(0, 200)
print(nums)
print('均值:', nums.mean())
print('中位数:', nums.median())
print('方差:', nums.var())
print('标准差:', nums.std())

❤ Excel小技巧

1. 文本数据分列

选中单元格 → 数据 → 分列 →

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wkVKCU4-1634975911772)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211016174253728.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Hxaj4oB-1634975911772)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211016174328879.png)]

❤ 异常处理

代码本身即便没有问题,但在运行时可能因为外部环境或资源的问题,导致代码无法运行,程序出现异常状况,如果异常状况没有得到处理,那么程序就会崩溃,
具体表现就是代码直接停止运行。如果不希望程序崩溃,就有对代码进行异常状况的处理,在
Python中,可以使用try语法将可能出现状况的代码保护起来执行,在出现状况的时
候,使用except进行异常状况捕获并给出相应的处理。

try

用try把可能出现异常状况的代码保护起来执行

except

如果程序没有发生状况,except都不会执行

如果程序发生异常,则对号入座,并在对应异常处进行捕获并处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdj3PTW6-1634975911773)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014171304865.png)]

finally

实际项目中,通常用finally来释放外部资源(例如:网络连接、数据库连接、文件关闭等)。因为finally处的代码在任何情况下一定会被执行到,我们把finally的代码称为总是执行代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1Hq2s3I-1634975911773)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014171637254.png)]

raise

自定义异常类型通常都是继承Exception类或者它的子类

自定义异常类型通常不需要写什么代码,主要就是定义一种新的自定义的类型来标记特殊的异常状况,代码直接复用父类Exception的代码

通过raise关键字 异常对象来引发异常,如果使用这个代码的人没有做异常处理,那么程序就会在这个地方崩溃

# raise示例:
class FractionException(Exception):
    pass
    
    
class Fraction:

    def __init__(self, num, den):
    if den == 0:
    	raise FractionException('分母不能为0')

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uJ2E2y0U-1634975911774)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211014171015437.png)]

Python中的异常处理机制:异常处理案例

# 异常处理案例:
import sys
import time

while True:
    try:
        with open('readme.txt') as file:
            print(file.read())
            
    except FileNotFoundError:
        print('错误提示:文件不存在,5秒钟以后重新尝试读取文件')
        time.sleep(5)
        
    except IOError:
        # 文件读取过程中U盘被拔掉;文件太大,内存不够
        print('错误提示:读取文件失败,请确认设备是否就绪')
        sys.exit(1)
        
    # 捕获多种异常
    except (FileNotFoundError, IOError):
        print('错误提示:文件不存在,5秒钟以后重新尝试读取文件')
        time.sleep(5)
        
    # 捕获意料以外的异常(子类异常也属于父类异常)
    except Exception:
        print('错误提示:程序发生了一点小问题,请拨打400-800-8855寻求帮助')
        exit(1)

    finally:
        # 这个地方最适合释放外部资源。例如:关闭文件、网络连接、数据库连接等
        # 因为这里的代码在任何情况下一定会被执行到,称为总是执行代码
        print('这个地方最适合释放外部资源!!!')
        file.close()
        print('程序结束!!!')

❤ 第三方库的使用

1. PyQt5 - 创建窗口界面

import sys

from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox


def close_window():
    reply = QMessageBox.question(
        window, "Message", "Are you sure to quit?",
        QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes
    )
    if reply == QMessageBox.Yes:
        app.quit()


app = QApplication(sys.argv)

window = QWidget()
window.resize(800, 600)
window.setWindowTitle('My First App')

button = QPushButton('Hit Me!', window)
button.setToolTip('这是一个按钮')
button.resize(80, 30)
button.move(100, 100)
button.clicked.connect(close_window)

window.show()

sys.exit(app.exec())

2. pyecharts - 绘制统计图表

from pyecharts.charts import Bar

bar = Bar()
bar.add_xaxis(["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"])
bar.add_yaxis("商家A", [5, 20, 36, 10, 35, 20])
bar.add_yaxis("商家B", [15, 21, 13, 30, 55, 39])
bar.render('bar.html')

2.1 Pie - 饼图

from pyecharts.charts import Pie
from pyecharts import options

x_data = ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
y_data = [335, 310, 274, 235, 400]
data_pair = [list(z) for z in zip(x_data, y_data)]
data_pair.sort(key=lambda x: x[1], reverse=True)
print(data_pair)

pie = Pie(init_opts=options.InitOpts(width='800px', height='400px'))
pie.add(
    series_name='引流渠道', data_pair=data_pair,
    radius=['40%', '60%'], center=['50%', '50%'],
)
pie.set_series_opts(
    label_opts=options.LabelOpts(
        formatter='{b}: {c} ({d}%)'
    )
)
pie.render('pie.html')

2.1.1 将多个列表合并为一个列表:zip(列表1,列表2,列表3,…)

x_data = ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
y_data = [335, 310, 274, 235, 400]
data_pair = [list(z) for z in zip(x_data, y_data)]
data_pair.sort(key=lambda x: x[1], reverse=True)
print(data_pair)

2.2 Bar - 柱状图

from pyecharts.charts import Bar

bar = Bar()
bar.add_xaxis(["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"])
bar.add_yaxis("商家A", [5, 20, 36, 10, 35, 20])
bar.add_yaxis("商家B", [15, 21, 13, 30, 55, 39])
bar.render('bar.html')

❤ day21-25---------------------------

❤ 自动化办公

1. 邮件自动发基本流程

# smtplib  - 登录邮箱;发送邮件
import smtplib
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText


# 第一步:登录邮箱
# 1. 连接邮箱服务器
# smtplib.SMTP_SSL(服务器地址, 邮箱服务端口)    -   连接邮箱服务器并且返回一个连接对象
connect = smtplib.SMTP_SSL('smtp.qq.com', 465)

# 2.登录邮箱
# 连接对象.login(账号, 密码)
connect.login('726550822@qq.com', 'ryjjmssnbbjgbcbb')

# 第二步:准备邮件
# 1. 创建邮件对象
email = MIMEMultipart()

# 2. 设置邮件主题
# Header(邮件标题, 编码方式)
email['Subject'] = Header('第一次发送邮件', 'utf-8').encode()

# 3. 设置邮件接受者信息(收件人)
email['To'] = 'y_t209@163.com'

# 4. 设置邮件发件人
email['From'] = '726550822@qq.com <726550822@qq.com>'

# 5. 添加正文
"""
MIMEText(文字内容, 类型, 编码方式)
类型 - plain(普通文字)、html(超文本)
"""
text = MIMEText('你好吗?吃饭了吗?', 'plain', 'utf-8')
email.attach(text)

# 第三步:通过连接发送邮件
# 连接对象.sendmail(发件人, 收件人, 邮件对象.as_string())
connect.sendmail('726550822@qq.com', 'y_t209@163.com', email.as_string())
connect.quit()    # 关闭连接

2. 发送附件

import smtplib
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage

# 1. 登录
connect = smtplib.SMTP_SSL('smtp.qq.com', 465)
connect.login('726550822@qq.com', 'ryjjmssnbbjgbcbb')

# 2. 准备邮件
email = MIMEMultipart()

email['Subject'] = Header('发送附件', 'utf-8').encode()
email['To'] = 'y_t@163.com'
email['From'] = '726550822@qq.com <726550822@qq.com>'

text = MIMEText('照片在附件中,请注意查收!', 'plain', 'utf-8')
email.attach(text)

# ==========1. 图片附件=========
# 1)打开图片读取图片二进制数据
image_data = open('files/luffy4.jpg', 'rb').read()
# 2)创建图片对象
image = MIMEImage(image_data)
# 3)设置图片为附件
image['Content-Disposition'] = 'attachment; filename="luffy.jpg"'
# 4)将图片附件添加到邮件对象中
email.attach(image)

# ==========2.其他文件附件========
file_data1 = open('files/a.zip', 'rb').read()
file1 = MIMEText(file_data1, 'base64', 'utf-8')
file1['Content-Disposition'] = 'attachment; filename="a.zip"'
email.attach(file1)

file_data2 = open('files/服务能力提供规范.xlsx', 'rb').read()
file2 = MIMEText(file_data2, 'base64', 'utf-8')
file2['Content-Disposition'] = 'attachment; filename="data.xlsx"'
email.attach(file2)

# 3. 发送邮件
connect.sendmail('726550822@qq.com', 'y_t209@163.com', email.as_string())
connect.quit()

3. 超文本邮件内容

import smtplib
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage

connect = smtplib.SMTP_SSL('smtp.qq.com', 465)
connect.login('726550822@qq.com', 'ryjjmssnbbjgbcbb')

email = MIMEMultipart()
email['Subject'] = Header('HTML邮件', 'utf-8').encode()
email['To'] = 'y_t209@163.com'
email['From'] = '726550822@qq.com <726550822@qq.com>'

# ========================邮件正文=====================
content = open('files/send.html', encoding='utf-8').read()
text = MIMEText(content, 'html', 'utf-8')
email.attach(text)

image_data = open('files/luffy4.jpg', 'rb').read()
image = MIMEImage(image_data)
image.add_header('Content-ID', '<image1>')
email.attach(image)


connect.sendmail('726550822@qq.com', 'y_t209@163.com', email.as_string())
connect.quit()

4. 图片批量发送

import smtplib
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import os

connect = smtplib.SMTP_SSL('smtp.qq.com', 465)
connect.login('726550822@qq.com', 'ryjjmssnbbjgbcbb')

email = MIMEMultipart()
email['Subject'] = Header('HTML邮件', 'utf-8').encode()
email['To'] = 'y_t209@163.com'
email['From'] = '726550822@qq.com <726550822@qq.com>'

# ========================邮件正文=====================
all_images = os.listdir('images')
ids = []
for name in all_images:
    image_data = open(f'images/{name}', 'rb').read()
    image = MIMEImage(image_data)
    cid = name.split(".")[0]
    ids.append(cid)
    image.add_header('Content-ID', f'<{cid}>')
    email.attach(image)

send = ''
for cid in ids:
    send += f'<img src="cid:{cid}" alt=""><br>'
text = MIMEText(send, 'html', 'utf-8')
email.attach(text)


connect.sendmail('726550822@qq.com', 'y_t209@163.com', email.as_string())
connect.quit()

5. excel文件的读操作

import openpyxl

# 1. 加载文件(只能加载后缀是.xlsx文件)
wb = openpyxl.load_workbook('files/学生信息.xlsx')

# 2. 从工作簿中获取工作表相关信息
# 1)获取所有工作表的表名
names = wb.sheetnames
print(names)

# 2)获取活跃表
sheet1 = wb.active
print(sheet1)

# 3)根据表名获取指定表
# 工作簿对象[表名]   -  获取指定工作表
sheet2 = wb['Sheet1']
print(sheet2)

# 3. 从工作表中获取表相关内容
# 1) 获取表名: 工作表对象.title
print(sheet1.title)     # 'Sheet1'

# 2) 获取最大行数
print(sheet1.max_row)

# 3) 获取最大列数
print(sheet1.max_column)

# 4. 通过表获取单元格
# 1)获取指定一个单元格
# a. 工作表['列号行号']   -  获取指定位置对应的单元格(列号是字母)
cell1 = sheet1['A2']
print(cell1)

# b. 工作表.cell(行号, 列号)   -  获取指定位置对应的单元格(列号是数字)
cell2 = sheet1.cell(2, 1)


# 2)获取单元格的内容
# 单元格对象.value
print(cell1.value)
print(cell2.value)

# 练习1:获取整个表中所有的单元格中的内容
for row in range(1, sheet1.max_row + 1):
    for col in range(1, sheet1.max_column+1):
        print(sheet1.cell(row, col).value, end=' ')
    print()

# 练习: 获取第2行所有内容
for col in range(1, sheet1.max_column+1):
    print(sheet1.cell(2, col))

# 练习:获取第3列所有内容
for row in range(1, sheet1.max_row+1):
    print(sheet1.cell(row, 3))

6. excel文件的写操作

import openpyxl
import os

# 注意:excel所有的写操作,如果要有效必须在操作完成后对excel文件对应的工作簿进行保存操作。

# 1. 新建工作簿
if os.path.exists('files/test.xlsx'):
    print('打开')
    wb = openpyxl.load_workbook('files/test.xlsx')
else:
    print('新建')
    wb = openpyxl.Workbook()
    wb.save('files/test.xlsx')


# 2. 保存工作簿
# 工作簿对象.save(文件路径)
# wb.save('files/test.xlsx')

# 3. 针对表的写操作
# 1)新建表:工作簿对象.create_sheet(表名, 位置)
# wb.create_sheet('老师表')
# wb.create_sheet('学生表', 0)

# 2)删除表:工作簿对象.remove(表对象)
try:
    wb.remove(wb['老师表'])
except KeyError:
    pass

# 3)修改表的表名: 表对象.title = 新的表名
if 'Sheet' in wb.sheetnames:
    wb['Sheet'].title = '课程表'


# 4. 修改单元格中的内容
# 表对象['列号行号'] = 数据
# 单元格对象.value = 数据

# sheet1 = wb.active
# sheet1['A1'] = '姓名'
# sheet1['B1'] = '年龄'
# sheet1['A1'] = ''

# sheet1.cell(1, 2).value = 'Age'
# print(sheet1.cell(1, 2).value)

# 练习:将列表中所有的数据添加到课程表的第3行:['Python', '余婷', '5个月', 10000]
sheet2 = wb['课程表']
list1 = ['Python', '余婷', '5个月', 10000]
for index, item in enumerate(list1):
    sheet2.cell(3, index+1).value = item


wb.save('files/test.xlsx')

7. csv文件操作

import csv

# 1. 创建文件对应的reader对象
# 1)
# csv.reader(文件对象)      -      读数据的时候每一行内容对应一个列表(返回值是包含文件每行内容的的迭代器)
# csv.DictReader(文件对象)   -     读数据的时候每一行内容对应一个字典(返回值是包含文件每行内容的的迭代器)
f = open('files/2018年北京积分落户数据.csv', encoding='utf-8')
# reader = csv.reader(f)
reader = csv.DictReader(f)

# 2) 通过reader读数据
print(next(reader))
print(next(reader))

# 2. 创建文件对应的writer对象
# 1)csv.writer(文件对象)
f = open('files/test.csv', 'w', encoding='utf-8')
writer = csv.writer(f)

# 写数据: writer.writerow(列表)      -       一次写一行
writer.writerow(['name', 'age', 'score', 'gender'])
writer.writerows([
    ['小明', 18, 100, '男'],
    ['小花', 20, 92, '女'],
    ['老王', 30, 80, '男']
])

# 2) csv.DictWriter(文件对象)
f = open('files/test2.csv', 'w', encoding='utf-8')
writer = csv.DictWriter(f, ['name', 'age', 'score', 'gender'])

# 写数据
writer.writeheader()     # 将原来设定好的字典的键作为第一行内容写入文件
writer.writerow({'name': '小明', 'age': 18, 'score': 100, 'gender': '男'})
writer.writerows([
    {'name': '小花', 'age': 30, 'score': 92, 'gender': '女'},
    {'name': '小红', 'age': 28, 'score': 67, 'gender': '女'},
    {'name': '老王', 'age': 25, 'score': 88, 'gender': '男'}

练习 - 小宝剑大药房

import openpyxl, csv


def read_data():
    wb = openpyxl.load_workbook('files/小宝剑大药房.xlsx')
    sheet = wb.active
    m_row = sheet.max_row
    m_col = sheet.max_column
    all_data = []
    for row in range(1, m_row+1):
        data = []
        for col in range(1, m_col+1):
            value = sheet.cell(row, col).value
            data.append(value)
        all_data.append(data)
    return all_data


if __name__ == '__main__':
    writer = csv.writer(open('files/小宝剑大药房.csv', 'w', encoding='utf-8'))
    writer.writerows(read_data())

8. pdf文件操作

import PyPDF2, openpyxl

# 1. 读操作  - 获取pdf文件内容
# 1) 以读的方式打开pdf文件
reader = PyPDF2.PdfFileReader('files/HEU_KMS_Activator_v20.0.0用户使用手册.pdf')

# 2) 获取总的页数
total_page = reader.getNumPages()
print(total_page)

# 3) 获取指定页面(PageObject的对象)
# reader.getPage(页数)
page0 = reader.getPage(0)
page1 = reader.getPage(1)
page2 = reader.getPage(2)
page3 = page1.rotateClockwise(90)    # 旋转
# page1.scale(100, 200)       # 缩放
# page1.mergePage(page2)

# 2. 写操作
# 1)创建空的pdf文件对象
# PyPDF2.PdfFileWriter()   -  创建一个空的pdf文件对象
writer1 = PyPDF2.PdfFileWriter()

# 2)添加页面
# writer.addPage(页面对象)  -   指定页
# writer.addBlankPage()    -  添加空白页
writer1.addPage(page0)
writer1.addBlankPage()
writer1.addPage(page1)
writer1.addBlankPage()
writer1.addPage(page2)
writer1.addBlankPage()

# 3)保存pdf文件
f = open('files/test.pdf', 'wb')
writer1.write(f)

练习 - PyPDF2

import PyPDF2

# 1.打开文件
reader1 = PyPDF2.PdfFileReader('files/存储引擎的讲解.pdf')
reader2 = PyPDF2.PdfFileReader('files/HEU_KMS_Activator_v20.0.0用户使用手册.pdf')
writer = PyPDF2.PdfFileWriter()

# 2. 获取总页数
total1 = reader1.getNumPages()
total2 = reader2.getNumPages()

# 3. 依次获取每一页写入新文件
for page_num in range(total1):
    writer.addPage(reader1.getPage(page_num))

for page_num in range(total2):
    writer.addPage(reader2.getPage(page_num))

# 4. 保存文件
f = open('files/test2.pdf', 'wb')
writer.write(f)

9. 添加水印

import PyPDF2

# 1. 准备需要添加水印的pdf
reader1 = PyPDF2.PdfFileReader('files/存储引擎的讲解.pdf')
# 2. 准备水印文件
walter_page = PyPDF2.PdfFileReader('files/demo1.pdf').getPage(0)
# 3. 准备空的pdf,用来存放添加完水印的页
new_pdf = PyPDF2.PdfFileWriter()
# 4. 遍历原文件的每页内容,添加水印后添加到空的pdf中
for page_num in range(reader1.getNumPages()):
    # 获取原文件的一页内容
    page = reader1.getPage(page_num)
    # 添加水印
    page.mergePage(walter_page)
    # 将新的页添加到空的pdf中
    new_pdf.addPage(page)
# 5.保存新的pdf文件
f = open('files/test3.pdf', 'wb')
new_pdf.write(f)

10. 创建文印文件

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

# 1. 注册字体文件
pdfmetrics.registerFont(TTFont('Font1', 'files/Vera.ttf'))
pdfmetrics.registerFont(TTFont('Font2', 'files/青呱石头体.ttf'))

# 2.生成文字
# 1)创建空的pdf文件
walter_pdf = canvas.Canvas('files/walter.pdf')

# 2)设置字体
walter_pdf.setFont('Font1', 40)

# 3)设置文字颜色
walter_pdf.setFillColorRGB(1, 0, 0, 1)

# 4)旋转
walter_pdf.rotate(45)

# 5)渲染文字内容
walter_pdf.drawString(200, 100, 'hello world!')

walter_pdf.setFillColorRGB(0, 1, 0, 1)
walter_pdf.setFont('Font2', 40)
walter_pdf.drawString(400, 200, 'hello world!')


# 6)保存
walter_pdf.save()

11. 作业

# 1. 设计⼀个函数,统计字符串中英⽂字⺟和数字各⾃出现的次数。
def count_letter_num(str1: str):
    pass
# 5. 封装⼀个函数,输⼊⼀串分数相加,求最终得值。
# ⽐如:输⼊1/2+1/3+1/4 输出:13/12
from functools import reduce


# 方法一:
def score_sum(str1: str):
    # 获取所有的分子和分母
    score_nums = str1.split('+')
    fen_zis = []
    fen_mus = []
    for x in score_nums:
        xs = x.split('/')
        fen_zis.append(int(xs[0]))
        fen_mus.append(int(xs[1]))

    # 计算所有分数求和后的分子和分母
    fen_mu = reduce(lambda x, y: x*y, fen_mus, 1)
    fen_zi = 0
    for index in range(len(fen_mus)):
        fen_zi += fen_mu // fen_mus[index] * fen_zis[index]

    # 约分
    divisor = min((fen_zi, fen_mu))
    for x in range(divisor, 0, -1):
        if fen_mu % x == 0 and fen_zi % x == 0:
            fen_zi //= x
            fen_mu //= x
            break

    print(f'{fen_zi}/{fen_mu}')


# 方法二:
from re import findall


def score_sum2(str1: str):
    # 获取所有的分子和分母
    fen_mus = [int(x) for x in findall(r'/(\d+)', str1)]
    fen_zis = [int(x) for x in findall(r'(\d+)/', str1)]
    # 计算所有分数求和后的分子和分母
    fen_mu = reduce(lambda x, y: x * y, fen_mus, 1)
    fen_zi = 0
    for index in range(len(fen_mus)):
        fen_zi += fen_mu // fen_mus[index] * fen_zis[index]
    # 约分
    divisor = min((fen_zi, fen_mu))
    for x in range(divisor, 0, -1):
        if fen_mu % x == 0 and fen_zi % x == 0:
            fen_zi //= x
            fen_mu //= x
            break
    print(f'{fen_zi}/{fen_mu}')


score_sum2('1/2+1/3+1/14')

❤ 办公自动化

1.用Python读取Excel文件

office2007开始,都是xml文件,即标记语言(信息都存在标签内的)

接归档 - 解压缩

office2007以前的文件采用: xlrd,xlwt

xlrd的官文链接: → tuo

辅助工具:xlutils

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyqhSdjW-1634975911775)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018094458449.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-acQbtEtB-1634975911775)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018094918717.png)]

1.1 设置显示文件扩展名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v5WzgWKU-1634975911776)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018095749881.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3qaxTnCF-1634975911776)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018191147722.png)]

1.2 openpyxl官文

openpyxl官文

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Je0CqUIo-1634975911777)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018191406702.png)]

1.3 技巧:代码重构(将部分代码抽取到一个函数里面)

将部分代码抽取到一个函数里面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UjYFUusr-1634975911778)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018104159839.png)]

from datetime import datetime

import openpyxl
from openpyxl.cell.cell import Cell
from openpyxl.chart import LineChart, Reference
from openpyxl.styles import Alignment, Font, Border, Side
from openpyxl.worksheet.worksheet import Worksheet


def display(value, delim=' '):
     """显示样式设置
    :param value: 值
    :param delim: 空格符
    :return:
    """
    # if type(value) == float:   
    if isinstance(value, float):
        print(f'{value:>10.2f}', end=delim)
    elif isinstance(value, int):
        print(f'{value:>10d}', end=delim)
    elif isinstance(value, datetime):
        print(value.strftime('%Y年%m月%d日'), end=delim)
    else:
        print(value, end=delim)

        
def main():
    # 1. 加载工作簿
    workbook = openpyxl.load_workbook('resources/阿里巴巴2020年股票数据.xlsx')
    
    # 2. 获取工作表
    # sheet = workbook.active  # 当前工作表
    # sheet = workbook.worksheets  # 所有的工作表
    # print(sheet)  # [<Worksheet "股票数据">, <Worksheet "Sheet2">, <Worksheet "Sheet3">]
    # for sheet in workbook.worksheets: 
    #     print(sheet.title)  # 股票数据 Sheet2 Sheet3
    # sheet = workbook.worksheets[0]  # 第一个工作表
    sheet = workbook['股票数据']  # type: Worksheet(指定对象类型,以获得对象的属性和方法提示)
    
    # 3. 获取工作表的属性
    print(sheet.max_row, sheet.max_column)  # 255 7
    print(sheet.dimensions)  # A1:G255
	print(type(sheet))  # <class 'openpyxl.worksheet.worksheet.Worksheet'>

    # 4. 操作单元格
    cell = sheet['E5']  # type: Cell(指定对象类型,以获得对象的属性和方法提示)
    # cell = sheet.cell(5, 5)  # (行,列) -   第5行第5列
    print(cell.value)
    # 修改单元格的值
    # cell.value = 200  
    # sheet['E5'].value = 200
    # sheet.cell(5,5).value = 200
    sheet.cell(5, 5, 200)
    
    # 5. 循环遍历所有单元格
    for row in range(2, sheet.max_row + 1):
        for col in range(1, sheet.max_column + 1):
            display(sheet.cell(row, col).value)
        print()
        
    # 6. 公式计算
    # sheet['E256'] = '=average(e2:e255)'
    # sheet['F256'] = '=sum(f2:f255)'
    
    # 单元格数据样式设置
    cell = sheet['E256']
    cell.value = '我爱你中国'
    # 对齐方式
    cell.alignment = Alignment(horizontal='center', vertical='center')
    # 字体
    cell.font = Font(name='ArialUnicodeMS', size=20, color='330099')
    # 边框
    side = Side(color='ff6600', style='double')
    cell.border = Border(left=side, right=side, top=side, bottom=side)
    # 行高和列宽
    sheet.row_dimensions[256].height = 50
    sheet.column_dimensions['E'].width = 35

    # 绘制折线图
    c1 = LineChart()
    c1.title = '一月份收盘价和开盘价'
    c1.style = 13
    c1.y_axis.title = '价格'
    c1.x_axis.title = '日期'
    # 给图添加数据引用
    data = Reference(sheet, min_col=4, min_row=1, max_row=23)
    c1.add_data(data, titles_from_data=True)  
    # 设置横轴
    cats = Reference(sheet, min_col=1, min_row=2, max_row=23)
    c1.set_categories(cats)
    # 给数据系列设置样式
    s1 = c1.series[0]
    s1.marker.symbol = 'triangle'
    s1.marker.graphicalProperties.solidFill = 'ff0000'  # 红色
    s1.marker.graphicalProperties.line.solidFill = '0000ff'  # 绿色
    s1.smooth = True
    # 将图添加到工作表中
    sheet.add_chart(c1, 'A258')

    # 7. 保存工作簿
    workbook.save('resources/阿里巴巴2020年股票数据.xlsx')


if __name__ == '__main__':
    main()

1.4 加载工作簿、获取工作表、单元格操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Njd7kQ3w-1634975911778)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018191644625.png)]

1.5 格式、样式设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XvCzgmq-1634975911779)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018190910879.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3znhKut-1634975911779)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192405433.png)]

1.6 给对象注释类型,可以在后面代码中为对象提示属性和方法

sheet = workbook['股票数据']  # type:Worksheet

cell = sheet['E5']  # type: Cell

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNVQulYu-1634975911780)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192258797.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8nAsWE0w-1634975911780)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018191427989.png)]

1.7 创建图表对象并设置样式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzXOfZ0S-1634975911781)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192508779.png)]

2. 用Python发送邮件

网络协议:

HTTP

邮件服务器 → 自己搭建/买三房邮件服务

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。

SMTP → simple mail transfer protocol(简单邮件传输协议) → 跟邮件服务器进行网络通信

python标准库已经对SMTP进行了封装,我们主要通过创建对象,给对象发消息就可以完成邮件发送

smtplib模块→ SMTP_SSL(安全的) → login() / sendmail()

邮件服务器:

  • 126邮箱: smtp.126.com

  • qq邮箱:smtp.qq.com

账号:1090340336@qq.com

授权码(密码):withofhdrrrofjgi

获取邮件服务器、授权码:

  • QQ邮箱

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvYQgUJl-1634975911781)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192657023.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvwBAYCH-1634975911783)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018144324555.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZoK5g4oR-1634975911783)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018144010000.png)]

  • 163邮箱

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBvJQw3e-1634975911784)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192841163.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t88vDZ8p-1634975911785)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192818545.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mvl4NXo5-1634975911785)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192900373.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQNb6qMh-1634975911786)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018192917850.png)]

MIME → Multi-purpose Internet Mail Extension

—> text/html

—>image/png

—>audio/mp3

video/mp4

MIMEMultipart —> attach —> 添加文本、附件等其他内容

MIMEText —> 可以用来封装文本内容,也可以封装二进制数据(需要经过BASE64编码处理)

查找一个文件的MIME类型:MIME参考手册 → ctrl+f(查找)

2.1 创建 SMTP 对象

Python创建 SMTP 对象:

语法:

import smtplib

smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )

说明:

  • host: SMTP 服务器主机。 你可以指定主机的ip地址或者域名如:runoob.com,这个是可选参数。
  • port: 如果你提供了 host 参数, 你需要指定 SMTP 服务使用的端口号,一般情况下SMTP端口号为25。
  • local_hostname: 如果SMTP在你的本机上,你只需要指定服务器地址为 localhost 即可。

2.2 SMTP对象使用sendmail方法发送邮件

Python SMTP对象使用sendmail方法发送邮件:

语法:

SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options]

说明:

  • from_addr: 邮件发送者地址。

  • to_addrs: 字符串列表,邮件发送地址。

  • msg: 发送消息。

    • 注意:msg是字符串,表示邮件

      邮件一般由标题,发信人,收件人,邮件内容,附件等构成,发送邮件的时候,要注意msg的格式(即smtp协议中定义的格式

2.2.1 用Python发送普通(纯文本)邮件

# 示例
 
import smtplib
from email.mime.text import MIMEText
from email.header import Header
 
sender = 'from@runoob.com'
receivers = ['429240967@qq.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
 
# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码
message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8')
message['From'] = Header("菜鸟教程", 'utf-8')     # 发送者
message['To'] =  Header("测试", 'utf-8')          # 接收者
 
subject = 'Python SMTP 邮件测试'
message['Subject'] = Header(subject, 'utf-8')
 
 
try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("邮件发送成功")
except smtplib.SMTPException:
    print ("Error: 无法发送邮件")

2.2.2 用Python发送HTML格式的邮件

Python发送HTML格式的邮件与发送纯文本消息的邮件不同之处就是将MIMEText中_subtype设置为html

#!/usr/bin/python3
 
import smtplib
from email.mime.text import MIMEText
from email.header import Header
 
sender = 'from@runoob.com'
receivers = ['429240967@qq.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
 
mail_msg = """
<p>Python 邮件发送测试...</p>
<p><a href="http://www.runoob.com">这是一个链接</a></p>
"""
message = MIMEText(mail_msg, 'html', 'utf-8')
message['From'] = Header("菜鸟教程", 'utf-8')
message['To'] =  Header("测试", 'utf-8')
 
subject = 'Python SMTP 邮件测试'
message['Subject'] = Header(subject, 'utf-8')
 
 
try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, message.as_string())
    print ("邮件发送成功")
except smtplib.SMTPException:
    print ("Error: 无法发送邮件")
练习:发送普通邮件
# 发送普通邮件
import smtplib
from email.mime.text import MIMEText

# 创建SMTP_SSL对象
smtp_obj = smtplib.SMTP_SSL(host='smtp.qq.com', port=465)  # SMTP服务器、端口
# 1. 登录授权
smtp_obj.login('1090340@qq.com', 'withofhdrrro')  # 账号名、授权码

content = """格式化输出:format=”%s%d”%(str1,num)print format 
2 用+进行字符串的合并:str1=”hello” str2=”world”result=str1+str2 
3 字符串截取可以通过索引/切片,也可以通过split函数。
4 通过切片截取字符串: word=”wo...
    祝,好!
    骆骆 即日
"""

mime_text = MIMEText(content, 'plain', 'utf-8')  # 构造邮件主题对象-文本内容
# 邮件内容还可以写明:
mime_text['From'] = '1090340@qq.com'  # 发件人
mime_text['To'] = '1090340@qq.com'  # 收件人
# mime_text['To'] = '1090340@qq.com; 109034012@qq.com' # 多个收件人用分号隔开
# mime_text['Cc']='109034012@qq.com'  # 抄送人
mime_text['Subject'] = 'Python发送邮件'  # 主题

# 2. 发送邮件
smtp_obj.sendmail(
    from_addr='1090340@qq.com',  # 发件人
    # '' # 收件人,多个收件人以分号隔开
    # to_addrs='1090340@qq.com; 109034012@qq.com'
    to_addrs=['1090340@qq.com'],  # 收件人
    # to_addrs=['1090340@qq.com','109034012@qq.com']
    msg=mime_text.as_string()  # 邮件正文内容
)

# 3. 结束会话(断开连接 / 终止与服务器的对话)
smtp_obj.quit()
# smtp_obj.close()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVzNUyH5-1634975911786)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018193145761.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQTKRhAe-1634975911787)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018193154977.png)]

2.4.3 Python 发送带附件的邮件

发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后利用smtplib.smtp发送。

# 发送带有pdf、Excel附件的邮件
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# 创建SMTP_SSL对象
smtp_obj = smtplib.SMTP_SSL(host='smtp.qq.com', port=465)  # SMTP服务器、端口

# 1. 登录授权
smtp_obj.login('1090340@qq.com', 'withofhdrrro')  # 邮件用户名、授权码

# 创建MIMEMultipart()实例
m_part = MIMEMultipart()  
m_part['From'] = '1090340@qq.com'  # 发件人
m_part['To'] = '1090340@qq.com'  # 发件人
m_part['Subject'] = 'Python办公自动化学习内容'  # 主题

# 邮件正文内容
content = """
    在前面的课程中,我们已经教会大家如何用Python程序自动的生成Excel、Word、PDF文档,
接下来我们还可以更进一步,就是通过邮件将生成好的文档发送给指定的收件人,然后用短信告知对方我们发出了邮件。
这些事情利用Python程序也可以轻松愉快的解决。

    祝,好!

    骆 即日
"""
mime_text = MIMEText(content, 'plain', 'utf-8')
m_part.attach(mime_text)

# 构建附件1
with open('resources/第29课:用Python发送邮件和短信.pdf', 'rb') as file:
    pdf_file = MIMEText(file.read(), 'base64', 'utf-8')  # 二进制文件
    pdf_file['content-type'] = 'application/pdf'  # 设置内容类型
    pdf_file['content-disposition'] = 'attachment; filename="Python-email-sms.pdf"'  # 设置内容的处理方式(可下载的附件)
    m_part.attach(pdf_file)

# 构建附件2
with open('resources/阿里巴巴2020年股票数据.xlsx', 'rb') as file:
    excel_file = MIMEText(file.read(), 'base64', 'utf-8')   # 二进制文件
    excel_file['content-type'] = 'application/vnd.ms-excel'  # 设置内容类型
    filename = quote("阿里巴巴2020年股票数据.xlsx") # 将url中不支持的内容处理成百分数编码
    excel_file['content-disposition'] = 'attachment; filename=f'{filename}'  # 设置内容的处理方式(可下载的附件)
    # 其中的filename
    m_part.attach(excel_file)  

# 2. 发送邮件
smtp_obj.sendmail(
    from_addr='1090340@qq.com',  # 发件人
    to_addrs=['1090340@qq.com'],  # 收件人
    msg=m_part.as_string()  # 
)
# 3. 结束会话
smtp_obj.quit()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDnbnSje-1634975911787)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018193206298.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ps2rjJEP-1634975911788)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018193214494.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RVqYrCIB-1634975911789)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211018193226165.png)]

POP3 → post office protocol version 3 收邮件

IMAP → internet mail access protocol 发邮件

❤HTML

1. 简介

超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。

您可以使用 HTML 来建立自己的 WEB 站点,HTML 运行在浏览器上,由浏览器来解析。

HTML —> Hyper-Text Markup Language

标签 —> 内容

层叠样式表(CSS)—> 显示(用于网页内容渲染)

JavaScript —> 行为

浏览器相当于是HTML的解释器

内容放到标签中,然后通过层叠样式表进行渲染

HTML学习参考手册:

菜鸟教程

W3school

HTML编辑器:

  • VS Code:https://code.visualstudio.com/
  • Sublime Text:http://www.sublimetext.com/

1.1 VSCode安装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oar5j8mE-1634975911789)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019091118134.png)]

1.2 VSCode的使用

新建一个文件后保存在.html文件后,再编辑时会有html文件的相关提示

1.3 html文件的结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CUWhOgY6-1634975911790)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019173609042.png)]

1. 示例

主体(最终呈现在浏览器窗口的内容)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>骆昊的技术专栏</title>
        <!-- 内部样式 -->
        <style>
            /* 选择器 ---> 通配符选择器 */
            * {
                margin: 0;
                padding: 0;
            }
            /* 选择器 --> 标签选择器 */
            h1 {
                color: darkgreen;
                font-size: 32px;
                font-family: "Courier New", "monospace", "华文楷体";
                text-align: center;
            }
            p {
                margin: 20px 50px;
                padding: 10px 0;
                font-size: 22px;
                letter-spacing: 0.3cm;
                text-align: center;
            }
            /* 选择器 --> 类选择器 */
            .foo {
                color: darkred;
                border: 1px dashed blue;
            }
            /* 相邻兄弟选择器 */
            p+hr {
                border: 1px dashed blue;
            }
            /* 父子选择器 */
            p>a {
                color: red;
                text-decoration: none;
            }
            /* ID选择器 */
            #bar {
                font-weight: bolder;
                border: 1px solid darkgray;
                width: 320px;
                margin: 20px auto;
            }
        </style>
    </head>
    <body>
        <!-- heading -->
        <!-- 内嵌样式 -->
        <h1>静(Jing)夜(Ye)思(Si)</h1>
        <p id="bar">
            [唐] 
            <!-- anchor -->
            <a href="http://www.baidu.com/s?wd=%E6%9D%8E%E7%99%BD" target="_blank">李白</a>
        </p>
        <!-- horizontal ruler -->
        <hr>
        <!-- paragraph -->
        <p>床前<strong>明月</strong>光,</p> 
        <p class="foo">疑似地上霜。</p>
        <p class="foo">举头<em><strong></strong></em>明月,</p>
        <p>低头思故乡。</p>
        <!-- image -->
        <img src="images/libai.jpg" alt="李白的图片" width="100">
        <hr>
        <a href="https://map.baidu.com" target="_blank">百度地图</a>
        <a href="https://news.163.com">网易新闻</a>
        <script>
            window.alert('hello, world!')
        </script>
    </body>
</html>

浏览器的白色空间折叠:无论有多少个空格、制表符等,显示都只有一个空格,要想按原样显示,需将内容放入p标签(paragraph)

常用标签

h1 - 水平分割线

hr -

p - 段落

sup - 上标

sub - 下标

strong - 加粗

em - 斜体

a - 链接

​ a - 当前窗口打开

​ a:blank - 新页面打开

2. 内嵌样式:

<h1 style="color:blue;font-size: 48px;font-family: 微软雅黑;">静夜思</h1>

静夜思

3. 选择器样式

3.1 通配符选择器

*{
	border: 1px solid yellow;
}

3.2 标签选择器

h1{
    color: yellowgreen;
    font-size: 64px;
    font-family: Arial, Helvetica, sans-serif, "微软雅黑";
}
p{
    font-size: 28px;
    letter-spacing: 0.5cm;
    text-align: center;
}

3.3 内选择器

.foo{
    font-size: 32px;
    color: tomato;
    letter-spacing: 0.3cm;
    text-align: right;
}
  • 写法.foo

  • 应用时:class=“foo”

4 选择器使用规则:

4.1 优先级

优先级:内选择器>标签选择器>通配符选择器

4.2 就近原则

就近原则:同一优先级的选择器,谁离得近听谁的

4. 其他选择器

4.1 相邻兄弟选择器

相邻兄弟选择器 - 作用在与前面标签挨得最近的后标签上

4.2 父子选择器

父子选择器 - 作用在父标签内部的字标签上

4.3 ID选择器

ID选择器 - 最具体的选择器,只用用到使用它的那一个标签

# 相邻兄弟选择器	-	作用在与前面标签挨得最近的后标签上
p+hr{  # 作用在p标签相邻的hr标签(即作用在与p标签隔得最近的hr标签上)


}

# 父子选择器    -    作用在父标签内部的字标签上
p>a{  # 作用在p标签内部的a标签

}

# ID选择器	-	最具体的选择器,只用用到使用它的那一个标签
#bar{

}

5. 边距设置

5.1 清除默认的内、外边距

h1,p,body,li{
    margin: 0;
    padding: 0;
}

*{
    margin: 0;
    padding: 0;
}

5.2 修改边距

外边距margin

  • 文字边框距离其他内容的距离

外边距padding

  • 文字边框距离边框内文字的距离

写法:

  • 上右下左
margin:10px 0 20px 0;
  • 上右下左均为10px
margin:10px;

6. 标签类型

开始标签 *元素内容结束标签 *

这是一个段落
这是一个链接

换行

❤用python发送短信

短信网关平台:螺丝帽

螺丝帽发送短信:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Vlvy6T2-1634975911790)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019184934390.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNXqQXbo-1634975911791)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019184950686.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPxfYsHa-1634975911792)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019185122040.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iUnHRCKF-1634975911792)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019185147167.png)]

**IP地址查询:**百度IP地址,便能达到本机的IP地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1oqpm76-1634975911793)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019185228224.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WAFmtxHS-1634975911793)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019185244539.png)]

1. 示例:

import random

import requests

def send_messag(tel, message):  
    """调用螺丝帽短信网关发送短信

    :param tel: 接收短信的手机号
    :param message: 短信内容
    """
    resp = requests.post(
        url='http://sms-api.luosimao.com/v1/send.json',
        auth=('api', 'key-ae81126514528b9d46c227177bd15ea4'),
        data={
            'mobile': tel,
            'message': message
        },
        timeout=3,  # 超时:3秒后无反应变报错
        verify=False  # 是否需要走安全验证
    )
    return resp.json()


def random_code(length=6):
    """生成随机(数字)验证码
    :param length: 验证码的长度
    """
    return ''.join(random.choices('0123456789', k=length))


code = random_code()
print(code)
result = send_messag('13548041193', f'骆老师,学生成绩表已经发送至您的邮箱,请查收!  - 婷【Python小课】')
print(result)

2. 作业:发送一个学生成绩表的excel文件至老师邮箱,并短信通知老师

# 发送一个学生成绩表的excel文件至老师邮箱,并短信通知老师

# ============发邮件==============
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from urllib.parse import quote

# 创建SMTP_SSL对象
smtp_obj = smtplib.SMTP_SSL(host='smtp.qq.com', port=465)
# 1. 登录授权
smtp_obj.login('1090340335@qq.com', 'withofhdrrrofjgi')

m_part = MIMEMultipart()
m_part['From'] = '1090340335@qq.com'
m_part['To'] = '1090340335@qq.com;957658@qq.com' # 骆昊邮箱957658@qq.com
# m_part['Cc'] = '957658@qq.com'
m_part['Subject'] = '学生成绩表'

content = """
    <p>骆老师,您好!</p>
    <p>附件是<em><strong style="font-size: 24px;color: teal ;">学生成绩表</strong></em>,请查收!</p>
    <p>祝,好!</p>
    <p>伍婷婷 即日</p>
"""
mime_text = MIMEText(content,'html','utf-8')
m_part.attach(mime_text)

with open('学生成绩表.xlsx', 'rb') as file:
    excel_file = MIMEText(file.read(),'base64','utf-8')
    excel_file['content-type'] = 'application/vnd.ms-excel'
    filename = quote('学生成绩表.xlsx')
    excel_file['content-disposition'] = f"attachment;filename*=utf8''{filename}"
    m_part.attach(excel_file)

# 2. 发送邮件
smtp_obj.sendmail(
    from_addr='1090340335@qq.com',
    to_addrs=['1090340335@qq.com','957658@qq.com'],
    msg=m_part.as_string()
)

# 3. 结束会话
smtp_obj.quit()


# ===========发短信===============
import requests

def send_messag(tel, message):
    """调用螺丝帽短信网关发送短信

    :param tel: 接收短信的手机号
    :param message: 短信内容
    """
    resp = requests.post(
        url='http://sms-api.luosimao.com/v1/send.json',
        auth=('api', 'key-ae81126514528b9d46c227177bd15ea4'),
        data={
            'mobile': tel,
            'message': message
        },
        timeout=3,
        verify=False
    )
    return resp.json()


result = send_messag('13548041193', f'骆老师,学生成绩表已经发送至您的邮箱,请查收!  - 伍婷婷【Python小课】')
print(result)

注意:

HTTP响应头中不能出现中文。

发送邮件时附件名称不能为中文,如果想要附件名称显示为中文(这里的中文是一个泛指,准确的说法是附件名称中不能出现非URL字符)

base64中的base64encode(…)会将二进制内容处理成64个人类可阅读的文本符号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C6m5xzpy-1634975911794)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019190009655.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HnDczXfQ-1634975911794)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019190035266.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hIdTtbRp-1634975911795)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019161442824.png)]

2.1 将附件名称显示为中文

2.2 示例(通用版):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oaa6Op3d-1634975911795)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211019191107086.png)]

用python操作pdf文件

PDF是Portable Document Format的缩写,这类⽂文件通常使⽤用 .pdf 作为其扩展名。

在Python中,可以使⽤用名为 PyPDF2 的三⽅方库来读取PDF⽂文件,可以使⽤用下⾯面的命令来安装它。

pip install PyPDF2
import PyPDF2

1. 旋转页面

page_obj.rotateClockwise(90)  # type:PageObject  # 旋转页面,顺时针旋转90度

2. 叠加页面(如:添加水印)

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)
    page_obj.rotateClockwise(90)  # type:PageObject  # 旋转页面,顺时针旋转90度
    writer.addPage(page_obj)

3. 添加空白页

blank_page = writer.addBlankPage() # type:PageObject  # 加空白页

4. 设置口令(密码)

writer.encrypt('520520')  # 设置口令(密码)

5. 完整示例

给pdf添加空白页、旋转
import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)
    page_obj.rotateClockwise(90)  # type:PageObject  # 旋转页面,顺时针旋转90度
    writer.addPage(page_obj)
    blank_page = writer.addBlankPage() # type:PageObject  # 加空白页
    blank_page.rotateClockwise(90)

writer.encrypt('520520')  # 设置口令(密码)

with open('resources/XGBoost_modified.pdf','wb') as file:
    writer.write(file)

6. 添加水印(叠加)

import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/watermark.pdf')
wm_page = reader.getPage(0)

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()

for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)  # type:PageObject
    page_obj.mergePage(wm_page)  # 添加水印
    writer.addPage(page_obj)  

with open('resources/XGBoost_modified.pdf','wb') as file:
    writer.write(file)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tycKovB9-1634975911796)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211020115224480.png)]

# 给不同页添加不同水印
import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/qf-python-watermark.pdf')
wm_page_1 = reader.getPage(0)
wm_page_2 = reader.getPage(1)

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)  # type: PageObject
    if page_num % 2 == 0:
        page_obj.mergePage(wm_page_1)
    else:
        page_obj.mergePage(wm_page_2)
    writer.addPage(page_obj)
with open('resources/XGBoost_watermarked.pdf', 'wb') as file:
    writer.write(file)

7. 创建pdf(生成水印页)

示例1:

# 生成水印页
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

pdf_canvas = canvas.Canvas('resources/demo.pdf', pagesize=A4)
width, height = A4

# 绘图
image = canvas.ImageReader('resources/xiaoxin.jpg')
pdf_canvas.drawImage(image, 20, height - 395, 250, 375)

# 显示当前页
pdf_canvas.showPage()  # 关闭当前页,开启新的一页(即在新的一页添加后面的内容)

# 注册字体文件
pdfmetrics.registerFont(TTFont('Font1', 'resources/Action.ttf'))
pdfmetrics.registerFont(TTFont('Font2', 'resources/青呱石头体.ttf'))

# 写字
pdf_canvas.setFont('Font2', 40)
pdf_canvas.setFillColorRGB(0.9, 0.5, 0.3, 0.4) # 0.4 - 透明度:1 - 不透明;0 - 全透明
pdf_canvas.drawString(width // 2 - 120, height // 2, '好次!')

pdf_canvas.setFont('Font1', 40)
pdf_canvas.setFillColorRGB(0, 1, 0, 0.5)
pdf_canvas.rotate(30)  # 想在画布上斜着写字,现旋转画布,再写字
pdf_canvas.drawString(250, 250, 'delicious1!')

pdf_canvas.drawString(520, 250, 'delicious2!')

pdf_canvas.setFont('Font1', 40)
pdf_canvas.setFillColorRGB(0, 1, 0, 0.5)
pdf_canvas.rotate(30)  # 二次旋转是在一次旋转的基础上继续旋转的,此时画布呈60度
pdf_canvas.drawString(490, 250, 'delicious3!')

# 保存
pdf_canvas.save()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PXTdmC3X-1634975911796)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211020150520213.png)]

示例2:

# 生成水印页

from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

pdf_canvas = canvas.Canvas('resources/qf-python-watermark.pdf', pagesize=A4)
width, height = A4
print(width, height)

pdfmetrics.registerFont(TTFont('F1', 'resources/fonts/青呱石头体.ttf'))
pdfmetrics.registerFont(TTFont('F2', 'resources/fonts/Georgia.ttf'))

pdf_canvas.rotate(45)
pdf_canvas.setFont('F1', 36)
pdf_canvas.setFillColorRGB(0.8, 0.8, 0.8, 0.25)
for i in range(1, 10, 2):
    pdf_canvas.drawString(width // 2 + 20 * i, height // 2 - 70 * i, '千锋Python人工智能学院')

pdf_canvas.showPage()

pdf_canvas.rotate(-45)
pdf_canvas.setFont('F2', 36)
pdf_canvas.setFillColorRGB(0.8, 0.8, 0.8, 0.25)
for i in range(1, 10, 2):
    pdf_canvas.drawString((320 - width) + 10 * i, (920 - height) + i * 70, 'Data Analysis & Mining')

pdf_canvas.save()

8. 判断是否是文件夹/文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ufwZ7tGl-1634975911797)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211021195819778.png)]

完整代码:

import os

for file in os.listdir():
    if os.path.isdir(file):
        print(os.listdir(file))
    else:
        _, suffix = os.path.splitext(file)
        print(file, suffix)

"""
结果:
.DS_Store 
['.gitignore', 'day23.iml', 'inspectionProfiles', 'misc.xml', 'modules.xml', 'workspace.xml']	-	如果是文件夹就打印出了文件夹内的文件
example01.py .py
example02.py .py
example03.py .py
example04.py .py
example05.py .py
example06.py .py
example07.py .py
example08.py .py
requirements.txt .txt
['.DS_Store', 'fonts', 'guido.jpg', 'watermark.pdf', 'XGBoost.pdf', '离职证明模板.docx']
"""

9. 作业:从文件夹中自动读取所有pdf文件,并添加水印

要求:从文件夹中自动读取所有pdf文件,并添加水印

注意:文件夹内还有文件夹,需要读取最里边文件夹内的pdf文件【多层文件夹还没实现】

# 从文件夹中自动读取所有pdf文件,并添加水印
# 文件夹内还有文件夹,需要读取最里边文件夹内的pdf文件【多层文件夹还没实现】
# ==================制作水印:包括两页===================
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

pdf_canvas = canvas.Canvas('./resources/demo2.pdf', pagesize=A4)
width, height = A4

# 注册字体文件
pdfmetrics.registerFont(TTFont('Font1', './resources/Action.ttf'))
pdfmetrics.registerFont(TTFont('Font2', './resources/青呱石头体.ttf'))

# 写字
pdf_canvas.setFont('Font2', 56)
pdf_canvas.setFillColorRGB(0.9, 0.5, 0.3, 0.4) # 0.4 - 透明度:1 - 不透明;0 - 全透明
pdf_canvas.rotate(15)  # 想在画布上斜着写字,现旋转画布,再写字
pdf_canvas.drawString(width // 2 -100, height//2+200, '多读书')
pdf_canvas.drawString(width // 2 -50, height//2+100, '多看报')
pdf_canvas.drawString(width // 2 +50, height // 2-100, '少吃零食')
pdf_canvas.drawString(width // 2 +100, height // 2-200, '多睡觉')

pdf_canvas.showPage()  # 关闭当前页,开启新的一页(即在新的一页添加后面的内容)

pdf_canvas.setFont('Font1', 68)
pdf_canvas.setFillColorRGB(0, 1, 0, 0.5)
pdf_canvas.rotate(-15)  # 想在画布上斜着写字,现旋转画布,再写字
for dh in range(50,550,80):
    pdf_canvas.drawString(dh//2, 2*dh, 'delicious!')

# 保存
pdf_canvas.save()


# =============获取所有pdf文件:文件路径/文件名/扩展名============
import os

# # 试验:
# _, suffix = os.path.splitext('./resouces/xiaoxin.jpg')
# print(_)
# print(suffix)
# """
# ./resouces/xiaoxin
# .jpg
# """

# # 试验:
# dir = './homework_file'
# print(os.listdir(dir))
# # ['cnki 检索词.docx', 'cnki1.pdf', 'cnki2.pdf', 'cnki3.pdf', '新建文件夹']

# files = []
#
# def find_pdf_file(dir):
#     for file in os.listdir(dir):
#         path_name, suffix = os.path.splitext(dir)
#         if suffix == '':  # 是文件夹 → 返回文件夹所对应的路径
#             return find_pdf_file(f'{path_name}{suffix}')
#         elif suffix == '.pdf':  # 是pdf文件
#             print(f'{path_name}{suffix}')
#             files.append(f'{path_name}{suffix}')
#             return files
# files1 = find_pdf_file('./homework_file')
# print(files1)

import PyPDF2
from PyPDF2.pdf import PageObject

file_paths = []
for file in os.listdir('homework_file'):
    _, suffix = os.path.splitext(file)
    if suffix == '.pdf':
        file_paths.append(f'./homework_file/{file}')

# ==========给所有pdf文件加水印:奇数页加的水印1,偶数页加水印2=============
# 获取两个水印
reader = PyPDF2.PdfFileReader('resources/demo2.pdf')  # 读取水印文件
wm_page1 = reader.getPage(0)  # 第一页的水印:多吃零食
wm_page2 = reader.getPage(1)  # 第二页的水印:delicious


def get_file_name(file_path):
    """
    根据文件路径获取不带路径不带后缀名的文件名
    :param file_path: 文件路径
    :return: 不带路径不带后缀名的文件名
    """
    _,file = os.path.split(file_path)
    file_name,suffix = os.path.splitext(file)
    return file_name
    # 例如:
    # file_name = get_file_name('resources/demo2.pdf')
    # print(file_name)  # demo2

# 给pdf文件加水印
for file_index in range(len(file_paths)):

    reader = PyPDF2.PdfFileReader(file_paths[file_index])
    writer = PyPDF2.PdfFileWriter()
    for page_num in range(reader.getNumPages()):
        page_obj = reader.getPage(page_num)  # type:PageObject
        if (page_num+1) % 2:  # 奇数页
            page_obj.mergePage(wm_page1)
        else:
            page_obj.mergePage(wm_page2)
        writer.addPage(page_obj)
        with open(f'homework_file/file_watermarked/{get_file_name(file_paths[file_index])}_modified.pdf','wb') as file:
            writer.write(file)

10. 读取PDF文件,抽取文字

"""
pip install PyPDF2
"""
import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
page = reader.getPage(0)  # type: PageObject
print(page.extractText())

11. 将PDF转成PNG图片

# 将PDF转成JPG图片
"""
pip install fitz
"""
import os.path

import fitz


def get_filename(file_path):
    """获取不带路径和后缀的文件名
    :param file_path: 文件路径
    """
    _, fullname = os.path.split(file_path)
    filename, _ = os.path.splitext(fullname)
    return filename


def pdf_image(pdf_file, img_path, zoom_x=4, zoom_y=4, rotation_angle=0):
    """将PDF文件转成PNG图片
    :param pdf_file: PDF文件路径
    :param img_path: 保存图片的路径
    :param zoom_x: 缩放比例(横向)
    :param zoom_y: 缩放比例(纵向)
    :param rotation_angle: 旋转角度
    """
    pdf = fitz.open(pdf_file)
    for page_num in range(pdf.pageCount):
        page_obj = pdf[page_num]
        # 创建用于图像变换的矩阵
        trans = fitz.Matrix(zoom_x, zoom_y).preRotate(rotation_angle)
        # 将PDF页面处理成图像
        pm = page_obj.getPixmap(matrix=trans, alpha=False)
        temp = get_filename(pdf_file)
        pm.writePNG(f'{img_path}{temp}_{page_num + 1}.png')
    pdf.close()


pdf_image('resources/XGBoost.pdf','resources/images/',2,2)

12. 给pdf添加空白页、旋转

# 给PDF文件添加水印
import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)
    page_obj.rotateClockwise(90)  # type:PageObject  # 旋转页面,顺时针旋转90度
    writer.addPage(page_obj)
    blank_page = writer.addBlankPage() # type:PageObject  # 加空白页
    blank_page.rotateClockwise(90)

# 给PDF文件设置密码(口令)
writer.encrypt('520520')  

with open('resources/XGBoost_modified.pdf','wb') as file:
    writer.write(file)
# 给不同页添加不同水印
import PyPDF2
from PyPDF2.pdf import PageObject

reader = PyPDF2.PdfFileReader('resources/qf-python-watermark.pdf')
wm_page_1 = reader.getPage(0)
wm_page_2 = reader.getPage(1)

reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.getNumPages()):
    page_obj = reader.getPage(page_num)  # type: PageObject
    if page_num % 2 == 0:
        page_obj.mergePage(wm_page_1)
    else:
        page_obj.mergePage(wm_page_2)
    writer.addPage(page_obj)
with open('resources/XGBoost_watermarked.pdf', 'wb') as file:
    writer.write(file)

获取不带路径不带后缀名的文件名

import os


def get_filename(file_path):
    """
    根据文件路径获取不带路径不带后缀名的文件名
    :param file_path: 文件路径
    :return: 不带路径不带后缀名的文件名
    """
    _, fullname = os.path.split(file_path)
		# _ - 文件所在文件夹路径,如:'./resources/'    
        # fullname - 文件名(带后缀名)    
    filename, _ = os.path.splitext(fullname)
		# file_name - 文件名(不带后缀名)
        # _ - 后缀名
    return filename
  
    
# 例如:
file_name = get_filename('resources/demo2.pdf')
print(file_name)  # demo2

批量安装三方库

获取已安装的三方库的列表文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kVRsqQOv-1634975911798)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211020103914008.png)]

根据列表文件逐一安装三方库

【待补充】

用python操作word

示例:批量制作离职证明

from docx import Document
from docx.document import Document as Doc

# 将真实信息用字典的方式保存在列表中
employees = [
    {
        'name': '骆昊',
        'id': '100200198011280001',
        'sdate': '2008年3月1日',
        'edate': '2012年2月29日',
        'department': '产品研发',
        'position': '架构师',
        'company': '成都华为技术有限公司'
    },
    {
        'name': '王大锤',
        'id': '510210199012125566',
        'sdate': '2019年1月1日',
        'edate': '2021年4月30日',
        'department': '产品研发',
        'position': 'Python开发工程师',
        'company': '成都谷道科技有限公司'
    },
    {
        'name': '李元芳',
        'id': '2102101995103221599',
        'sdate': '2020年5月10日',
        'edate': '2021年3月5日',
        'department': '产品研发',
        'position': 'Java开发工程师',
        'company': '同城企业管理集团有限公司'
    },
]
# 对列表进行循环遍历,批量生成Word文档
for emp_dict in employees:
    # 读取离职证明模板文件
    doc = Document('resources/离职证明模板.docx')  # type: Doc
    # 循环遍历所有段落寻找占位符
    for p in doc.paragraphs:
        if '{' not in p.text:
            continue
        # 不能直接修改段落内容,否则会丢失样式
        # 所以需要对段落中的元素进行遍历并进行查找替换
        for run in p.runs:
            if '{' not in run.text:
                continue
            # 将占位符换成实际内容
            start, end = run.text.find('{'), run.text.find('}')
            key, place_holder = run.text[start + 1:end], run.text[start:end + 1]
            run.text = run.text.replace(place_holder, emp_dict[key])
    # 每个人对应保存一个Word文档
    doc.save(f'resources/{emp_dict["name"]}离职证明.docx')

day24 ----------------------------------------------

网络爬虫

大作业,拿到指定网站的指定数据

1. 爬虫违法吗?

法不禁止即为许可,爬虫可以写,但是注意:

  • 隐匿身份

  • 不要被举证有破坏动产的行为

  • 不要将代码到处公开

  • 尽可能遵守爬虫协议—>robots.txt —>君子协议

2. 爬虫的分类

  • 通用爬虫(搜索引擎—>牛屎马粪都拿过来)
  • 定向爬虫(确定目标,只爬取某个领域的数据)

3. 编写爬虫程序

编写爬虫程序步骤:

  1. 获取网页源代码—–>HTML code
  2. 解析页面,提取内容—> ???
  3. 数据持久化—> cSv、Excel、数据库、大数据平台
  4. 数据分析、数据可视化、数据挖掘、建模预测

4. URL

URL —> 统一资源定位符 —> 网址

Universal Resource Locator —>唯一标识一个(网络)资源

例如,百度首页的URL:

  • https:// www. baidu.com/
  • https:// www.baidu.com:443/index.html
  • https://14.215.177.38:443/index.html

URL = URL + URN

5. HTTP / HTTPS

HTTP / HTTPS —> 超文本传输协议 —> 请求响应式协议
Hyper-Text Transfer Protocol

学习手册:Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档

HTTP请求头(headers):

  • 请求行:请求动作 资源路径 协议版本

    GET

    POST

  • 请求头:键值对(元数据,跟用户请求和浏览器相关的信息)
    User-Agent
  • 空行:\r\n

  • 消息体:浏览器发送给服务器的数据

HTTP响应头(response):

  • 响应行:协议版本 响应状态码

    200

    404

  • 响应头:键值对(元数据,跟响应和服务器的相关信息)

    content-Type

    Content-Disposition

  • 空行:\r\n

  • 消息体:服务器发给浏览器的数据(网页、图片、音视频、JSON)

请求方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifQODB8F-1634975911798)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211021104122115.png)]

响应状态码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQiTOkal-1634975911799)(C:\Users\32873\AppData\Roaming\Typora\typora-user-images\image-20211021104205413.png)]

响应码总结:

  • 2xx—>成功
  • 3xx —>重定向
  • 4xx—>请求有问题
  • 5××-—->服务器故障

元数据:描述性数据

6. 查看网页源代码/请求头

7. 查看网页请求头

8. 精准搜索

9. 获取网页的爬虫协议(robots.txt)

在对应网页网址后方加 /robots.txt

例如:查询淘宝的robots.txt

10.urllib —> 三方库requests

学习手册:Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档

页面爬取需使用三方库requests

# 控制台安装三方库
pip install requests
# 使用前先导入三方库
import requests

示例:拿搜狐页面、请求百度logo图片

示例:爬取豆瓣网排名前50的电影信息

11. 解封禁

如果频繁请求同一网页,本地主机的IP地址可能会被当作恶意攻击而被网页封禁,如:短时间内多次爬取豆瓣电影数据后被封禁,进入豆瓣网会看到

接下来,便是解封禁的办法,有:

  1. 让爬虫冒充是一个登录过的用户
  2. 使用代理服务器代理我们的请求

11.1 解封禁:使用Cookie伪装登录

通过Cookie向服务器亮明身份,破解封禁IP地址的反爬手段。

Cookie是服务器向浏览器写入的临时数据,很多时候被用于用户跟踪(记住这个用户是谁)。

登录过的页面的请求头(request heater)中会多出一个名为’Cookie’的数据。cookie - 浏览器保存本地数据的一种方式,对于登录过的用户,服务器可以通过向浏览器写入cookie数据的方式来记录用户标识,下次请求是,只要在请求头中携带该信息,服务器就可以识别出你是一个登录过的用户。

案例:通过Cookie解封禁(1)

案例:通过Cookie解封禁(2)

对cookie数据进行解析,因为有时候只需要cookie数据中的部分内容(真正有效的只是其中的部分内容)

11.2 解封禁:使用代理服务器

通过使用代理服务器访问豆瓣网,破解封禁IP的反爬手段。

常用的商业IP代理:快代理,讯代理,芝麻代理,蘑菇代理,阿布云代理。

使用代理服务器的步骤及注意事项:

  1. 先通过商业代理拿到代理服务地址列表

  2. 通过random.choice()从代理服务地址中随机抽取一个

random.choice() - 拿到列表中的元素

random.choices() - 拿到的是列表

  1. 使用代理服务器地址去爬取数据

    注意:不要每次都采用同一地址进行爬取

  2. 代理服务器可能会比较慢,采用timeout()进行超时控制,超时设定时间没反应便报错

11.2.1 获取代理服务的IP地址(url)

11.2.2 python中请求代理IP地址的url,获取代理IP地址

11.2.3 使用获取的IP地址获取网络资源

11.3 解封禁:采用socks代理

作业:

拿到前250部电影的数据,并写入excel列表中 【用css解析页面的方法】

re模块是python提供的专门用支持正则表达式的一个模块

import re  # 必不可少,非常重要!!!

网络教程:正则表达式30分钟入门教程

1. re模块的相关函数

1.1 match、fullmatch

match(正则表达式, 字符串) - 从头开始匹配,检查字符串是否与正则表达式匹配,如果匹配失败返回None;

fullmatch(正则表达式, 字符串) - 让正则表达式和指定字符串进行完全匹配,如果匹配失败返回None;

from re import fullmatch
re_str = r'abc'
print(fullmatch(re_str, 'abc'))

1.2 findall

findall(正则表达式, 字符串) - 获取字符串中所有满足正则表达式的子串,返回一个列表

from re import findall
str1 = '12ksksj78ss 34抗生素,89 试7试看90 56 江苏省23'
result1 = findall(r'\d\d', str1)
print(result1)
# ['12', '78', '34', '89', '90', '56', '23']

result2 = findall(r'\d\d\b', str1)
print(result2)
# ['89', '90', '56', '23']

result3 = findall(r'\b\d\d\b', str1)
print(result3)
# ['89', '56']

2. 正则语法

正则表达式 - 正则表达式是一个让字符串复杂问题变得简单的工具。

写正则表达式的主要工作:用正则符号描述清楚相关字符串的规则。

python正则比表示方式:r’正则表达式’

js的正则:/正则表达式/

2.1 普通字符(普通符号)

在正则除了有特殊功能或者特殊意义以外的符号;

普通字符在正则中表示这个符号本身。

# 匹配一个字符串有三个字符分别是a、b和c
re_str = r'abc'
print(fullmatch(re_str, 'abc'))

2.2 . - 匹配任意一个字符

注意:一个.只能匹配一个任意字符

# 匹配一个字符,长度是三,第一个字符是a,最后一个字符是c,a和c之间是任意一个字符
re_str = r'a.c'
print(fullmatch(re_str, 'abc'))
print(fullmatch(re_str, 'a+c'))
print(fullmatch(re_str, 'a好c'))

re_str = r'abc...'
print(fullmatch(re_str, 'abcm./'))
print(fullmatch(re_str, 'abcm\t/'))
print(fullmatch(re_str, 'abc G/'))

2.3 \d - 匹配任意一个数字字符

re_str = r'a\d\dc'
print(fullmatch(re_str, 'a78c'))
print(fullmatch(re_str, 'a00c'))

2.4 \s - 匹配任意一个空白字符

空白字符:空格、回车(\n)、制表符(\t)

re_str = r'a\sb'
print(fullmatch(re_str, 'a b'))
print(fullmatch(re_str, 'a\nb'))
print(fullmatch(re_str, 'a\tb'))
print(fullmatch(re_str, 'a  b'))    # None

2.5 \w - 匹配任意一个字母、数字或者下划线 (不好用)

2.6 \D、\S - 匹配任意一个非数字字符/非空白字符

\D - 匹配任意一个非数字字符

\S - 匹配任意一个非空白字符

print(fullmatch(r'a\Sb\D', 'a>b='))
print(fullmatch(r'a\Sb\D', 'a b='))         # None
print(fullmatch(r'a\Sb\D', 'a>b0'))         # None

2.7 [字符集] - 匹配字符集中任意一个字符

注意:一个[]只能匹配一个字符

[多个普通字符]

  • 例如:[abc],可以匹配a或者b或者c

[包含\开头的特殊符号的字符集]

  • 例如:[\dabc],可以匹配任意数据或者a或者b或者c

[包含减号在两个字符之间的字符集] - 这个时候的减号表示谁到谁(注意:减号前面的字符的编码必须小于减号后面的)

  • 例如:

    [a-z] - 匹配任意一个小写字母

    [a-d] - 匹配a、b、c、d中的任意一个字符

    [A-Z] - 匹配任意一个大写字母

    [1-9] - 匹配1到9中任意一个数字字符

    [\u4e00-\u9fa5] - 匹配任意一个中文字符

    [a-zA-Z]、[A-Za-z] - 匹配任意一个字母

    [a-z123] - 匹配任意一个小写字母,或者1或者2或者3

    [a-z\d] - 匹配任意一个小写字母或者任意一个数字

re_str = r'a[xym]b'
print(fullmatch(re_str, 'axb'))
print(fullmatch(re_str, 'ayb'))
print(fullmatch(re_str, 'amb'))
print(fullmatch(re_str, 'azb'))     # None

re_str = r'a[16]b'
print(fullmatch(re_str, 'a1b'))
print(fullmatch(re_str, 'a6b'))

re_str = r'a[a\db]b'
print(fullmatch(re_str, 'a1b'))
print(fullmatch(re_str, 'aab'))
print(fullmatch(re_str, 'abb'))

print(fullmatch(r'x[a-z]y', 'xmy'))

print(fullmatch(r'x[a-zA-Z]y', 'xmy'))
print(fullmatch(r'x[a-zA-Z]y', 'xKy'))

print(fullmatch(r'x[a-zA-Z*&]y', 'x*y'))
print(fullmatch(r'x[a-zA-Z*&]y', 'xMy'))

print(fullmatch(r'x[0-9]y', 'x5y'))

print(fullmatch(r'x[-09]y', 'x-y'))
print(fullmatch(r'x[-09]y', 'x0y'))
print(fullmatch(r'x[-09]y', 'x9y'))

2.8 [ ^ 字符集] - 匹配任意一个不在字符集任意一个字符

[^abc] - 匹配除了a、b、c以外的任意一个字符

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

注意:[]中的-和^只有放在指定位置的时候才有特殊功能,否则在[]中就是一个普通的符号。

print(fullmatch(r'a[^\u4e00-\u9fa5]c', 'a是c'))      # None
print(fullmatch(r'a[^a-zA-Z]c', 'aKc'))             # None
print(fullmatch(r'a[a-z^]c', 'a^c'))

3. 检测类符号

检测类符号的存在不影响被匹配的字符串长度,它的作用是在匹配成功的前提下检测符号所在的位置是否符号要求。

检测类符号的用法:先去掉检测类符号,看是否能匹配成功,如果失败整个正则匹配失败。如果成功,再来看检测类符号所在的位置是否符合要求。

3.1 \b - 检测是否是单词边界

单词边界 - 能够区分出两个不同单词的符号都属于单词边界,例如:空白、标点符号、字符串开头、字符串结尾

re_str = r'abc\b123'
print(fullmatch(re_str, 'abc123'))      # None
print(fullmatch(re_str, 'abc 123'))     # None

re_str = r'abc,\b123'
print(fullmatch(re_str, 'abc,123'))

print(fullmatch(r'abc\s\b123', 'abc 123'))
# findall(正则表达式, 字符串)   -   获取字符串中所有满足正则表达式的子串,返回一个列表
str1 = '12ksksj78ss 34抗生素,89 试7试看90 56 江苏省23'
result1 = findall(r'\d\d', str1)
print(result1)
# ['12', '78', '34', '89', '90', '56', '23']

result2 = findall(r'\d\d\b', str1)
print(result2)
# ['89', '90', '56', '23']

result3 = findall(r'\b\d\d\b', str1)
print(result3)
# ['89', '56']

3.2 \B - 检测是否是非单词边界

str1 = '12ksksj78ss 34抗生素,89 试7试看90 56 江苏省23'
result3 = findall(r'\d\d\B', str1)
print(result3)
# ['12', '78', '34']

3.3 ^ - 检测是否是字符串开头

str1 = '12ksksj78ss 34抗生素,89 试7试看90 56 江苏省23'
re_str = r'^\d\d'
print(fullmatch(re_str, '12'))  # <re.Match object; span=(0, 2), match='12'>
print(findall(r'^\d\d',  str1))  # ['12']

3.4 $ - 检测是否是字符串结尾

re_str = r'\d\d$'
print(fullmatch(re_str, '67'))  # <re.Match object; span=(0, 2), match='67'>

4. 匹配次数

4.1 * - 匹配0次或者多次(任意次数)

用法:匹配类符号*

a*      -      匹配任意多个a
\d*     -      匹配任意多个数字字符 
print(fullmatch(r'a*b', 'b'))
print(fullmatch(r'a*b', 'aab'))
print(fullmatch(r'a*b', 'aaaaaaab'))
print(fullmatch(r'\d*b', '245899b'))
print(fullmatch(r'[abc]*x', 'aabccax'))

4.2 + - 匹配1次或多次(至少一次)

print(fullmatch(r'a+b', 'b'))     # None
print(fullmatch(r'a+b', 'ab'))
print(fullmatch(r'a+b', 'aaaaab'))

4.3 ? - 匹配0次或1次

re_str = r'[-+]?[1-9]\d'
print(fullmatch(re_str, '-12'))
print(fullmatch(r'a?b', 'b'))
print(fullmatch(r'a?b', 'ab'))
print(fullmatch(r'a?b', 'aab'))     # None

4.4 {}

{N}     -      匹配N次
{M,N}   -      匹配M到N次
{,N}    -      匹配最多N次
{M,}    -      匹配至少M次
* == {0,}
+ == {1,}
? == {0,1}
print(fullmatch(r'\d{4}', '5670'))

print(fullmatch(r'\d{3,5}', '234'))
print(fullmatch(r'\d{3,5}', '2334'))
print(fullmatch(r'\d{3,5}', '23342'))
print(fullmatch(r'\d{3,5}', '233429'))          # None
print(fullmatch(r'\d{3,5}', '23'))              # None

print(fullmatch(r'\d{,5}', '23'))
print(fullmatch(r'\d{,5}', ''))
print(fullmatch(r'\d{,5}', '899080'))           # None

print(fullmatch(r'\d{3,}', '23'))           # None
print(fullmatch(r'\d{3,}', '23234234233'))

注意:匹配次数对应的符号的前面必须是匹配类符号

4.3 贪婪和非贪婪

在匹配次数不确定的时候,匹配模式分为贪婪和非贪婪两种,默认是贪婪模式。

在匹配成功的前提下,贪婪是匹配次数选最多的那个次数;非贪婪是匹配次数最少的那个次数。

print(match(r'\d{3}', '234hj监管机构'))
print(match(r'a.*b', 'asmmdb监管机构'))     # asmmdb
print(match(r'a.*?b', 'asmmdb监管机构'))    # asmmdb

# 'asb'、'asbmmb'、'asbmmbdb' 有三种情况可以成功,因为贪婪所以最后匹配次数最多的情况
print(match(r'a.*b', 'asbmmbdb监管机构'))       # asbmmbdb
print(match(r'a.*?b', 'asbmmbdb监管机构'))      # asb
# 实例
response = requests.get('http://api.tianapi.com/auto/index?key=c9d408fefd8ed4081a9079d0d6165d43&num=10')
result = findall(r'"title":"(.+?)"', response.text)
print(result)

'''
结果:
['MEB平台打造,纯电续航430km 大众ID.3售15.9888万起', '20万内纯电价值之选 品质乐趣纯电跨界车ID.3上市', '明日座驾长安CS75PLUS:领先对手不难,难的是超越自己', '上汽大众ID.3逢「3」成趣上市发布会', '继电动野马后,其实老美更期待的是这款福特', '古思特性能版预告,土豪换车预警', '说好的氢动力呢,丰田说啥,你就信了?', '买下濒临破产的品牌,就为了超越特斯拉?', '全新千匹V8发动机预告,还是自然吸气,雪佛兰太热血了', 'Z世代放弃本田CR-V 选择传祺GS4 PLUS 理由让人无法拒绝']
'''

5. 分组和分支

5.1 () - 分组

作用1:将()中的内容作为一个整体,进行整体相关操作,例如,整体控制次数

作用2:通过 ‘\M’ 重复前面第M个分组匹配到的结果, M从1开始

作用3:捕获(在findall中讲)

from re import fullmatch

str1 = '78nm34ms10xp'
print(fullmatch(r'\d\d[a-z]{2}\d\d[a-z]{2}\d\d[a-z]{2}', str1))
print(fullmatch(r'(\d\d[a-z]{2}){3}', str1))

str1 = r'abababab'
print(fullmatch(r'(ab)+', str1))


print(fullmatch(r'(\d{2})abc\1', '89abc89'))
print(fullmatch(r'(\d{2})abc\1', '89abc34'))        # None

"""print(fullmatch(r'\d{2}abc\1', '89abc89'))"""   # re.error

print(fullmatch(r'(\d{3})([a-z]{3})-\2', '234ams-ams'))
print(fullmatch(r'(\d{3})([a-z]{3})-\1', '234ams-234'))
print(fullmatch(r'(\d{3})([a-z]{3})-\2\1', '234ams-ams234'))
print(fullmatch(r'(\d{3})([a-z]{3})-\1{2}', '234ams-234234'))

"""print(fullmatch(r'(\d{3})-\2([a-z]{3})', '234ams-ams'))"""     #  re.error,

5.2 | - 分支

正则1|正则2 - 先用正则1进行匹配,如果成就直接匹配成功,如果匹配失败再用正则2进行匹配

# 要求能同时匹配'abc98' 和 'abcMKP'
print(fullmatch(r'abc\d{2}|abc[A-Z]{3}', 'abcKMP'))
print(fullmatch(r'abc(\d{2}|[A-Z]{3})', 'abcMKP'))

5.3 转义符号

在特殊符号前加\,让符号的功能消失,变成一个普通符号

print(fullmatch(r'\+\d{3}', '+234'))
print(fullmatch(r'\[\d{3}\]', '[234]'))
print(fullmatch(r'\\dabc', '\dabc'))

注意:如果是独立存在有特殊功能的符号,将符号放入[]中其功能也会自动消失

print(fullmatch(r'[+*?|()^$.]abc', '$abc'))
print(fullmatch(r'[\^abc\-z\]]123', ']123'))

6. re模块

import re

6.1 compile(正则表达式) - 编译正则表达式

compile(正则表达式) - 编译正则表达式,返回一个正则表达式对象

6.2 fullmatch、match

fullmatch(正则表达式, 字符串) - 让正则表达式和整个字符串进行匹配(完全匹配),匹配失败返回None,匹配成功返回匹配对象

match(正则表达式, 字符串) - 匹配字符串开头(判断字符串的开头是否符合正则的规则),匹配失败返回None,匹配成功返回匹配对象

re_obj = re.compile(r'\d{3}')
print(re_obj.fullmatch('234'))

print(re.fullmatch(r'\d{3}', '234'))

result = re.fullmatch(r'(\d{3})-([A-Z]+)', '345-K')
print(result)    # <re.Match object; span=(0, 3), match='345'>  - 匹配对象

6.2.1 group() - 获取匹配到的字符串

匹配对象.group() / 匹配对象.group(0) - 获取整个正则匹配到的结果

匹配对象.group(N) - 获取第N个分组匹配到的结果

print(result.group())       # 345-K
print(result.group(1))      # 345
print(result.group(2))      # K

6.3 search(正则表达式, 字符串)

search(正则表达式, 字符串) - 获取字符串中第一个满足正则表达式的子串。返回结果是None或者匹配对象

result = re.search(r'\d{3}', '试试看234ksjs,345')
print(result)       # <re.Match object; span=(3, 6), match='234'>
print(result.group())       # 234

6.3.1 span() - 获取匹配结果在原字符串中的位置信息

匹配对象.span() - 返回的是一个元组,元组中的元素是开始下标和结束下标,结束下标对应的位置取不到

匹配对象.span(N)

print(result.span())

6.4 findall(正则表达式, 字符串)

findall(正则表达式, 字符串) - 获取字符串中所有满足正则的子串,返回的是列表,列表中的元素是子串(没有分组的时候)

如果正则中只有一个分组:返回的列表中的元素是每个分组匹配到的结果

如果正则中有两个或两个以上的分组: 返回的列表中的元素是元组,元组中的元素是每个分组匹配到的结果

result = re.findall(r'\d{2}', '34ssd908计算机上23,udh89,试试89123')
print(result)   # ['34', '90', '23', '89', '89', '12']

result = re.findall(r'(\d{2})\D', '34ssd908计算机上23,udh89,试试89123')
print(result)   # ['34', '08', '23', '89']

result = re.findall(r'((\d[a-z]){2})', '2m4m司机师傅9k0o试试3k5l--')
print(result)       # [('2m4m', '4m'), ('9k0o', '0o'), ('3k5l', '5l')]

result = re.findall(r'(\d{2})-([a-z]{3})', '23-msn数据是98-kop圣诞节发货')
print(result)       # [('23', 'msn'), ('98', 'kop')]

6.5 finditer(正则表达式, 字符串)

finditer(正则表达式, 字符串) - 获取字符串中所有满足正则的子串,返回的是一个迭代器,迭代器是匹配对象

result = re.finditer(r'(\d{2})-([a-z]{3})', '23-msn数据是98-kop圣诞节发货')
print(result)
r1 = next(result)
print(r1, r1.group(), r1.group(1), r1.group(2))

6.6 split(正则表达式, 字符串)

split(正则表达式, 字符串) - 将字符串中所有满足正则表达式的子串作为切割点,对字符串进行切割

re.split(正则表达式, 字符串, N) - 将字符串中前N个满足正则表达式的子串作为切割点,对字符串进行切割

result = re.split(r'\d+', '是是9564s双生视界09世纪东方和3d失敬失敬2试试')
print(result)

6.7 sub(正则表达式, 字符串1, 字符串2)

sub(正则表达式, 字符串1, 字符串2) - 将字符串2中所有满足正则表达式的子串替换成字符串1

sub(正则表达式, 字符串1, 字符串2, N) - 将字符串2中前N个满足正则表达式的子串替换成字符串1

result = re.sub(r'\d+', '*', '是是9564s双生视界09世纪东方和3d失敬失敬2试试')
print(result)

message = 'f u c    k you! 打团了,你TM的没看见吗?SB'
re_str = open('badLanguage.txt', encoding='utf-8').read()
re_str = r'(?i)%s' % re_str
result = re.sub(re_str, '*', message)
print(result)

6.8 flags参数

上面的每一个函数都一个参数flags,用来设置正则参数

6.8.1 单行匹配和多行匹配参数:re.S、re.M(默认的)

单行匹配和多行匹配参数:re.S、re.M(默认的)

单行匹配:.可以匹配\n

多行匹配:.不能和\n匹配

flags=re.S <==> r’(?s)正则表达式’

6.8.2 忽略大小写:re.I

忽略大小写:re.I

flags=re.I <==> r’(?i)正则表达式’

flags=re.S|re.I <==> r’(?si)正则表达式’

print(re.fullmatch(r'a.b', 'a\nb', flags=re.M))     # None
print(re.fullmatch(r'a.b', 'a\nb'))                 # None
print(re.fullmatch(r'a.b', 'a\nb', flags=re.S))
print(re.fullmatch(r'(?s)a.b', 'a\nb'))

print('---------------------------------------------------')
print(re.fullmatch(r'abc', 'abc'))
print(re.fullmatch(r'abc', 'Abc'))      # None
print(re.fullmatch(r'abc', 'ABc', flags=re.I))
print(re.fullmatch(r'(?i)abc', 'ABc'))

print(re.fullmatch(r'a.b', 'A\nb', flags=re.S|re.I))
print(re.fullmatch(r'(?is)a.b', 'A\nb'))

7. 正则基本符号总结表

符号解释示例例说明
.匹配任意字符b.t可以匹配bat / but / b#t / b1t等
\w匹配字⺟母/数字/下 划线b\wt可以匹配bat / b1t / b_t等 但不不能匹配b#t
\s匹配空⽩白字符(包 括\r、 \n、 \t等)love\syou可以匹配love you
\d匹配数字\d\d可以匹配01 / 23 / 99等
\b匹配单词的边界\bThe\b
^匹配字符串串的开始^The可以匹配The开头的字符串串
$匹配字符串串的结束.exe$可以匹配.exe结尾的字符串串
\W匹配⾮非字⺟母/数字/ 下划线b\Wt可以匹配b#t / b@t等 但不不能匹配but / b1t / b_t等
\S匹配⾮非空⽩白字符love\Syou可以匹配love#you等 但不不能匹配love you
\D匹配⾮非数字\d\D可以匹配9a / 3# / 0F等
\B匹配⾮非单词边界\Bio\B
[]配来⾃自字符集的 任意单⼀一字符[aeiou]可以匹配任⼀一元⾳音字⺟母字符
[^]匹配不不在字符集中 的任意单⼀一字符[^aeiou]可以匹配任⼀一⾮非元⾳音字⺟母字符
*匹配0次或多次\w*
+匹配1次或多次\w+?
?匹配0次或1次\w?
{N}匹配N次\w{3}
{M,}匹配⾄至少M次\w{3,}
{M,N}匹配⾄至少M次⾄至多 N次\w{3,6}
|分⽀支foo|bar可以匹配foo或者bar
(?#)注释
(exp)匹配exp并捕获到 ⾃自动命名的组中
(? exp)匹配exp并捕获到 名为name的组中
(?:exp)匹配exp但是不不捕 获匹配的⽂文本
(?=exp)匹配exp前⾯面的位 置\b\w+(?=ing)可以匹配I’m dancing中的danc
(?<=exp)匹配exp后⾯面的位 置(? <=\bdanc)\w+\b可以匹配I love dancing and reading中的第⼀一 个ing
(?!exp)匹配后⾯面不不是exp 的位置
(?<!exp)匹配前⾯面不不是exp 的位置
*?重复任意次,但尽 可能少重复a.b a.?b将正则表达式应⽤用于aabab,前者会匹配整个 字符串串aabab,后者会匹配aab和ab两个字符 串串
+?重复1次或多次, 但尽可能少重复
??重复0次或1次,但 尽可能少重复
{M,N}?重复M到N次,但 尽可能少重复
{M,}?重复M次以上,但 尽可能少重复

8. re模块的核心函数总结表

函数说明
compile(pattern, flags=0)编译正则表达式返回正则表达式对象
match(pattern, string, flags=0)⽤用正则表达式匹配字符串串 成功返回匹配对象 否则返回 None
search(pattern, string, flags=0)搜索字符串串中第⼀一次出现正则表达式的模式 成功返回匹配对象 否 则返回 None
split(pattern, string, maxsplit=0, flags=0)⽤用正则表达式指定的模式分隔符拆分字符串串 返回列列表
sub(pattern, repl, string, count=0, flags=0)⽤用指定的字符串串替换原字符串串中与正则表达式匹配的模式 可以 ⽤用 count 指定替换的次数
fullmatch(pattern, string, flags=0)match 函数的完全匹配(从字符串串开头到结尾)版本
findall(pattern, string, flags=0)查找字符串串所有与正则表达式匹配的模式 返回字符串串的列列表
finditer(pattern, string, flags=0)查找字符串串所有与正则表达式匹配的模式 返回⼀一个迭代器器
purge()清除隐式编译的正则表达式的缓存
re.I / re.IGNORECASE忽略略⼤大⼩小写匹配标记
re.M / re.MULTILINE多⾏行行匹配标记

元字符1

# 元字符
^ - 字符串的开始
$ - 字符串的结束
\d - 数字
\D - 非数字
\w - 英文大小写字母、数字、下划线
\W - 不是英文字母、数字、下划线
\s - 空白字符
\S - 不是空白字符

字符集 - []

# 字符集 - [] - 方括号中的字符任取其一
[a-z0-9A-Z] -   英文字母(大小写)、数字
[\u4E00-\u9FA5]	-	中文
[adiuo]	-	adiuo中任一字符
[1-9]	-	1到9的数字

量词1

# 量词
{n}   -   前面挨着的表达式,刚好n次
{n,}    -   最少n次
{,n}    -   最多n次
{,n}    -   0到n次
{n,m}   -   n到m次

\

\\  -   \

r - 阻止转义

r    -   阻止转义,表字符串的原有字符(不转义)

从头开始匹配

# 从头开始匹配
match - 检查字符串是否与正则表达式匹配
fullmatch - 检查字符串是否与正则表达式完全匹配
import re

# 从头开始匹配:match()、fullmatch()

# 匹配电话号码

tel = input('请输入手机号:')
matcher = re.match(r'1[3-9]\d{9}',tel)  # 15882460265
print(matcher)  # <re.Match object; span=(0, 11), match='15882460265'>
# 例如:
# 请输入手机号:15882460265
# <re.Match object; span=(0, 11), match='15882460265'>
# 缺点:上面的正则表达式还能匹配多余11位且前11位满足规则的。如:
# 请输入手机号:158824602655
# <re.Match object; span=(0, 11), match='15882460265'>

# 匹配正好11位且满足规则的电话号码:
matcher = re.match(r'^1[3-9]\d{9}$', tel)
print(matcher)
matcher = re.fullmatch(r'1[3-9]\d{9}', tel)
print(matcher)

从中间匹配

从中间匹配   -  search()

匹配开始 / 结束位置

上次匹配结束的位置   -   end()
上次匹配开始的位置   -   start()
上次匹配开始的位置   -   span()

前瞻(向后面(右边)看) / 回顾(向前面(左边)看)

前瞻(向后面(右边)看) ---> (?=\d) ---> 后面必须是数字
                   ---> (?!\d) ---> 后面不能是数字
回顾(向前面(左边)看) ---> (?<=\d) ---> 后面必须是数字
                   ---> (?<!\d) ---> 后面不能是数字

常见应用

应用一:摘取数据中的手机号

应用一:摘取下列数据中的手机号

import re

content = """余婷的手机号是13011223344,不是15800224567,也不是110。
骆昊的135的手机号已经停用了,请拨打13899887766这个号码。
骆昊的银行存款没有1350099887766元。"""

# 方法一:循环
matcher = re.search(r'1[3-9]\d{9}',content)
while matcher:
    print(matcher.group())
    content = content[matcher.end():]  # 得到匹配后剩下的字符串
    matcher = re.search(r'1[3-9]\d{9}', content)

# 方法二:re.findall()       -   得到一个列表
items = re.findall(r'1[3-9]\d{9}', content)
for item in items:
    print(item)

# 方法三:re.finditer()   -   返回一个迭代器
iter_obj = re.finditer(r'1[3-9]\d{9}', content)
for matcher in iter_obj:
    print(matcher.group())

# 上述正则表达式的bug:会取到1350099887766的前11位
# 消除上述bug:匹配到的字符串前后都不能再有数字,具体如下:
matcher = re.search(r'(?<!\d)1[3-9]\d{9}(?!\d)', content)
# (?<=\d) ---> 后面必须是数字
# (?!\d) ---> 后面不能是数字

元字符2

元字符
.   -   任意字符

量词2

*   -   前面的字符出现0次或任意多次
*?   -    的惰性匹配(尽可能短的匹配)

常见组合

.*  -   任意字符,任意长度
.*? -   任意字符,任意长度,惰性匹配

分组 —> 捕获组 —> ()

分组 —> 捕获组 —> () —> 返回一个列表,元素为捕获结果

# 捕获一个数据
names_list = re.findall(r'<span class="title">([\u4E00-\u9FA5]*?)</span>', resp.text)  # \u4E00-\u9FA5
scores_list = re.findall(r'<span class="rating_num" property="v:average">(.*?)</span>', resp.text)
quotes_list = re.findall(r'<span class="inq">(.*?)</span>', resp.text)

# 捕获多个数据
anchors_list = re.findall(r'<a.*?href="(.*?)".*?title="(.*?)".*?>', resp.text)

CSS(层叠样式表)解析器解析页面

三方库beautifulsoup4能够支持CSS页面数据解析

pip install beautifulsoup4

注意:三方库beautifulsoup4导入时写bs4

import bs4

标签写法:

  • ‘a[title]’ - 获取有title样式的a标签

创建 BeautifulSoup对象:

  • soup = bs4.BeautifulSoup(resp.text,‘html.parser’)

通过CSS选择器定位页面元素:

  • soup.select(‘a[title]’) - [Tags],得到一个标签列表
  • soup.select_one(‘a[title]’) - Tag,得到一个标签

获取标签属性和内容:

  • anchor = soup.select_one(‘a[title]’)
  • anchor.attrs[‘title’] - 获取标签属性
  • anchor.text - 获取标签内容

版权声明:本文为yihanwtt8原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。