BeautifulSoup学习笔记(二)数据定位方法

BeautifulSoup的find()和findAll()

第一个用于只寻找一个的情形,第二个则用于寻找所有符合的情形。示例一段简单的抓取网页中所有绿色内容的代码:

1
2
3
nameList = bsObj.findAll("span", {"class":"green"})
for name in nameList:
print(name.get_text())

其中默认返回的是带有原标签的文本,如果不想要输出这个标签,就用get_text()去处理,一定要记住,一般在最后输出的时候才用到这个方法,在此前应该尽量保持大多数标签。

文档中对这两个函数的定义如下:

findAll(tag, attributes, recursive, text, limit, keywords)

find(tag, attributes, recursive, text, keywords)

标签参数 tag 可以传递一个标签的名称或者多个标签名称组成的 Python 列表,比如下面的用法:

.findAll({“h1”,”h2”,”h3”,”h4”,”h5”,”h6”})

参数参数 attributes 使用一个 Python 字典封装一个标签的若干属性和对应的属性值,例如下面返回 HTML 文档中的 span 标签里面 class 属性为 green 与 red 的值:

.findAll(“span”, {“class”:{“green”, “red”}})

递归参数 recursive 数一个布尔变量。你想抓取 HTML 文档标签结构里多少层的信息?如果 recursive 设置为 True,findAll 就会根据你的要求去查找标签参数的所有子标签,以及子标签的子标签。如果 recursive 设置为 False,findAll 就只查找文档的一级标签。findAll 默认是支持递归查找的(recursive 默认值是 True);一般情况下这个参数不需要设置,除 你真正了解自己需要哪些信息,而且抓取速度非常重要,那时你可以设置递归参数。

文本参数 text 有点不同,它是用标签的文本内容去匹配,而不是用标签的属性。假如我们 想查找前面网页中包含“the prince”内容的标签数量,我们可以把之前的 findAll 方法换 成下面的代码:

1
2
nameList = bsObj.findAll(text="the prince")
print(len(nameList))

范围限制参数 limit,显然只用于 findAll 方法。find 其实等价于 findAll 的 limit 等于 1 时的情形。如果你只对网页中获取的前 x 项结果感兴趣,就可以设置它。但是要注意, 这个参数设置之后,获得的前几项结果是按照网页上的顺序排序的,未必是你想要的那 前几项。

还有一个关键词参数 keyword,可以让你选择那些具有指定属性的标签。例如:

1
2
allText = bsObj.findAll(id="text")
print(allText[0].get_text())

值得至于的是,keyword虽然在一些场景中非常有用,但是它是 BeautifulSoup 在技术上做的一个冗余功能。任何用关键词参数能够完成的任务同样可以用别的技术来替代,比如以下两行代码完全等价:

1
2
bsObj.findAll(id="text")
bsObj.findAll("", {"id":"text"})

另外在使用 class 属性来寻找标签的时候,用于其是 Python 的保留字,所以不能够当做变量或者参数名来使用,为此 BeautifulSoup 提出了一个方案是使用 class_ 来进行替代,也可以用属性参数把 class 用引号包起来:

1
2
bsObj.findAll(class_="green")
bsObj.findAll("", {"class":"green"})

BeautifulSoup 中的所有对象

  • BeautifulSoup 对象

  • 标签 Tag 对象

  • NavigableString 对象

    用来表示标签里的文字,不是标签(有些函数可以操作和生成 NavigableString 对象, 而不是标签对象)

  • Comment 对象

    用来查找 HTML 文档的注释标签

导航树

处理子标签和其他后代标签

和人类的家谱一样,子标签就是一个父标签的下一级,而后代标签是指一个父标签下面所有级别的标签。如果只想要找出子标签,用 .children 标签就好了:

1
2
3
4
5
6
7
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page3.html") bsObj = BeautifulSoup(html)
for child in bsObj.find("table",{"id":"giftList"}).children:
print(child)

这段代码会打印 giftList 表格中所有产品的数据行。如果你用 descendants() 函数而不是 children() 函数,那么就会有二十几个标签打印出来,这些都是子标签的子标签。

处理兄弟标签

BeautifulSoup 的 next_siblings() 函数可以让收集表格数据成为简单的事情,尤其是处理 带标题行的表格:

1
2
3
4
5
6
7
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html)
for sibling in bsObj.find("table",{"id":"giftList"}).tr.next_siblings:
print(sibling)

注意: 这里获取的兄弟标签不包含 giftList 本身理由如下:

  • 对象不能把自己作为兄弟标签。任何时候你获取一个标签的兄 弟标签,都不会包含这个标签本身。
  • 这个函数只调用后面的兄弟标签。

为了让爬虫代码的鲁棒性更强,一般不会直接调用bsObj的属性而是用 find 来获取所要的内容,如下所示:

1
bsObj.find("table",{"id":"giftList"}).tr

值得一提的是,还有 previous_siblings 的用法。

父标签处理

偶尔也会需要用到自下层寻找下层的函数,此时需要调用 parent 或者 parents,比如下面所示:

1
2
3
4
5
6
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html)
print(bsObj.find("img",{"src":"../img/gifts/img1.jpg" }).parent.previous_sibling.get_text())

这段代码可以打印出第一个图片商品对应的价格,查看网页的内容可以知道 img 标签的父亲的上一个兄弟标签就是价格表单项。

正则表达式

和编译原理中的正则表达式很类似,给定正则表达式,可以识别正则字符串 (regular string);也就是说,如果给定的字符串符合规则,那么久返回它,反之,就忽略它。比如:

  • 字母“a”至少出现一次;
  • 后面跟着字母“b”重复 5 次;
  • 后面再跟字母“c”重复任意偶数次;
  • 最后一位是字母“d”,也可以没有。

那么可以用正则表达式表示为:

aa*bbbbb(cc)*(d | )

可以发现和编译原理中所学的是几乎一样了。我们可以写好之后到 RegexPal ( http://regexpal.com/ )这样的网站中去检测我们写的正则表达式是否符合要求。

很容易想到的一个应用是识别邮箱的地址,虽然不同服务器具体规则不尽数相同,但是还是可以写出下面这样的通用规则:

[A-Za-z0-9\._+]+@[A-Za-z]+\.(com|org|edu|net)

部分需要解释的点如下:

  • [A-Za-z0-9._+]+:括号内的A-Z表示的是从A到Z的任意大写字母,把所有可能出现的序列和符号都放在中括号(不是小括号)里面,表示括号中的任何一个符号。要注意后面的加号,它表示示“这些符号都可以出现多次,且至少出现 1 次”。

  • \.:在域名前必须有一个点号(.),这里用反斜杠来表示转义。

下面给出正则表达式中常用的符号:

符号表

正则表达式和BeautifulSoup

为了提高代码的鲁棒性,以及预防捕捉到用于其他用途的贴图,一般不会采用利用位置定位抓取图片的方法而是选择使用正则表达式,比如下面的代码范例:

1
2
3
4
5
6
7
8
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html) images = bsObj.findAll("img",{"src":re.compile("\.\.\/img\/gifts/img.*\.jpg")})
for image in images:
print(image["src"])

获取属性

对于一个标签对象,可以用下面的代码来获取它的全部属性:

1
myTag.attrs

其返回一个Python字典对象,可以获取和操作这些属性。比如要获取图片资源位置 src,可以用下面这行代码:

1
myImgTag.attrs["src"]

Lambda表达式

Lambda 表达式本质上就是一个函数,可以作为其他函数的变量使用;也就是说,一个函 数不是定义成 f(x, y),而是定义成 f(g(x), y),或 f(g(x), h(x)) 的形式。

BeautifulSoup 允许我们把特定函数类型当作 findAll 函数的参数。唯一的限制条件是这些 函数必须把一个标签作为参数且返回结果是布尔类型。BeautifulSoup 用这个函数来评估它 遇到的每个标签对象,最后把评估结果为“真”的标签保留,把其他标签剔除。

例如,下面的代码就是获取有两个属性的标签:

1
soup.findAll(lambda tag: len(tag.attrs) == 2)