Python网络爬虫(一)
BeautifulSoup装载HTML文档
BeautifulSoup的安装
- BeautifulSoup是第三方的工具,它包含在一个名称为bs4的文件包中,需要另外安装。在命令窗体中进入Python的安装目录(例如Python在c:\Python36),再进入Scripts子目录,找到pip程序,执行pip install bs4。判断是否安装bs4,可在python的命令窗口中执行语句:from bs4 import BeautifulSoup,如果这条语句没有报错,则代表安装成功了。
BeautifulSoup装载HTML文档
- 如果doc是一个HTML文档,通过:
from bs4 import BeautifulSoup
soup=BeautifulSoup(doc,“lxml”)
就可以创建一个名称为BeautifulSoup对象,其中doc是一个HTML文档字符串,“lxml”是一个参数,表示创建的是一个通过lxml解析器解析的文档,BeautifulSoup有多种解析器,其中“lxml”是最常用的一个。
通过调用:
soup.prettify()
就可以把soup对象的文档树变成一个字符串。
BeautifulSoup装载文档的功能十分强大,它在装载的过程中如果发现HTML文档中的元素有缺失的情况,它会尽可能对文档进行修复,使得最后的文档树是一颗完整的树。这一点十分重要,因为我们面临的大多数网页都或多或少有些元素是缺失的,BeautifulSoup都能正确的装载他们。
BeautifulSoup查找HTML元素
-
查找文档的元素是我们爬取网页信息的重要手段,BeautifulSoup提供了一系列的查找元素的方法,其中功能强大的find_all、find函数就是其中常用的。
-
find_all函数是查找所有满足要求的元素节点,原型如下:
find_all(self,name=None,attrs={},recursive=True,text=None,limit=None,
**kwarges)
self表明它是一个类成员函数;
name是要查找的tag元素名称,默认是None,如果不提供,就是查找所有的元素;
attrs是元素的属性,它是一个字典,默认是空,如果提供就是查找有这个指定属性的元素;
recursive指定查找是否在元素节点的字数下面全范围进行,默认是True;
后面的text、limit、kwarges参数比较复杂,将会在后面的博客中介绍。
find_all函数返回查找到的所有指定的元素的列表,每个元素是一个bs4.element.Tag对象。 -
find函数,与find_all不同的是,find函数只会查找一个元素节点,原型如下:
find(self,name=None,attrs={},recursive=True,text=None,limit=None,
**kwarges)
使用方法与find_all类型。 -
假如有个doc文档如下:
from bs4 import BeautifulSoup
doc='''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">
Once upon a time there were three little sisters;and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Llsie</a>and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.
</p>
<p class="story">...</p>
</body>
</html>
'''
- 查找文档中的
<title>
元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("title")
print(type(tag),tag)
结果显示:<class 'bs4.element.Tag'><title>The Dormouse's story</title>
由此可见查找到<title>
元素,元素类型是一个bs4.element.Tag对象。
- 查找文档中的
<a>
元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tags=soup.find_all("a")
for tag in tags:
print(tag)
程序结果找到3个<a>
元素:
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Llsie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
- 查找文档中的第一个
<a>
元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("a")
print(tag)
程序结果找到第一个<a>
元素:
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
- 查找文档中
class="title"
的<p>
元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("p",attrs={"class":"title"})
print(tag)
程序结果找到class="title"
的<p>
元素,这里用的是find函数,只能找到最初的一个。
<p class="title"><b>The Dormouse's story</b></p>
- 查找文档中
class="sister"
的元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tags=soup.find_all(name=None,attrs={"class":"sister"})
for tag in tags:
print(tag)
其中name=None表示无论是什么名字的元素,程序结果找到3个
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Llsie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
BeautifulSoup获取元素的属性值
- 如果一个元素已经找到,例如找到了
<a>
元素,那么怎么样获取它的属性值呢?BeautifulSoup使用:
tag[attrName]
来获取tag元素的名称为attrName的属性值,其中tag是一个bs4.Telement.Tag对象。 - 例如,还是上面的那个doc文档,我们查找文档中的所有超级链接地址,程序如下:
soup=BeautifulSoup(doc,"lxml")
tags=soup.find_all("a")
for tag in tags:
print(tag["href'])
程序结果为:
http://example.com/elsie
http://example.com/lacie
http://example.com/tillie
BeautifulSoup获取元素包含的文本值
- 如果一个元素元素已经找到,例如找到了
<a>
元素,那么怎么样获取它包含的文本值呢?BeautifulSoup使用:
tag.text
来获取tag元素包含的文本值,其中tag是一个bs4.Telement.Tag对象。 - 例如,还是上面的那个doc文档,我们查找文档中所有
<a>
超级链接包含的文本值,程序如下:
soup=BeautifulSoup(doc,"lxml")
tags=soup.find_all("a")
for tag in tags:
print(tag.text)
程序结果:
Elsie
Lacie
Tillie
BeautifulSoup的高级查找
一般的find或者find_all都能满足我们的需要,如果还不能就可以设计一个查找函数来进行查找。
- 查找文档中的
href="http://example.com/lacie"
的节点元素<a>
def myFilter(tag):
print(tag.name)
return (tag.name=="a" and tag.has_attr("href") and tag["href"]=="http://example.com/lacie"
soup=BeautifulSoup(doc,"lxml")
tag=soup.find_all(maFilter)
print(tag)
程序显示结果:
html
head
title
body
a
a
a
[<a href="htttp://example.com/lacie">Lacie</a>]
说明:在程序中我们定义了一个筛选函数myFilter(tag),它的参数是tag对象,在调用soup.find_all(myFilter)时程序会把每个tag元素传递给myFilter函数,由该函数决定这个tag的取舍,如果myFilter返回True就保留这个tag到函数中,不然就丢掉这个tag。因此程序执行时就可以看到html,body,head,title,body,a,a,a等一个个经过myFilter的筛选,只用节点<a href="htttp://example.com/lacie">Lacie</a>
满足要求。其中:
tag.name是tag的名称;
tag.has_attr(attrName)判断tag是否有attrName属性;
tag[attrName]是tag的attrName属性值。
- 通过函数查找可以查找到一些复杂的节点元素,查找文本值以
"cie"
结尾的所有<a>
节点
def endsWith(s,t):
if len(s)>=len(t):
return s[len(s)-len(t):]==t
return False
def myFilter(tag):
return (tag.name=="a" and endsWith(tag.text,"cie"))
soup=BeautifulSoup(doc,"lxml")
tag=soup.find_all(maFilter)
print(tag)
程序结果为:
<a href="htttp://example.com/lacie">Lacie</a>
程序中定义了一个endWith(s,t)函数判断s字符串是否以字符串t结尾,是就返回True,不然就返回False,在myFilter中调用这个函数判断tag.text是否以"cie"
结尾,最后找出所有文本值以 "cie"
结尾的<a>
节点。
BeautifulSoup获取元素节点的父节点
- BeautifulSoup通过:
tag.parent
获取tag节点的父节点,其中根节点<html>
的父节点是名称为[document]的节点,这个[document]节点的父节点是None。 - 对于上述的doc文档,找出文档中
<p class="title"><b>The Dormouse's story</b></p>
的<b>
元素节点的所有父节点的名称。
soup=BeautifulSoup(doc,"lxml")
print(soup.name)
tag=soup.find("b")
while tag:
print(tag.name)
tag=tag.parent
程序结果:
[document]
b
p
body
html
[document]
依次可见,节点的父节点依次是<p>
、<body>
、<html>
BeautifulSoup获取元素节点的直接子元素节点
- BeautifulSoup通过:
tag.children
获取tag节点的所有直接子节点,包括element、text等类型的节点。 - 如,获取
<p>
元素的所有直接子元素节点
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("p")
for x in tag.children:
print(x)
程序结果:
<b>The Dormouse's story</b>
BeautifulSoup获取元素节点的所有子孙元素节点
- BeautifulSoup通过:
tag.desendants
获取tag节点的所有子孙节点元素,包括element、text等类型的节点。 - 从上述文档中获取
<p>
元素的所有子孙元素节点
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("p")
for x in tag.descendants:
print(x)
程序结果为:
<b>The Dormouse's story</b>
The Dormouse's story
BeautifulSoup获取元素节点的兄弟节点
-
BeautifulSoup通过:
tag.next_sibling
tag.previous_sibling
来获取下一个和前一个兄弟节点,其中 tag.next_sibling是tag的临近的下一个兄弟节点, tag.previous_sibling是tag的临近的前一个兄弟节点。 -
从doc文档中查找前后兄弟节点
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("a")
print(tag.previous_sibling)
print(tag.next_sibling)
程序结果如下,第一个<a>
的前一个节点是text
,下一个节点是,
。
Once upon a time there were three little sisters;and their names were
,
BeautifulSoup使用CSS语法查找元素
- BeautifulSoup除了可以用find与find_all函数查找HTML文档树的节点元素外,还可以采用CSS类似的语法来查询,规则是:
tag.select(css)
其中tag是一个bs4.element.Tag对象,即HTML中的一个element节点元素,select是它的查找方法,css是类似css语法的一个字符串,一般结构如下:
[tagName][attrName[=value]],其中[…]部分是可选的。
tagName是元素名称,如果没有指定就是所有元素;
attrName=value是属性名称,value是它对应的值,可以不指定属性,在指定了属性后也可以不指定值;
tag.select(css)返回一个bs4.element.Tag的列表,哪怕只有一个元素也是一个列表。 - 语法有如下:
-
soup.select("a")
查找文档中所有<a>
元素节点; -
soup.select("p a")
查找文档中所有<p>
节点下的所有<a>
元素节点; -
soup.select("p[class='story'] a")
查找文档中所有属性class="story"
的<p>
节点下的所有<a>
元素节点; -
soup.select("p[class] a")
查找文档中所有具有class
属性的<p>
节点下的所有<a>
元素节点; -
soup.select("a[id='link1']")
查找属性id="link1"
的<a>
节点; -
soup.select("body head title")
查找<body>
下面的<head>
下面的<title>
节点; -
soup.select("body[class] ")
查找<body>
下面所有具有class
属性的节点; -
soup.select("body [class] a")
查找<body>
下面所有具有class
属性的节点下面的<a>
节点
属性的语法规则
在CSS结构中的[attrName=value]中表示属性attrName与value相等,也可以指定不等、包含等运算关系,具体运算如下表:
选择器 | 描述 |
---|---|
[attrName] | 用于选取带有特定属性的元素。 |
[attrName=value] | 用于选取带有指定属性和值的元素。 |
[attrName^=value] | 匹配属性值以指定值开头的每个元素。 |
[attrName$=value] | 匹配属性值以指定值结尾的每个元素。 |
[attrName*=value] | 匹配属性值中包含指定值的每个元素。 |
因此:
-
soup.select("a[href='http://example.com/elsie']")
查找href="http://example.com/elsie"
的<a>
节点; -
soup.select("a[href$='sie']")
查找href以"sie"
结尾的<a>
节点; -
soup.select("a[href^='http://example.com']")
查找href="http://example.com"
开始的<a>
节点; -
soup.select("a[href*='example.com']")
查找href
的值中包含"example"
字符串的<a>
节点;
select查找子孙节点
在select(css)中的css有多个节点时,节点元素之间用空格分开,就是查找子孙节点,例如soup.select("div p")
是查找所有<div>
节点下面的所有子孙<p>
节点。
from bs4 import Beautiful
doc="
<div>
<p>A</p>
<span>
<p>B</p>
</span>
</div>
<div>
<p>C</p>
</div>
"
soup=BeautifulSoup(doc,"lxml")
tags=soup.select("div p")
for tag in tags:
print(tag)
程序结果:
<p>A</p>
<p>B</p>
<p>C</p>
其中tags=soup.select("div p")
是查找<div>
下面的所有子孙节点<p>
,因此包含<span>
下面的<p>B</p>
select查找直接子节点
- 在select(css)中的css有多个节点时,节点元素之间用" > "分开(注意>的前后至少包含一个空格),就是查找直接子节点,例如
soup.select("div > p")
是查找所有<div>
节点下面的所有直接子节点<p>
,不包含孙节点。 - 查找子孙节点
from bs4 import Beautiful
doc="
<div>
<p>A</p>
<span>
<p>B</p>
</span>
</div>
<div>
<p>C</p>
</div>
"
soup=BeautifulSoup(doc,"lxml")
tags=soup.select("div > p")
for tag in tags:
print(tag)
程序结果:
<p>A</p>
<p>C</p>
其中tags=soup.select("div > p")
是查找<div>
下面的直接子节点<p>
,因此不包含<span>
下面的<p>B</p>
select查找兄弟节点
- 在select中用
" ~ "
连接两个节点表示查找前一个节点后面的所有同级别的兄弟节点(注意~
号前后至少有一个空格),例如soup.select("div ~ p")
查找<div>
后面的所有同级别的<p>
兄弟节点。 - 在select中用
" + "
连接两个节点表示查找前一个节点后面的第一个同级别的兄弟节点(注意+
号前后至少有一个空格)。 - 查找兄弟节点
from bs4 import Beautiful
doc="
<body>
demo
<div>A</div>
<b>X</b>
<p>B</p>
<span>
<p>C</p>
</span>
</div>
<p>D</p>
</div>
</body>
"
soup=BeautifulSoup(doc,"lxml")
tags=soup.select("div ~ p")
for tag in tags:
print(tag)
print()
tags=soup.select("div + p")
for tag in tags:
print(tag)
程序结果:
<p>B</p>
<p>D</p>
其中tags=soup.select("div ~ p")
找到<div>
后面同级别的所有<p>
节点,不包含<span>
中的<p>C</p>
,因为它与<div>
不同级别。而tags=soup.select("div + p")
要找<div>
的下一个兄弟节点<p>
,但是<div>
的下一个兄弟节点是<b>X</b>
,不是<p>
节点,因此没有找到,注意结果不是<p>B</p>
。
上一篇: [转]Python 网络爬虫
下一篇: python网络爬虫