4.字符串
- 字符串的本质是:字符序列。
- Python 不支持单字符类型,单字符也是作为一个字符串使用的。
1 字符串的编码
Python3 直接支持 Unicode,可以表示世界上任何书面语言的字符。
Python3 的字符默认就是 16 位 Unicode 编码,ASCII 码是 Unicode 编码的子集。

使用内置函数 ord() 可以把字符转换成对应的 Unicode 码;
使用内置函数 chr() 可以把十进制数字转换成对应的字符。
#%%
print(ord('A'))
print(chr(65))
ord('小')

2 字符串的定义
字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单,只要为变量分配一个值即可。
案例 1:使用单引号或双引号定义字符串变量。
str1 = 'abcdefg'
str2 = "hello world"
print(type(str1)) # <class 'str'>
print(type(str2)) # <class 'str'>

案例 2:使用 3 个引号定义字符串变量。
name1 = '''I am Tom, Nice to meet you!'''
print(name1)
print(type(name1))
print('-' * 20)
name2 = """I am Jennify,
Nice to meet you!"""
print(name2)
print(type(name2))

案例 3:思考如何使用字符串定义 I'm Tom
使用单引号情况
str1 = 'I'm Tom'
运行结果:

出现以上问题的主要原因在于,以上字符串的定义代码出现了(SyntaxError)语法错误。单引号在字符串定义中必须成对出现,而且 Python 解析器在解析代码时,会自动认为第一个单引号和最近的一个单引号是一对!
如果一定要在单引号中在放入一个单引号,必须使用 反斜杠(\) 进行转义。
str1 = 'I\'am Tom'

使用双引号情况
str2 = "I'm Tom"

2.1 空字符串和 len() 函数
len() 用于计算字符串含有多少字符。
#%%
str = '小龙女'
len(str)

Python 允许空字符串的存在,不包含任何字符且长度为 0。例如:
#%%
str = ''
len(str)

2.2 转义字符
我们可以使用 \ + 特殊字符 ,实现某些难以用字符表示的效果。比如:换行等。常见的转义字符有这些:
| 转义字符 | 描述 |
|---|---|
\(在行尾时) |
续行符 |
\\ |
反斜杠符号 |
\' |
单引号 |
\" |
双引号 |
\b |
退格(Backspace) |
\n |
换行 |
\t |
横向制表符 |
\r |
回车 |
3 字符串输入
在 Python 代码中,我们可以使用 input() 方法来接收用户的输入信息。记住:在 Python 中,input() 方法返回的结果是一个 字符串类型 的数据。
name = input('请输入您的姓名:')
age = input('请输入您的年龄:')
address = input('请输入您的住址:')
print(name, age, address)
4 字符串的输出
4.1 ☆ 普通输出
print(变量名称)
print(变量名称1, 变量名称2, 变量名称3)
4.2 ☆ 格式化输出
① 百分号(Python2 和 Python3)
name = input('请输入您的姓名:')
age = input('请输入您的年龄:')
address = input('请输入您的住址:')
print('我的名字是%s,今年%d岁了,家里住在%s...' % (name, age, address))
② format 方法(Python3)
name = input('请输入您的姓名:')
age = input('请输入您的年龄:')
address = input('请输入您的住址:')
print('我的名字是{},今年{}岁了,家里住在{}...'.format(name, age, address))
③ f 形式(Python3)
name = input('请输入您的姓名:')
age = input('请输入您的年龄:')
address = input('请输入您的住址:')
print(f'我的名字是{name},今年{age}岁了,家里住在{address}...')
延伸:
name = input('请输入您购买商品的名称:')
price = float(input('请输入您购买商品的价格:')) # 18.5
print(f'购买商品名称:{name},商品价格:{price:f}')
5 字符串拼接
-
可以使用
+将多个字符串拼接起来。例如:'aa' + 'bb' ==> 'aabb'。-
如果
+两边都是字符串,则拼接。#%% 'aa' + 'bb'![images/4.字符串/Snipaste_2022-06-26_23-05-51.png]()
-
如果
+两边都是数字,则加法运算。#%% 1 + 1![images/4.字符串/Snipaste_2022-06-26_23-09-19.png]()
-
如果
+两边类型不同,则抛出异常。#%% 1 + 'a'![images/4.字符串/Snipaste_2022-06-26_23-09-42.png]()
-
-
可以将多个字面字符串直接放到一起实现拼接。例如:
'aa' 'bb' ==> 'aabb'#%% 'aa' 'bb'![images/4.字符串/Snipaste_2022-06-26_23-10-53.png]()
6 字符串复制
使用 * 可以实现字符串复制。
#%%
'小龙女' * 2

7 str() 实现数字转型字符串
str() 可以帮助我们将其他数据类型转换为字符串。
#%%
print(str(5.20))
#%%
print(str(314e-2))
#%%
print(str(True))

当我们调用 print() 函数时,解释器自动调用了 __str__() 将非字符串的对象转成了字符串。我们在面向对象章节中详细讲解这部分内容。
8 字符串在计算机底层的存储形式
在计算机中,Python 中的字符串属于序列结构。所以其底层存储占用一段连续的内存空间。
str1 = 'itheima'
结构原理图:

注意:索引下标从 0 开始。
9 聊聊索引下标
索引下标,就是编号。比如火车座位号,座位号的作用:按照编号快速找到对应的座位。同理,下标的作用即是通过下标快速找到对应的数据。

举个例子:
name = 'abcdef'
print(name[0]) # a
print(name[3]) # d

10 使用 [] 提取字符
字符串的本质就是字符序列,我们可以通过在字符串后面添加 [],在 [] 里面指定偏移量,可以提取该位置的单个字符。
-
正向搜索:
最左侧第一个字符,偏移量是 0,第二个偏移量是 1,以此类推。直到
len(str)-1为止。 -
反向搜索:
最右侧第一个字符,偏移量是 -1,倒数第二个偏移量是 -2,以此类推,直到
-len(str)为止。
#%%
a = 'abcdefghijklmnopqrstuvwxyz'
a
#%%
a[0]
#%%
a[3]
#%%
a[26-1]
#%%
a[-1]
#%%
a[-26]
#%%
a[-30]


11 字符串切片
11.1 什么是字符串切片
所谓的切片是指对操作的对象截取其中一部分的操作。字符串、列表、元组都支持切片操作。
11.2 字符串切片基本语法
包左不包右:
序列名称[起始偏移量start: 终止偏移量end: 步长step]
- 不包含结束位置下标对应的数据,正负整数均可;
- 步长是选取间隔,正负整数均可,正数从左向右,负数从右向左。默认步长为 1。
☆ 典型操作(三个量为正数的情况)如下:
| 操作和说明 | 示例 | 结果 |
|---|---|---|
[:] 提取整个字符串 |
"abcdef"[:] |
"abcdef" |
[start:] 从 start 索引开始到结尾 |
"abcdef"[2:] |
"cdef" |
[:end] 从头开始直到 end-1 |
"abcdef"[:2] |
"ab" |
[start: end] 从 start 到 end-1 |
"abcdef"[2:4] |
"cd" |
[start: end: step] 从 start 提取到 end-1,步长是 step |
"abcdef"[1:5:2] |
"bd" |
☆ 其他操作(三个量为负数)的情况:
| 操作和说明 | 示例 | 结果 |
|---|---|---|
| 倒数三个 | "abcdefghijklmnopqrstuvwxyz"[-3:] |
"xyz" |
| 倒数第八个到倒数第三个(包左不包右) | "abcdefghijklmnopqrstuvwxyz"[-8: -3] |
"stuvw" |
| 步长为负,从右到左反向提取 | "abcdefghijklmnopqrstuvwxyz"[:: -1] |
"zyxwvutsrqponmlkjihgfedcba" |
还是有点陌生,没关系,给你举个栗子:
numstr = '0123456789'
如果想对 numstr 字符串进行切片,如下图所示:

#%%
number = '0123456789'
#%%
number[0: 6]
#%%
number[0: 6: 1]
#%%
number[0: 6: 2]
#%%
number[-1:-6:-1]
#%%
number[-1::-1]

#%%
str1 = 'abcdefg'
len(str1)
#%%
str1[-100::]
#%%
str1[-69:-3:]
#%%
str1[-100:100]

11.3 字符串切片小口诀
记口诀:切片其实很简单,只顾头来尾不管,步长为正正向移,步长为负则逆向移
如果还是有点不太清楚这个原理,建议大家对字符串进行画图:

11.4 字符串切片的小栗子
案例 1:
numstr = '0123456789'
# 1、从 2 到 5 开始切片,步长为 1
print('numstr[2:5:1]:', numstr[2:5:1])
print('numstr[2:5]:', numstr[2:5])
# 2、只有结尾的字符串切片:代表从索引为 0 开始,截取到结尾字符 -1 的位置
print('numstr[:5]:', numstr[:5])
# 3、只有开头的字符串切片:代表从起始位置开始,已知截取到字符串的结尾
print('numstr[1:]:', numstr[1:])
# 4、获取或拷贝整个字符串
print('numstr[:]:', numstr[:])
# 5、调整步阶:类似求偶数
print('numstr[::2]:', numstr[::2])
# 6、把步阶设置为负整数:类似字符串翻转
print('numstr[::-1]:', numstr[::-1])
# 7、起始位置与结束位置都是负数(遵循一个原则:必须是从左向右截取)
print('numstr[-4:-1]:', numstr[-4:-1])
# 8、结束字符为负数,如截取 012345678
print('numstr[:-1]:', numstr[:-1])

案例 2:给定一个图片的名称为 avatar.png,使用 Python 方法获取这个图片的名称 avatar 以及这个图片的后缀 .png。
分析:
- 建议先获取点号的位置(目前还未学习,只能一个一个数);
- 从开头切片到点号位置,得到的就是文件的名称;
- 从点号开始切片,一直到文件的结尾,则得到的就是文件的后缀。
filename = 'avatar.png'
# 获取点号的索引下标
index = 6
# 使用切片截取文件的文件
name = filename[:index]
print(f'上传文件的名称:{name}')
# 使用切片截取文件的后缀
postfix = filename[index:]
print(f'上传文件的后缀:{postfix}')
12 字符串的操作方法(内置)
12.1 字符串中的查找方法
所谓字符串查找方法即是查找子串在字符串中的位置或出现的次数。
基本语法:
字符串.find(要查找的字符或者子串)
| 函数 | 作用 |
|---|---|
find() |
检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则返回 -1。 |
index() |
检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则报异常。 |
rfind() |
和 find() 功能相同,但查找方向为右侧开始。 |
rindex() |
和 index() 功能相同,但查找方向为右侧开始。 |
count() |
返回某个子串在字符串中出现的次数。 |
☆ find() 方法
作用:检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则返回 -1。
# 定义一个字符串
str1 = 'hello world hello linux hello python'
# 查找linux子串是否出现在字符串中
print(str1.find('linux'))
# 在str1中查找不存在的子串
print(str1.find('and'))

案例:使用 input 方法输入任意一个文件名称,求点号的索引下标
filename = input('请输入您要上传文件的名称:')
# 获取点号的索引下标
index = filename.find('.')
print(index)
# 求文件名称
print(filename[:index])
# 求文件后缀
print(filename[index:])

☆ index() 方法
index() 方法其功能与 find() 方法完全一致,唯一的区别在于当要查找的子串没有出现在字符串中时,find() 方法返回 -1,而 index() 方法则直接报错。
str1 = 'apple, banana, orange'
# 判断apple是否出现在字符串str1中
print(str1.index('apple'))
print(str1.index('pineapple'))
运行结果:

☆ rfind() 与 rindex() 方法
r = right,代表从右开始查找。
字符串序列.rfind(子串)
字符串序列.rindex(子串)
强调:rfind() 方法与 rindex() 方法适合于查找子串在字符串中出现了多次的情况。
案例:有一个文件名称叫 20210310axvu.avatar.png,其中点号出现了两次,这个时候,如果想获取文件的后缀 .png,代码应该如何编写?
filename = '20210310axvu.avatar.png'
# 求出点号在字符串中第一次出现的位置
# index = filename.find('.')
# print(index)
# 求出点号在字符串中最后一次出现的位置
index = filename.rfind('.')
print(index)
print(filename[:index])
print(filename[index:])

rfind() 方法和 rindex() 方法语法上完全一致,唯一的区别就是对子串没有出现在字符串的中的情况,rfind() 返回 -1,rindex() 返回错误。
☆ count() 方法
主要功能:求子串在字符串中出现的次数。
基本语法:
字符串.count('子串', 开始位置下标, 结束位置下标)
案例:获取字符串中 and 关键字出现的次数。
str1 = 'hello world and hello linux and hello python'
# 不限定字符串长度
ands = str1.count('and')
# 限定开始查找的位置和结束位置
# ands = str1.count('and', 10, 30)
print(f'and字符串出现的次数为:{ands}')

☆ 查找方法效率问题
-
find 和 rfind 这两个方法效率高吗?要不要用?
这两个方法效率真不高,都是在字符串中遍历搜索,但是如果找子串工作必不可少,那么必须这么做,但是能少做就少做。
index方法和find方法很像,不好的地方在于找不到抛异常。推荐使用find方法。count方法效率极低,推荐不用。字符串查找问题,一般都涉及到复杂的算法问题,难度较大,偶尔用一下查找方法也是可以的。
-
时间复杂度
find、index和count方法都是O(n)。随着字符串数据规模的增大,而效率下降。
12.2 字符串的修改方法
所谓修改字符串,指的就是通过函数(方法)的形式修改字符串中的数据。
| 函数 | 作用 |
|---|---|
replace() |
返回替换后的字符串 |
split() |
返回切割后的列表序列 |
capitalize() |
首字母大写 |
title() |
所有单词首字母大写 |
upper() 与 lower() |
返回全部大写或小写的字符串 |
swapcase() |
产生新的字符串,新字符串所有字母大小写转换 |
lstrip()、rstrip() 与 strip() |
去除左边、右边以及两边的空白字符 |
ljust()、rjust() 与 center() |
返回原字符串左对齐、右对齐以及居中对齐 |
☆ replace() 方法
基本语法:
字符串.replace(要替换的内容, 替换后的内容, [替换的次数])
案例:编写一个字符串,然后把字符串中的 linux 替换为 python。
str1 = 'hello linux and hello linux'
# 把字符串中所有 linux 字符替换为 python
print(str1.replace('linux', 'python'))
# 把字符串中的第一个 linux 进行替换为 python
print(str1.replace('linux', 'python', 1))
# 把 and 字符串替换为 &&
print(str1.replace('and', '&&'))

目前在工作中,replace 主要用于实现关键字替换或过滤功能。北京 ==> BJ,论坛关键字过滤,人民币 ==> 软妹币。
☆ split() 方法
作用:对字符串进行切割操作,返回一个 list() 列表类型的数据。
str1 = 'apple-banana-orange'
print(str1.split('-'))

☆ capitalize() 方法
作用:把字符串的首字母大写,其他字符全部小写。
#%%
str1 = 'i love Linux and Java'
str1.capitalize()

☆ title() 方法
作用:把字符串中的所有单词的首字母大写,组成大驼峰。
str1 = 'myName'
# 把str1变成首字母大写字符串
print(str1.capitalize())
str2 = 'student_manager'
# 把str2变成大驼峰
print(str2.title().replace('_', ''))

☆ upper() 与 lower() 方法
upper():把字符串全部转换为大写形式。
lower():把字符串全部转换为小写形式。
# 用户名以及密码验证案例
username = input('请输入您的账号:')
password = input('请输入您的密码:')
# 把username和password全部转换为大写或小写
print(username.lower())
print(password.upper())


☆ swapcase() 方法
作用:产生新的字符串,新字符串所有字母大小写转换。
#%%
str1 = 'I love Python and Linux'
print('转换前:', str1)
print('转换后:', str1.swapcase())

☆ lstrip()、rstrip() 与 strip()
strip() 方法主要作用:删除字符串两边的空白字符(如空格)。
lstrip() 方法 == left + strip,作用:只删除字符串左边的空白字符。
rstrip() 方法,作用:只删除字符串右边的空白字符。
# 用户名验证案例
username = input('请输入您的账号:')
# 去除username两边的空白字符
print(len(username))
print(username.strip())
print(len(username.strip()))

☆ ljust()、rjust()、center()
作用:返回原字符串左对齐、右对齐以及居中对齐。
基本语法:
字符串序列.ljust(长度, [填充字符])
没有指定填充字符,默认使用空格填充。
案例:定义一个字符串,要求返回长度为 10 个字符,不足的使用 . 进行填充。
str1 = 'python'
# 左对齐
print(str1.ljust(10, '.'))
# 右对齐
print(str1.rjust(10, '#'))
# 居中对齐
print(str1.center(10))
print(str1.center(10, '@'))

split() 分割和 join() 合并
split() 可以基于指定分隔符将字符串分隔成多个子字符串(存储到列表中)。如果不指定分隔符,则默认使用空白字符(换行符/空格/制表符)。
#%%
a = "to be or not to be"
a.split() # ['to', 'be', 'or', 'not', 'to', 'be']
a.split('be') # ['to ', ' or not to ', '']

join() 的作用和 split() 作用刚好相反,用于将一系列子字符串连接起来。
#%%
a = ['sxt','sxt100','sxt200']
'*'.join(a)

测试 + 拼接符和 join(),不同的效率:
#%%
a = ['sxt','sxt100','sxt200']
'*'.join(a)
#%%
import time
time01 = time.time() #起始时刻
a = ""
for i in range(1000000):
a += "sxt"
time02 = time.time() #终止时刻
print("运算时间:"+str(time02-time01))
time03 = time.time() #起始时刻
li = []
for i in range(1000000):
li.append("sxt")
a = "".join(li)
time04 = time.time() #终止时刻
print("运算时间:"+str(time04-time03))

12.3 字符串的判断方法
所谓判断即是判断真假,返回的结果是布尔型数据类型:True 或 False。
| 函数 | 作用 |
|---|---|
startswith() |
检查字符串是否是以指定子串开头,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。 |
endswith() |
检查字符串是否是以指定子串结尾,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。 |
isalpha() |
如果字符串所有字符(至少有一个字符)都是字母则返回 True,否则返回 False。 |
isdigit() |
如果字符串只包含数字则返回 True 否则返回 False。 |
isalnum() |
Python isalnum() 方法检测字符串是否由字母和数字组成。如果字符串所有字符(至少有一个字符)都是字母或数字则返回 True,否则返回 False。 |
isspace() |
如果字符串中只包含空白,则返回 True,否则返回 False |
☆ startswith()
字符串.startswith('子串', [开始位置下标, [结束位置下标]])
作用:检查字符串是否是以指定子串开头,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。
#%%
str1 = 'python program'
str2 = 'I love Python and Linux'
print(str1.startswith('python'))
str2.startswith('Python', 7, -1)

☆ endswith()
字符串.endswith('子串', [开始位置下标, [结束位置下标]])
作用:检查字符串是否是以指定子串结尾,是则返回 True,否则返回 False。如果设置开始和结束位置下标,则在指定范围内检查。
str2 = 'avatar.png'
print(str2.endswith('.png'))
if str2.endswith('.png') or str2.endswith('.jpg') or str2.endswith('.gif'):
print('是一张图片格式的图片')
else:
print('您上传的文件格式异常')

☆ isalpha()
作用:如果字符串只包含字母则返回 True,否则返回 False。
str1 = 'admin'
str2 = 'admin123'
print(str1.isalpha()) # True
print(str2.isalpha()) # False

☆ isdigit()
作用:如果字符串只包含数字则返回 True,否则返回 False。
password = input('请输入您的银行卡密码:')
if len(password) == 6 and password.isdigit():
print('输入密码成功,正在验证...')
else:
print('密码输入错误,请重新输入')


☆ isalnum()
作用:Python isalnum() 方法检测字符串是否只由字母和数字组成。如果字符串所有字符都是字母或数字则返回 True,否则返回 False。
username = input('请输入的您的用户名(只能为字母+数字形式):')
if username.isalnum():
print('合理的用户名,正在录入系统...')
else:
print('输入的用户名有误,请重新输入...')


☆ isspace()
作用:如果字符串中只包含空白,则返回 True,否则返回 False(逆向思维)。
str1 = ' ' # 最少要包含一个空白字符
print(str1.isspace())
username = input('请输入的您的用户名:')
if len(username) == 0 or username.isspace():
print('您没有输入任何字符...')
else:
print(f'您的输入的字符{username}')

☆ 判断方法效率问题
startswith 和 endswith 方法本身效率比较高,可以使用。因为一般来说要比对的子串规模都比较小。但是这两个方法的效率是随着子串的规模变大而降低,时间复杂度为 O(n),n 为子串长度。在子串规模较大的情况下,慎用。比如在 DNA 序列中匹配子串问题。
13 字符串比较
我们可以直接使用 == 对字符串进行比较,是否含有相同的字符。
我们使用 is / not is,判断两个对象是否为同一个对象。比较的是对象的地址,即 id(obj1) == id(obj2)。
14 成员操作符
in /not in 关键字,判断某个字符(子字符串)是否存在于字符串中。
15 字符串驻留机制
15.1 什么是字符串的驻留机制
字符串驻留:是一种在内存中保存一份且不可变字符串的方法(相同的字符串只保留一份)。
不同的值被存放在字符串的驻留池当中,Python 的驻留机制对相同的字符串只保留一份拷贝,后续创建相同的字符串时,不会开辟新的空间,而是把字符串的地址赋给新的创建变量。
#%%
a='hello'
b="hello"
c="""hello"""
print(a, id(a)) # hello 2967447375728
print(a, id(b)) # hello 2967447375728
print(a, id(c)) # hello 2967447375728

15.2 原理
- 系统维护
interned字典,记录已被驻留的字符串对象 - 当字符串对象需要驻留时,先在
interned检测是否存在,若是存在的字符串对象,引用数+1;不存在则记录到interned中。
15.3 驻留机制的优缺点
- 背景:Python 一切皆对象,频繁的创建和销毁对象会影响性能。
- 优点:当需要值相同的字符串时候,可以直接从字符串池拿来使用,避免频繁的创建和销毁,提升效率和节约内存。
- 缺点:创建驻留机制的字符串要花费更多的时间。
15.4 驻留机制的几种情况(交互模式)
这里强调交互模式,是因为 PyCharm 等平台对字符串做了优化,本来不符合的字符串也会指向同一个位置。
- 字符串长度为 0 或者 1;
- 符合标识符的字符串(只包含字母数字下划线);
- 字符串只在编译时进行驻留,而非运行时;
[-5,256]之间的整数数字。
案例
-
字符串长度为 0 时:
#%% s1 = '' s2 = '' s1 is s2![images/4.字符串/Snipaste_2022-06-27_18-13-27.png]()
-
字符串长度为 1 时:
#%% s1 = '@' s2 = '@' s1 is s2![images/4.字符串/Snipaste_2022-06-27_18-13-34.png]()
-
符合标识符的字符串(只包含字母、数字和下划线):
#%% s1 = 'abc' s2 = 'abc' print(s1 is s2) s1 = 'abc@' s2 = 'abc@' print(s1 is s2)![images/4.字符串/Snipaste_2022-06-27_18-13-42.png]()
-
字符串只在编译时进行驻留,而非运行时:
#%% s1='xyz' s2='xy'+'z' #编译时 s3=''.join(['xy','z']) #运行时 print(s1 is s2) print(s1 is s3)![images/4.字符串/Snipaste_2022-06-27_18-13-48.png]()
-
[-5,256]之间的整数数字:#%% s1=-5 s2=-5 print(s1 is s2) s1=-6 s2=-6 print(s1 is s2)![images/4.字符串/Snipaste_2022-06-27_18-13-54.png]()
15.5 强制驻留 sys.intern
sys 模块中的 intern 方法可以强制两个字符串指向同一个对象。
#%%
import sys
s1='abc%'
s2='abc%'
print(s1 is s2)
s1=sys.intern(s2)
print(s1 is s2)

16 可变字符串
在 Python 中,字符串属于不可变对象,不支持原地修改,如果修改其中的值,会自动创建新的字符串对象。但是,经常我们确实需要原地修改字符串,可以使用 io.StringIO 对象或 array 模块。
#%%
import io
s = "hello, sxt"
sio = io.StringIO(s)
print(sio)
print(sio.getvalue())
sio.seek(7)
sio.write("g")
print(sio.getvalue())











浙公网安备 33010602011771号