预计阅读本页时间:-
了解面向对象编程的基础之后,我们就可以利用Python中多种多样的对象了。这些对象能提供丰富的功能,正如本章我们将看到的文件读写、时间日期管理、正则表达式和网络爬虫。熟悉了这些功能强大的对象后,我们就可以实现很多有用的功能了。用程序来实现这些功能,并在电脑上实际运行,才是编程的趣味所在。
5.1 存储
1.文件
我们知道,Python中的数据都保存在内存中。当电脑断电时,就好像患了失忆症,内存中的数据就会消失。另一方面,如果Python程序运行结束,那么分配给这个程序的内存空间也会清空。为了长期持续地存储,Python必须把数据存储在磁盘中。这样,即使断电或程序结束,数据依然存在。
磁盘以文件为单位来存储数据。对于计算机来说,数据的本质就是有序的二进制数序列。如果以字节为单位,也就是每8位二进制数序列为单位,那么这个数据序列就称为文本。这是因为,8位的二进制数序列正好对应ASCII编码中的一个字符。而Python能够借助文本对象来读写文件。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
在Python中,我们可以通过内置函数open来创建文件对象。在调用open时,需要说明文件名,以及打开文件的方式:
f = open(文件名,方式)
文件名是文件存在于磁盘的名字,打开文件的常用方式有:
"r" # 读取已经存在的文件 "w" # 新建文件,并写入 "a" # 如果文件存在,那么写入到文件的结尾。如果文件不存在,则新建文件并写入
例如:
>>>f = open("test.txt","r")
就是用只读的方式,打开了一个名为test.txt的文件。
通过上面返回的对象,我们可以读取文件:
content = f.read(10) # 读取10个字节的数据 content = f.readline() # 读取一行 content = f.readlines() # 读取所有行,储存在列表中,每个元素是一行。
如果以"w"或"a"方式打开,那么我们可以写入文本:
f = open("test.txt", "w") f.write("I like apple") # 将"I like apple"写入文件
如果想写入一行,则需要在字符串末尾加上换行符。在UNIX系统中,换行符为"\n"。在Windows系统中,换行符为"\r\n"。
f.write("I like apple\n") # UNIX f.write("I like apple\r\n") # Windows
打开文件端口将占用计算机资源,因此,在读写完成后,应该及时的用文件对象的close方法关闭文件:
f.close()
2.上下文管理器
文件操作常常和上下文管理器一起使用。上下文管理器(context manager)用于规定某个对象的使用范围。一旦进入或者离开该使用范围,则会有特殊操作被调用,比如为对象分配或者释放内存。上下文管理器可用于文件操作。对于文件操作来说,我们需要在读写结束时关闭文件。程序员经常会忘记关闭文件,无谓的占用资源。上下文管理器可以在不需要文件的时候,自动关闭文件。
下面是一段常规的文件操作程序:
# 常规文件操作 f = open("new.txt", "w") print(f.closed) # 检查文件是否打开 f.write("Hello World!") f.close() print(f.closed) # 打印True
如果我们加入上下文管理器的语法,就可以把程序改写为:
# 使用上下文管理器 with open("new.txt", "w") as f: f.write("Hello World!") print(f.closed)
第二段程序就使用了with...as...结构。上下文管理器有隶属于它的程序块,当隶属的程序块执行结束时,也就是语句不再缩进时,上下文管理器就会自动关闭文件。在程序中,我们调用了f.closed属性来验证是否已经关闭。通过上下文管理器,我们相当于用缩进来表达文件对象的打开范围。对于复杂的程序来说,缩进的存在能让程序员更清楚地意识到文件在哪些阶段打开,减少忘记关闭文件的可能性。
上面的上下文管理器基于f对象的__exit__()特殊方法。使用上下文管理器的语法时,Python会在进入程序块之前调用文件对象的__enter__()方法,在结束程序块的时候调用文件对象的__exit__()方法。在文件对象的__exit__()方法中,有self.close()语句。因此,在使用上下文管理器时,我们就不用明文关闭文件了。
任何定义了__enter__()方法和__exit__()方法的对象都可以用于上下文管理器。下面,我们自定义一个类Vow,并定义它的__enter__()方法和__exit__()方法。因此,由Vow类的对象可以用于上下文管理器:
class Vow(object): def __init__(self, text): self.text = text def __enter__(self): self.text = "I say: " + self.text # 增加前缀 return self # 返回一个对象 def __exit__(self,exc_type,exc_value,traceback): self.text = self.text + "!" #增加后缀 with Vow("I'm fine") as myVow: print(myVow.text) print(myVow.text)
运行结果如下:
I say: I'm fine I say: I'm fine!
初始化对象时,对象的text属性是"I'm fine"。我们可以看到,在进入上下文和离开上下文时,对象调用了__enter__()方法和__exit__()方法,从而造成对象的text属性改变。
__enter__()返回一个对象。上下文管理器会使用这一对象作为as所指的变量。我们自定义的__enter__()返回的是self,也就是新建的Vow类对象本身。在__enter__()中,我们为text属性增加了前缀"I say:"。在__exit__()中,我们为text属性增加了后缀"!"。
值得注意的是, __exit__()有四个参数。当程序块中出现异常时,__exit__()参数中的exc_type、exc_value、traceback用于描述异常。我们可以根据这三个参数进行相应的处理。如果正常运行结束,则这三个参数都是None。
3.pickle包
我们能把文本存于文件。但Python中最常见的是对象,当程序结束或计算机关机时,这些存在于内存的对象会消失。那么,我们能否把对象保存到磁盘上呢?
利用Python的pickle包就可以做到这一点。英文里,pickle是腌菜的意思。大航海时代的海员们常把蔬菜做成腌菜,装在罐头里带着走。Python中的pickle也有类似的意思。通过pickle包,我们可以把某个对象保存下来,再存成磁盘里的文件。
实际上,对象的存储分为两步。第一步,我们将对象在内存中的数据直接抓取出来,转换成一个有序的文本,即所谓的序列化(Serialization)。第二步,将文本存入文件。等到需要时,我们从文件中读出文本,再放入内存,就可以获得原有的对象。下面是一个具体的例子,首先是第一步序列化,将内存中的对象转换为文本流:
import pickle class Bird(object): have_feather = True reproduction_method = "egg" summer = Bird() # 创建对象 pickle_string = pickle.dumps(summer) # 序列化对象
使用pickle包的dumps()方法可以将对象转换成字符串的形式。随后我们用字节文本的存储方法,将该字符串储存在文件。继续第二步:
with open("summer.pkl", "wb") as f: f.write(pickle_string)
上面程序故意分成了两步,以便更好地展示整个过程。其实,我们可以使用dump()的方法,一次完成两步:
import pickle class Bird(object): have_feather = True reproduction_method = "egg" summer = Bird() with open("summer.pkl", "w") as f: pickle.dump(summer, f) # 序列化并保存对象
对象summer将存储在文件summer.pkl中。有了这个文件,我们就可以在必要的时候读取对象了。读取对象与存储对象的过程正好相反。首先,我们从文件中读出文本。然后使用pickle的loads()方法,将字符串形式的文本转换为对象。我们也可以使用pickle的load()的方法,将上面两步合并。
有时候,仅仅是反向恢复还不够。对象依赖于它的类,所以Python在创建对象时,需要找到相应的类。因此当我们从文本中读取对象时,程序中必须已经定义过类。对于Python总是存在的内置类,如列表、词典、字符串等,不需要再在程序中定义。但对于用户自定义的类,就必须要先定义类,然后才能从文件中载入该类的对象。下面是一个读取对象的例子:
import pickle class Bird(object): have_feather = True reproduction_method = "egg" with open("summer.pkl", "rb") as f: summer = pickle.load(f) print(summer.have_feather) # 打印True
5.2 一寸光阴
1.time包
计算机可以用来计时。从硬件上来说,计算机的主板上有一个计时的表。我们可以手动或者根据网络时间来调表。这块表有自己的电池,所以即使断电,表也不会停。在硬件的基础上,计算机可以提供挂钟时间(Wall Clock Time)。挂钟时间是从某个固定时间起点到现在的时间间隔。对于UNIX系统来说,起点时间是1970年1月1日的0点0分0秒。其他的日期信息都是从挂钟时间计算得到的。此外,计算机还可以测量CPU实际运行的时间,也就是处理器时间(Processor Clock Time),以测量计算机性能。当CPU处于闲置状态时,处理器时间会暂停。
我们能通过Python编程来管理时间和日期。标准库的time包提供了基本的时间功能。下面使用time包:
import time print(time.time()) # 挂钟时间,单位是秒
还能借助模块time测量程序运行时间。比如:
import time
start = time.clock()
for i in range(100000):
print(i**2)
end = time.clock()
print(end - start)
上面的程序调用了两次clock()方法,从而测量出镶嵌其间的程序所用的时间。在不同的计算机系统上,clock()的返回值会有所不同。在UNIX系统上,返回的是处理器时间。当CPU处于闲置状态时,处理器时间会暂停。因此,我们获得的是CPU运行时间。在Windows系统上,返回的则是挂钟时间。
方法sleep()可以让程序休眠。根据sleep()接收到的参数,程序会在某时间间隔之后醒来继续运行:
import time print("start") time.sleep(10) # 休眠10秒 print("wake up")
time包还定义了struct_time对象。该对象将挂钟时间转换为年、月、日、时、分、秒等,存储在该对象的各个属性中,比如tm_year、tm_mon、tm_mday……下面几种方法可以将挂钟时间转换为struct_time对象:
st = time.gmtime() #返回struct_time格式的UTC时间 st = time.localtime() #返回struct_time格式的当地时间,当地时区根据系 #统环境决定。
我们也可以反过来,把一个struct_time对象转换为time对象:
s = time.mktime(st) # 将struct_time格式转换成挂钟时间
2.datetime包
datetime包是基于time包的一个高级包,用起来更加便利。datetime可以理解为由date和time两个部分组成。date是指年、月、日构成的日期,相当于日历。time是指时、分、秒、毫秒构成的一天24小时中的具体时间,提供了与手表类似的功能。因此,datetime模块下有两个类:datetime.date类和datetime.time类。你也可以把日历和手表合在一起使用,即直接调用datetime.datetime类。这里只介绍综合性的datetime.datetime类,单独的datetime.date和datetime.time类与之类似。
一个时间点,比如2012年9月3日21时30分,我们可以用如下方式表达:
import datetime t = datetime.datetime(2012,9,3,21,30) print(t)
对象t有如下属性:
hour, minute, second, millisecond,microsecond:小时、分、秒、毫秒、微秒 year, month, day, weekday:年、月、日、星期几
借助datetime包,我们还可以进行时间间隔的运算。它包含一个专门代表时间间隔对象的类,即timedelta。一个datetime.datetime的时间点加上一个时间间隔,就可以得到一个新的时间点。比如今天的上午3点加上5个小时,就可以得到今天的上午8点。同理,两个时间点相减可以得到一个时间间隔:
import datetime t = datetime.datetime(2012,9,3,21,30) t_next = datetime.datetime(2012,9,5,23,30) delta1 = datetime.timedelta(seconds = 600) delta2 = datetime.timedelta(weeks = 3) print(t + delta1) # 打印2012-09-03 21:40:00 print(t + delta2) # 打印2012-09-24 21:30:00 print(t_next - t) # 打印2 days, 2:00:00
在给datetime.timedelta传递参数时,除了上面的秒(seconds)和星期(weeks)外,还可以是天(days)、小时(hours)、毫秒(milliseconds)、微秒(microseconds)。
两个datetime对象能进行比较运算,以确定哪个时间间隔更长。比如使用上面的t和t_next:
print(t >t_next) # 打印False
3.日期格式
对于包含有时间信息的字符串来说,我们可以借助datetime包,把它转换成datetime类的对象,比如:
from datetime import datetime str = "output-1997-12-23-030000.txt" format = "output-%Y-%m-%d-%H%M%S.txt" t = datetime.strptime(str, format) print(t) # 打印1997-12-23 03:00:00
包含有时间信息的字符串是"output-1997-12-23-030000.txt",是一个文件名。字符串format定义了一个格式。这个格式中包含了几个由%引领的特殊字符,用来代表不同时间信息。%Y表示年份、%m表示月、%d表示日、%H表示24小时制的小时、%M表示分、%S表示秒。通过strptime方法,Python会把需要解析的字符串往格式上凑。比如说,在格式中%Y的位置,正好看到"1997",就认为1997是datetime对象t的年。以此类推,就从字符串中获得了t对象的时间信息。
反过来,我们也可以调用datetime对象的strftime方法,将一个datetime对象转换为特定格式的字符串,比如:
from datetime import datetime format = "%Y-%m-%d %H:%M" t = datetime(2012,9,5,23,30) print(t.strftime(format)) # 打印2012-09-05 23:30
可以看到,格式化转化的关键是%号引领的特殊符号。这些特殊符号有很多种,分别代表不同的时间信息。常用的特殊符号还有:
%A: 表示英文的星期几,如Sunday、Monday…… %a:简写的英文星期几,如Sun、Mon…… %I:表示小时,12小时制 %p:上午或下午,即AM或PM %f:表示毫秒,如2、0014、000001
但如果想在格式中表达%这个字符本身,而不是特殊符号,那么可以使用%%。
5.3 看起来像那样的东西
1.正则表达式
正则表达式(Regular Expression)的主要功能是从字符串(string)中通过特定的模式,搜索希望找到的内容。前面,我们已经简单介绍了字符串对象的一些方法。我们可以通过这些方法来实现简单的搜索功能,例如,从字符串"I love you"中搜索"you"这一子字符串。但有些时候,我们只是想要找到符合某种格式的字符串,而不是具体的"you"。类似的例子还有很多,比如说找到小说中的所有人名,再比如说想找到字符串中包含的数字。幸好,这种格式化的搜索可以写成正则表达式。Python中可以使用包re来处理正则表达式。下面是一个简单的应用,目的是找到字符串中的数字:
import re m = re.search("[0-9]","abcd4ef") print(m.group(0))
re.search()接收两个参数,第一个参数"[0-9]"就是我们所说的正则表达式,它告诉Python,“听着,我想从字符串中找从0到9的任意一个数字字符”。
re.search()如果从第二个参数中找到符合要求的子字符串,就返回一个对象m,你可以通过m.group()的方法查看搜索到的结果。如果没有找到符合要求的字符,则re.search()会返回None。
除了search()方法外,re包还提供了其他搜索方法,它们的功能有所差别:
m = re.search(pattern, string) # 搜索整个字符串,直到发现符合的子字符串 m = re.match(pattern, string) # 从头开始检查字符串是否符合正则表达式。 # 必须从字符串的第一个字符开始就相符
我们可以从这两个函数中选择一个进行搜索。上面的例子中,如果使用re.match()的话,则会得到None,因为字符串的起始为"a",不符合"[0-9]"的要求。再一次,我们可以使用m.group()来查看找到的字符串。
我们还可以在搜索之后将搜索到的子字符串进行替换。下面的sub()利用正则pattern在字符串string中进行搜索。对于搜索到的字符串,用另一个字符串replacement进行替换。函数将返回替换后的字符串:
str = re.sub(pattern, replacement, string)
此外,常用的方法还有
re.split() # 根据正则表达式分割字符串,将分割后的所有子字符串 # 放在一个表(list)中返回 re.findall() # 根据正则表达式搜索字符串,将所有符合条件的子字符串 # 放在一个表(list)中返回
2.写一个正则表达式
正则表达式的功能其实非常强大,关键在于如何写出有效的正则表达式。我们先看正则表达式的常用语法。正则表达式用某些符号代表单个字符:
. # 任意的一个字符 a|b # 字符a或字符b [afg] # a或者f或者g的一个字符 [0-4] # 0-4范围内的一个字符 [a-f] # a-f范围内的一个字符 [^m] # 不是m的一个字符 \s # 一个空格 \S # 一个非空格 \d # 一个数字,相当于[0-9] \D # 一个非数字,相当于[^0-9] \w # 数字或字母,相当于[0-9a-zA-Z] \W # 非数字非字母,相当于[^0-9a-zA-Z]
正则表达式还可以用某些符号来表示某种形式的重复,这些符号紧跟在单个字符之后,就表示多个这样类似的字符:
* # 重复超过0次或更多次
+ # 重复1次或超过1次
? # 重复0次或1次
{m} # 重复m次。比如,a{4}相当于aaaa,再比如,[1-3]{2}相当于[1-3][1-3]
{m, n} # 重复m到n次。比如说a{2, 5}表示a重复2到5次。
# 小于m次的重复,或者大于n次的重复都不符合条件
下面是重复符号的例子:
正则表达 相符的字符串举例 不相符字符串举例 [0-9]{3,5} "9678" "12", "1234567" a?b "b","ab" "cb" a+b "aaaaab" "b"
最后,还有位置相关的符号:
^ # 字符串的起始位置 $ # 字符串的结尾位置
下面是位置符号的一些例子:
正则表达 相符的字符串举例 不相符的字符串
^ab.*c$ abeec cabeec
3.进一步提取
有的时候,我们想在搜索的同时,对结果进一步提炼。比如说,我们从下面一个字符串中提取信息:
content = "abcd_output_1994_abcd_1912_abcd"
如果我们把正则表达式写成:
"output_\d{4}"
那么用search()方法可以找到"output_1994"。但如果我们想进一步提取出1994本身,则可以在正则表达式上给目标加上括号:
output_(\d{4})
括号()包围了一个小的正则表达式\d{4}。这个小的正则表达式能从结果中进一步筛选信息,即四位的阿拉伯数字。用括号()圈起来的正则表达式的一部分,称为群(group)。一个正则表达式中可以有多个群。
我们可以group(number)的方法来查询群。需要注意的是,group(0)是整个正则表达的搜索结果。group(1)是第一个群,以此类推:
import re m = re.search("output_(\d{4})", "output_1986.txt") print(m.group(1)) # 将找到4个数字组成的1986
我们还可以将群命名,以便更好地使用group查询:
import re m = re.search("output_(?P<year>\d{4})", "output_1986.txt") #(?P<name>...) 为group命名 print(m.group("year")) # 打印1986
上面的(?P<year>…)括住了一个群,并把它命名为year。用这种方式来产生群,就可以通过"year"这个键来提取结果。
5.4 Python有网瘾
1.HTTP通信简介
通信是一件奇妙的事情。它让信息在不同的个体间传递。动物们散发着化学元素,传递着求偶信息。人则说着甜言蜜语,向情人表达爱意。猎人们吹着口哨,悄悄地围拢猎物。服务生则大声地向后厨吆喝,要加两套炸鸡和啤酒。红绿灯指挥着交通,电视上播放着广告,法老的金字塔刻着禁止进入的诅咒。有了通信,每个人都和周围的世界连接。在通信这个神秘的过程中,参与通信的个体总要遵守特定的协议(Protocol)。在日常交谈中,我们无形中使用约定俗成的语法。如果两个人使用不同的语法,那么就是以不同的协议来交流,最终会不知所云。
计算机之间的通信就是在不同的计算机间传递信息。所以,计算机通信也要遵循通信协议。为了多层次地实现全球互联网通信,计算机通信也有一套多层次的协议体系。HTTP协议是最常见的一种网络协议。它的全名是the Hypertext Transfer Protocol,即超文本传输协议。HTTP协议能实现文件,特别是超文本文件的传输。在互联网时代,它是应用最广的互联网协议之一。事实上,当我们访问一个网址时,通常会在浏览器中输入http打头的网址,如http://www.example.com。这里的http字样,说的就是要用HTTP协议访问相应网站。
HTTP的工作方式类似于快餐点单:
1)请求(request):顾客向服务员提出请求“来个鸡腿汉堡”。
2)回复(response):服务员根据情况,回应顾客的请求。
根据情况不同,服务员的回应可能有很多种,比如:
- 服务员准备鸡腿汉堡,将鸡腿汉堡交给顾客。(一切OK)
- 服务员发现自己工作在甜品站。他让顾客前往正式柜台点单。(重新定向)
- 服务员告诉顾客鸡腿汉堡没有了。(无法找到)
交易结束后,服务员就将刚才的交易抛到脑后,准备服务下一位顾客。

图5-1 HTTP服务器
计算机发出请求会遵照下面的格式:
GET /index.html HTTP/1.1 Host: www.example.com
在起始行中,有三段信息:
- GET方法。用于说明想要服务器执行的操作。
- /index.html 资源的路径。这里指向服务器上的index.html文件。
- HTTP/1.1协议的版本。HTTP第一个广泛使用的版本是1.0,当前版本为1.1。
早期的HTTP协议只有GET方法。遵从HTTP协议,服务器接收到GET请求后,会将特定资源传送给客户。这类似于客户点单,并获得汉堡的过程。GET方法之外,最常用的是POST方法。它用于从客户端向服务器提交数据,请求的后面会附加上要提交的数据。服务器会对POST方法提交的数据进行一定的处理。样例请求中有一行头信息。这个头信息的类型是Host,说明了想要访问的服务器的地址。
服务器在接收到请求之后,会根据程序,生成对应于该请求的回复,比如:
HTTP/1.1 200 OK Content-type: text/plain Content-length: 12 Hello World!
回复的起始行包含三段信息:
- HTTP/1.1:协议版本
- 200:状态码(status code)
- OK:状态描述
OK是对状态码200的文字描述,它只是为了便于人类的阅读。电脑只关心三位的状态码(Status Code),即这里的200。200表示一切OK,资源正常返回。状态码代表了服务器回应的类型。其他常见的状态码还有很多,例如:
- 302,重新定向(Redirect):我这里没有你想要的资源,但我知道另一个地方xxx有,你可以去那里找。
- 404,无法找到(Not Found):我找不到你想要的资源,无能为力。
下一行Content-type说明了主体所包含的资源的类型。根据类型的不同,客户端可以启动不同的处理程序(比如显示图像文件、播放声音文件等)。下面是一些常见的资源:
- text/plain:普通文本
- text/html:HTML文本
- image/jpeg:jpeg图片
- image/gif:gif图片
Content-length说明了主体部分的长度,以字节(byte)为单位。
剩下的是回复的主体部分,包含了主要的文本数据。这里是普通类型的一段文本,即:
Hello World!
通过一次HTTP交易,客户端从服务器那里获得了自己请求的资源,即这里的文本。上面是对HTTP协议工作过程的一个简要介绍,省略了很多细节。以此为基础,我们可以看看Python是如何进行HTTP通信的。
2.http.client包
Python标准库中的http.client包可用于发出HTTP请求。在上一节中我们已经看到,HTTP请求最重要的一些信息是主机地址、请求方法和资源路径。只要明确这些信息,再加上http.client包的帮助,就可以发出HTTP请求了。
import http.client conn = http.client.HTTPConnection("www.example.com") # 主机地址 conn.request("GET", "/") # 请求方法和资源路径 response = conn.getresponse() # 获得回复 print(response.status, response.reason)# 回复的状态码和状态描述 content = response.read() # 回复的主体内容 print(content)
如果网络正常,那么上面的程序将访问网址,并获得对应位置的超文本文件。在浏览器中,这个超文本文件显示为图5-2所示内容。

图5-2 超文本文件显示的内容
5.5 写一个爬虫
有了前面四个小节的准备,我们可以用Python来写一个相对复杂的程序了,即一个网络爬虫。这段程序能自动浏览网页,并从网页上抓取我们想要的信息。网络爬虫应用很广,很多搜索引擎都是用爬虫抓取并分析网页信息,从而让不同的网页对应不同的搜索关键字。许多研究互联网行为的学者也会用爬虫抓取网络信息,用来进一步分析人们使用互联网的行为。还有一些下载网络视频或图片的软件,也是基于爬虫来完成主要工作的。很多时候,爬虫可以非常复杂,运行起来也相当耗时。这里,我们想用爬虫做一件简单的事,即让它访问笔者的博客首页,提取出最近文章的发表日期和阅读量。
第一步当然是访问博客首页,获得首页的内容。根据5.4节的内容,这非常简单。笔者的博客的地址是www.cnblogs.com/vamei,主机地址是www.cnblogs.com,资源位置是/vamei。这个页面是一个超文本文件,所以我们用HTTP协议访问:
import http.client conn = http.client.HTTPConnection("www.cnblogs.com") # 主机地址 conn.request("GET", "/vamei") # 请求方法和资源路径 response = conn.getresponse() # 获得回复 content = response.read() # 回复的主体内容 content = content.split("\r\n") # 分割成行
这里的content是列表,列表的每个元素是超文本的一行。对于我们所关心的信息来说,它们存在的行看起来是下面的样子:
<div class="postDesc">posted @ 2014-08-12 20:55 Vamei 阅读(6221) 评论(11) <a href ="http://i.cnblogs.com/EditPosts.aspx?postid=3905833" rel="nofollow">编辑</a></div>
我们想要的信息,如2014-08-12 20:55,以及阅读量6221镶嵌在一串文字中。要想提取出类似这样的信息,我们很自然地想到了5.3节的正则表达式:
import re pattern = "posted @ (\d{4}-[0-1]\d-{0-3}\d [0-2]\d:[0-6]\d) Vamei 阅读\((\d+)\) 评论" for line in content: m = re.search(pattern, line) if m != None: print(m.group(1), m.group(2))
把两段程序合在一起,将打印出如下结果:
2016-03-23 14:08 9622 2016-03-23 07:12 1787 2016-03-22 11:20 1161 2015-05-11 13:08 5864 2014-10-01 12:50 5584 2014-09-01 05:41 9073 2014-08-20 10:48 6971 2014-08-16 11:51 5682 2014-08-13 22:43 7119 2014-08-12 20:55 6221
根据本章的内容,你还可以把日期转换成日期对象,进行更复杂的操作,如查询文章是星期几发表的。你还可以把上面的内容写入文件,长久的保存起来。可以看到,这个简单的程序中包含了不同方面的知识内容。编程的乐趣就在于此,通过对基本知识的组合,创造出新颖有趣的功能。