• 爬虫(七):BeatifulSoup模块


    1. Beautiful Soup介绍

    Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。能将即将要进行解析的源码加载到bs对象,调用bs对象中相关的方法或属性进行源码中的相关标签的定位,并获取定位到的标签之间存在的文本或者属性值。

    它能够通过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。Beautiful Soup会帮你节省数小时甚至数天的工作时间。

    1.1 安装bs4

    pip install 包名 -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com

    1.2 初始化

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup("<html>A Html Text</html>", "html.parser")

    两个参数:第一个参数是要解析的html文本,第二个参数是使用那种解析器,对于HTML来讲就是html.parser,这个是bs4自带的解析器。

    如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的。

    格式化输出:

    soup.prettify()

    1.3 对象

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

    1.3.1 tag

    Tag对象与xml或html原生文档中的tag相同。

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','xml')
    tag = soup.b
    print(type(tag))# <class 'bs4.element.Tag'>

    如果不存在,则返回 None,如果存在多个,则返回第一个。

    每个tag都有自己的名字。

    tag = soup.b
    print(tag.name)# b

    tag的属性是一个字典。

    tag = soup.b
    print(tag['class'])# boldest
    print(tag.attrs)# {'class': 'boldest'}
    print(type(tag.attrs))# <class 'dict'>

    多值属性,最常见的多值属性是class,多值属性的返回list。

    soup = BeautifulSoup('<p class="body strikeout"></p>','xml') 

    print(soup.p['class']) # ['body', 'strikeout'] print(soup.p.attrs) # {'class': ['body', 'strikeout']}

    如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回。

    soup = BeautifulSoup('<p id="my id"></p>', 'html.parser')
    print(soup.p['id'])    # 'my id'

    text属性返回tag的所有字符串连成的字符串。

    1.3.2 NavigableString

    字符串常被包含在tag内,BeautifulSoup用NavigableString类来包装tag中的字符串。但是字符串中不能包含其他tag。

    soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','xml')
    
    s = soup.b.string
    
    print(s)        # Extremely bold
    
    print(type(s))  # <class 'bs4.element.NavigableString'>

    1.3.3 BeautifulSoup

    BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作Tag对象。但是BeautifulSoup对象并不是真正的HTML或XML的 tag,它没有attribute属性,name属性是一个值为“[document]”的特殊属性。

    1.3.4 Comment

    Comment一般表示文档的注释部分。

    oup = BeautifulSoup("<b><!--This is a comment--></b>",'xml')
    
    comment = soup.b.string
    
    print(comment)          # This is a comment
    
    print(type(comment))    # <class 'bs4.element.Comment'>

    1.4 遍历

    1.4.1 子节点

    contents属性返回所有子节点的列表,包括NavigableString类型节点。如果节点当中有换行符,会被当做是NavigableString类型节点而作为一个子节点。

    NavigableString 类型节点没有contents属性,因为没有子节点。

    soup = BeautifulSoup("""<div>
    <span>test</span>
    </div>
    """,'xml')
    
    element = soup.div.contents
    
    print(element)          # ['
    ', <span>test</span>, '
    ']

    children属性跟contents属性基本一样,只不过返回的不是子节点列表,而是子节点的可迭代对象。

    descendants属性返回tag的所有子孙节点。

    如果tag只有一个NavigableString类型子节点,那么这个tag可以使用.string得到子节点。

    如果一个tag仅有一个子节点,那么这个tag也可以使用.string方法,输出结果与当前唯一子节点的.string结果相同。

    如果tag包含了多个子节点,tag就无法确定.string方法应该调用哪个子节点的内容,.string 的输出结果是None。

    soup = BeautifulSoup("""<div>
        <p><span><b>test</b></span></p>
    </div>
    """,'xml')
    
    element = soup.p.string
    
    print(element)          # test
    
    print(type(element))    # <class 'bs4.element.NavigableString'>

    特别注意,为了清楚显示,一般我们会将html节点换行缩进显示,而在BeautifulSoup中会被认为是一个NavigableString类型子节点,导致出错。上例中,如果改成element = soup.div.string就会出错。

    如果tag中包含多个字符串,可以用strings属性来获取。如果返回结果中要去除空行,则可以用stripped_strings属性。

    soup = BeautifulSoup("""<div>
        <p>      </p>
        <p>test 1</p>
        <p>test 2</p>
    </div>
    """, 'html.parser')
    
    element = soup.div.stripped_strings
    
    print(list(element))          # ['test 1', 'test 2']

    1.4.2 父节点

    parent属性返回某个元素(tag、NavigableString)的父节点,文档的顶层节点的父节点是BeautifulSoup对象,BeautifulSoup对象的父节点是None。

    parents属性递归得到元素的所有父辈节点,包括BeautifulSoup对象。

    1.4.3 兄弟节点

    next_sibling返回后一个兄弟节点,previous_sibling返回前一个兄弟节点。直接看个例子,注意别被换行缩进搅了局。

    soup = BeautifulSoup("""<div>
        <p>test 1</p><b>test 2</b><h>test 3</h></div>
    """, 'html.parser')
    
    print(soup.b.next_sibling)      # <h>test 3</h>
    
    print(soup.b.previous_sibling)  # <p>test 1</p>
    
    print(soup.h.next_sibling)      # None

    next_siblings返回后面的兄弟节点

    previous_siblings返回前面的兄弟节点

    1.4.4 回退和前进

    把html解析看成依次解析标签的一连串事件,BeautifulSoup提供了重现解析器初始化过程的方法。

    next_element属性指向解析过程中下一个被解析的对象(tag或NavigableString)。

    previous_element属性指向解析过程中前一个被解析的对象。

    另外还有next_elements和previous_elements属性,不赘述了。

    1.5 搜索

    1.5.1 过滤器

    介绍find_all()方法前,先介绍一下过滤器的类型,这些过滤器贯穿整个搜索的API。过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中。

    html = """
    <div>
        <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></p>
    </div>
    """
    
    soup = BeautifulSoup(html, 'html.parser')

    查找所有的<b>标签

    print(soup.find_all('b'))  # [<b>The Dormouse's story</b>]

    传入正则表达式作为参数,返回满足正则表达式的标签。下面例子中找出所有以b开头的标签。

    print(soup.find_all(re.compile("^b")))  # [<b>The Dormouse's story</b>]

    传入列表参数,将返回与列表中任一元素匹配的内容。下面例子中找出所有<a>标签和<b>标签。

    print(soup.find_all(["a", "b"]))  # [<b>The Dormouse's story</b>, <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>]

    True可以匹配任何值,下面的代码查找到所有的tag,但是不会返回字符串节点。

    print(soup.find_all(True))

    如果没有合适的过滤器,还可以自定义一个方法,方法只接收一个元素参数,如果这个方法返回True表示当前元素匹配被找到。

    例子,返回所有包含class属性但不包含id属性的标签:

    def has_class_but_no_id(tag):
        return tag.has_attr('class') and not tag.has_attr('id')
    
    print(soup.find_all(has_class_but_no_id))

    结果:

    结果怎么不对呢,<a>标签含有id属性。其实返回的list中只有2个元素,都是<p>标签,<a>标签是<p>标签的子节点。

    1.5.2 find和find_all

    搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。 

    语法:

    find(name=None, attrs={}, recursive=True, text=None, **kwargs)

    find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

    参数:

    name:查找所有名字为name的tag,字符串对象会被自动忽略掉。上面过滤器示例中的参数都是name参数。当然,其他参数中也可以使用过滤器。

    attrs:按属性名和值查找。传入字典,key为属性名,value为属性值。

    recursive:是否递归遍历所有子孙节点,默认True。

    text:用于搜索字符串,会找到 .string方法与text参数值相符的tag,通常配合正则表达式使用。也就是说,虽然参数名是 text,但实际上搜索的是string属性。

    limit:限定返回列表的最大个数。

    kwargs:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作tag的属性来搜索。这里注意,如果要按class属性搜索,因为class是python的保留字,需要写作class_。

    Tag的有些属性在搜索中不能作为kwargs参数使用,比如H5中的data-*属性。

    data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
    
    print(data_soup.find_all(data-foo="value"))
    
    # SyntaxError: keyword can't be an expression

    但是可以通过attrs参数传递

    data_soup = BeautifulSoup('<div data-foo="value">foo!</div>','xml')
    
    print(data_soup.find_all(attrs={"data-foo": "value"}))
    
    # [<div data-foo="value">foo!</div>]

     

    1.6 CSS选择器

    BeautifulSoup支持大部分的CSS选择器,这里直接用代码来演示。

    from bs4 import BeautifulSoup
    
    html = """
    <html>
    <head><title>标题</title></head>
    <body>
     <p class="title" name="dromouse"><b>标题</b></p>
     <div name="divlink">
      <p>
       <a href="http://example.com/1" class="sister" id="link1">链接1</a>
       <a href="http://example.com/2" class="sister" id="link2">链接2</a>
       <a href="http://example.com/3" class="sister" id="link3">链接3</a>
      </p>
     </div>
     <p></p>
     <div name='dv2'></div>
    </body>
    </html>
    """
    
    soup = BeautifulSoup(html, 'lxml')
    
    # 通过tag查找
    print(soup.select('title'))  # [<title>标题</title>]
    
    # 通过tag逐层查找
    print(soup.select("html head title"))  # [<title>标题</title>]
    
    # 通过class查找
    print(soup.select('.sister'))
    # [<a class="sister" href="http://example.com/1" id="link1">链接1</a>,
    # <a class="sister" href="http://example.com/2" id="link2">链接2</a>,
    # <a class="sister" href="http://example.com/3" id="link3">链接3</a>]
    
    
    # 通过id查找
    print(soup.select('#link1, #link2'))
    # [<a class="sister" href="http://example.com/1" id="link1">链接1</a>,
    # <a class="sister" href="http://example.com/2" id="link2">链接2</a>]
    
    
    # 组合查找
    print(soup.select('p #link1'))# [<a class="sister" href="http://example.com/1" id="link1">链接1</a>]
    
    
    # 查找直接子标签
    print(soup.select("head > title"))# [<title>标题</title>]
    
    print(soup.select("p > #link1"))# [<a class="sister" href="http://example.com/1" id="link1">链接1</a>]
    
    print(soup.select("p > a:nth-of-type(2)")) # [<a class="sister" href="http://example.com/2" id="link2">链接2</a>]
    # nth-of-type 是CSS选择器
    
    
    # 查找兄弟节点(向后查找)
    print(soup.select("#link1 ~ .sister"))
    # [<a class="sister" href="http://example.com/2" id="link2">链接2</a>,
    # <a class="sister" href="http://example.com/3" id="link3">链接3</a>]
    
    print(soup.select("#link1 + .sister"))
    # [<a class="sister" href="http://example.com/2" id="link2">链接2</a>]
    
    
    # 通过属性查找
    print(soup.select('a[href="http://example.com/1"]'))
    
    # ^ 以XX开头
    print(soup.select('a[href^="http://example.com/"]'))
    
    # * 包含
    print(soup.select('a[href*=".com/"]'))
    
    # 查找包含指定属性的标签
    print(soup.select('[name]'))
    
    # 查找第一个元素
    print(soup.select_one(".sister"))

    1.7 实例

    我们来爬取三国演义的内容,我甚至有个想法,去那些小说网里爬小说去。

    import requests
    from bs4 import BeautifulSoup
    
    url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'
    }
    page_text = requests.get(url=url,headers=headers).text
    
    soup = BeautifulSoup(page_text,'lxml')
    
    a_list = soup.select('.book-mulu > ul > li > a')
    
    fp = open('sanguo.txt','w',encoding='utf-8')
    for a in a_list:
        title = a.string
        detail_url = 'http://www.shicimingju.com'+a['href']
        detail_page_text = requests.get(url=detail_url,headers=headers).text
    
        soup = BeautifulSoup(detail_page_text,'lxml')
        content = soup.find('div',class_='chapter_content').text
    
        fp.write(title+'
    '+content)
        print(title,'下载完毕')
    print('over')
  • 相关阅读:
    微访谈之1:解答各位朋友关心的问题
    深入浅出SQL Server中的死锁(实战篇)
    怎样玩转千万级别的数据
    Another MySQL daemon already running with the same unix socket
    c++ undefined reference to mysqlinit
    Another MySQL daemon already running with the same unix socket
    linxu 挂载分区
    C# RSA
    谷歌地图实现车辆轨迹移动播放(google map api)
    百度地图实现车辆轨迹移动播放(baidu map api)
  • 原文地址:https://www.cnblogs.com/liuhui0308/p/12050439.html
Copyright © 2020-2023  润新知