概述
我生也有涯,而知也无涯,以有涯随无涯,殆矣!——庄子
如同Python的所有库一样,BeautifulSoup易于上手,想要如臂使指却要下一番功夫。我们不妨通过一个例子来细细品味几个有趣的细节,来看看我们是否真的了解了BeautifulSoup。
- 初识——BeautifulSoup是通过将网页数据解析成搜索树来加速元素查找的python 库
- BeautifulSoup总结下来两种搜索文档树的方法最重要:find_all(name,attrs,string)和select(selector)
- 例子地址——URL:https://www.itcodemonkey.com/article/14987.html
- 这个例子,我们随后会发现比百度文档简单很多
import requests
from bs4 import BeautifulSoup as Soup
resp = requests.get('https://www.itcodemonkey.com/article/14987.html')
soup = Soup(resp.text,'lxml')
soup.title
<title>一个超有意思的 Python 综合能力测试网站 - IT程序猿 </title>
soup.title.name #访问标签名而不是标签的name属性,即使有name属性,也需要通过get()提取
'title'
soup.title.attrs #title标签没有任何属性
{}
soup.title.text
'u200b一个超有意思的 Python 综合能力测试网站 - IT程序猿 '
soup.title.text[1:]
'一个超有意思的 Python 综合能力测试网站 - IT程序猿 '
tags_search_by_attrs = soup.find_all(attrs={'class':'kq__article-power'})
tags_search_by_attrs
[<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 方法也支持筛选条件为正则表达式的情况
import re
tags_search_by_string = soup.find_all(string=re.compile('power'))
tags_search_by_string
[]
tags_search_by_string = soup.find_all(string=re.compile('作者:苏克1900'))
tags_search_by_string
['(微信号:Mocun6),作者:苏克1900']
BeautifulSoup有NavigableString对象,找到的就是文本内容对象而不是包含此文本内容的 p标签,如果需要的是p标签,可以如下:
tags_search_by_string[0].findParent()
<p>来自:<a href="https://mp.weixin.qq.com/s/x5n2YeK5J56h-VAzb1Cejw" target="_blank">高级农民工</a>(微信号:Mocun6),作者:苏克1900</p>
- 从这里我们看到find_all()传string参数找到的是NavigaleString对象
- 如果要找到文字所在标签,使用.findParent方法,如下
type(tags_search_by_string[0])
bs4.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
tag1 = soup.find(attrs={'class':'kq__article-power'})
tag1
<div class="kq__article-power">
<p>来自:<a href="https://mp.weixin.qq.com/s/x5n2YeK5J56h-VAzb1Cejw" target="_blank">高级农民工</a>(微信号:Mocun6),作者:苏克1900</p> </div>
tag1.text
'n来自:高级农民工(微信号:Mocun6),作者:苏克1900 '
tag1.text.strip()
'来自:高级农民工(微信号:Mocun6),作者:苏克1900'
tag.text属性返回标签内所有标签的文本内容,比stripped_strings显得更方便,而且保留了一定的文本排版格式,建议优先使用
- tag.string是获取本标签文本内容
- tag.strings是以生成器返回tag内所有标签文本
- tag.stripped_strings是以列表返回所有非空字符文本,并去除前后不可见字符
- tag.text属性返回tag内部所有标签文本的’’.join(),等于’’.join(tag.strings)
list(tag1.strings)
['n', '来自:', '高级农民工', '(微信号:Mocun6),作者:苏克1900', ' ']
''.join(tag1.strings)
'n来自:高级农民工(微信号:Mocun6),作者:苏克1900 '
list(tag1.stripped_strings)
['来自:', '高级农民工', '(微信号:Mocun6),作者:苏克1900']
type(tag1.string) #有strings属性,就没有string属性
NoneType
标签的唯一性体现在 id , name , class 这三个属性中,前端设计中, id , class是css样式表定义需要的,name是javascript可能需要的
content_tag = soup.find(id='article_content')
content_tag.name #TagName是div
'div'
content_tag.attrs
{'id': 'article_content'}
我们可方便的使用get方法获取到标签属性值
content_tag.get('id')
'article_content'
也可以简单用访问符[],如下,等同与.get
content_tag['id']
'article_content'
for tag in soup.select('div p'):
print(tag.text)
来自:高级农民工(微信号: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方法
soup.name
'[document]'
上面这行代码是怎么回事?实际上soup对象对应html DOM(文档对象树),即document对象,所有不奇怪
soup.html.name #html标签是根标签
'html'
tag_one = soup.select_one('div h2')
s = tag_one.string
type(s)
bs4.element.NavigableString
s
'u200b一个超有意思的 Python 综合能力测试网站'
我们知道这里没有media标签,我们试着看看如果查找会发生什么
soup.img
<img src="/themes/simplebootx/Public/assets/images/logo.jpg"/>
type(soup.media) #返回是None
NoneType
NavigbleString好像就是字符串,嗯,还是挺大区别的,试试下面
s.findParent()
<h2>一个超有意思的 Python 综合能力测试网站</h2>
## 目前也就这个findPrarent方法有点用--通过文本找标签--但是绝对鸡肋
在学习scrapy的时候我被scrapy的resp.css方法所采用的pseudo-element思路震撼,bs4的NavigableString是相似的思路,但好像不太一样
没用的NavigableString都说了,不妨再加上Comment对象
tag_with_com = soup.select_one('div.article-infobox')
tag_with_com
<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>
tag_with_com.contents
['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']
q = tag_with_com.contents[1]
q
' <span>2019-06-04 11:16:15 by 腾云苏克1900</span> '
type(q)
bs4.element.Comment
接下介绍重要的find_all方法。学习爬虫初期,我只使用select和select_one方法,其他如find、find_all完全摒弃——事实证明,find_all还是有用的。
- find_all返回一个满足过滤器条件的tag的列表
- find_all(过滤器)的作用体现在当你需要满足某种条件的所有tag,而用css selector不太方便的时候,譬如你需要遍历所有标签
tags_by_filter = soup.find_all(True)
len(tags_by_filter)
496
import re
tags_by_fs = soup.find_all(string=re.compile('超有意思'))
- 注意按位传参find_all第一个是name,即标签名
- **find_all( name , attrs , recursive , string , kwargs )
- 可以如上按不限量关键字传参string=XXX,或者id=XXX,
tags_by_fs
['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方法,但是实话说前者真的很厉害
for t in tags_by_fs:
print(type(t))
<class 'bs4.element.NavigableString'>
<class 'bs4.element.NavigableString'>
<class 'bs4.element.Comment'>
如前面所说,find_all(string=正则表达式)这种形式找到的是匹配的NavigableString对象,嗯,Comment对象是特殊的NavigableString对象,所以也会被找到
tags_by_fs[2].findParent()
<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>
总结一下就是"注释"是特殊的文本
special_tag = tags_by_fs[2].findParent()
special_tag.text
'nn 2019-06-04 11:16:15 分类:Python编程nnn'
最后
以上就是无辜钥匙为你收集整理的深入理解BeautifulSoup的全部内容,希望文章能够帮你解决深入理解BeautifulSoup所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复