解析网页哪家强-Xpath和正则表达式(re)及BeautifulSoup的比较(文中含有三者的基本语法介绍)

解析网页哪家强,中国山东找lanxiang,哈哈哈,开个玩笑
解析网页有三种方法:Xpath和正则表达式(re)及BeautifulSoup,那么到底是哪种好一些呢,这个可说不好,各有千秋,在不同的情境下,使用最佳的方法才是王道,接下来让我们分析一下它们
PS:对它们的语法结构都有所了解的小伙伴可以跳过语法介绍


语法介绍

工欲善其事,必先利其器,想要做比较做分析肯定要了解它们的用法,下面笔者收集了三者具有权威性的文档的链接,可以更加详细的学习
官方文档:re — 正则表达式操作
W3Shool:Xpath教程
官方文档:Beautiful Soup 4.4.0 文档

笔者在此分别对它们的官方文档进行引用总结

一、Xpath

先使用etree模块对网页请求下来的html进行补充
root = etree.HTML(HTML.content)
接着就是root.xpath()采用以下的语法对其解析

XPath 使用路径表达式在 XML 文档中选取节点。
节点是通过沿着路径或者 step 来选取的

表达式描述
nodename选取此节点的所有子节点
/从根节点选取
//从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
.选取当前节点
选取当前节点的父节点
@选取属性

谓语(Predicates)谓语用来查找某个特定的节点或者包含某个指定的值的节点。
谓语被嵌在方括号中。

路径表达式结果
/bookstore/book[1]选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()]选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1]选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position() < 3]选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang]选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=‘eng’]选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00]选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

选取未知节点
XPath 通配符可用来选取未知的 XML 元素。

通配符描述
*匹配任何元素节点。
@*匹配任何属性节点。
node()匹配任何类型的节点。

选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。

路径表达式结果
//book/title | //book/price选取 book 元素的所有 title 和 price 元素。
//title | //price选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

二、正则表达式(re)

正则表达式,在笔者认为就是通过不同的特定符号的组合以匹配我们所需要的字符串。接下来就介绍一下正则表达式中的特定字符

元字符

元字符描述
\将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如,“\n”匹配\n。“\n”匹配换行符。序列“\”匹配“\”而“(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。
^匹配输入字行首。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
$匹配输入行尾。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
*匹配前面的子表达式任意次。例如,zo*能匹配“z”,也能匹配“zo”以及“zoo”。*等价于{0,}。
+匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
?匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。
{n}n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
{n,}n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
{n,m}m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
?当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串,而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多地匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少地匹配“o”,得到结果 [‘o’, ‘o’, ‘o’, ‘o’]
.点匹配除“\n”和"\r"之外的任何单个字符。要匹配包括“\n”和"\r"在内的任何字符,请使用像“[\s\S]”的模式。
(pattern)匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“(”或“)”。
(?:pattern)非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(
(?=pattern)非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95
(?!pattern)非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
(?<=pattern)非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。*python的正则表达式没有完全按照正则表达式规范实现,所以一些高级特性建议使用其他语言如java、scala等
(?<!patte_n)非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。

*python的正则表达式没有完全按照正则表达式规范实现,所以一些高级特性建议使用其他语言如java、scala等

元字符描述
x|y匹配x或y。例如,“z
[xyz]字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
[^xyz]负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”任一字符。
[a-z]字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身。
[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
\b匹配一个单词的边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”;“\b1_”可以匹配“1_23”中的“1_”,但不能匹配“21_3”中的“1_”。
\B匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
\cx匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
\d匹配一个数字字符。等价于[0-9]。grep 要加上-P,perl正则支持
\D匹配一个非数字字符。等价于[^0-9]。grep要加上-P,perl正则支持
\f匹配一个换页符。等价于\x0c和\cL。
\n匹配一个换行符。等价于\x0a和\cJ。
\r匹配一个回车符。等价于\x0d和\cM。
\s匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
\S匹配任何可见字符。等价于[^ \f\n\r\t\v]。
\t匹配一个制表符。等价于\x09和\cI。
\v匹配一个垂直制表符。等价于\x0b和\cK。
\w匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。
\W匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
\xn匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。
\num匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
\n标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
\nm标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
\nml如果n为八进制数字(0-7),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
\un匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。
\p{P}小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。中括号内的“P”表示Unicode 字符集七个字符属性之。

三、BeautifulSoup

示例文档

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')

以下是使用BeautifulSoup的语句对文档的解析

语句结果
soup.title<title>The Dormouse's story</title>
soup.title.nameu'title'
soup.title.stringu'The Dormouse's story'
soup.title.parent.nameu'head'
soup.p<p class="title"><b>The Dormouse's story</b></p>
soup.p[‘class’]u'title'
soup.a<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
soup.find_all(‘a’)[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.find(id=“link3”)<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

通过实例比较三者

之前介绍了那么久,终于到正题了,比较一下它们。

  1. 正则表达式是原生python自带的,而xpath和bs4是属于第三方库的
  2. bs4 和 xpath 都可以说是专门用来解析html数据的, 相比之下,xpath的速度会快一点,它们在一定程度上比正则表达式更便捷一些。
  3. 正则表达式使用元字符,xpath和bs4将获取的源码转化成一个对象,有一定的层级结构,相反正则表达式没有层级结构,只有先后顺序.

不过平时在分析时,希望解析网页能快一些,那么我们通过实例来看看到底谁快,下面是实例源码

from lxml import etree
import requests
import re
from bs4 import BeautifulSoup
import time

url = 'https://www.meishij.net/chufang/diy/guowaicaipu1/japan/'

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                         'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'}

response = requests.get(url, headers=headers)

# XPath解析网页
def XpathTest():
    root = etree.HTML(response.content)
    image_list = root.xpath('//div[@class="listtyle1"]/a')
    for image in image_list:
        # . : 表示从当前节点开始获取
        image_alt = image.xpath('./img/@alt')[0]
        image_src = image.xpath('./img/@src')[0]
        image_rq = image.xpath('./div/div/div/span/text()')[0].split(' ')[3]
        # print('image_rq: {} ; image_alt: {} ; image_src: {} ;'.format(image_rq,image_alt,image_src))


# Re模块解析网页
def ReTest():
    pattern = re.compile(r'<div class="listtyle1".*?>.*?<a.*?class="big".*?title="(.*?)".*?>.*?<img class="img".*?src="(.*?)".*?>.*?<div class="c1">.*?<span>(.*?)</span>',re.S)
    result = pattern.findall(response.content.decode("utf-8"))
    for image in result:
        image_alt =image[0]
        image_src = image[1]
        image_rq = image[2].split(' ')[3]
        # print('image_rq: {} ; image_alt: {} ; image_src: {} ;'.format(image_rq,image_alt,image_src))


# Bs4解析网页
def Bs4Test():
    soup = BeautifulSoup(response.content, 'lxml')
    image_list = soup.select('div.listtyle1')
    # print(image_list)
    for tag in image_list:
        value = tag.find_all('img')
        for image in value:
            image_alt = image.get('alt')
            image_src = image.get('src')
        image_rq = tag.span.string.split(' ')[3]
        # print('image_rq: {} ; image_alt: {} ; image_src: {} ;'.format(image_rq,image_alt,image_src))


if __name__ == '__main__':
    t_Xpath_start = time.time()
    XpathTest()
    print('XpathTime:{}'.format(time.time() - t_Xpath_start))
    print("----------------------------------------------------------------------")
    t_Re_start = time.time()
    ReTest()
    print('ReTime:{}'.format(time.time() - t_Re_start))
    print("----------------------------------------------------------------------")
    t_Bs_start =time.time()
    Bs4Test()
    print('Bs4Time:{}'.format(time.time() - t_Bs_start))
    

我们对美食杰日式料理网页进行爬取,并分别用Xpath和正则表达式(re)及BeautifulSoup对其进行解析,查找其中菜品的人气数、菜品名称、图片,通过时间比较它们的速度,以下是多次运行的结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在本例中,很显然re模块速度最快,Xpath次之,Bs4的耗时是前两者耗时之和的数倍之多。
Bs4在完美解析的过程中也消耗了大量的时间
分享经验
笔者刚开始接触爬虫并要解析网页的时候是用re模块的,每每解析要构造re表达式的时候头疼欲裂啊(QWQ太难了),后面接触到了Bs4(BeautifulSoup),真心舒服啊比正则表达式用的舒心多了,不用再去死命分析网页代码了,再后来,老师说现在用Xpath比较好,建议用Xpath,我那时还心想说用BeautifulSoup用的蛮好的不想换,emmm往往这里就是真香警告,当我用了Xpath的时候,啊真香,虽然它没有BeautifulSoup语义上那么清晰,但是也非常容易掌握,而且用起来很快,后面查了资料,原来它所在的库lxml是用C语言写的,难怪了,总的来说用Xpath就是nice,耗时较少,使用方便

  • 12
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值