python-初始BeautifulSoup

初始BeautifulSoup

Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。
能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。


使用Beautiful Soup4

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器.
其中一个是lxml。由于lxml解析速度比标准库中的HTML解析器的速度快得多,用lxml作为新的解析器。
另一个可供选择的解析器是纯Python的实现的html5lib,它的解析方式与浏览器相同。

解析器比较

解析器使用方法优势劣势
Python标准库BeautifulSoup(markup, ‘html.parser’)Python的内置标准库,执行速度适中,文档容错能力强一些版本中文档容错能力差
lxml HTML解析器BeautifulSoup(markup, ‘lxml’)速度快,文档容错能力强需要按照C语言库
lxml XML解析器BeautifulSoup(markup, [‘lxml’, ‘xml’])速度快,唯一支持XML的解析器需要按照C语言库
html5libBeautifulSoup(markup, ‘html5lib’)最好的容错性,以浏览器的方式解析文档,生成Html5格式的文档速度慢,不依赖外部扩展
BeautifulSoup的使用

快速开始
首先导入bs4库:from bs4 import BeautifulSoup。接着创建包含HTML代码的字符串,用来进行解析。

html_str = """
<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:/lexample.com/elsie" class="sister" id="1ink1"><!-- E1sie --></a>,
<a href="http:llexample.com/lacie" class="sister" id="1ink2"><!-- Lacie --></a> and
<a href="http:/lexample.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a wel1.</p>
<p ciass="story"> ...</p>
</body>
</html>
"""

接下来的数据解析和提取都是以这个字符串为例子。

然后创建BeautifulSoup对象,创建BeautifulSoup对象有两种方式。
一种直接通过字符串创建:soup = BeautifulSoup(html_str, ‘lxml’)
另一种通过文件来创建,加入将html_str字符串保存为index.html文件:soup = BeautifulSoup(open(‘index.html’))

Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器那么BeautifulSoup会选择指定的解析器来解析文档。

对象种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树型结构,每个节点都是Python对象,所有对象可以归纳为4中:
Tag, NavigableString, BeautifulSoup, Comment

    Tag: Tag对象与XML或HTML原生文档中的Tag相同,通俗点说就是标记。比如<title> The Dormouse's story</title>title标记及其里面的内容称为Tag对象。
         怎样从html_str中抽取Tag,实例如下:
            抽取title:soup.title
            抽取a:soup.a
            抽取p:soup.p
         从上可以看出利用soup加标签名就可以获取到这些标签内容。
         Tag有两个最重要的属性:name 和 attributes。每个Tag都有自己的名字,通过name来获取。
            soup.name
            soup.title.name
         soup对象本身比较特殊,它的name为[document],对于其他内部标记,输出的值便为标记本身的名称。
         Tag不仅可以获取name,还可以修改name,改变之后将影响所有通过当前Beautiful Soup对象生成的HTML文档。
            soup.title.name = 'mytitle'
            soup.title
            soup.mytitle
         这里已经将title标记成功修改为mytitle。
         再说一下Tag中的属性,<p class="title">....</p>有一个'class'属性,值为'title'。Tag的属性的操作方法与字典相同:
            soup.p['class']
            soup.p.get('class')
         也可以直接 '点' 取属性,比如:.attrs,用于获取Tag中所有属性:
            soup.p.attrs
         和name一样,也可以对标记中的这些属性和内容等进行修改:
            soup.p['class'] = 'myClass'


    NavigableString:得到了标记的内容,要想获取标记内部的文字,需要用到.string
        soup.p.string
        type(soup.p.string)


    BeautifulSoup: 表示的是一个文档的全部内容。
        大部分时候,可以把它当作Tag对象,是一个特殊的Tag,因为BeautifulSoup对象并不是真正的HTML或XML的标记,所以没有name和attribute属性。
        但为了将BeautifulSoup对象标准化为Tag对象,实现接口的统一,依然可以分别获取它的name和attribute属性:
            type(soup.name)
            soup.name
            soup.attrs

    Comment:Tag,NavigableString,BeautifulSoup几乎覆盖了HTML和XMl中的所有内容,但是还有一些特殊对象。
        容易让人担心的内容是文档的注释部分:
            soup.a.string
            type(soup.a.string)
        a标签中的内容时间行是注释,但是如果利用.string输出,会发现注释符号去掉了。打印它的类型,会发现是一个Comment类型。如果不清楚这个标记.string的情况下,可能造成数据提取混乱。因此在提取字符串时,可以判断一下类型:
            if type(soup.a.string) == bs4.element.Comment:
                soup.a.string

遍历文档数
BeautifulSoup会将HTML转化为文档树进行搜索,既然是树形结构,节点的概念必不可少。

子节点
    首先说一下直接子节点,Tag中的.contents和.children是非常重要的。Tag的.content属性可以将Tag子节点以列表的方式输出:
        soup.head.contents
    既然输出方式是列表,我们就可以获取列表的大小,并通过列表索引里面的值:
        len(soup.head.contents)
        soup.head.contents[0].string
    有一点需要注意:字符串没有.contents属性,因为字符串没有子节点。.children属性返回的是一个生成器,可以对Tag的子节点进行属性:
        for child in soup.head.children:
            print(child)
    .contents和.children属性仅包含Tag的直接子节点。例如,<head>标记只有一个直接子节点<title>。但是<title>标记也包含一个子节点,那么就是<head>的子孙节点。
    .descendants属性可以对所有tag的子孙节点进行递归循环:
        for child in soup.head.descendants:
            print(child)

    下一步如何获取节点的内容,这涉及.string, .strings,.stripped_strings三个属性。
    .string这个属性很有特点:如果一个标记里面没有标记了,那么.string就会返回标记里的内容。如果只有一个标记,那么会返回最里面的内容。如果tag包含了多个子节点,无法确定的时候,输出结果是None。
        soup.head.string
        soup.title.string
        soup.html.string
    .strings属性主要应用于tag中包含多个字符串的情况,可以进行循环遍历:
        for string in soup.strings:
            print(repr(string))
    .stripped_strings属性可以去掉输出字符串中包含的空格或空行:
        for string in soup.stripped_strings:
            print(repr(string))


父节点
    每个Tag或字符串都有父节点:被包含在某个Tag中。
    通过.parent属性来获取某个元素的父节点。
        soup.title
        soup.title.parent
    通过.parents属性可以递归得到元素的所有父辈节点。
        soup.a
        for parent in soup.a.parents:
            if parent is None:
                print(parent)
            else:
                print(parent.name)


兄弟节点
    从soup.prettify()的输出结果中,可以看到<a>有很多兄弟节点。
    .next_sibling属性可以获取该节点的下一个兄弟节点。
    .previous_sibling则与之相反。如果不存在,则返回None。
        soup.p.next_sibling
        soup.p.prev_sibling
        soup.p.next_sibling.next_sibling
    第一个输出结果为空白,因为空白或者换行也可以被视作一个节点。
    通过.next_siblings和.previous_siblings属性可以对当前节点的兄弟节点迭代输出。
        for sibling in soup.a.next_siblings:
            print()


前后节点
    前后节点需要使用.next_element,.previous_element这两个属性。
        soup.head
        soup.head.next_element
    如果想遍历所有的前或后节点,通过.next_elements和.previous_elements
        for element in soup.a.next_elements:

搜索文档树
Beautiful Soup定义了很多搜索方法,着重介绍find_all()方法,其他方法的参数和用法类似。
find_all(name, attrs, recursive, text, **kwargs)
name:

    参数可以查找所有名字为name的标记,字符串对象会被自动忽略掉。name参数取值可以是字符串,正则表达式,列表,Ture和方法。
    最简单的过滤器的字符串。输入一个字符串,会查找与字符串完整匹配的内容,下例子查找文档中所有的\<b>标记
        soup.find_all('b')
    如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的match()来匹配内容
        for tag in soup.find_all(re.compile('^b'))
            print(tag.name)
    如果传入的参数是True,True可以匹配任何值,查找到所有的Tag,但是不会返回字符串节点:
        for tag in soup.find_all(True):
            print(tag.name)
    如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数Tag节点。
    如果这个方法返回True表示当前元素匹配并且被找到,如果不是则返回false,比如过滤包含class属性,也包含id属性的元素。
        def hasClass_Id(tag):
            return tag.has_attr('class') and tag.has_attr('id')
        soup.find_all(hasClass_Id)

kwargs:

    kwargs参数在Python中表示为keyword参数。如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字Tag的属性来搜索。
    搜索指定名字的属性可以使用的参数值包括字符串,正则表达式,列表,True。
    如果包含id参数,Beautiful Soup会搜索每个tag的 id属性。
        soup.find_all(id='link2')
    如果传入href参数,Beautiful Soup会搜索每个Tag的 'href'属性。
        soup.find_all(href=re.compile('elsie'))
    下面代码再文档树中查找所有包含id属性的Tag
        soup.find_all(id=True)
    如果想用class过滤,但是class时python的关键字,需要再class后面加个下划线
        soup.find_all('a', class_='sister')
    使用多个指定名字的参数可以同时过滤tag的多个属性:
        soup.find_all(href=re.compile(elsie), id='link1')
    有些tag属性在搜索不能使用,比如HTML5中的data-*属性
        data_soup = BeautifulSoup('<div data-foo='value'>foo!<div>')
        data_soup.find_all(data-foo='value')
    这样的代码在Python中是不合法的,但是可以通过find_all()方法的attrs参数定义一个字典参数来搜索包含特殊属性的tag
        data.soup.find_all(attrs={'data-foo': 'value'})

text:

    通过text参数可以搜索文档中的字符串内容。与name参数的可选值一样,text参数接受字符串,正则表达式,列表,True
        soup.find_all(text='Elsie')
        soup.find_all(text=['Tillie', 'Elsie', 'Lacie'])
        soup.find_all(text=re.compile('Dormouse'))
    虽然text参数用于搜索字符串,还可以与其他参数混合使用来过滤tag。BeautifulSoup会找到.string方法与text参数值相符的tag。
        soup.find_all('a', text='Elsie')

    limit:
    find_all()方法返回全部的搜索结构,如果文档树很大那么搜索会很慢。可以使用limit参数限制返回结果的数量。
        soup.find_all('a', limit=2)

    recursive:
    调用tag的find_all()方法时,BeautifulSoup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,使用参数recursive=False
        soup.find_all('title', recursive=False)

CSS选择器
筛选元素,用到的方法soup.select(),返回类型是list。

    通过标签名称进行查找:
    通过标签名称剋直接查找,逐层查找,也可以找到某个标签下的直接子标签和兄弟节点标记。
        直接查找title标签
        soup.select('title')
        逐层查找title标签
        soup.select('html head title')
        查找直接子节点
        查找head下的title标签
        soup.select('head > title')
        查找p下的id='link1'的标签
        soup.select('p > #link1')
        查找兄弟节点
        查找id='link1'之后class=sister的所有兄弟标签
        soup.select('#link1 ~ .sister')
        查找紧跟着id='link1'之后 class=sister的子标签
        soup.select('#link1 + .sister')

    通过CSS的类名查找
        soup.select('.sister')
        soup.select('[class~=sister]')

    通过tag的id查找
        soup.select('#link1')
        soup.select('a#link2')

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

    通过属性值来查找
        soup.select('a[href="http://example.com/elsie"]')
        soup.select('a[href^='http://example.com/']')
        soup.select('a[href$='tillie']')
        soup.select('a[href*='.com/el']')

lxml的Xpath解析

BeautifulSoup可以将lxml作为默认的解析器使用,同样lxml可以单独使用。
BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,时间和内存开销会大很多。
lxml是使用Xpath技术查询和处理Html/xml文档的库,只会局部遍历,速度会快一点。
    from lxml import etree
    html_str = ""
    html = etree.HTML(html_str)
    result = etree.tostring(html)
    print(result)
lxml会自动修正一些html代码。
除了读取字符串之后,lxml还可以直接读取html文件。
    html = etree.parse('index.html')
    result = etree.tostring(html, pretty_print=True)
    接下来使用XPath语法抽取其中所有的URL
    html.xpath(".//*[@class='sister']/@href")
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值