
【千锋教育干货暴击】
如果你想更好的学习python乃至转行,弯道超车,快人一步!本课程零基础即可加入学习,抓住大数据、机器学习、人工智能时代的红利,开启你的第一行代码吧!
↓ ↓ ↓
千锋教育Python教程_700集零基础Python入门到精通教程(保姆级新手教程)
千锋教育Python教程全套_python零基础入门到精通(学完可达到Python工程师水平)
上周的爬虫文章中我们给大家简单介绍了一下网页的基本结构,同时也认识了一些常见的HTML标签。因为爬虫就是从这些标签中提取数据。
比如以我的知乎页面为例,要想提取文章列表中的每篇文章的标题和内容简介等信息,就要读懂HTML:

我们如何解析出来HTML标签中的内容呢?解析页面局部的文本内容,一般都会在标签之间或者标签对应的属性中进行存储,我们可以通过:

常见的方式有:

正则我们已经给大家简单介绍过了,本次主要介绍bs4的使用。
Beautiful Soup 是 python 的一个库,最主要的功能是从网页抓取数据。官方解释如下:

因为它属于第三方库,所以需要使用pip下载安装
pip install bs4 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple 安装的lxml是bs4在解析过程中所要使用的一种解析器。
除了lxml之外还有:

如果需要使用html5lib解析器,也是需要单独安装下载的
pip install html5lib 如何使用
无论哪种解析器都需要导入BeautifulSoup,即: from bs4 import BeautifulSoup
from bs4 import BeautifulSoup
soup = BeautifulSoup(markup, "lxml") # 参数位置的markup表示从网络获取的网页 当使用不同的解析器时只需要改变上面代码的第二个参数即可:
soup = BeautifulSoup(markup, "html.parser") # Python标准库
或者
soup = BeautifulSoup(markup, "html5lib") # html5lib库 推荐使用lxml作为解析器,因为效率更高。
对象的种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: , , , 。
Tag
Tag即HTML中的标签,比如<div></div>,<p></p>,....

一个tag可能有很多个属性。比如

有 “href” 的属性,值为 “//zhuanlan.zhihu.com/p/137323184” ,有target属性,值为_blank。
对于 Tag,它有两个重要的属性,是 name 和 attrs。
from bs4 import BeautifulSoup
markup = '<a href="//zhuanlan.zhihu.com/p/137323184" target="_blank" rel="noopener noreferrer" data-za-detail-view-element_name="Title">分享几道LeetCode中的MySQL题目解法</a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.a) # 获取soup中的a标签对象
# 获取a标签对象的name和attrs属性
print(soup.a.name) # 获取标签名
print(soup.a.attrs) # 获取标签属性 结果:

我们可以发现tag的属性的操作方法与字典相同,所以可以像操作字典一样操作属性。
print(soup.a.get('href'))
或者
print(soup.a.['href'])) 2.NavigableString
字符串常被包含在tag内. Beautiful Soup用 类来包装tag中的字符串。
from bs4 import BeautifulSoup
xml_soup = BeautifulSoup('<p class="body strikeout">我是一个段落</p>', 'lxml')
tag = xml_soup.p
print(tag['class']) # 获取class属性值
print(tag.string) # 获取tag中的文本内容 3.BeautifulSoup 和Comment(此部分了解即可不做举例)
对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag
, , 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象是文档的注释部分Comment。
对象是一个特殊类型的 对象
遍历文档树就是把整个文档看成一颗树形结构。如下图

见上图我们以body为例:
子节点一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点。Beautiful Soup提供了许多操作和遍历子节点的属性。
操作文档树最简单的方法就是告诉它你想获取的tag的name。
from bs4 import BeautifulSoup
markup = '<head><title>The Dormouse\'s story</title></head><body><h1>我是<em>标题</em></h1><p><em>段落1</em><strong><a href="#"><em>链接测试</em></a></strong></p></body>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.body.name)
print(soup.body.contents) 结果:

获取某个tag中的字标签可以使用属性:contents,返回结果是一个列表,也可以使用children属性,返回结果是一个生成器,可以通过for循环迭代输出每个子标签。
如果想获取所有子孙节点可以使用:.descendants 属性
文本内容的获取
如果想获取某个标签的文本内容可以使用.string 或者text,但是需要注意:
1.如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点:
2.如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None .此时可以考虑使用text
for child in soup.body.children:
print(child.string) 结果:

但是如果换成:
for child in soup.body.children:
print(child.text) # 也可以使用child.get_text() 两种方式是一样的结果只不过 结果:

父节点
每个tag或字符串都有父节点:被包含在某个tag中。如果想获取父节点或者所有的父节点可以考虑使用 .parent 属性或者 .parents 属性
from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em>标题</em></h1><p><em>段落1</em><strong><a href="#"><em>链接测试</em></a></strong></p></body></html>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.em.parent) 结果:
<h1>我是标题</h1>
兄弟节点
next_sibling .previous_sibling 属性: .next_sibling 属性获取了该节点的下一个兄弟节点,.previous_sibling 则与之相反,如果节点不存在,则返回 None
.next_siblings .previous_siblings 属性: 通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出
前后节点
.next_element .previous_element 属性与 .next_sibling .previous_sibling 不同,它并不是针对于兄弟节点,可以获取到临近它的上/下一个节点,而不分层次关系。
from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em>标题</em></h1><p><em>段落1</em><strong><a href="#"><em>链接测试</em></a></strong></p><div>div1</div><div>div2</div></body></html>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.p.next_sibling) # p的兄弟标签
print(soup.p.next_element) # 临近的节点 结果:
<div>div1
<em>段落1
搜索文档树
find_all( name , attrs , recursive , text , **kwargs ):find_all () 方法搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件

比如:
html结构的截图:

from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em >标题</em></h1><p><em>段落1</em><strong><a href="http://www.linktest.com"><em>链接测试</em></a></strong></p><div id="div1" data-foo="value1" class="c1">div1</div><div id="div2" class="c1">div2</div></body></html>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.find_all(name='div')) #查找所有的div标签
print(soup.find_all(id='div1')) # 查找id是div1的标签
print(soup.find_all(name='div',class_='c1')) # 查找有class=c1的div标签
print(soup.find_all(attrs={'data-foo':'value1'})) # 查找所有有属性是data-foo的标签

我们再看一个正则的例子(html内容不变):
import re
from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em >标题</em></h1><p><em>段落1</em><strong><a href="http://www.linktest.com"><em>链接测试</em></a></strong></p><div id="div1" data-foo="value1" class="c1">div1</div><div id="div2" class="c1">div2</div></body></html>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.find_all(href=re.compile("link"))) # href也是关键字的形式,值这个部分可以使用正则搜索查找
print(soup.find_all(text=re.compile("div"))) # tag中文本存在div的标签tag对象 结果:

find( name , attrs , recursive , text , **kwargs )它与 find_all () 方法唯一的区别是 find_all () 方法的返回结果是值包含一个元素的列表,而 find () 方法直接返回结果。
from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em >标题</em></h1><p><em>段落1</em><strong><a href="http://www.linktest.com"><em>链接测试</em></a></strong></p><div id="div1" data-foo="value1" class="c1">div1</div><div id="div2" class="c1">div2</div></body></html>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.find(name='div')) #查找所有的div标签,只返回一个
print(soup.find(id='div1')) # 查找id是div1的标签,只返回一个对象
print(soup.find(name='div',class_='c1')) # 查找有class=c1的div标签,只返回一个
print(soup.find(attrs={'data-foo':'value1'})) # 查找所有有属性是data-foo的标签,只 结果:

我们在写 CSS 时,标签名不加任何修饰,类名前加点,id 名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
from bs4 import BeautifulSoup
markup = '<html><head><title>The Dormouse\'s story</title></head><body><h1>我是<em >标题</em></h1><p><em>段落1</em><strong><a href="http://www.linktest.com"><em>链接测试</em></a></strong></p><div id="div1" data-foo="value1" class="c1">div1</div><div id="div2" class="c1">div2</div></body></html>'
# 创建soup对象
soup = BeautifulSoup(markup, 'lxml')
# 使用select选择器选择查找
print(soup.select('div')) # 通过标签选择器查找
print(soup.select('#div1')) # 查找id选择器是div1的标签
print(soup.select('.c1')) # 查找class类选择器是c1的标签 结果:

也可以通过组合查找即和写 class 文件时,标签名与类名、id 名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1 的内容,二者需要用空格分开
print(soup.select('p em')) # 查找p中em标签

在选择器中查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print(soup.select("div[data-foo]")) # 查找有data-foo属性的div标签 select的使用与 find_all 方法有异曲同工之妙的查找方法,是不是感觉很方便?
案例思考
爬取Running的知乎文章列表并保存到csv文件中,如何实现?
需要资料也可以关注微信公众号:Python专栏,事不宜迟,一起进步吧!
