内容提取神器 beautiful Soup 的用法

图片来自 unsplash

上篇文章只是简单讲述正则表达式如何读懂以及 re 常见的函数的用法。我们可能读懂别人的正则表达式,但是要自己写起正则表达式的话,可能会陷入如何写的困境。正则表达式写起来费劲又出错率高,那么有没有替代方案呢?俗话说得好,条条道路通罗马。目前还两种代替其的办法,一种是使用 Xpath 神器,另一种就是本文要讲的 BeautifulSoup。

1 BeautifulSoup 简介

引用 BeautifulSoup 官网的说明:

Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.

大致意思如下: BeautifulSoup 是一个能从 HTML 或 XML 文件中提取数据的 Python 库。它能通过自己定义的解析器来提供导航、搜索,甚至改变解析树。它的出现,会大大节省开发者的时间。

2 安装 BeautifulSoup

目前 BeautifulSoup 最新版本是 4.6.0,它是支持 Python3的。所以可以大胆去升级安装使用。

安装方法有两种:

  • 使用pip
    比较推荐使用这种方式,既简单又方便管理。
pip install beautifulsoup4
# 如果出现因下载失败导致安装不上的情况,可以先启动 ss 再执行安装命令
# 或者在终端中使用代理
pip --proxy http://代理ip:端口 install beautifulsoup4
  • 使用easy_install
easy_install beautifulsoup4
  • 使用系统包管理
sudo apt-get install Python-bs4
# 适用于 ubuntu 系统以及 Debian 系统

3 初始 BeautifulSoup

首先导入 BeautifulSoup 库,然后创建一个 BeautifulSoup 对象,再利用对象做文章。
具体参考示例代码:

from bs4 import BeautifulSoup

soup = BeautifulSoup(response)
print(soup.prettify())

上面代码中,response 可以urlllib或者request请求返回的内容,也可以是本地 HTML 文本。如果要打开本地,代码需要改为

soup = BeautifulSoup(open("index.html"))
# 打开当前目录下 index.html 文件

soup.prettify()函数的作用是打印整个 html 文件的 dom 树,例如上面执行结果如下:

<html>
 <head>
   <title>
       The Dormouse's story
   </title>  
 </head>  
 <body>
   <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
   <p class="story">Once upon a time there were three little sisters; and their names were</p>
   <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>
 </body>
</html>

4 解析 BeautifulSoup 对象

想从 html 中获取到自己所想要的内容,我归纳出三种办法:

1)利用 Tag 对象

从上文得知,BeautifulSoup 将复杂 HTML 文档转换成一个复杂的树形结构,每个节点都是Python对象。跟安卓中的Gson库有异曲同工之妙。节点对象可以分为 4 种:Tag, NavigableString, BeautifulSoup, Comment

Tag 对象可以看成 HTML 中的标签。这样说,你大概明白具体是怎么回事。我们再通过例子来更加深入了解 Tag 对象。以下代码是以 prettify() 打印的结果为前提。

  • 例子1

获取head标签内容

print(soup.head)
# 输出结果如下:
<head><title>The Dormouse's story</title></head>
  • 例子2

获取title标签内容

print(soup.title)
# 输出结果如下:
<title>The Dormouse's story</title>
  • 例子3

获取p标签内容

print(soup.p)
# 输出结果如下:
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

如果 Tag 对象要获取的标签有多个的话,它只会返回所以内容中第一个符合要求的标签。

对象一般含有属性,Tag 对象也不例外。它具有两个非常重要的属性, <font color='red'>name</font><font color='red'>attrs</font>

name
name 属性是 Tag 对象的标签名。不过也有特殊的,soup 对象的 name 是 [document]

print(soup.name)
print(soup.head.name)
# 输出结果如下:
[document]
head

attrs
attrs 属性是 Tag 对象所包含的属性值,它是一个字典类型。

print(soup.p.attrs)
# 输出结果如下:
{'class': ['title'], 'name': 'dromouse'}

其他三个属性也顺带介绍下:

  • NavigableString

说白了就是:Tag 对象里面的内容

print(soup.title.string)
 # 输出结果如下:
The Dormouse's story
  • BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象。它是一个特殊的 Tag。

print(type(soup.name))
print(soup.name)
print(soup.attrs)
# 输出结果如下:
<type 'unicode'>
[document]
{} 空字典
  • Comment

Comment 对象是一个特殊类型的 NavigableString 对象。如果 HTML 页面中含有注释及特殊字符串的内容。而那些内容不是我们想要的,所以我们在使用前最好做下类型判断。例如:

if type(soup.a.string) == bs4.element.Comment:
    ...    # 执行其他操作,例如打印内容

2)利用过滤器

过滤器其实是一个find_all()函数, 它会将所有符合条件的内容以列表形式返回。它的构造方法如下:

find_all(name, attrs, recursive, text, **kwargs )

name 参数可以有多种写法:

  • (1)节点名
print(soup.find_all('p'))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>, <p class="story">Once upon a time there were three little sisters; and their names were</p>]
  • (2)正则表达式
print(soup.find_all(re.compile('^p')))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>, <p class="story">Once upon a time there were three little sisters; and their names were</p>]
  • (3)列表
    如果参数为列表,过滤标准为列表中的所有元素。看下具体代码,你就会一目了然了。
print(soup.find_all(['p', 'a']))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>,  <p class="story">Once upon a time there were three little sisters; and their names were</p>,  <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>]

另外 attrs 参数可以也作为过滤条件来获取内容,而 limit 参数是限制返回的条数。

3)利用 CSS 选择器

以 CSS 语法为匹配标准找到 Tag。同样也是使用到一个函数,该函数为select(),返回类型也是 list。它的具体用法如下, 同样以 prettify() 打印的结果为前提:

  • (1)通过 tag 标签查找
print(soup.select(head))
# 输出结果如下:
[<head><title>The Dormouse's story</title></head>]
  • (2)通过 id 查找
print(soup.select('#link1'))
# 输出结果如下:
[<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>]
  • (3)通过 class 查找
print(soup.select('.sister'))
# 输出结果如下:
[<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>]
  • (4)通过属性查找
print(soup.select('p[name=dromouse]'))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>]
print(soup.select('p[class=title]'))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>]
  • (5)组合查找
print(soup.select("body p"))
# 输出结果如下:
[<p class="title" name="dromouse"><b>The Dormouse's story</b></p>,
<p class="story">Once upon a time there were three little sisters; and their names were</p>]
print(soup.select("p > a"))
# 输出结果如下:
[<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>]
print(soup.select("p > .sister"))
# 输出结果如下:
[<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>]

5 处理上下关系

从上文可知,我们已经能获取到节点对象,但有时候需要获取其父节点或者子节点的内容,我们要怎么做了?这就需要对parse tree进行遍历

(1)获取子节点
利用.children属性,该属性会返回当前节点所以的子节点。但是它返回的类型不是列表,而是迭代器

(2)获取所有子孙节点
使用.descendants属性,它会返回所有子孙节点的迭代器

(3)获取父节点
通过.parent属性可以获得所有子孙节点的迭代器

(4)获取所有父节点
.parents属性,也是返回所有子孙节点的迭代器

(5)获取兄弟节点
兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling属性获取了该节点的下一个兄弟节点,.previous_sibling则与之相反,如果节点不存在,则返回 None

注意:实际 HTML 中的 tag 的.next_sibling.previous_sibling属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行

(5)获取所有兄弟节点
通过.next_siblings.previous_siblings属性可以对当前节点的兄弟节点迭代输出


上篇文章:Python 正则表达式
推荐阅读:详解 python3 urllib


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

推荐阅读更多精彩内容