网络爬虫之Requests库入门
信息组织及分析之Beautiful Soup
从先前的文章中已经介绍了爬虫的流程,先用requests框架获取HTML文本,用beautifulsoup4库的BeautifulSoup类解析标签树,通过类方法获取指定结构下的内容,此时我们还需要在内容中找到我们最想要的部分,正则表达式便是最便捷且有效的文本过滤方式。
正则表达式(regex)
正则表达式是一种文本特征的匹配模板,简洁表达一组字符串典型特征用于文本内容过滤获取我们想要的特定部分。re库是 Python的正则表达式标准库,下面将介绍正则表达式的书写格式和re库的基本使用。
Python中的正则表达式匹配流程
- import re导入re库
- re.compile()方法用字符串构建Regex对象
- 向Regex对象的search()方法传入字符串,返回包含所需字符串的Match对象
- 调用Match对象的group()方法,返回匹配文本的字符串
# 简单的正则表达式匹配
import re
# 用complie函数构建正则表达式对象
phoneRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
# Regex对象调用search函数对字符串进行匹配返回一个Match对象
matchObject = phoneRegex.search("School's number is 123-456-7890")
# 以上代码re.compile+regex.search等价于re.search()
matchObject = re.search(r'\d\d\d-\d\d\d-\d\d\d\d',"School's number is 123-456-7890")
print(type(matchObject))
print(matchObject.group())
注意:在第2步中用在字符串前面用r表明是原始字符串
为了表达一些特殊字符字符串引入了转义的机制;而正则表达式同样需要使用部分字符如*、()、?等来表示特定的解析方式,在匹配这些字符原本内涵时需要加转义字符\。在python程序编译时,字符串都会被解析器进行转义检查获取原始字符串,在compiler方法中对正则表达式字符串再进行正则转义。
# 正则转义
string = 'd:\\abc\\n'
print(string)
import re
tran_string1 = re.match('d:\\\\',string).group()
print(tran_string1)
tran_string2 = re.match(r'd:\\',string).group()
print(tran_string2)
- 分组匹配
如果我们希望提取出匹配内容的特定部分如电话号码中的区号、姓名中的姓,可以利用()对正则表达式进行分组。Metch对象中有一个regs属性记录了整体匹配和分组匹配的初末索引值元组,第一个便是整体匹配,因此输入group(1)会返回整个匹配的文本。
# 分组匹配
import re
# 利用()对正则表达式进行分组
phoneRegex = re.compile(r'(\d\d\d-)(\d\d\d)-(\d\d\d\d)')
# search函数是找到第一个匹配项
matchObject = phoneRegex.search("School's number is 123-456-7890")
# regex.group()方法传入0和不传入参数都是返回整个匹配的文本
print(matchObject.group())
print(matchObject.group() == matchObject.group(0))
print(matchObject.group(1))
# regex.group()方法获取包含所有分组的元组
print(matchObject.groups())
for group in matchObject.groups():
print(group)
- 管道匹配
# 管道匹配用于匹配多个表达式中的一个(hero或者是people)
import re
pileRegex = re.compile(r'hero|people')
matchObject = pileRegex.search("hero and people")
print(matchObject.group())
matchObject = pileRegex.search("people and hero")
print(matchObject.group())
利用管道匹配符号|
和分组匹配可以简易表达某个前缀下的多个表达式
# 管道匹配和分组匹配构建前缀li下的lihaha、lihuanhuan、lidada
import re
qianRegex = re.compile(r'li(haha|huanhuan|dada)')
matchObject = qianRegex.search("I'm lihaha")
print(matchObject.group())
matchObject = qianRegex.search("I'm lihuanhuan" )
print(matchObject.group())
print(matchObject.group(1))
- 可选匹配(匹配零次或者一次)
在有些情况下,信息并不总是按照格式那么完备,存在部分缺省如电话号码中没有了区号,我们如何兼备有区号和没有区号的电话,就可以在电话模板中将区号设为可选。
# 可选匹配
import re
kexuanRegex = re.compile(r'(\d\d\d-)?\d\d\d-\d\d\d\d')
matchObject = kexuanRegex.search("123-456-7890")
print(matchObject.group())
matchObject = kexuanRegex.search("456-7890")
print(matchObject.group())
- 可选匹配+重复匹配(匹配零次或者多次)
用星号*
实现零次或者多次匹配,指定部分完全不存在或者多次重复
# 可选匹配+重复匹配(匹配零次或者多次)
import re
xingRegex = re.compile(r'Ba(wo)*man')
matchObject = xingRegex.search("Baman")
print(matchObject.group())
print(xingRegex.search("Bawowoman").group() is not None)
print(xingRegex.search("Bawowowoman").group() is not None)
重复匹配
用加号+
可以用于匹配一次或者多次,零次是不匹配的
# 重复匹配
import re
chongRegex = re.compile(r'Ba(wo)+man')
matchObject = chongRegex.search("Baman")
print(matchObject is None)
print(chongRegex.search("Bawoman").group() is not None)
指定次数匹配
在分组后面利用花括号指定分组出现的特定次数
# 指定次数匹配
import re
limitRegex = re.compile(r'Ba(wo){3}man')
matchObject = chongRegex.search("Bawowowoman")
print(matchObject.group())
print(limitRegex.search("Bawowoman") is None)
- 贪心匹配与非贪心匹配
Python中的正则表达式默认是贪心匹配模式,可以在特定模块后加上?来声明非贪心匹配
# 贪心匹配与非贪心匹配
import re
tanxinRegex = re.compile(r'Ba(wo){3,5}man')
matchObject = tanxinRegex.search("Bawowowowowoman")
print(matchObject.group())
feitanxinRegex = re.compile(r'Ba(wo){3,5}?man')
matchObject = feitanxinRegex.search("Bawowowoman")
print(matchObject.group())
字符集分类
前面展示了正则表达式中使用了部分字符作为功能性字符对格式进行限定,有些字符是同一个类别比如0-9为数字字符,可以用表示性字符\d来聚合多个数字字符。而\w可以匹配单词字符,\s可以匹配空白字符。
# 字符集匹配
import re
zifuRegex = re.compile(r'\d+\s\w+')
matchAll = zifuRegex.findall("12 mouths, 4 seasons,10 people")
print(matchAll)
search函数是从字符串中第一次找到匹配项并返回,findall函数是在整个字符串中完整查找
除了既定的字符集外我们可以建立自己的字符分类,用[]进行构建,同时在方括号内普通的正则表达式符号不会被解析。
# 字符分类
import re
zifuRegex = re.compile(r'[0-9]+')
matchAll = zifuRegex.findall("12 mouths, 4 seasons,10 people")
print(matchAll)
通配字符是正则表达式中表示字符包容性最强的,它匹配除了换行符之外的所有字符,可以产生新的搭配组合。
- 用
.*
匹配除了换行符外所有字符
如果想要提取出某个标注后面所有的字符,可以用.*
匹配所有字符
import re
tongRegex = re.compile(r'感想:(.*)')
matchAll = tongRegex.search("感想:这篇文章怎么这么长啊!")
print(matchAll.group(1))
- 用
.*
匹配所有字符
通过传入re.DOTALL作为re.compile()的第二个参数,可以匹配所有字符包含换行符。
# 匹配所有字符
import re
noLineRegex = re.compile(r'.*')
print(noLineRegex.search('I\n love you').group())
lineRegex = re.compile(r'.*',re.DOTALL)
print(lineRegex.search('I\n love you').group())
限定始末位置匹配
当我们想要在字符串中匹配出以某个模式开始或以某个模式结束的内容,可以使用插入符号(^)和美元符号($)来分别限定。
### 始末匹配
import re
# 初匹配python
shiRegex = re.compile(r'(^python).*')
matchObject = shiRegex.search("python hello")
print(matchObject.group())
matchObject = shiRegex.search("hello python")
print(matchObject is None)
# 末匹配python
moRegex = re.compile(r'.*(python$)')
matchObject = moRegex.search("python hello")
print(matchObject is None)
matchObject = moRegex.search("hello python")
print(matchObject.group())
# 初末限定匹配
chumoRegex = re.compile(r'^s(\d+)s$')
matchObject = chumoRegex.search("s12315s")
print(matchObject.group())
不区分大小写字母的匹配
正则表达式是大小写敏感进行匹配的,我们可以设定re.comiler()方法的第二个参数为re.IGNORECASE或re.I来满足我们只关心字母的情况。
# 不区分大小写
import re
ignoreRegex = re.compile(r'^ems(\d+)',re.I)
matchObject = ignoreRegex.search("EMS1234")
print(matchObject.group())
正则表达式中的注释
对复杂的正则表达式进行管理的时候我们可以将原始字符串用三重引号'''创建多行字符串,并向re.compile方法的第二个参数传入re.VERBOSE来忽略正则表达式字符串中的空白符和注释。
# 正则表达式中的注释
import re
phoneRegex = re.compile(r'''(
(\d{3}|\(\d{3}\))? #area code
(\s|-|\.)? #separator
(\d{3}) #first 3 digits
(\s|-|\.) #separator
(\d{4}) #last 4 digits
)''',re.VERBOSE)
matchObject = phoneRegex.search("my phone is 123-456-7890")
print(matchObject.group())