我生也有涯,而知也无涯,以有涯随无涯,殆矣!——庄子
如同Python的所有库一样,BeautifulSoup易于上手,想要如臂使指却要下一番功夫。我们不妨通过一个例子来细细品味几个有趣的细节,来看看我们是否真的了解了BeautifulSoup。
- 初识——BeautifulSoup是通过将网页数据解析成搜索树来加速元素查找的python 库
- BeautifulSoup总结下来两种搜索文档树的方法最重要:find_all(name,attrs,string)和select(selector)
- 例子地址——URL:https://www.itcodemonkey.com/article/14987.html
- 这个例子,我们随后会发现比百度文档简单很多
1
2
3import requests from bs4 import BeautifulSoup as Soup
1
2resp = requests.get('https://www.itcodemonkey.com/article/14987.html')
1
2soup = Soup(resp.text,'lxml')
1
2soup.title
1
2<title>一个超有意思的 Python 综合能力测试网站 - IT程序猿 </title>
1
2soup.title.name #访问标签名而不是标签的name属性,即使有name属性,也需要通过get()提取
1
2'title'
1
2soup.title.attrs #title标签没有任何属性
1
2{}
1
2soup.title.text
1
2'u200b一个超有意思的 Python 综合能力测试网站 - IT程序猿 '
1
2soup.title.text[1:]
1
2'一个超有意思的 Python 综合能力测试网站 - IT程序猿 '
1
2tags_search_by_attrs = soup.find_all(attrs={'class':'kq__article-power'})
1
2tags_search_by_attrs
1
2
3[<div class="kq__article-power"> <p>来自:<a href="https://mp.weixin.qq.com/s/x5n2YeK5J56h-VAzb1Cejw" target="_blank">高级农民工</a>(微信号:Mocun6),作者:苏克1900</p> </div>]
find 与 find_all 方法也支持筛选条件为正则表达式的情况
1
2import re
1
2tags_search_by_string = soup.find_all(string=re.compile('power'))
1
2tags_search_by_string
1
2[]
1
2tags_search_by_string = soup.find_all(string=re.compile('作者:苏克1900'))
1
2tags_search_by_string
1
2['(微信号:Mocun6),作者:苏克1900']
BeautifulSoup有NavigableString对象,找到的就是文本内容对象而不是包含此文本内容的 p标签,如果需要的是p标签,可以如下:
1
2tags_search_by_string[0].findParent()
1
2<p>来自:<a href="https://mp.weixin.qq.com/s/x5n2YeK5J56h-VAzb1Cejw" target="_blank">高级农民工</a>(微信号:Mocun6),作者:苏克1900</p>
- 从这里我们看到find_all()传string参数找到的是NavigaleString对象
- 如果要找到文字所在标签,使用.findParent方法,如下
1
2type(tags_search_by_string[0])
1
2bs4.element.NavigableString
find跟find_all的区别让我们想起selinium的find_element_by_id跟find_elements_by_id的区别,Python里面经常碰到这种匹配一个与多个的简单逻辑.譬如select与select_one,正则re库中的re.find与re.find_all,还有mongodb中的sheet.insert_one与sheet.insert_many
1
2tag1 = soup.find(attrs={'class':'kq__article-power'})
1
2tag1
1
2
3<div class="kq__article-power"> <p>来自:<a href="https://mp.weixin.qq.com/s/x5n2YeK5J56h-VAzb1Cejw" target="_blank">高级农民工</a>(微信号:Mocun6),作者:苏克1900</p> </div>
1
2tag1.text
1
2'n来自:高级农民工(微信号:Mocun6),作者:苏克1900 '
1
2tag1.text.strip()
1
2'来自:高级农民工(微信号:Mocun6),作者:苏克1900'
tag.text属性返回标签内所有标签的文本内容,比stripped_strings显得更方便,而且保留了一定的文本排版格式,建议优先使用
- tag.string是获取本标签文本内容
- tag.strings是以生成器返回tag内所有标签文本
- tag.stripped_strings是以列表返回所有非空字符文本,并去除前后不可见字符
- tag.text属性返回tag内部所有标签文本的’’.join(),等于’’.join(tag.strings)
1
2list(tag1.strings)
1
2['n', '来自:', '高级农民工', '(微信号:Mocun6),作者:苏克1900', ' ']
1
2''.join(tag1.strings)
1
2'n来自:高级农民工(微信号:Mocun6),作者:苏克1900 '
1
2list(tag1.stripped_strings)
1
2['来自:', '高级农民工', '(微信号:Mocun6),作者:苏克1900']
1
2type(tag1.string) #有strings属性,就没有string属性
1
2NoneType
标签的唯一性体现在 id , name , class 这三个属性中,前端设计中, id , class是css样式表定义需要的,name是javascript可能需要的
1
2content_tag = soup.find(id='article_content')
1
2content_tag.name #TagName是div
1
2'div'
1
2content_tag.attrs
1
2{'id': 'article_content'}
我们可方便的使用get方法获取到标签属性值
1
2content_tag.get('id')
1
2'article_content'
也可以简单用访问符[],如下,等同与.get
1
2content_tag['id']
1
2'article_content'
1
2
3for tag in soup.select('div p'): print(tag.text)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46来自:高级农民工(微信号:Mocun6),作者:苏克1900 这里是「每周分享」的第 28 期。往期分享内容可以在公众号后台的 「不务正业」菜单中找到,Python 类的文章在另一个「不误正业」菜单中。 这一期的话题是:一个学习 Python 的趣味网站 。 最近在网上看到一个非常有意思的 Python 游戏通关网站,一共有 33 关,每一关都需要利用 Python 知识解题找到答案,然后进入下一关。很考验对 Python 的综合掌握能力,比如有的闯关需要用到正则表达式,有的要用到爬虫。 我们平常学 Python 都是按章节顺序、包或者模块来学,容易前学后忘。正好可以拿这个网站来综合测试一下对 Python 的掌握情况,以便查缺补漏。 来说说这个网站怎么玩。 这是网站主页面,很有历史感对吧,诞生了已有十几年了。但千万不要因为看着像老古董而小瞧它。 我们来玩玩看,点击「get challenged」开始挑战。 第 0 关是 Warming up 热身环节: 这一关要求是修改 URL 链接,给的提示是电脑上的数学表达式:2 的 38 次方,所以大概就是需要计算出数值,然后修改url 进入下一关。 所以这关就是考 Python 的基本数值运算,你知道怎么算么? 打开 Python 自带终端,一行代码就能计算出结果: 把原链接中的 0替换为 274877906944回车就会进入下一关: 游戏这就正式开始了。图片中的笔记本给了三组字母,很容易发现规律:前面的字母往后移动两位就是后面的字母。 那么需要做的就是根据这个规律把下面的提示字符串,做位移解密得到真正的句子含义: 这道题考察字符串编码和 for 循环相关知识,代码实现如下: 得到结果: 作者很风趣,当然不能手动去一个推算了,推荐用 string.maketrans() 这个方法解决,我们上面采取的是比较直接的方法,官方给出了更为精简的方法: 然后把 url 中的 map 改为ocr回车就来到了第 2 关: 作者接着说过关的提示可能在书里(当然不可能了)也可能在网页源代码里。那就右键查看源代码往下拉看到绿色区域,果然找到了问题: 意思就是:要在下面这一大串字符里找到出现次数最少的几个字符 考察了这么几个知识点: 正则表达式提取字符串 list 计数 条件语句 如果是你,你会怎么做? 来看下,十行代码快速实现: 首先,用 Requests 请求网页然后用正则提取出字符串,接着 for 循环计算每个字符出现的次数。 可以看到出现次数最少的就是最后几个字符,合起来是「equality」,替换 url 字符就闯过过了第 2 关进入下一关继续挑战。是不是有点意思? 后面每一关都需要用到相关的 Python 技巧解决,比如第 4 关: 这一关作者弄了个小恶作剧,需要手动输入数值到 url 中然后回车,你以为这样就完了么?并没有它有会不断重复弹出新的数值让你输入,貌似无穷尽。 所以,这一关肯定不能采取手动输入的方法闯关,自然要用到 Python 了。要实现自动填充修改 url 回车跳转到新 url,循环直到网页再也无法跳转为止这一功能。 如果是你,你会怎么做? 其实,一段简单的爬虫加正则就能搞定。思路很简单,把每次网页中的数值提取出来替换成新的 url 再请求网页,循环下去,代码实现如下: 输出结果如下: 可以看到,最终循环了 85 次找到了最后一个数字16044,输入到 url 中就闯关成功。 33 关既有趣又能锻炼使用 Python 解决问题的技巧,感兴趣的话去玩玩看。 网址:http://www.pythonchallenge.com/ 如果遇到不会做的题,可以在这里找到参考答案: 中参考文教程: https://www.cnblogs.com/jimnox/archive/2009/12/08/tips-to-python-challenge.html 官方参考教程: http://garethrees.org/2007/05/07/python-challenge/ © 2017-2018 IT程序猿 闽ICP备08108865号-1
现在我们回过头总结一下
BeautifulSoup4将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- Tag对象,对应html文档对象树的所有标签节点
- BeautifulSoup对象,大部分时候可以将其当成特殊的Tag对象,也就是说,两者属性想通
- NavigableString对象,即.string属性返回的对象
- Comment对象,一种特殊的Navigable对象
接下来随便看下select_one与select方法
1
2soup.name
1
2'[document]'
上面这行代码是怎么回事?实际上soup对象对应html DOM(文档对象树),即document对象,所有不奇怪
1
2soup.html.name #html标签是根标签
1
2'html'
1
2tag_one = soup.select_one('div h2')
1
2s = tag_one.string
1
2type(s)
1
2bs4.element.NavigableString
1
2s
1
2'u200b一个超有意思的 Python 综合能力测试网站'
我们知道这里没有media标签,我们试着看看如果查找会发生什么
1
2soup.img
1
2<img src="/themes/simplebootx/Public/assets/images/logo.jpg"/>
1
2type(soup.media) #返回是None
1
2NoneType
NavigbleString好像就是字符串,嗯,还是挺大区别的,试试下面
1
2s.findParent()
1
2
3<h2>一个超有意思的 Python 综合能力测试网站</h2> ## 目前也就这个findPrarent方法有点用--通过文本找标签--但是绝对鸡肋
在学习scrapy的时候我被scrapy的resp.css方法所采用的pseudo-element思路震撼,bs4的NavigableString是相似的思路,但好像不太一样
没用的NavigableString都说了,不妨再加上Comment对象
1
2tag_with_com = soup.select_one('div.article-infobox')
1
2tag_with_com
1
2
3
4
5
6
7
8
9
10
11
12
13<div class="article-infobox"> <!-- <span>2019-06-04 11:16:15 by 腾云苏克1900</span> --> <span> 2019-06-04 11:16:15 分类:<a href="/category/LovePython/" target="_blank">Python编程</a> </span> <!-- <span> <a href="javascript:;"><i class="fa fa-eye"></i><span>407</span></a> <a href="/article/do_like/id/14987" class="js-count-btn"><i class="fa fa-thumbs-up"></i><span class="count">0</span></a> <a href="/user/favorite/do_favorite/id/14987" class="js-favorite-btn" data-title="一个超有意思的 Python 综合能力测试网站" data-url="/article/index/id/14987/cid/12" data-key="ff47aHivkIvsKwOnbWEhDDusXW2GsFcKAwqUheOLnziCwBiraKjR1ZDyGc21M7qxxUpoTDm275NxfLs"> <i class="fa fa-star-o"></i> </a> </span> --> </div>
1
2tag_with_com.contents
1
2
3
4
5
6
7
8
9
10['n', ' <span>2019-06-04 11:16:15 by 腾云苏克1900</span> ', 'n', <span> 2019-06-04 11:16:15 分类:<a href="/category/LovePython/" target="_blank">Python编程</a> </span>, 'n', ' <span>ntt ttt<a href="javascript:;"><i class="fa fa-eye"></i><span>407</span></a>ntttttt<a href="/article/do_like/id/14987" class="js-count-btn"><i class="fa fa-thumbs-up"></i><span class="count">0</span></a>ntttttt<a href="/user/favorite/do_favorite/id/14987" class="js-favorite-btn" data-title="u200b一个超有意思的 Python 综合能力测试网站" data-url="/article/index/id/14987/cid/12" data-key="ff47aHivkIvsKwOnbWEhDDusXW2GsFcKAwqUheOLnziCwBiraKjR1ZDyGc21M7qxxUpoTDm275NxfLs">nttttttt<i class="fa fa-star-o"></i>ntttttt</a>nttttt</span> ', 'n']
1
2q = tag_with_com.contents[1]
1
2q
1
2' <span>2019-06-04 11:16:15 by 腾云苏克1900</span> '
1
2type(q)
1
2bs4.element.Comment
接下介绍重要的find_all方法。学习爬虫初期,我只使用select和select_one方法,其他如find、find_all完全摒弃——事实证明,find_all还是有用的。
- find_all返回一个满足过滤器条件的tag的列表
- find_all(过滤器)的作用体现在当你需要满足某种条件的所有tag,而用css selector不太方便的时候,譬如你需要遍历所有标签
1
2tags_by_filter = soup.find_all(True)
1
2len(tags_by_filter)
1
2496
1
2import re
1
2tags_by_fs = soup.find_all(string=re.compile('超有意思'))
- 注意按位传参find_all第一个是name,即标签名
- **find_all( name , attrs , recursive , string , kwargs )
- 可以如上按不限量关键字传参string=XXX,或者id=XXX,
1
2tags_by_fs
1
2
3
4
5['u200b一个超有意思的 Python 综合能力测试网站 - IT程序猿 ', 'u200b一个超有意思的 Python 综合能力测试网站', ' <span>ntt ttt<a href="javascript:;"><i class="fa fa-eye"></i><span>414</span></a>ntttttt<a href="/article/do_like/id/14987" class="js-count-btn"><i class="fa fa-thumbs-up"></i><span class="count">0</span></a>ntttttt<a href="/user/favorite/do_favorite/id/14987" class="js-favorite-btn" data-title="u200b一个超有意思的 Python 综合能力测试网站" data-url="/article/index/id/14987/cid/12" data-key="bd5bYLBaA374IaeyFBnpH9EkbjSP1HFeBba4nuijZGIjxySbjNrj/CIpNIhCOvEl3J7Z/P6BT3c6Pfk">nttttttt<i class="fa fa-star-o"></i>ntttttt</a>nttttt</span> ']
- 节目最后说明一下BeautifulSoup的核心就是CSS Selector,就是要采取一种比XPath更优秀的解析方式
- scrapy的resp.css方法对应soup.select方法,但是实话说前者真的很厉害
1
2
3for t in tags_by_fs: print(type(t))
1
2
3
4<class 'bs4.element.NavigableString'> <class 'bs4.element.NavigableString'> <class 'bs4.element.Comment'>
如前面所说,find_all(string=正则表达式)这种形式找到的是匹配的NavigableString对象,嗯,Comment对象是特殊的NavigableString对象,所以也会被找到
1
2tags_by_fs[2].findParent()
1
2
3
4
5
6
7
8
9
10
11
12
13<div class="article-infobox"> <!-- <span>2019-06-04 11:16:15 by 腾云苏克1900</span> --> <span> 2019-06-04 11:16:15 分类:<a href="/category/LovePython/" target="_blank">Python编程</a> </span> <!-- <span> <a href="javascript:;"><i class="fa fa-eye"></i><span>414</span></a> <a href="/article/do_like/id/14987" class="js-count-btn"><i class="fa fa-thumbs-up"></i><span class="count">0</span></a> <a href="/user/favorite/do_favorite/id/14987" class="js-favorite-btn" data-title="一个超有意思的 Python 综合能力测试网站" data-url="/article/index/id/14987/cid/12" data-key="bd5bYLBaA374IaeyFBnpH9EkbjSP1HFeBba4nuijZGIjxySbjNrj/CIpNIhCOvEl3J7Z/P6BT3c6Pfk"> <i class="fa fa-star-o"></i> </a> </span> --> </div>
总结一下就是"注释"是特殊的文本
1
2special_tag = tags_by_fs[2].findParent()
1
2special_tag.text
1
2'nn 2019-06-04 11:16:15 分类:Python编程nnn'
最后
以上就是无辜钥匙最近收集整理的关于深入理解BeautifulSoup的全部内容,更多相关深入理解BeautifulSoup内容请搜索靠谱客的其他文章。
发表评论 取消回复