Beautiful Soup 采坑之旅

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/
有问题或建议欢迎与我联系讨论,转载或引用希望标明出处,感激不尽!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354

推荐阅读更多精彩内容