5.1 存储
1.文件
为了长期持续的存储文件,可以用Python将数据文件保存。
Python能够借助文本对象来读写文件。
在Python中,我们通过内置函数open()来创建文件对象。在调用open时,需要说明文件名,以及打开文件的方式:
变量名 = open(文件名,文件打开方式)
f = open(文件名,文件打开方式)
# 文件名是文件存在于磁盘的名字
# 打开文件的常用方式有:
"r" # 读取已经存在的文件
"w" # 新建文件,并写入
"a" # 如果文件存在,那么写入到文件的结尾;如果文件不存在,则新建文件并写入
- 读取文件内容:以"r"方式打开
f = open("test.txt","r") # 以读取文件方式,打开test.txt文件。
#通过f对象,我们可以读取文件的内容:
content0 = f.read() # 读取文件所有内容
content1 = f.read(10) # 读取10个字节的数据
content2 = f.readline() # 读取一行
content3 = f.readlines() #读取所有行,存储在列表中,每个元素是一行。
#用完f对象之后,我们需要将文件关闭
f.close()
- 写入文件内容:以"w"或"a"方式打开
f = open("test.txt","w")
f.write("我是文件写入方式")
f.write("我是文件写入方式并且换行,不然都是在同一行\n") # \n是换行符
f.close() # 记得关闭噢
2.上下文管理器
文件操作通常与上下文管理器一同使用。
上下文管理器用于规定某对象的使用范围。一旦进入范围或者离开范围,就会执行相应的操作。(可以将上下文管理器想象为孙悟空画的圈,唐僧就是那个对象,唐僧进入圈之后就启动了圈子的保护功能,唐僧离开圈子后就被妖怪捉走。)
上下文管理器可以用于文件操作。
对于文件操作,按照常规操作进行的话,我们可能会忘记关闭文件,而上下文管理器会帮助我们自动关闭文件。
"""常规文件操作"""
f = open("new.txt","w") # 打开文件
f.write("HelloWorld")
print(f.closed) # # closed不是close()。closed是属性,用来检查文件是否关闭。
# 此时文件没有关闭,打印False。
这时就是忘了将文件关闭,正确的做法:
f= open("new.txt","w")
print(f.closed) # 还没有关闭,打印False
f.write("HeHe")
f.close() # 关闭
print(f.closed) # 关闭了,打印True
可以看出,如果按照常规操作,很容易忘记关闭文件,而上下文管理器可以自动帮助关闭。
# 上下文管理器
with open("test.txt","w") as f:
f.write("HelloWorld")
print(f.closed) #虽然没有f.close(),但是仍然打印True。
上下文管理器打开文件常使用with…as…(变量名)结构。
上下文管理器通过缩进来代表它的程序块是否结束,来表达文件对象的打开范围。当语句不再有缩进时,上下文管理器就会自动关闭文件。
上面我们提到过,进入或离开上下文管理器的时候,会触发特殊的操作:
1) __enter__()方法 # 进入范围时执行的操作
2) __exit__()方法 # 离开范围时执行的操作
class People(object):
def __init__(self,text): # 初始化方法,无论如何都将进行
self.text = text
def __enter__(self):
self.text = "我是" + self.text + ",我进入(enter)了圈子"
return self # 返回一个对象
def __exit__(self,exc_type,exc_value,traceback):
self.text = self.text + "后,遇见了exit出口,我离开了圈子。"
with People("唐僧") as tangseng:
print(tangseng.text) #进入了上下文管理器
print(tangseng.text) #离开了上下文管理器
结果:
我是唐僧,我进入(enter)了圈子
我是唐僧,我进入(enter)了圈子后,遇见了exit出口,我离开了圈子。
初始化对象时,对象的text属性是"唐僧"。
__enter__()
返回一个对象。上下文管理器会使用这一对象作为as所指的变量。
__exit__()
有四个参数。后三个参数是用于表述异常的,可以对三个参数处理,如果没有,且运行正常则三个参数都是None。
3.pickle包
Python的pickle包可以保存对象,再放进磁盘的文件。
对象的存储:
- 两步:
1)将对象在内存中的数据直接抓取出来,转换为一个有序的文本,即序列化。使用pickle包中的dumps()方法。
2)将文本存入文件。
需要对象时,我们可以从文件中读出文本,再放入内存中,就可以获取原来的对象。
import pickle
class People(object):
Name = True
Age = True
Sex = True
People0 = People() #创建对象
#第一步对象序列化
People0_String = pickle.dumps(People0)
#第二步用字节文本的存储方式,将该字符串存储在文件中
with open("People0.pkl","wb") as f: # 这里的b表示以二进制方式写入文件
f.write(People0_String)
上述两步我们也可以结合起来利用dump()方法,写成一步。
- 一步到位:
import pickle
class People(object):
Name = True
Age = True
Sex = True
People0 = People() #创建对象
with open("People0.pkl","w") as f:
pickle.dump(People0,f) #将People0序列化并且保存到f(People0.pkl)中
运行结束后,我们得到下面的文件对象的读取:
读取自定义的类时,需要先定义类,才可以读取对象,内置的就不需要。
- 两步:
1)从文件中读出文本。
2)使用pickle包的loads()方法,将字符串形式的文本转化为对象。
import pickle
class People(object):
Name = True
Age = True
Sex = True
with open("People0.pkl","rb") as f:
People0_String = f.read() # 从文件中读出文本。
People0 = pickle.loads(People0_String)
# 使用pickle包的loads()方法,将字符串形式的文本转化为对象。
print(People0.Name)
结果:
True
- 一步到位:
使用load方法,将两步合并。
import pickle
class People(object):
Name = True
Age = True
Sex = True
with open("People0.pkl","rb") as f:
People0 = pickle.load(f)
print(People0.Name)
结果:
True
5.2 一寸光阴
1.time包
我们可以通过Python中的time包来管理时间和日期。下面来看一下它的功能
import time as t
print(t.time()) # 挂钟时间,单位是s,这个结果时刻都在变化。
结果:
1581152080.6639836
我们可以利用sleep(),让程序睡眠,过了时间后程序将醒来,继续运行。
import time as t
import time as t
print("停三秒,倒计时3,2,1")
t.sleep(3)
print("程序结束")
结果:
停三秒,倒计时3,2,1
程序结束
我们可以用time包的perf_counter()或process_time()来测量运行时间。
clock()方法在Python3.8中被移除
import time as t
import time as t
start = t.perf_counter()
t.sleep(3)
end = t.perf_counter()
print("中间停了3秒,运行时间为",end - start)
结果:
中间停了3秒,运行时间为 3.0004140000000916
import time as t
st1 = t.gmtime() # 返回struct_time格式的UTC时间
st2 = t.localtime() # 返回struct_time格式的当地时间
print(st1)
print(st2)
结果:
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=8, tm_hour=9, tm_min=9, tm_sec=9, tm_wday=5, tm_yday=39, tm_isdst=0)
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=8, tm_hour=17, tm_min=9, tm_sec=9, tm_wday=5, tm_yday=39, tm_isdst=0)
我们也可以把struct_time对象变为time对象
import time as t
st = t.gmtime()
s = t.mktime(st) # 以秒s计算
print("将struct_time对象转为time对象:",s)
结果:
将struct_time对象转为time对象 1581124309.0
2.datetime包
datetime包更加便利,可以理解为有date和time两部分组成。
date由年、月、日构成的日期。
time由时、分、秒、毫秒构成的一天的具体时间。
因此,datetime下面有datetime.time类和datetime.date类。也有结合的datetime.datetime类。这里说明datetime.datetime类。
一个时间点,我们可以用下列方法表示
import datetime as dt
t = dt.datetime(2020,2,2,2,20)
print(t)
"""
t的属性如下:
日期:year年、month月、day日、weekday周几
时间:hour小时、minute分钟、second秒、millisecond毫秒、microsecond微秒
"""
结果:
2020-02-02 02:20:00
我们可以借助datetime包下的timedelta类,进行时间间隔的运算。
import datetime as dt
t1 = dt.datetime(2010,1,1,10,30)
t2 = dt.datetime(2010,1,2,12,30)
delta1 = dt.timedelta(seconds = 600) # 延迟600s 即10分钟
delta2 = dt.timedelta(weeks = 3) # 延迟3周 即21天
print(t1 + delta1) # t1时间点加上时间间隔delta1
print(t1 + delta2) # t1时间点加上时间间隔delta2
print(t2 - t1) # t2 和 t1 的间隔时间 1 day, 2:00:00 一天两小时
结果:
2010-01-01 10:40:00
2010-01-22 10:30:00
1 day, 2:00:00
两个datetime对象可以进行比较
import datetime as dt
t1 = dt.datetime(2010,1,1,10,30)
t2 = dt.datetime(2010,1,2,12,30)
print(t2 > t1)
结果:
True
3.日期格式
对含有时间信息的字符串,我们可以借助datetime包,把它转换成datetime类的对象,用datetime类下的strptime()方法。
import datetime as dt
str1 = "time/02/2000/02/022022"
format1 = "time/%d/%Y/%m/%H%M%S"
t = dt.datetime.strptime(str1,format1)
print(t)
"""
这里的str1是文件名。
这里的format1定义了格式:由 %Y表示年份、%m表示月份、%d表示日、
%H表示24时制的小时、%M表示分钟、%S表示秒
通过strptime()方法,Python会发要解析的字符串往格式凑,
然后自动改成年月日小时分钟秒的格式,就可以得到t对象的时间信息。
"""
结果:
2000-02-02 02:20:22
反过来,我们可以调用datetime类的strftime()方法,将一个datetime类的对象转换为特定格式的字符串。
import datetime as dt
format1 = "time:%m/%d/%Y/%H:%M"
t = dt.datetime(2013,2,5,23,20)
print(t.strftime(format1))
结果:
time:02/05/2013/23:20
格式转化的关键符合是%,还有。
5.3 看起来像那样的东西
1.正则表达式
正则表达式的主要功能是从字符串中通过特定的模式,搜索希望找到的内容。我们可以使用Python中的re包来处理正则表达式。下面举一个例子,目的是找到字符串中的数字:
import re
m = re.search("[0-9]","abc4ef")
print(m)
print(m.group(0)) # .group()用来查看搜索的值
结果:
<re.Match object; span=(3, 4), match='4'>
4
re.search()接收两个参数,第一个参数"[0-9]"就是我们所说的正则表达式,它告诉Python:“帮我找到字符串中从0-9的任意一个字符。”
然后search()如果找到符合的子串就返回一个对象m,通过m.group()方法查看搜索结果。(或者直接re.search(正则表达式,搜索对象).group());如果没有符合的字符串,就返回None。再多几个例子加深理解
import re
s = "134abce56"
print(re.search("([0-9]*)([a-z]*)([0-9])",s).group(0))
print(re.search("([0-9]*)([a-z]*)([0-9])",s).group(1))
print(re.search("([0-9]*)([a-z]*)([0-9])",s).group(2))
print(re.search("([0-9]*)([a-z]*)([0-9])",s).group(3))
结果:
134abc5
134
abce
5
"""
1. 正则表达式中的三组括号把匹配结果分成三组
group():同group(0)就是匹配正则表达式整体结果
group(1):列出第一个括号匹配部分
group(2):列出第二个括号匹配部分
group(3):列出第三个括号匹配部分。
2. 没有匹配成功的,re.search()返回None
3. 当正则表达式中没有括号,group(1)肯定报错。
"""
第一个例子若使用match()的话,会返回None,因为第一个例子的字符串起始为"a",不符合[0-9]的要求,但我们依旧可以用.group()方法查看搜索结果。
我们还可以将搜索到的子串进行替换。sub()就可以利用正则表达式在字符串中进行搜索,对于搜索结果,用另外的子串代替。即
re.sub(正则表达式,替换的字符串,搜索字符串)
import re
s = "abc3xyz"
replace_s = "456"
print(re.sub("[0-9]",replace_s,s))
结果:
abc456xyz
re包中常用的方法有:
import re
s = "abc125xyz"
print(re.split("[0-4]",s)) # 返回的是一个列表
print(re.findall("[0-5]",s))
结果:
['abc', '', '5xyz']
['1', '2', '5']
2.写一个正则表达式
正则表达式的关键在于:如何写出有效的正则表达式,接下来正则表达式我们将用findall()方法帮助理解。
import re
s = "abc125xyz "
print(re.findall(".",s)) # 任意一个字符
print(re.findall("a|d",s)) # 字符a或者字符d
print(re.findall("[acd]",s)) # a或者c或者d的字符
print(re.findall("[0-4]",s)) # 0-4范围内的字符
print(re.findall("[^a^1^2^5]",s)) # 不是a 1 2 5的字符
print(re.findall("\s",s)) # 是空格的子串
print(re.findall("\S",s)) # 非空格的字符
print(re.findall("\d",s)) # 数字,相当于[0-9]
print(re.findall("\D",s)) # 非数字,相当于[^0-9]
print(re.findall("\w",s)) # 数字或字母,相当于[0-9a-zA-Z]
print(re.findall("\W",s)) # 非数字非字母,相当于[^0-9a-zA-Z]
结果:
['a', 'b', 'c', '1', '2', '5', 'x', 'y', 'z', ' ', ' ', ' ']
['a']
['a', 'c']
['1', '2']
['b', 'c', 'x', 'y', 'z', ' ', ' ', ' ']
[' ', ' ', ' ']
['a', 'b', 'c', '1', '2', '5', 'x', 'y', 'z']
['1', '2', '5']
['a', 'b', 'c', 'x', 'y', 'z', ' ', ' ', ' ']
['a', 'b', 'c', '1', '2', '5', 'x', 'y', 'z']
[' ', ' ', ' ']
正则表达式还可以用某些符号表示某种形式的重复,这些符号紧跟在单个字符之后,表示多个这样类似的字符:
import re
s = "abcabcac125xyz "
print(re.search("[a-c]*",s).group()) # 匹配0次或者多次a-c的任意字符 abc acb bac bca……
print(re.search("[a-c]+",s).group()) # 匹配1次或者多次a-c的任意字符
print(re.search("[a-c]?",s).group()) # 匹配0次或者1次a-c的任意字符子串
print(re.search("[a-c]{5}",s).group()) # 匹配m次a-c,[a-c]{5},相当于[a-c][a-c][a-c][a-c][a-c]
print(re.search("[a-c][1-9]{2,4}",s).group()) # 匹配m到n次[a-c][1-9]的字符
结果:
abcabcac
abcabcac
a
abcab
c125
最后是位置相关的符号:
import re
s = "abcabcac125xyz"
print(re.findall("^abca",s))
print(re.findall("xyz$",s))
结果:
['abca']
['xyz']
正则表达式符号详解:https://www.jianshu.com/p/cff9eb797fbc
3.进一步提取
有时候,我们想要对得到的结果进行提取。如下例import re
content = "abcd_output_1994_abcd_1912_abcd"
print(re.search("output_\d{4}",content).group()) # 正则表达式为:"output_\d{4}"
结果:
output_1994
若我们想要对上面这个结果进一步提取出 "1994",那我们可以在正则表达式上给目标加上括号。
import re
content = "abcd_output_1994_abcd_1912_abcd"
print(re.search("output_(\d{4})",content).group(1))
结果:
1994
其中用()括起来的正则表达式的一部分,称为群(group),一个正则表达式有多个群。其中,group(0)是整个正则表达式的结果,group(1)是第一个群,依次类推。
import re
content = "abcd_output_1994_abcd_1912_abcd"
print(re.search("output_(?P<year>\d{4})",content).group("year"))
(?P<名字>……)
来对群命名,通过名字得到结果。group("名字")。
5.4 Python有网瘾
1.HTTP通信简介
人与人之间的沟通就是通信,而说话有底线是约定俗成的协议。
计算机之间的通信就是不同的计算机之间传递信息,计算机通信也要遵守协议。HTTP是最常见的协议。
客户端向服务器发送请求,服务器根据情况向客户端发送回复,有以下几种情况:
-
发送请求
用GET方法发送请求
POST方法用于从客户端向服务器提交数据,请求的后面会附加上要提交的数据。服务器会对POST方法提交的数据进行处理。请求中有一行头信息,类型为Host,说明了想要访问的服务器的地址。 -
发送回复
服务器回复起始行信息
其中,"OK"是对状态码200的描述,表示一切ok。状态码就是200,也是计算机所真正关心的,它代表了服务器回应的类型。200是一切OK,其他的有其他状态码
Content-type说明了主体所包含的资源类型。不同的资源类型
Content-length说明了主体部分的长度,以字节(byte)为单位。
4)主体部分:包含了主体的数据。这里面是Hello World!
这就是简单的客户端到服务器的http交易。我们可以看Python如何进行http通信的。
2.http.client包
Python中的http.client包可用于发出http请求。HTTP请求最重要的信息是主机地址、请求方式、资源路径。只要明确这些信息,再加上http.client包的帮助,就可以发出HTTP请求。
import http.client
host = http.client.HTTPConnection("www.example.com") # 主机地址
host.request("GET","/") # 请求方法 和 资源路径
response = host.getresponse() # 获得回复
print(response.status,response.reason) # 查看回复状态码和状态描述
content = response.read() # 回复的主体内容
print(content)
结果:
200 OK
b'<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset="utf-8" />\n <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n <style type="text/css">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href="https://www.iana.org/domains/example">More information...</a></p>\n</div>\n</body>\n</html>\n'
通过上面的代码得到的结果可以看出,content是网页的html语法源码,将该源码复制成html文件,打开可以看到下面效果3.requests包
实际上,现在的网页爬虫,更常用的是requests包发送请求,与client包相似。而且更加简单易懂。
import requests
req = requests.get("http://www.example.com")
print(req.raise_for_status)
print(req.apparent_encoding)# 非常重要,返回网页编码方式
print(req.encoding) # 非常重要,返回编码方式,对encoding赋值,可赋值为网页的编码方式
req.encoding = req.apparent_encoding
print(req.text) # 访问该网页的源码
"""访问该网页的源码"""
结果:
<bound method Response.raise_for_status of <Response [200]>>
ascii
UTF-8
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
-
beautifulsoup4包
网页的HTML格式有一定规则,通过它的结构,可以处理然后提取信息。这时处理HTML结构的就可以使用Python的beautifulsoup4。
beautifulsoup4的简介:https://www.jianshu.com/p/f0f4c253bb14beautifulsoup4的中文文档https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
5.5 写一个爬虫
网络爬虫是一个浏览网页并从网页上抓取我们想要的信息的程序。
有了前面的铺垫,我们可以编写一个相对复杂的爬虫。
爬取2016年中国前十所大学的排名。
第一,访问网站,获取源码内容。http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html
allUniv = []
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
第二,分析网页内容并提取有用数据到恰当的数据结构中;
def fillUnivList(soup):
data = soup.find_all('tr') # 查找所有tr标签
for tr in data:
ltd = tr.find_all('td') # 在tr中找所有的td标签
if len(ltd) == 0:
continue
singleUniv = []
for td in ltd:
singleUniv.append(td.string) # 提取td中的信息
allUniv.append(singleUniv)
第三,利用数据结构展示或进一步处理数据。
def printUnivList(num):
tplt = "{:^4} {:^10} {:^5} {:^8} {:^10}"
print(tplt.format("排名", "学校名称", "省市", "总分", "培养规模"))
for i in range(num):
u = allUniv[i]
print(tplt.format(u[0], u[1], u[2], u[3], u[6]))
由于大学排名是一个典型的二维数据,因此,采用二维列表存储该排名所涉及的表单数据。完整代码如下:
import requests
from bs4 import BeautifulSoup
allUniv = []
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
def fillUnivList(soup):
data = soup.find_all('tr') # 查找所有tr标签
for tr in data:
ltd = tr.find_all('td') # 在tr中找所有的td标签
if len(ltd) == 0:
continue
singleUniv = []
for td in ltd:
singleUniv.append(td.string) # 提取td中的信息
allUniv.append(singleUniv)
def printUnivList(num): # 格式调整
tplt = "{:^4} {:^10} {:^5} {:^8} {:^10}"
print(tplt.format("排名", "学校名称", "省市", "总分", "培养规模"))
for i in range(num):
u = allUniv[i]
print(tplt.format(u[0], u[1], u[2], u[3], u[6]))
def main(num): # 主程序
url = 'http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html'
html = getHTMLText(url)
soup = BeautifulSoup(html, "html.parser")
fillUnivList(soup)
printUnivList(num)
main(10)
结果:
排名 学校名称 省市 总分 培养规模
1 清华大学 北京市 95.9 37342
2 北京大学 北京市 82.6 36137
3 浙江大学 浙江省 80 41188
4 上海交通大学 上海市 78.7 40417
5 复旦大学 上海市 70.9 25519
6 南京大学 江苏省 66.1 20722
7 中国科学技术大学 安徽省 65.5 18507
8 哈尔滨工业大学 黑龙江省 63.5 25249
9 华中科技大学 湖北省 62.9 23503
10 中山大学 广东省 62.1 23837