python网络数据采集——BeautifulSoup

BeautifulSoup

BeautifulSoup 是 python 中最受欢迎的 html 解析库。如果 BeautifulSoup 这个第三方库不能解决问题,读者可以考虑另外两个库:

1
2
3
4
5
6
7
8
xml:
可以用来解析 HTML 和 XML 文档,以非常底层的实现而闻名于世,大部分源代码是用 C 语言写的。
虽然学习它需要花一些时间(其实学习曲线越陡峭,表明你可以越快地学会它),但它在处理绝大多数 HTML 文档时速度都非常快。
如果遇到的是引用第三方库处理时遇到的性能瓶颈,可以考虑。

HTML parser:
这是 Python 自带的解析库(https://docs.python.org/3/library/html.parser.html )。
因为它不用安装(只要装了 Python 就有),所以可以很方便地使用。

find 和 find_all

在 BeautifulSoup 中,这两个函数是经常使用的,专门用来查找 html 中指定的标签。接下来笔者用链接:http://www.pythonscraping.com/pages/warandpeace.html 做为例子来使用这两个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#! /usr/local/bin/python3
# encoding:utf-8

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObject = BeautifulSoup(html, "html.parser")
# 使用 findAll 方法查找 指定标签 指定class属性的
nameList = bsObject.find_all("span", {"class": "green"})
for name in nameList:
print(name.get_text())

# 像这样,你就抓取了html中所有的人名
# 因为经过分析,发现 span 标签且 class 为 green 的内容都是小说的人名

通过源码你会发现 find_all 方法拥有以下参数:name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs,在此说明一下 以前的 BeautifulSoup 版本方法名是 findAll

在开发的过程中,95%的情况下你只会使用到 name attars 这两个参数,但是在这里还是要说明一下其他参数的作用:

  • name html中tag的名称 比如span h1 你可以传入多个tag的集合来查找 这个集合是一个set结构
  • attrs 字典集 用来指定 tag的一些属性 比如class width height
  • recursive 表示在查找时 你是否想要递归 到子标签中查找 默认为True 递归
  • text 直接查找文本 有相同文本的匹配
  • limit 指定查找的数量 查找的顺序是按照 html 的顺序来的 默认 全部
  • kwargs 字典集 查找具有指定属性的标签 业界认为是冗余的设计 不推荐使用 并且存在缺陷 比如无法指定class属性 因为class是python的保留关键字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    hList = bsObject.find_all({"h1", "h2", "h3"})
    print(hList)

    tList = bsObject.find_all(text="Anna Pavlovna")
    print(tList)

    nameList = bsObject.find_all(class_="green")
    print(nameList)
    # 但是你可以使用 class_ 来代替

find 的使用方式和 find_all 差不多,只是缺少了参数 limit 而已。

导航树

先抛出一个问题:
find_all 函数是通过标签的名称和属性来查找标签的,但是如果需要通过标签在文档中的位置来查找标签,该如何?

引入导航树(Navigating Trees)的概念就是为何解决这个问题,比如:bsObject.tag.subTag.anotherSubTag。

在这里我们使用虚拟的购物网站:http://www.pythonscraping.com/pages/page3.html 来作为实例。对于使用导航树,你只需要学会处理三个问题:

  • 处理子标签和后代标签
  • 处理兄弟标签
  • 处理父标签

子标签和后代标签

1
2
3
4
5
6
7
8
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObject = BeautifulSoup(html, "html.parser")

for child in bsObject.find("table", {"id": "giftList"}).children:
# print(type(child))
print(child)

子标签和后代标签在定义上是不同的:子标签只有一个层级的差距,后代标签则是一个或者多个层级差距。Tag对象 函数 children 和 descendants 分别获取子标签和后代标签。

兄弟标签

兄弟标签表示 Tag类型相同且处于同一层级的,Tag对象 函数 next_siblings 和 previous_siblings 分别获取定位标签的上面的所有Tag和下面所有Tag。

1
2
3
4
5
6
7
8
9
10
11
12
比如:
<tr class="tr1"></tr>
<tr class="tr2"></tr>
<tr class="tr3"></tr>
<tr class="tr4"></tr>
<tr class="tr5"></tr>
现在定位tr3,next_siblings可获取tr4,tr5;previous_siblings可获取tr2,tr1

for sibling in bsObject.find("table", {"id": "giftList"}).tr.next_siblings:
print(sibling)
# 与第一段代码相比,可以发现少了表格标题的tr,因为第一个tr标签不能将自己视为兄弟
# 当然也可以使用next_sibling 和 previous_sibling 只不过获取到的是距离最近的那一个

父标签

查找父标签在提取信息的过程中是偶尔的情况下才会用到的,如果那个信息比较深入,并且它的子标签有明显的特点方便查找,可以考虑使用。

1
2
3
4
5
6
7
print(bsObject.find("img", {"src": "../img/gifts/img1.jpg"})
.parent.previous_sibling.get_text())

# 1.找到Tag类型是 img 的且 src 是 ../img/gifts/img1.jpg
# 2.向上寻找到父标签 即 td
# 3.找到 td 标签的上一个兄弟标签 还是 td
# 4.得到里面的 text 文本,即商品的价格

显示 Gitment 评论