当完成了网页html的download以后,下一步固然是从网页中解析咱们想要的数据了。那如何解析这些网页呢?Python中有许多种操做简单且高效的工具能够协助咱们来解析html或者xml,学会这些工具抓取数据是很容易了。
html
说到爬虫的html/xml解析(如今网页大部分都是html),可以使用的方法实在有不少种,如:正则表达式
正则表达式express
BeautifulSoup编程
Lxml并发
PyQueryapp
CSSselectoride
其实也不止这几种,还有不少,那么到底哪种最好呢?这个很难说,萝卜白菜各有所爱,这些方法各有特点,只能说选择一款你用着顺手的。博主将会陆续给你们介绍这些好用的解析器,可是本篇从正则表达式开始。函数
那是否是只要掌握一种就能够了?用不着会那么多吧。确实,熟练掌握一种也能够完成数据的抓取,但随着你解析网页的数量增多,你会发现有时候使用多种方法配合解析网页会更简单,高效,由于这些方法各有特点,不一样环境下发挥的做用不同。所以,建议你们熟练掌握至少两种为佳,这样当你面对复杂结构网页的时候,解析方法会更灵活。工具
好了,开始咱们的解析之旅吧!性能
--------------------------
正则表达式
--------------------------
正则表达式(regular expression)简称(regex), 是一种处理字符串的强大工具。它做为一种字符串的匹配模式,用于查看指定字符串是否存在于被查找字符串中,替换指定字符串,或是经过匹配模式查找指定字符串。正则表达式在不一样的语言里面,语法也基本是相同的,也就是说学会了一种语言的正则,再学习其它的就很快了。
其主要的匹配过程是:
先用正则语法定义一个规则(pattern)
而后用这个规则与你download的网页字符串进行对比,根据pattern提取你想要的数据。
好了,让咱们看看Python正则表达式的语法:
好乱,看不懂!!!
别着急,开始都是这样的(固然会的小伙伴能够直接跳过)。下面看几个例子,你立刻就学会了。
--------------------------
你的第一个正则表达式
--------------------------
咱们举一个常遇到的一个例子。好比,一我的的邮箱是这样的lixiaomei@qq.com,那么咱们如何从一大堆的字符串把它提取出来呢?
根据正则语法,咱们能够这样来定义一个pattern:\w+@\w+\.com
为何这么定义呢?让咱们来看看。
"\w" 的意思是单词字符[A-Za-z0-9_]。注意是 "单字符串",能够是A-Z或者a-z或者0-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核心编程》一书,关注公众号并发送 “学习资料” 即可轻松拿到。
--------------------------
re模块核心函数
--------------------------
上面简单的介绍了正则表达式的pattern是如何设置的,那么下一步咱们就能够开始咱们的提取工做了。在Python的re模块中有几个核心的函数专门用来进行匹配和查找。
compile()函数
函数定义: compile(pattern, flag=0)
函数描述:编译正则表达式pattern,而后返回一个正则表达式对象。
为何要对pattern进行编译呢?《Python核心编程 》里面是这样解释的:
使用预编译的代码对象比直接使用字符串要快,由于解释器在执行字符串形式的代码前都必须把字符串编译成代码对象。
一样的概念也适用于正则表达式。在模式匹配发生以前,正则表达式模式必须编译成正则表达式对象。因为正则表达式在执行过程当中将进行屡次比较操做,所以强烈建议使用预编译。并且,既然正则表达式的编译是必需的,那么使用预编译来提高执行性能无疑是明智之举。re.compile()可以提供此功能。
原来是这样,因为compile的使用很简单,因此将在如下几个匹配查找的函数使用方法中体现。
match()函数
函数定义: match(pattern, string, flag=0)
函数描述:只从字符串的最开始与pattern进行匹配,匹配成功返回匹配对象(只有一个结果),不然返回None。
re s1 s2 pattern result1 re.matchpattern, s1result2 re.matchpattern, s2result1result2
注意:
match函数是从最开始匹配的,意思是若是第一个字符就不匹配,那就直接玩完,返回None。
Python中pattern字符串前面的 " r " 表明了原生字符串的意思。
问题来了,为何result1结果有这么多的东西啊?貌似最后一个才是要匹配的对象。这个要怎么提取出来呀?别着急,咱们如今获得的是匹配对象,须要用必定的方法提取,咱们后面会在《匹配对象的方法》章节来解决这个问题,继续往下看。
search()函数
函数定义: search(pattern, string, flag=0)
函数描述:与match()工做的方式同样,可是search()不是从最开始匹配的,而是从任意位置查找第一次匹配的内容。若是全部的字串都没有匹配成功,返回None,不然返回匹配对象。
re s1 s2 pattern pattern_compile re.compilepatternresult1 re.searchpattern_compile, s1result2 re.searchpattern_compile, s2result1result2
能够看到不管字符串最开始是否匹配pattern,只要在字符串中找到匹配的部分就会做为结果返回(注意是第一次匹配的对象)。
findall()函数
函数定义: findall(pattern, string [,flags])
函数描述:查找字符串中全部(非重复)出现的正则表达式模式,并返回一个匹配列表
re s1 s2 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s2result2 re.searchpattern_compile, s1result1result2result3
上面同时列出了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方法
方法定义:group(num=0)
方法描述:返回整个的匹配对象,或者特殊编号的字组
re s1 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s1.groupresult2 re.matchpattern_compile, s1.startresult3 re.matchpattern_compile, s1.endresult4 re.matchpattern_compile, s1.spanresult1result2result3result4
这样匹配字符串就提取出来了。
re s1 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s1.groupresult2 re.matchpattern_compile, s1.groupresult3 re.matchpattern_compile, s1.groupresult1result2result3
这里就须要用到咱们以前提到的分组概念。
分组的意义在于:咱们不只仅想获得匹配的整个字符串,咱们还想获得整个字符串里面的特定子字符串。
如上例中,整个字符串是“我12345+abcde”,可是想获得 “abcde”,咱们就能够用括号括起来。所以,你能够对pattern进行任何的分组,提取你想获得的内容。
另外,若是匹配对象时None,那么继续使用匹配对象方法会报错AttributeError,所以也建议使用except异常来处理。
groups方法
方法定义:groups(default =None)
方法描述:返回一个含有全部匹配子组的元组,匹配失败则返回空元组
re s1 pattern pattern_compile re.compilepatternresult1 re.searchpattern_compile, s1.groupsresult1
--------------------------
re模块的属性
--------------------------
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为例:
re s1 pattern pattern_compile re.compilepattern, re.IGNORECASEresult1 re.findallpattern, s1result1
re s1 pattern result1 re.findallpattern, s1, re.IGNORECASEresult1
这里注意:
若是咱们定义了compile编译,须要将flag填到compile函数中,不然填到匹配函数中会报错
若是没有定义compile,则能够直接在匹配函数findall中填写flag