欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python网络爬虫(一)

程序员文章站 2022-03-02 22:44:38
...

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>
'''
  1. 查找文档中的<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对象。

  1. 查找文档中的<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>
  1. 查找文档中的第一个<a>元素,程序如下:
soup=BeautifulSoup(doc,"lxml")
tag=soup.find("a")
print(tag)

程序结果找到第一个<a>元素:

<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
  1. 查找文档中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>
  1. 查找文档中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>