上一节我们讲解了requests库,在实现方面可以做到和urllib库相同的程度,但在操作上却更为简单,之前我们一直在讲解如何向Web服务器发送请求,以及处理在请求过程中可能出现的问题。本节课我们将讲解如何从响应的网页源代码中提取出我们想要的信息。即采用正则表达式的方法提取出对我们有用的数据。
正则表达式是处理字符串的强大工具,它有自己特定的语法结构,可以实现字符串的检索、替换、匹配验证等功能。对于爬虫来说,我们可以用正则表达式提取想要的信息。
我们可以打开开源中国提供的正则表达式测试工具http://tool.oschina.net/regex/,输入待匹配的文本,然后选用常用的正则表达式。就可以得到相应的匹配结果。
从图片中我们可以看到我所输入的文本是:Hello,my favorite programming-language is python.my telephone number is 15734679870.And my website is
https://zhihu.com
然后我选择了匹配网址URL,正则表达式变为:[a-zA-z]+://[^\s]*,
最终的匹配结果是:https://zhihu.com。这是一个正则表达式匹配的举例,也就是用一定的规则将特定的文本提取出来。对于URL,开头是协议名称,然后是冒号加双斜线,最后是域名加路径。对于URL,可以使用这个正则表达式匹配:[a-zA-z]+://[^\s]*,用这个正则表达式匹配一个字符串,如果这个字符串包含类似URL的文本,就会被提取出来。这个正则表达式看上去很乱,实际上都是有特定的语法规则的。例如a-z代表匹配任意的小写字母,\s表示匹配任意的空白字符,*就代表匹配前面的字符任意多个。
1.常用的匹配规则
正则表达式不是python独有的,他也可以用在其他编程语言当中。但是python的re库提供了整个正则表达式的实现,利用这个库就可以在python中使用正则表达式。
2.match()
常用的匹配方法——match(),向它传入要匹配的字符串以及正则表达式,就可以检查这个正则表达式是否匹配字符串。
match()方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配就返回None。
示例代码:
import re
string = 'This is a 1234567 demo'
print(len(string))
result = re.match('^This\s\w\s\w\s\d',string)
print(result)
print(result.group())
print(result.span())
运行结果:
首先我们声明了一个字符串叫做string,包含空格、字母和数字。接下来我们写了一个正则表达式:^This\s\w\s\w\s\d,用来匹配这个长字符串。开头的^是匹配字符串的开头,也就是以This开头;然后\s匹配一个空白字符,用来匹配空格字符;\w用来匹配两次字母、数字或下划线,这里匹配了两个字母‘is’;\s又匹配了一个空格;\w匹配了字母‘a’;\s又匹配了一个空格;\d匹配了七个数字‘1234567’。这里其实并没有把目标字符串匹配完,不过依然可以这样匹配,只不过匹配结果短一点而已。
在match()方法中,第一个参数传入了正则式,第二个参数传入了要匹配的字符串。
从输出结果中我们可以看到result是一个re.Match对象,这证明成功匹配。这个对象有两个方法:group()方法可以输出匹配到的内容,结果是‘This is a 1234567’,span()方法可以输出匹配的范围,结果是(0, 17),这就是匹配到的结果字符串在原字符串中的位置范围。
3.匹配目标
我们使用match()方法可以得到匹配到的字符串的内容,但是如果想从字符串中提取一部分内容可以使用( )括号将想提取的子字符串括起来。( )实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应一个分组,调用group()方法传入分组的索引即可获取提取的结果。
示例代码:
import re
string = 'This is a 1234567 demo'
print(len(string))
result = re.match('^This\s\w\s\w\s(\d)',string)
print(result)
print(result.group())
print(result.group(1))
print(result.span())
运行结果:
我们想要将数字部分提取出来,可以将数字部分的正则表达式用括号括起来,然后调用group(1)获取匹配结果。假如正则表达式还有其他( )包括的内容,那么可以依次使用group(2)、group(3)等来获取。
4.通用匹配
我们之前所写的正则表达式比较复杂,出现空白字符就要用\s来匹配,出现数字就要用\d来匹配,这样的工作量很大。所以我们可以使用万能匹配,那就是.*(点星)。其中.(点)可以匹配任意字符(除换行符),*(星)代表匹配前面的字符无限次,所以它们组合到一起就可以匹配任意字符无限次了。我们可以将上面的代码改进一下。
示例代码:
import re
string = 'This is a 1234567 demo'
print(len(string))
result = re.match('^This.*\d',string)
print(result)
print(result.group())
print(result.span())
运行结果:
我们可以看到代码中将中间的部分直接省略,全部用.*来代替,最后加一个结尾字符串即可。运行得到的结果是相同的,但是却极大减少了工作量。
5.贪婪和非贪婪
有时我们使用通用匹配.*时,可能匹配到的结果并不是我们想要的。
示例:
import re
string = 'This is a 1234567 demo'
print(len(string))
result = re.match('^Th.*(\d+).*demo$',string)
print(result)
print(result.group())
print(result.group(1))
print(result.span())
运行结果:
我们发现结果中只出现了一个7,出现这种问题的原因就涉及到了贪婪匹配和非贪婪匹配的问题。在贪婪匹配下,.*会尽可能多的匹配字符。正则表达式中.*后面是\d+,也就是说至少匹配一个数字,但是并没有具体指定到底匹配多少个数字,因此.*就尽可能匹配多的字符,在上面的代码中就把123456匹配了,给\d+留下一个可满足条件的7,最后得到的内容就是7。很明显这样的结果不是我们想要的,有时候匹配结果会莫名其妙地少了一部分内容,所以我们只需要使用非贪婪匹配就可以了。非贪婪匹配的写法是.*?,多了一个?,它所能达到的效果是:
示例代码:
import re
string = 'This is a 1234567 demo'
print(len(string))
result = re.match('^This.*?(\d+).*demo$',string)
print(result)
print(result.group())
print(result.group(1))
print(result.span())
运行结果:
此时我们观察结果可以发现成功获取了1234567,原因就是贪婪匹配可以尽可能多的匹配字符,而非贪婪匹配就是尽可能少的匹配字符。当.*?匹配到This后面的字母和空白之后,再往后就是数字了,而\d+恰好可以匹配后面是数字,所以这样.*?匹配了尽可能少的字符,所以\d+的结果就是1234567。
所以说在做匹配的时候,字符串中间尽量使用非贪婪匹配,也就是.*?来代替.*,以免出现匹配结果缺失的情况。但是我们需要注意:如果匹配的结果在字符串结尾,.*?就有可能匹配不到任何内容了,因为它会尽可能少的匹配字符。
示例代码:
import re
content = 'http://weibo.com/comment/KEraCN'
result1 = re.match('http.*?comment/(.*?)',content)
result2 = re.match('http.*?comment/(.*)',content)
print('result1 ',result1.group(1))
print('result2 ',result2.group(1))
运行结果:
我们可以看到第一个非贪婪通配符什么字符都没有匹配到,而第二个贪婪通配符匹配到了结尾的字符串。
6.修饰符
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。
示例代码:
import re
content = '''This is a
1234567 demo.'''
result = re.match('^This.*?(\d+).*',content)
print(result.group(1))
运行结果:
过程中去抛出了异常,返回结果为None,而我们又调用了group()方法导致AttributeError异常。这是因为.匹配的是除换行外的任意字符,当遇到了换行符时,.*?就不能再匹配了,所以导致了匹配失败我们可以观察到我在字符串中加入了一个换行符,正则表达式还是一样匹配其中的数字。运行。这里只需加一个修饰符re.S,即可修正这个错误。
修改语句为:re.match(‘^This.*?(\d+).*’,content,re.S),这个修饰符的作用是使.匹配包括换行符
在内的所以字符。此时运行结果为1234567。
修饰符:
在网页匹配中常用的有re.S和re.I。
7.转义匹配
字符串里面就我们知道正则表达式定义了许多匹配模式,如.匹配除换行符之外的任意字符,但是如果目标包含.,就需要使用转义匹配了。例如我们在正则表达式中想使用.来匹配.,不想让.匹配任意字符,我们就可以使用转义匹配。
示例代码:
import re
content = 'This is a . 1234567 demo.'
result =re.match('^This.*?a\s\.',content)
print(result.group())
运行结果:
This is a .
8.search()
match()方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败了。它更适合用来检测某个字符串是否符合某个正则表达式的规则。
还有另外一个方法search(),它在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。也就是说,正则表达式可以是字符串的一部分,在匹配时,search()方法会依次扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配内容,如果搜索完了还没有找到,就返回None。
示例代码:
import re
html='''
经典老歌
经典老歌列表
一路有你
沧海一声笑往事随风光辉岁月记事本但愿人长久
'''
results = re.search('
(.*?)',html,re.S)
print(results.group(1),results.group(2))
运行结果:
任贤齐 沧海一声笑
在这段程序中,search()方法匹配到第二个
,将歌手的名字和歌曲名挑去出来。而且使用了修饰符re.S,使.可以匹配换行符。
9.findall()
前面介绍的search()方法只可以返回匹配正则表达式的第一个内容,但是如果想要获取匹配正则表达式的所有内容,就要借助findall()方法。该方法会搜索整个字符串,然后返回匹配正则字符串的所有内容。同样是上面的代码,我们看看最终的结果:
示例代码:
import re
html='''
经典老歌
经典老歌列表
一路有你沧海一声笑往事随风光辉岁月记事本但愿人长久
'''
results = re.findall('
(.*?)',html,re.S)
for result in results:
print(result[0],result[1])
运行结果:
可以看到,返回的列表中的每个元素都是元组类型,我们用对应的索引依次取出即可。
本节课我们就讲这么多,下节课我们将进行网络爬虫实战。
文案:王为广
排版:谭嘉佳
领取专属 10元无门槛券
私享最新 技术干货