经过了前面几篇文章的学习, 估计你已经会爬不少中小型网站了但是有人说, 前面的正则很难唉, 学不好正则的确很难, 有人说过: 如果一个问题用正则解决, 那么就变成了两个问题所以说学不会是很正常的, 不怕, 除了正则, 我们还可以用另外一个强大的库来解析 html 所以, 今天的主题就是来学习这个强大的库 --BeautifulSoup, 不过正则还是需要多多练习下的
因为是第三方库所以我们需要下载, 在命令行敲下以下代码进行下载
pip install beautifulsoup4
安装第三方解析库
- pip install lxml
- pip install html5lib
如果不知道有什么用请往下看
1. 相关解析库的介绍
这里官方推荐解析库为 lxml, 因为它的效率高下面都是用 lxml 解析库来进行解析的
2. 详细语法介绍
本文是进行解析豆瓣图书首页 book.douban.com/
1)创建 bs 对象
- from bs4 import BeautifulSoup
- import requests
- response = requests.get('https://book.douban.com/').text
- # print(response)
- # 创建 bs 对象
- soup = BeautifulSoup(response, 'lxml') # 使用到了 lxml 解析库
2)获取相关标签
<a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
上面的 a 就是一个标签名字, 最简单的就是 < a></a > 这样, 可以简单理解为 <> 里面的第一个单词就是标签名
- # 获取标签
- print(soup.li) # 这个只是获取第一个 li 标签
- # 结果
- <li class="">
- <a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
- </li>
3)获取标签的名字和内容
标签的名字和内容:
<a > 豆瓣</a>
如上面所说, a 就是标签名字, 而两个标签之中所夹杂的内容就是我们所说的内容, 如上, 豆瓣就是该标签的内容
- # 获取标签名字
- print(soup.li.name)
- # 获取标签内容
- print(soup.li.string) # 这个只能是这个标签没有子标签才能正确获取, 否则会返回 None
- # 结果
- li
- None
由于这个 li 标签里面还有个子标签, 所以它的文本内容为 None
下面这个就可以获取它的文本内容
- # 获取标签内的标签
- print(soup.li.a)
- print(soup.li.a.string) # 这个标签没有子标签所以可以获取到内容
- # 结果
- <a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
豆瓣
4)获取标签属性, 有两种方法
标签属性:
<a href="https://www.douban.com" target="_blank">豆瓣</a>
可以简单理解为属性就是在标签名字旁边而且在前一个 <> 符号里面的, 还有是有等号来进行体现的所以上面的 href 就是标签属性名字, 等号右边的就是属性的值, 上面的值是个网址
- # 获取标签属性
- print(soup.li.a['href']) # 第一种
- print(soup.li.a.attrs['href']) # 第二种
- # 结果
- https://www.douban.com
- https://www.douban.com
5)获取标签内的子标签
<li><a > 豆瓣</a></li>
比如我们现在获取的 li 标签, 所以 a 标签就是 li 标签的子标签
- # 获取标签内的标签
- print(soup.li.a)
- # 结果
- <a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
6)获取所有子节点
子节点: 这个和子标签是差不多的, 只不过这里是获取一个标签下的所有子标签, 上面的只是获取最接近该标签的子标签
- # 获取子节点
- print(soup.div.contents) # 返回一个列表 第一种方法
- for n, tag in enumerate(soup.div.contents):
- print(n, tag)
- # 结果
- ['\n', <div class="bd">
- <div class="top-nav-info">
- <a class="nav-login" href="https://www.douban.com/accounts/login?source=book" rel="nofollow">登录</a>
- ...
- 0
- 1 <div class="bd">
- <div class="top-nav-info">
- ...
这个是获取 div 下的所有子节点,.content 就是获取子节点的属性
7)第二种方法获取所有子节点
- # 第二种方法
- print(soup.div.children) # 返回的是一个迭代器
- for n, tag in enumerate(soup.div.children):
- print(n, tag)
这个是用. children 获取所有的子节点, 这个方法返回的是一个迭代器
8)获取标签的子孙节点, 就是所有后代
子孙节点:
- <ul>
- <li>
- <a > 豆瓣</a>
- </li>
- </ul>
从上面知道, li 标签是 ul 标签的子标签, a 标签是 li 标签的子标签, 若此时我们获取的是 ul 标签, 所以 li 标签和 a 标签都是 ul 标签的子孙节点
- # 获取标签的子孙节点
- print(soup.div.descendants) # 返回的是一个迭代器
- for n, tag in enumerate(soup.div.descendants):
- print(n, tag)
- # 结果
- ...
- <generator object descendants at 0x00000212C1A1E308>
- 0
- 1 <div class="bd">
- <div class="top-nav-info">
- <a class="nav-login" href="https://www.douban.com/accounts/login?source=book" rel="nofollow">登录</a>
- ...
这里用到了. descendants 属性, 获取的是 div 标签的子孙节点, 而且返回结果是一个迭代器
9)获取父节点和所有祖先节点
既然有了子节点和子孙节点, 反过来也是有父节点和祖先节点的, 所以都很容易理解的
- # 获取父节点
- print(soup.li.parent) # 返回整个父节点
- # 获取祖先节点
- print(soup.li.parents) # 返回的是一个生成器
- for n, tag in enumerate(soup.li.parents):
- print(n, tag)
.parent 属性是获取父节点, 返回来的是整个父节点, 里面包含该子节点. parents 就是获取所有的祖先节点, 返回的是一个生成器
10)获取兄弟节点
兄弟节点:
- <ul>
- <li>
- <a > 豆瓣 1</a>
- </li>
- <li>
- <a > 豆瓣 2</a>
- </li>
- <li>
- <a > 豆瓣 3</a>
- </li>
- </ul>
比如上面的 html 代码, 里面的 li 标签都是 ul 标签的子节点, 而 li 标签都是处于同级的, 所以上面的 li 标签都是各自的兄弟这就是兄弟节点
- # 获取兄弟节点
- print(soup.li.next_siblings) # 获取该标签的所有同级节点, 不包括本身 返回的是一个生成器
- for x in soup.li.next_siblings:
- print(x)
- # 结果
- <generator object next_siblings at 0x000002A04501F308>
- <li class="on">
- <a data-moreurl-dict='{"from":"top-nav-click-book","uid":"0"}' href="https://book.douban.com">读书</a>
- </li>
- ...
.next_siblings 属性是获取该标签的所有在他后面的兄弟节点, 不包括他本身同时返回结果也是一个迭代器
同理, 既然有获取他的下一个所有兄弟标签, 也有获取他前面的所有兄弟标签
soup.li.previous_siblings
如果只是获取一个即可, 可以选择把上面的属性后面的 s 字母去掉即可, 如下
- soup.li.previous_sibling # 获取前一个兄弟节点
- soup.li.next_sibling # 获取后一个兄弟节点
3.bs 库的更高级的用法
在前面我们可以获取标签的名字属性内容和所有的祖孙标签但是当我们需要获取任意一个指定属性的标签还是有点困难的, 所以, 此时有了下面这个方法:
soup.find_all( name , attrs , recursive , text , **kwargs )
name: 需要获取的标签名
attrs: 接收一个字典, 为属性的键值, 或者直接用关键字参数来替代也可以, 下面
recursive: 设置是否搜索直接子节点
text: 对应的字符串内容
limit: 设置搜索的数量
1)先使用 name 参数来进行搜索
- # 先使用 name 参数
- print(soup.find_all('li')) # 返回一个列表, 所有的 li 标签名字
- # 结果
- [<li class="">
- <a data-moreurl-dict='{"from":"top-nav-click-main","uid":"0"}' href="https://www.douban.com" target="_blank">豆瓣</a>
- </li>, <li class="on">
- ...
这里获取了所有标签名字为 li 的标签
2)使用 name 和 attrs 参数
- # 使用 name 和 attrs 参数
- print(soup.find_all('div', {'class': 'more-meta'})) # 这个对上个进行了筛选, 属性参数填的是一个字典类型的
- # 结果
- [<div class="more-meta">
- <h4 class="title">
刺
- </h4>
- ...
这里搜索了具有属性为 class='more-meta'的 div 标签
3)根据关键字参数来搜索
- # 对相关属性进行进行查找也可以这样
- print(soup.find_all(class_='more-meta')) # 使用关键字参数, 因为 class 是 python 关键字, 所以关键字参数时需要加多一个下划线来进行区别
- # 结果
和上面的结果一样
...
这里注意, 我们找的是 class 属性为 more-meta 的标签, 用了关键字参数, 但是 python 里面有 class 关键字, 所以为了不使语法出错, 所以需要在 class 加个下划线
其他参数的就不再介绍了, 可以自行去官网查看
4)find()方法
此方法与 find_all()方法一样, 只不过这个方法只是查找一个标签而已, 后者是查找所有符合条件的标签
还有很多类似的方法, 用法都差不多, 就不再一一演示了, 需要的可以去官网查看
5)select()方法
这个方法是使用 CSS 选择器来进行筛选标签的
css 选择器: 就是根据标签的名字, id 和 class 属性来选择标签
通过标签名: 直接写该标签名, 如 li a , 这个就是找 li 标签下的 a 标签
通过 class 属性: 用. 符号加 class 属性值, 如 .title .time 这个就是找 class 值为 title 下的 class 值为 time 的标签
通过 id 属性: 用# 加 id 属性值来进行查找, 如 #img #width 这个就是找 id 值为 img 下的 id 值为 width 的标签
上面三者可以混合使用, 如 ul .title #width
如果还不太会的话, 可以直接在浏览器上按下 f12 来查看
位置在箭头所指的位置就是选择器的表达
代码如下
- # 还可以用标签选择器来进行筛选元素, 返回的都是一个列表
- print(soup.select('ul li div')) # 这个是根据标签名进行筛选
- print(soup.select('.info .title')) # 这个是根据 class 来进行筛选
- print(soup.select('#footer #icp')) # 这个是根据 id 来进行筛选
- # 上面的可以进行混合使用
- print(soup.select('ul li .cover a img'))
这里的获取属性和文本内容
- # 获取属性
- for attr in soup.select('ul li .cover a img'):
- # print(attr.attrs['alt'])
- # 也可以这样
- print(attr['alt'])
- # 获取标签的内容
- for tag in soup.select('li'):
- print(tag.get_text()) # 里面可以包含子标签, 会将子标签的内容连同输出
.get_tex()方法和前面的. string 属性有点不一样哈, 这里的他会获取该标签的所有文本内容, 不管有没有子标签
写在最后
以上的这些都是个人在学习过程中做的一点笔记还有点不足, 如果有错误的话欢迎大佬指出哈如果想要查看更多相关用法可以去官方文档查看: beautifulsoup.readthedocs.io/zh_CN/lates
学习参考资料: https://edu.hellobi.com/course/157
来源: https://juejin.im/post/5ac0b76f6fb9a028ce7bbcd7