Beautiful Soup 采坑之旅

作者: CielNi | 来源:发表于2018-06-15 01:26 被阅读33次

    Beautiful Soup入门

    Beautiful Soup是一个Python库,用来解析html和xml结构的文档。具体关于Beautiful Soup的介绍与使用,可以参考以下资料:

    Python爬虫利器二之Beautiful Soup的用法

    Beautiful Soup 4.2.0 中文文档

    下面是我在使用Beautiful Soup时遇到的小问题。

    解析器选择

    官方对各个解析器的比较如下:

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

    首先,如果是解析从网页上爬下来的HTML文档,请不要使用lxml XML 解析器,因为HTML解析器和XML解析器对于一文档的解析方式是不同的。比如对于空标签<b />,因为空标签<b />不符合HTML标准,所以解析器把它解析成<b></b>,而使用XML解析时,空标签<b/>依然被保留。

    其次,在Python2.7.3之前的版本和Python3中3.2.2之前的版本中,标准库中内置的HTML解析方法不够稳定,所以我推荐使用lxml或者html5lib作为html文档的解析器,因为容错性比较好。

    在HTML或XML文档格式正确的情况下,不同解析器的差别只是解析速度的差别。而在很多情况下,我们从网页上爬取的HTML文档会有格式不严谨的地方,那么在不同的解析器中返回的结果可能是不一样的,此时用户需要选择合适的解析器来满足自己的需求。

    # lxml解析
    BeautifulSoup("<a></p>", "lxml")
    # <html><body><a></a></body></html>                     未补全
    BeautifulSoup("<a><p>", "lxml")
    # <html><body><a><p></p></a></body></html>              补全
    
    # html5lib库解析
    BeautifulSoup("<a></p>", "html5lib")
    # <html><head></head><body><a><p></p></a></body></html> 补全
    BeautifulSoup("<a><p>", "html5lib")
    # <html><head></head><body><a><p></p></a></body></html> 补全
    
    #Python内置库解析
    BeautifulSoup("<a></p>", "html.parser") 
    # <a></a>                                               未补全
    BeautifulSoup("<a><p>", "html.parser")
    # <a><p></p></a>                                        补全
    

    从上面的例子可以看出,html5lib对于文档的容错性是最好的,它能补全大多数的标签。而lxml和python内置解析器会忽略结束标签,补全开始标签。

    而对于部分没有结束标签的标签比如<input/><img/>等,在正常情况下,解析器都会正确解析,但如果是漏掉'/'的情况下,例如<input><a></a>:

    # lxml解析
    BeautifulSoup("<input><a></a>", "lxml")
    # <html><body><input/><a></a></body></html>                 补全
    
    # html5lib库解析
    BeautifulSoup("<input><a></a>", "html5lib")
    # <html><head></head><body><input/><a></a></body></html>    补全
    
    #Python内置库解析
    BeautifulSoup("<input><a></a>", "html.parser")
    # <input><a></a></input>                                    未补错误
    

    可见,Python内置库解析无法正确补全不需要结束标签的标签,比如<input>

    find_all()的attrs参数

    在find_all()方法中,如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索。如传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:

    soup.find_all(href=re.compile("elsie"))
    # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
    

    假如我们想用 class 过滤,不过 class 是 python 的关键词,这怎么办?加个下划线就可以

    soup.find_all("a", class_="sister")
    # [<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>]
    

    但有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性,同时name由于已经是find_all()方法中的一个参数名(代表tag的名字),所以也不可通过tag中的name属性来搜索tag,但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag,例如:

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

    .string 和 get_text()的区别

    在Beautiful Soup,有两种获取标签内容的方法:.string属性 和 get_text()方法。

    • .string 用来获取标签的内容 ,返回一个 NavigableString 对象。

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

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

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

    • get_text() 用来获取标签中所有字符串包括子标签的内容,返回的是 unicode 类型的字符串

    实际场景中我们一般使用 get_text 方法获取标签中的内容。

    .next_sibling 和 find_next_sibling()

    在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点。实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白。例如:

    <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
    

    如果以为第一个<a>标签的 .next_sibling 结果是第二个<a>标签,那就错了,真实结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符:

    link = soup.a
    link
    # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
    
    link.next_sibling
    # u',\n'
    
    link.next_sibling.next_sibling
    # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
    

    所以我建议使用 find_next_sibling() 方法来查询兄弟节点:

    link.find_next_sibling("a")
    # <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    
    link.find_next_siblings("a")
    # [<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>,
    # <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>]
    
    

    换行符的问题

    在HTML文档中经常会出现一些用来换行<br>标签,比如:

    <div>
      some text <br>
      <span> some more text </span> <br>
      <span> and more text </span>
    </div>
    

    Beautiful Soup会将其自动补全为以下错误的形式:

    <div>
      some text
      <br>
        <span> some more text </span>
        <br>
          <span> and more text </span>
        </br>
      </br>
    </div>
    

    因为<br>标签是为了展示的美观而出现的,而我们在解析文档时,这种标签的出现会影响我们解析的正确性(就如上面那个例子所示)。为了解决这个问题,我们需要使用extract()方法将文档中的<br>标签删掉

    soup = BeautifulSoup(text)
    for linebreak in soup.find_all('br'):
        linebreak.extract()
    

    这样最终的文档格式就变为:

    <div>
      some text
        <span> some more text </span>
        <span> and more text </span>
    </div>
    

    大小写问题

    因为HTML标签是大小写敏感的,所以3种解析器再出来文档时都将tag和属性转换成小写。例如文档中的 <TAG></TAG> 会被转换为 <tag></tag> 。如果想要保留tag的大写的话,那么应该将文档 解析成XML。

    参考资料


    文章标题:Beautiful Soup 采坑之旅
    文章作者:Ciel Ni
    文章链接:http://www.cielni.com/2018/06/14/beautifulsoup/
    有问题或建议欢迎与我联系讨论,转载或引用希望标明出处,感激不尽!

    相关文章

      网友评论

      • 王雨城:刚踩一个,html5lib解析的tag里如果有<!------->取不到parent.text

      本文标题:Beautiful Soup 采坑之旅

      本文链接:https://www.haomeiwen.com/subject/zfxpeftx.html