分享

python3爬虫系列04之网页解析器:re正则式,BeautifulSoup库的解释...

 天使之翼 ` 2020-11-21

1.前言——网页解析器的种类

现在,我已经会使用 python 模拟浏览器进行一些 Https 的抓包,发请求了,那么根据我们第一篇所说的结构: 网页下载器() -> 网页解析器() 的流程,接下来该说网页解析器了。

在网页解析器中,我们一般有这四个:

  • 正则表达式(re模糊匹配)

  • html.parser (Python自带)

  • BeautifulSoup (第三方插件)

  • lxml (第三方解析器)

我们知道,在发送请求完之后,服务器会返回给我们一堆源代码。
像这样的:
在这里插入图片描述

可以看到很多乱七八糟的东西,明显这可不是我们想要的,所以我们要提取出有价值的数据!!!

也就是在服务器返回给我们的源码之中我们要进行过滤,匹配或者说提取

只拿到我们想要的就好,其它就丢掉(卧槽,无情…)。

说到过滤,匹配,提取之类的操作,很多人就想到了鼎鼎大名的正则表达式了。

正则表达式就是定义一些特殊的符号来匹配不同的字符。

但是一个字!真特么难…

本篇完

再见


2. 正则表达式介绍

哈哈,开玩笑,虽然我不喜欢用正则,但是也还是把常用的列出来!

那么python是怎么使用正则表达式的呢?就要用python 的 re库了。

re模块

re 模块用正则表达式最常用的函数。

是这个方法:

re.match(x,y)

主要传入两个参数:

  • 第一个就是匹配规则

  • 第二个就是需要被过滤的源内容

例如:

想要从content 中拿到一个数字

import re

content = 'CSDNzoutao has 100 bananas'
res = re.match('^CS.*(\d+)\s.*s$',content)
print(res.group(1))

可以运行看看。

其次,在python爬虫中比较常用到的组合是:

.*?

就是这三个字符,它表示的就是匹配任意字符。

但是 .*?的 . 代表所有的单个字符,除了 \n \r

如字符串有换行了怎么办呢?
比如这样

content = """CSDNzoutao has 100 
bananas"""

就需要用到 re 的匹配模式了。

说来也简单,直接用 re.S

import re

content = """CSDNzoutao has 100 
bananas"""
res = re.match('^CS .*?(\d+)\s.*s$',content,re.S)
print(res.group(1))

结果:
在这里插入图片描述

再来说说 re 的另一个常用到的方法吧:

re.compile()

这个主要就是把匹配符封装。

import re

content = "CSDNzoutao has 100 bananas"
pattern = re.compile('CS .*?(\d+)\s.*s',re.S)
res = re.match(pattern,content)
print(res.group(1))

compile 就是数据之类的封装一下,方便下面调用。

更多正则请参考正则表达式详解


2.1 requests+re正则 爬虫实战:

好吧,虽然我很不情愿用正则,但是多少也是要写一个实战例子的是吧。

就使用 requests 和 re正则解析器来写一个爬虫。

python3+requests + re 爬虫实战篇

因为前一篇就是它,我就不写了。

好了 ,关于 re 模块和正则表达式就介绍完啦。

因为我们在现在的爬虫中,很多不用正则表达式了,毕竟我也看不明白。所以我们一般使用另外的网页解析器。

在这里插入图片描述

3.BeautifulSoup库的介绍

最常用的,就是第三方插件——BeautifulSoup库 了。比正则好记啊!

既然是第三方库,那么要安装一下这个库:

pip install beautifulsoup4

值得一提的是啊,在 beautifulsoup 库中,还支持不同的解析器。(很多人被搞懵逼了,记住我说的bs是库!仓库里面放不同的解析器,很合理吧?)

比如:

1.解析库:

BeautifulSoup

BeautifulSoup号称Python中最受欢迎的HTML解析库之一。

lxml

这个库可以用来解析HTML和XML文档,以非常底层的实现而闻名,大部分源码都是C语言写的,虽然学习这东西要花一定的时间,但是它的处理速度非常快。

HTML parser

这是python自带的解析库,所以很方便。


2.解析器:

其实上面提到的另外的这两个库,又可以作为BeautifulSoup库的解析器

下面对BeautifulSoup中的 各种 html 解析器的优缺点对比:

Python’s html.parser 默认用的是这个

使用语法:

BeautifulSoup(markup,"html.parser")

优点

python自身带有 速度比较快 能较好兼容 (as of Python 2.7.3 and 3.2.)

缺点

不能很好地兼容(before Python 2.7.3 or 3.2.2)

lxml’s HTML parser 最推荐

使用语法

BeautifulSoup(markup,"lxml")

优点

速度很快 兼容性好

缺点

需要额外的C语言支持

lxml’s XML parser 最推荐

使用语法

BeautifulSoup(markup, "lxml-xml") 

或者 

BeautifulSoup(markup,"xml")

优点

速度很快

缺点

现在只支持xml解析, 需要额外的C语言支持。

html5lib :最不常用

使用语法

BeautifulSoup(markup, "html5lib")

优点

兼容性很好
可以像web浏览器一样解析html页面
Creates valid HTML5

缺点

速度很慢, 需要额外的python支持。

如图:
在这里插入图片描述

所以,我们在使用beautifulsoup库的时候,必须要指定一个解析器。而lxml和html.parser都可以考虑拿来做解析器。

l其中 xml解析器通常更快,如果可以,我建议您安装并使用lxml以提高速度。

不同的解析器将为其生成不同的BeautifulSoup树,所以先来使用一个例子,

体验一下beautifulsoup 的一些常用的方法。

3.1 BeautifulSoup 常用方法

口水话我也懒得说了,其他博文估计讲得很清楚,我就举个栗子,常用方法已经用*** 标出。

假设我们使用网页下载器弄回来的网页是这样的:

  1. 得到html页面:

html_doc = """
<html>
    <head>
        <title>睡鼠的故事</title>
    </head>
<body>
    <p class="title" id="No0.1"><b>第一章bs示例</b></p>
    <p class="story">从前有三个小妹妹,她们的名字是:
        <a href="http:///elsie" class="sister" id="link1">1张三</a>,
        <a href="http:///lacie" class="sister" id="link2">2李四</a> and
        <a href="http:///tillie" class="sister" id="link3">3Jack</a>;
            and他们住在井底.
     </p>
    <div class="story">省略一万字。。。
        <ul id="producers">  
            <li class="producerlist">  
                <div class="name">香蕉</div>  
                <div class="number">100斤</div>  
            </li>  
            <li class="producerlist">  
                <div class="name">苹果</div>  
                <div class="number">200斤</div>  
            </li>  
        </ul>  
    </div>
</body>
</html>
"""
  1. 接着将 html 的源代码传给 BeautifulSoup解析:

# 2.引入库
from bs4 import BeautifulSoup
# 3.解析该html页面
soup = BeautifulSoup(html_doc, 'html.parser')

然后我们要做的就是从这个对象直接获取我们要的内容了。
比如:

  1. 基本查找元素的方式-可以直接根据html的标签来取值。

获取标题的内容:

print(soup.title.string)
# 睡鼠的故事
print(soup.head)#告诉它想获取的tag的name,是标签名,而不是标签的name属性
#<head>
#<title>睡鼠的故事story</title>
#</head>

获取超链接 a 标签或超链里面的文本内容:

print(soup.a) # 通过点取属性的方式只能获得当前名字的【第一个a标签】
# <a class="sister" href="http:///elsie" id="link1">1张三</a>
print(soup.a.string) # 通过.string获取a标签中的文本内容。
# 1张三

获取所有超链接:

print(soup.find_all('a'))   # 所有的<a>标签,或是通过名字得到更多内容
# [<a class="sister" href="http:///elsie" id="link1">1张三</a>, <a class="sister" href="http:///lacie" id="link2">2李四</a>, <a class="sister" href="http:///tillie" id="link3">3Jack</a>]

获取 id 为 link2 的超链接:

print(soup.find(id="link2"))
#<a class="sister" href="http:///lacie" id="link2">2李四</a>

获取网页中所有的内容:

print(soup.get_text())  # 获取网页中所有的文本内容

(*)去除多余空白内容: stripped_strings方法:

for string in soup.stripped_strings:
    print(repr(string))

(空格的行会被忽略掉,段首和段末的空白会被删除)

  1. **find_all()**方法。

    是一种模糊匹配。查找多组符号条件的标签,返回一个列表list可遍历,没有找到目标是返回空列表

soup.find_all('b')  # 查找文档中所有的<b>标签

import re
for tag in soup.find_all(re.compile("^b")):  # 还可以配合正则作参数,用 match()调用
    print(tag.name)

a2 = soup.find_all(id="link2")
print("===========================",a2,type(a2))

def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 6
print(soup.find_all(class_=has_six_characters))


print(soup.find_all("a", class_="sister"))
# 搜索内容里面包含“Elsie”的<a>标签:
print(soup.find_all("a", string="Elsie"))

# 使用 limit 参数限制返回结果的数量
print(soup.find_all("a", limit=2))

# 只想搜索tag的直接子节点,可以使用参数 recursive=False
print(soup.html.find_all("title", recursive=False))
  1. find()方法

    也是一种 模糊匹配,查找文档中符合条件的单个标签,返回是一个ResultSet结果集找不到目标时,返回 None ,所以soup.find()后面可以直接 接.text或get_text() 来获得标签中的文本内容。

唯一的区别:find_all()方法的返回的值是包含元素的列表,而find()方法直接返回的是一个结果。

举例子:
print(soup.find_all('title', limit=1))  # 跟下面一句等效。
print(soup.find('title'))

# ** 基于文本内容的查找必须用到参数text
p = soup.find("class",class_="name")  # None
print("==============",p)  # 虽说不报错,但返回的是空,匹配不到值。
p2 = soup.find("class",attrs={"class":"name"})  # None  匹配不到值。
print("++++++++++++++++++++++",p2)
# 层次结构
p3 = soup.find('div').find('li').find("div").text  # 这样写才是对的!
print("-----------------",p3)  # 香蕉

print(soup.get_text())  # 只要文本内容
print(soup.i.get_text())


# find_all_next() 方法返回所有符合条件的节点,
# find_next() 方法返回第一个符合条件的节点:
print(soup.a.find_next("p"))

是不是不太好理解? bs也这么想的,所以除了find方法、find_all()之外,如果你对css比较熟悉,就可以使用select ()方法

  1. select()方法
    是一个CSS选择器。xxx.select()方法中传入字符串参数,是 精确定位返回一个列表list

print(soup.select("title"))

# 逐层查找
print(soup.select("html head title"))

# ***找到某个tag标签下的直接子标签
print(soup.select("p > #link1"))

# 通过CSS的类名查找:  ***
print(soup.select(".sister"))

# 通过id查找:
print(soup.select("#link1"))

# 同时用多种CSS选择器查询元素:
print(soup.select("#link1,#link2"))

# 通过是否存在某个属性来查找:
print(soup.select('a[href]'))

# 通过属性的值来查找:
print(soup.select('a[href$="tillie"]'))

# *** 取查找元素的文本值
print(soup.select('a[class="sister"]')[0].text)

# 返回查找到的元素的第一个
print(soup.select_one(".sister"))


# 格式化输出: prettify()方法-对象或节点都可以调用。
print(soup.prettify())
print(soup.a.prettify())

基本上以上就是python爬虫中 BeautifulSoup 常用的方法,有了它,再也不用担心不会正则表达式了。

整个例子源码:

#!/usr/bin/python3
# 1.得到一个html页面
html_doc = """
<html>
    <head>
        <title>睡鼠的故情</title>
    </head>
<body>
    <p class="title" id="No0.1"><b>第一章bs示例</b></p>
    <p class="story">从前有三个小妹妹,她们的名字是:
        <a href="http:///elsie" class="sister" id="link1">1张三</a>,
        <a href="http:///lacie" class="sister" id="link2">2李四</a> and
        <a href="http:///tillie" class="sister" id="link3">3Jack</a>;
            and他们住在井底.
     </p>
    <div class="story">省略一万字。。。
        <ul id="producers">  
            <li class="producerlist">  
                <div class="name">香蕉</div>  
                <div class="number">100斤</div>  
            </li>  
            <li class="producerlist">  
                <div class="name">苹果</div>  
                <div class="number">200斤</div>  
            </li>  
        </ul>  
    </div>
</body>
</html>
"""
# 2.引入库
from bs4 import BeautifulSoup
# 3.解析该html页面
soup = BeautifulSoup(html_doc, 'html.parser')

# 一、基本查找元素的方式-可以直接根据html的标签来取值。
print(soup.head)        # 告诉它你想获取的tag的name,是标签名字,而不是标签的name属性!!!
print(soup.title)
print(soup.body.b)      # 获取<body>标签中的第一个<b>标签
print(soup.a)           # 通过点取属性的方式只能获得当前名字的【第一个a标签】:
print(soup.find_all('a'))   # 所有的<a>标签,或是通过名字得到更多内容

head_tag = soup.head
print('===========',head_tag.contents)      # 以列表的方式输出

#title_tag = head_tag.contents[0]  # 字符串没有.contents 属性,因为字符串没有子节点:
#print(title_tag.contents)


# (*)去除多余空白内容: 空格的行会被忽略掉,段首和段末的空白会被删除
for string in soup.stripped_strings:
    print(repr(string))


# (*)二、[更强的查找元素-过滤器find_all()方法。是模糊匹配。返回一个列表list可遍历,没有找到目标是返回空列表],

soup.find_all('b')  # 查找文档中所有的<b>标签

import re
for tag in soup.find_all(re.compile("^b")):  # 还可以配合正则作参数,用 match()调用
    print(tag.name)

a2 = soup.find_all(id="link2")
print("===========================",a2,type(a2))

def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 6
print(soup.find_all(class_=has_six_characters))


print(soup.find_all("a", class_="sister"))
# 搜索内容里面包含“Elsie”的<a>标签:
print(soup.find_all("a", string="Elsie"))

# 使用 limit 参数限制返回结果的数量
print(soup.find_all("a", limit=2))

# 只想搜索tag的直接子节点,可以使用参数 recursive=False
print(soup.html.find_all("title", recursive=False))


# 三、find()方法—返回文档中符合条件的标签,是一个ResultSet结果集,找不到目标时,返回 None .所以soup.find()后面可以直接接.text或get_text()来获得标签中的文本内容。
print(soup.find_all('title', limit=1))
print(soup.find('title'))

# *** [基于文本内容的查找必须用到参数text]
p = soup.find("class",class_="name")  # None
print("==============",p)  # 虽说不报错,但返回的是空,匹配不到值。
p2 = soup.find("class",attrs={"class":"name"})  # None  匹配不到值。
print("++++++++++++++++++++++",p2)
# 层次
p3 = soup.find('div').find('li').find("div").text  # 这样写才是对的!
print("-----------------",p3)  # 香蕉

# 唯一的区别:find_all()方法的返回结果是值包含元素的列表,而find()方法直接返回结果.

# find_all_next() 方法返回所有符合条件的节点,
# find_next() 方法返回第一个符合条件的节点:
print(soup.a.find_next("p"))


# 三、CSS选择器:.select()方法中传入字符串参数-精确定位,返回一个列表list
print(soup.select("title"))

# 逐层查找
print(soup.select("html head title"))

# ***找到某个tag标签下的直接子标签
print(soup.select("p > #link1"))

# *** 通过CSS的类名查找:  
print(soup.select(".sister"))

# 通过id查找:
print(soup.select("#link1"))

# 同时用多种CSS选择器查询元素:
print(soup.select("#link1,#link2"))

# 通过是否存在某个属性来查找:
print(soup.select('a[href]'))

# 通过属性的值来查找:
print(soup.select('a[href$="tillie"]'))

# *** 取查找元素的文本值
print(soup.select('a[class="sister"]')[0].text)

# 返回查找到的元素的第一个
print(soup.select_one(".sister"))


# 格式化输出: prettify()方法-对象或节点都可以调用。
print(soup.prettify())
print(soup.a.prettify())


# ***只想得到tag中包含的文本内容,那么可以用get_text()方法,结果作为Unicode字符串返回
markup = '<a href="http:///">\nI linked to <i></i>\n</a>'
soup = BeautifulSoup(markup,"html.parser")
print(soup.get_text())  # 只要文本内容
print(soup.i.get_text())

主要学会几个东西,取标签,取标签里面的文本内容,取多个标签,取单个标签,以及最常用的find_all() 、find()、 a.[0].text 和.get_text()几个方法的使用就可以了。


老规矩,也整个爬虫案例看看,对比一下我们的re模块和bs哪个好用些?

3.2 BeautifulSoup + reuqests实战

最近电影慌,就拿豆瓣电影 Top 250的那个来进行爬取啦。
以免篇幅过长,单独写出来。

BeautifulSoup + reuqests豆瓣电影 Top 250实战爬虫


好了,以上就是网页解析器的各种解释和使用。下一篇总结写BeautifulSoup 爬虫的关键做法,怎么定位标签和提取想要的内容?


参考地址:

https://blog.csdn.net/ITBigGod/article/details/102859854
https://blog.csdn.net/ITBigGod/article/details/102866720
https://www.cnblogs.com/fxxkpython/p/10832023.html

当然你也可以直接去看 BS4的官方文档。

bs的英文文档:

https://www./software/BeautifulSoup/bs4/doc/#installing-a-parser

解析器之间的区别:

https://www./software/BeautifulSoup/bs4/doc/#differences-between-parsers

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多