做者:xiaoyu
微信公众号:Python数据科学
知乎:Python数据分析师html
当完成了网页html的download以后,下一步固然是从网页中解析咱们想要的数据了。那如何解析这些网页呢?Python中有许多种操做简单且高效的工具能够协助咱们来解析html或者xml,学会这些工具抓取数据是很容易了。python
说到爬虫的html/xml解析(如今网页大部分都是html),可以使用的方法实在有不少种,如:正则表达式
其实也不止这几种,还有不少,那么到底哪种最好呢?这个很难说,萝卜白菜各有所爱,这些方法各有特点,只能说选择一款你用着顺手的。博主将会陆续给你们介绍这些好用的解析器,可是本篇从正则表达式
开始。express
那是否是只要掌握一种就能够了?用不着会那么多吧。确实,熟练掌握一种也能够完成数据的抓取,但随着你解析网页的数量增多,你会发现有时候使用多种方法配合解析网页会更简单,高效,由于这些方法各有特点,不一样环境下发挥的做用不同。所以,建议你们熟练掌握至少两种为佳,这样当你面对复杂结构网页的时候,解析方法会更灵活。编程
好了,开始咱们的解析之旅吧!微信
正则表达式
(regular expression)简称(regex), 是一种处理字符串的强大工具。它做为一种字符串的匹配模式,用于查看指定字符串是否存在于被查找字符串中,替换指定字符串,或是经过匹配模式查找指定字符串。正则表达式在不一样的语言里面,语法也基本是相同的,也就是说学会了一种语言的正则,再学习其它的就很快了。网络
其主要的匹配过程
是:并发
好了,让咱们看看Python正则表达式的语法
:函数
别着急,开始都是这样的(固然会的小伙伴能够直接跳过)。下面看几个例子,你立刻就学会了。工具
咱们举一个常遇到的一个例子。好比,一我的的邮箱是这样的lixiaomei@qq.com
,那么咱们如何从一大堆的字符串把它提取出来呢?
根据正则语法,咱们能够这样来定义一个pattern:\w+@\w+\.com
为何这么定义呢?让咱们来看看。
"\w"
的意思是单词字符[A-Za-z0-9_]
。注意是"+"
匹配前一个字符1次或无限次。那么 "\w+"
[A-Za-z0-9_]
组合的字符串。"@"
是邮箱的特定字符,因此固定不变。"\w+"
与前一个是一个道理,匹配一次或无限次的[A-Za-z0-9_]
组合的字符串。" \. "
的含义是将" . "
转义,由于 " . "
自己也是正则语法中的其中一种,为了真的获得 ".com"
" . "
, 因此在前面加上 "\"
转义字符。因此,不管是例子中的 lixiaomei@qq.com
,仍是其它如xiaoxiao@126.com
之类的邮箱,只要符合规则全均可以匹配,怎么样,简单吧!
问题来了,有的邮箱格式但是xiaoxiao@xxx.xxx.com
这样的!这样的话上面的规则就不能用了。没错,上面的规则比较特殊,只能匹配单一格式的邮箱名。那么怎样设计一个知足以上两种格式的pattern呢?看看这个:\w+@(\w+\.)?\w+\.com
这个又是什么意思?
\w+@
与以前同样(\w+\.)?
中的“ ? ”
是匹配0次或1次括号分组内的匹配内容,"()"
则表示被括内容是一个分组,分组序号从pattern字符串起始日后依次排列。分组的概念很是重要,在后面 “匹配对象方法” 章节会着重介绍其如何使用。 \w+\.com
与以前同样由于是匹配0次或1次,那么就意味着括号内的部分是无关紧要的,因此这个pattern就可能匹配两种邮箱格式。
“?”
是0次或1次,那么 \w+@(\w+\.)*\w+\.com
模式就更厉害了," * "
能够匹配0次或无限次。
明白了这个以后,相信你应该对正则表达式有一个概念了,但还有不少种语法以及组合方法须要在实践中反复练习。这里只介绍Python中正则表达式比较常见的匹配模式,更多内容可参考《Python核心编程》
一书,关注微信公众号Python网络爬虫
并发送 “学习资料” 即可轻松获得。
上面简单的介绍了正则表达式的pattern
是如何设置的,那么下一步咱们就能够开始咱们的提取工做了。在Python的re模块
中有几个核心的函数
专门用来进行匹配和查找。
compile(pattern, flag=0)
为何要对pattern进行编译呢?《Python核心编程》
里面是这样解释的:
使用预编译的代码对象比直接使用字符串要快,由于解释器在执行字符串形式的代码前都必须把字符串编译成代码对象。一样的概念也适用于正则表达式。在模式匹配发生以前,正则表达式模式必须编译成正则表达式对象。因为正则表达式在执行过程当中将进行屡次比较操做,所以强烈建议使用预编译。并且,既然正则表达式的编译是必需的,那么使用预编译来提高执行性能无疑是明智之举。re.compile()可以提供此功能。
原来是这样啊,因为compile
的使用很简单,因此将在如下几个匹配查找的函数使用方法中体现。
match(pattern, string, flag=0)
最开始
与pattern进行匹配,匹配成功返回匹配对象(只有一个结果),不然返回None。import re s1 = '我12345abcde' s2 = '.12345abcde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'\w.+' # 编译pattern pattern_compile = re.compile(pattern) # 对s1和s2分别匹配 result1 = re.match(pattern, s1) result2 = re.match(pattern, s2) print(result1) print(result2) >>> <_sre.SRE_Match object; span=(0, 11), match='我12345abcde'> >>> None
注意:
最开始
匹配的,意思是若是第一个字符就不匹配,那就直接玩完,返回None
。" r "
表明了原生字符串
的意思。问题来了,为何result1结果有这么多的东西啊?貌似最后一个才是要匹配的对象。这个要怎么提取出来呀?
别着急,咱们如今获得的是匹配对象
,须要用必定的方法提取,咱们后面会在《匹配对象的方法》
章节来解决这个问题,继续往下看。
search(pattern, string, flag=0
)match()
工做的方式同样,可是search()不是从最开始匹配的
,而是从任意位置查找第一次匹配的内容。
若是全部的字串都没有匹配成功,返回None,不然返回匹配对象。import re s1 = '我12345abcde' s2 = '+?!@12345abcde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'\w.+' pattern_compile = re.compile(pattern) result1 = re.search(pattern_compile, s1) result2 = re.search(pattern_compile, s2) print(result1) print(result2) >>> <_sre.SRE_Match object; span=(0, 11), match='我12345abcde'> >>> <_sre.SRE_Match object; span=(4, 14), match='12345abcde'>
能够看到不管字符串最开始是否匹配pattern,只要在字符串中找到匹配的部分就会做为结果返回(注意是第一次匹配的对象
)。
findall(pattern, string [,flags])
非重复
)出现的正则表达式模式,并返回一个匹配列表
import re s1 = '我12345abcde' s2 = '+?!@12345abcde@786ty' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'\d+' pattern_compile = re.compile(pattern) result1 = re.match(pattern_compile, s2) result2 = re.search(pattern_compile, s1) result3 = re.findall(pattern_compile, s2) print(result1) print(result2) print(result3) >>> None >>> <_sre.SRE_Match object; span=(1, 6), match='12345'> >>> ['12345', '786']
上面同时列出了match、search、findall
三个函数用法。findall与match和search不一样的地方是它会返回一个全部无重复匹配的列表。若是没找到匹配部分,就返回一个空列表。
以上re模块函数的返回内容能够分为两种:
<_sre.SRE_Match object; span=(0, 5), match='12345'>
这样的对象,可返回匹配对象的函数有match、search、finditer
。findall
。所以匹配对象的方法只适用match、search、finditer
,而不适用与findall
。
经常使用的匹配对象方法有这两个:group、groups
、还有几个关于位置的如 start、end、span
就在代码里描述了。
group(num=0)
import re s1 = '我12345+abcde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'\w+' pattern_compile = re.compile(pattern) # 返回匹配的字符串 result1 = re.match(pattern_compile, s1).group() # 返回匹配开始的位置 result2 = re.match(pattern_compile, s1).start() # 返回匹配结束的位置 result3 = re.match(pattern_compile, s1).end() # 返回一个元组包含匹配 (开始,结束) 的位置 result4 = re.match(pattern_compile, s1).span() print(result1) print(result2) print(result3) print(result4) >>> 我12345 >>> 0 >>> 6 >>> (0, 6)
这样匹配字符串就提取出来了,再来看看下面这种状况。
import re s1 = '我12345+abcde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern) # 返回匹配的整个字符串 result1 = re.match(pattern_compile, s1).group() # 返回匹配的第一个子组字符串 result2 = re.match(pattern_compile, s1).group(1) # 返回匹配的第二个子组字符串 result3 = re.match(pattern_compile, s1).group(2) print(result1) print(result2) print(result3) >>> 我12345+abcde >>> 我12345 >>> abcde
这里就须要用到咱们以前提到的分组
概念。
分组的意义在于:咱们不只仅想获得匹配的整个字符串,咱们还想获得整个字符串里面的特定子字符串。
如上例中,整个字符串是“我12345+abcde”
,可是想获得 “abcde”
,咱们就能够用括号()
括起来。所以,你能够对pattern
进行任何的分组,提取你想获得的内容。
另外,若是匹配对象时None
,那么继续使用匹配对象方法会报错AttributeErro
r,所以也建议使用except
异常来处理。
groups(default =None)
import re s1 = '我12345+abcde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern) # 返回含有全部子组的元组 result1 = re.search(pattern_compile, s1).groups() print(result1) >>> ('我12345', 'abcde')
re模块的经常使用属性有如下几个:
re.I 或者 re.IGNORECASE
匹配不分大小写;re.L 或者 re.LOCALE
根据使用的本地语言环境经过\w, \W, \b, \B, \s, \S
实现匹配;re.M 或者 re.MULTILINE
^和$分别匹配目标字符串中行的起始和结尾,而不是严格匹配整个字符串自己的起始和结尾;re.S 或者 rer.DOTALL
“.”(点号)一般匹配除了n(换行符)以外的全部单个字符;该标记表示“.”(点号)可以匹配所有字符;re.X 或者re.VERBOSE
经过反斜线转义,不然全部空格加上#(以及在该行中全部后续文字)都被忽略,除非在一个字符类中或者容许注释而且提升可读性;其实re模块的属性就是函数中的flag
参数,以第一个大小写flag为例:
import re s1 = '我12345+aBCde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern, re.IGNORECASE) # 返回一个匹配的列表 result1 = re.findall(pattern, s1) print(result1) >>> [('我12345', 'abcde')] import re s1 = '我12345+aBCde' # pattern字符串前加 “ r ” 表示原生字符串 pattern = r'(\w+)\+(\w+)' # 返回一个匹配的列表 result1 = re.findall(pattern, s1, re.IGNORECASE) print(result1) >>> [('我12345', 'abcde')]
这里注意:
compile编译
,须要先将flag
填到compile
函数中,不然填到匹配函数中会报错compile
,则能够直接在匹配函数findall中填写flag
本篇介绍正则表达式的快速入门方法,关于更多正则表达式的内容能够参考以下连接:
https://docs.python.org/2/library/re.html
学习资料
”关注微信公众号Python数据科学,获取 120G
人工智能 学习资料。