爬虫解析库BeautifulSoup的一些笔记
b44中文文档地址:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
《BeautifulSoup使用》
对象的种类
基本元素 | 说明 |
Tag | 标签,最基本的信息组织单元,分别是<>和</>标明开头和结尾 |
Name | 标签的名字,<p></p>的名字是/'p',格式:<tag>.name |
Attributes | 标签的属性,字典形式组织,格式:<tag>.attrs |
NavigableString | 标签内非属性字符串,<></>中字符串,格式:<tag>.string |
Comment | 标签内字符串的注释部分,一种特殊的Comment类型 |
<<BeautifulSoup的主要函数以及用法
1.创建BeautifulSoup对象
import lxml
import requests
from bs4 import BeautifulSoup
2.解析器的选择
python标准库 | BeautifulSoup(markup, "html.parser") | python内置的标准库 ,执行速度适中,文档容错能力强 | python2.7以及python3.2。2之前的文档容错能力差 |
lxml HTML解析器 | BeautifulSoup(markup, "lxml") | 速度快,文档容错能力强 | 需要安装C语言库需要安装C语言库 |
lxml XML解析器 | BeautifulSoup(markup, "xml") | 速度快,唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 速度慢,不依赖外部扩展 |
3.遍历文档树
- .contents 返回当前节点的所有子节点 返回类型是列表
- .children 返回当前节点的所有子节点 返回类型是list生成器对象
- .descendants 返回当前节点的所有子孙节点 返回类型是list生成器对象
- .parent 返回当前节点的父亲节点 返回类型是节点Tag
- .parents 返回当前节点的所有父亲节点 返回类型是list生成器对象
- .next_sibling 返回当前节点的下一个兄弟节点
- .previous_sibling 返回当前节点的所有兄弟节点 返回类型是list生成器对象
- .next_element 返回当前节点的下一个Tag
- .previous_sibling 返回当前节点的上一个节点
- .next_siblings 返回当前节点后的所有兄弟节点
- .previous_siblings 返回当前节点前的所有节点
- .string 返回当前节点标签内的内容
- 如果当前Tag包含了多个子节点Tag就无法确定 string方法应该调用哪个节点的内容
- .strings 返回多个内容 需要遍历获取
4.搜索文档
>>过滤器
介绍 find_all()
方法前,先介绍一下过滤器的类型 ,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中。
>>字符串
最简单的过滤器是字符串,在搜索方法中传入一个字符串参数,BeautifulSoup会查找与字符串完整匹配的内容,下面的梨子用于查找文档中所有的<b>标签:
soup.find_all('b')
如果传入字节码参数,BeautifulSoup会当作UTF-8编码,可以传入一段Unicode编码来避免BeautifulSoup解析编码错误。
>>正则表达式
如果传入正则表达式作为参数,BeautifulSoup会通过正则表达式的math()来匹配内容,下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该该被找到:
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
>>列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:
soup.find_all(["a", "b"])
>>Ture
True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点:
for tag in soup.find_all(True):
print(tag.name)
>>方法
如果有没有合适过滤器,那么还可以定义一个方法,只接收一个元素参数,如果这个方法返回True,表示当前元素匹配并且被找到,如果不是则返回False
下面方法检验了当前元素,如果包含class属性去不包含id属性,那么将返回Ture:
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id)
将这个方法作为参数传入 find_all()
方法,将得到所有<p>标签
>>find_all
find_all(name, attrs, recursive, string, **keyargs)
find_all方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件,这里有几个例子:
soup.find_all("title") soup.find_all("p", "title") soup.find_all("a")
soup.find_all(id="link2") import re
soup.find(string=re.compile("sisters"))
<name参数>
简单的用法如下:
soup.find_all("title")
重申:搜索name参数的值可以使任意类型的过滤器,字符串,正则表达式,列表,方法或是Ture。
<keyword参数>
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id
的参数,Beautiful Soup会搜索每个tag的”id”属性。
soup.find_all(id='link2')
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
但是可以通过 find_all()
方法的 attrs
参数定义一个字典参数来搜索包含特殊属性的tag:
data_soup.find_all(attrs={"data-foo": "value"})
<按Css搜索>
按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class
在Python中是保留字,使用 class
做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_
参数搜索有指定CSS类名的tag:
soup.find_all("a", class_="sister")
class_
参数同样接受不同类型的 过滤器
,字符串,正则表达式,方法或 True
:
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>] css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]
搜索 class
属性时也可以通过CSS值完全匹配:
css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]
完全匹配 class
的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:
<string参数>
通过string参数可以搜索文档中的字符串内容,与name参数的可选值一样,string参数接受字符换,正则表达,列表,True,看例子:
soup.find_all(string="Elsie") soup.find_all(string=["Tillie", "Elsie", "Lacie"]) soup.find_all(string=re.compile("Dormouse")) def is_the_only_string_within_a_tag(s):
""Return True if this string is the only child of its parent tag.""
return (s == s.parent.string) soup.find_all(string=is_the_only_string_within_a_tag)
虽然 string
参数用于搜索字符串,还可以与其它参数混合使用来过滤tag.Beautiful Soup会找到 .string
方法与 string
参数值相符的tag.下面代码用来搜索内容里面包含“Elsie”的<a>标签:
soup.find_all("a", string="Elsie")
<limit参数>
find_all()
方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit
参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit
的限制时,就停止搜索返回结果.
soup.find_all("a", limit=2)
<recursive参数>
调用tag的 find_all()
方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
<像调用 find_all()
一样调用tag>
find_all()
几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup
对象和 tag
对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all()
方法相同,下面各自两行代码是等价的:
soup.find_all("a")
soup("a") soup.title.find_all(string=True)
soup.title(string=True)
<<find
find( name , attrs , recursive , string , **kwargs )
find_all()
方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all()
方法来查找<body>标签就不太合适, 使用 find_all
方法并设置 limit=1
参数不如直接使用 find()
方法.下面两行代码是等价的:
soup.find_all('title', limit=1) soup.find('title')
唯一的区别是 find_all()
方法的返回结果是值包含一个元素的列表,而 find()
方法直接返回结果.find_all()
方法没有找到目标是返回空列表, find()
方法找不到目标时,返回 None
.
<<find_parents() 和 find_parent()
find_parents( name , attrs , recursive , string , **kwargs ) find_parent( name , attrs , recursive , string , **kwargs )
我们已经用了很大篇幅来介绍 find_all()
和 find()
方法,Beautiful Soup中还有10个用于搜索的API.它们中的五个用的是与 find_all()
相同的搜索参数,另外5个与 find()
方法的搜索参数类似.区别仅是它们搜索文档的不同部分.
记住: find_all()
和 find()
只搜索当前节点的所有子节点,孙子节点等. find_parents()
和 find_parent()
用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容.
<<find_next_siblings() 和 find_next_sibling()
find_next_siblings( name , attrs , recursive , string , **kwargs ) find_next_sibling( name , attrs , recursive , string , **kwargs )
这2个方法通过 .next_siblings 属性对当tag的所有后面解析的兄弟tag节点进行迭代, find_next_siblings()
方法返回所有符合条件的后面的兄弟节点, find_next_sibling()
只返回符合条件的后面的第一个tag节点.
<<find_previous_siblings() 和 find_previous_sibling()
find_previous_siblings( name , attrs , recursive , string , **kwargs ) find_previous_sibling( name , attrs , recursive , string , **kwargs )
这2个方法通过 .previous_siblings 属性对当前tag的前面解析的兄弟tag节点进行迭代, find_previous_siblings()
方法返回所有符合条件的前面的兄弟节点, find_previous_sibling()
方法返回第一个符合条件的前面的兄弟节点.
<<find_all_next() 和 find_next()
find_all_next( name , attrs , recursive , string , **kwargs ) find_next( name , attrs , recursive , string , **kwargs )
这2个方法通过 .next_elements 属性对当前tag的之后的tag和字符串进行迭代, find_all_next()
方法返回所有符合条件的节点, find_next()
方法返回第一个符合条件的节点.
<<find_all_previous() 和 find_previous()
find_all_previous( name , attrs , recursive , string , **kwargs ) find_previous( name , attrs , recursive , string , **kwargs )
这2个方法通过 .previous_elements 属性对当前节点前面的tag和字符串进行迭代, find_all_previous()
方法返回所有符合条件的节点, find_previous()
方法返回第一个符合条件的节点.
CSS选择器
Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html在Tag
或BeautifulSoup
对象的.select()
方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
soup.select("title") soup.select("p nth-of-type(3)")
通过tag标签逐层查找:
soup.select("body a") soup.select("html head title")
找到某个tag标签下的直接子标签
soup.select("head > title") soup.select("p > a") soup.select("p > a:nth-of-type(2)") soup.select("p > #link1") soup.select("body > a")
找到兄弟节点标签:
soup.select("#link1 ~ .sister")
# ~ 表示所有其他兄弟标签
soup.select("#link1 + .sister")
# + 表示第一个其他兄弟标签
通过CSS的类名查找:
soup.select(".sister") soup.select("[class~=sister]")
通过tag的id查找:
soup.select("#link1") soup.select("a#link2")
同时用多种CSS选择器查询元素:
soup.select("#link1,#link2")
通过是否存在某个属性来查找:
soup.select('a[href]')
通过属性的值来查找:
soup.select('a[href="http://example.com/elsie"]') soup.select('a[href^="http://example.com/"]') soup.select('a[href$="tillie"]') soup.select('a[href*=".com/el"]')
通过语言设置来查找:
multilingual_markup = """
<p lang="en">Hello</p>
<p lang="en-us">Howdy, y'all</p>
<p lang="en-gb">Pip-pip, old fruit</p>
<p lang="fr">Bonjour mes amis</p>
"""
multilingual_soup = BeautifulSoup(multilingual_markup)
multilingual_soup.select('p[lang|=en]')
# [<p lang="en">Hello</p>,
# <p lang="en-us">Howdy, y'all</p>,
# <p lang="en-gb">Pip-pip, old fruit</p>]
返回查找到的元素的第一个
soup.select_one(".sister")
对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API, 如果你仅仅需要CSS选择器的功能,那么直接使用 lxml
也可以, 而且速度更快,支持更多的CSS选择器语法,但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.
5.修改文档树
Beautiful Soup的强项是文档树的搜索,但同时也可以方便的修改文档树
<修改tag的名称和属性>
重命名一个tag,改变属性的值,添加或删除属性:
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b tag.name = "blockquote"
tag['class'] = 'verybold'
tag['id'] = 1
tag
# <blockquote class="verybold" id="1">Extremely bold</blockquote> del tag['class']
del tag['id']
tag
# <blockquote>Extremely bold</blockquote>
<修改 .string>
给tag的 .string
属性赋值,就相当于用当前的内容替代了原来的内容:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup) tag = soup.a
tag.string = "New link text."
tag
# <a href="http://example.com/">New link text.</a>
<append()>
Tag.append()
方法想tag中添加内容,就好像Python的列表的 .append()
方法:
soup = BeautifulSoup("<a>Foo</a>")
soup.a.append("Bar") soup
# <html><head></head><body><a>FooBar</a></body></html>
soup.a.contents
# [u'Foo', u'Bar']
使用append() 方法之后Tag.string方法便不可用
可以使用tag.strings获取tag的内容
<NavigableString() 和 .new_tag()>
如果想添加一段文本内容到文档中也没问题,可以调用Python的 append()
方法 或调用 NavigableString
的构造方法:
soup = BeautifulSoup("<b></b>")
tag = soup.b
tag.append("Hello")
new_string = NavigableString(" there")
tag.append(new_string)
tag
# <b>Hello there.</b>
tag.contents
# [u'Hello', u' there']
如果想要创建一段注释,或 NavigableString
的任何子类, 只要调用 NavigableString 的构造方法:
from bs4 import Comment
new_comment = soup.new_string("Nice to see you.", Comment)
tag.append(new_comment)
tag
# <b>Hello there<!--Nice to see you.--></b>
tag.contents
# [u'Hello', u' there', u'Nice to see you.']
# 这是Beautiful Soup 4.2.1 中新增的方法
创建一个tag最好的方法是调用工厂方法 BeautifulSoup.new_tag()
:
soup = BeautifulSoup("<b></b>")
original_tag = soup.b new_tag = soup.new_tag("a", href="http://www.example.com")
original_tag.append(new_tag)
original_tag
# <b><a href="http://www.example.com"></a></b> new_tag.string = "Link text."
original_tag
# <b><a href="http://www.example.com">Link text.</a></b>
第一个参数作为tag的name,是必填,其它参数选填
<insert()>
Tag.insert()
方法与 Tag.append()
方法类似,区别是不会把新元素添加到父节点 .contents
属性的最后,而是把元素插入到指定的位置.与Python列表总的 .insert()
方法的用法下同:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a tag.insert(1, "but did not endorse ")
tag
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
tag.contents
# [u'I linked to ', u'but did not endorse', <i>example.com</i>]
<insert_before() 和 insert_after()>
insert_before()
方法在当前tag或文本节点前插入内容:
soup = BeautifulSoup("<b>stop</b>")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.string.insert_before(tag)
soup.b
# <b><i>Don't</i>stop</b>
insert_after()
方法在当前tag或文本节点后插入内容:
soup.b.i.insert_after(soup.new_string(" ever "))
soup.b
# <b><i>Don't</i> ever stop</b>
soup.b.contents
# [<i>Don't</i>, u' ever ', u'stop']
<clear()>
Tag.clear()
方法移除当前tag的内容:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a tag.clear()
tag
# <a href="http://example.com/"></a>
<extract()>
PageElement.extract()
方法将当前tag移除文档树,并作为方法结果返回:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a i_tag = soup.i.extract() a_tag
# <a href="http://example.com/">I linked to</a> i_tag
# <i>example.com</i> print(i_tag.parent)
None
这个方法实际上产生了2个文档树: 一个是用来解析原始文档的 BeautifulSoup
对象,另一个是被移除并且返回的tag.被移除并返回的tag可以继续调用 extract
方法.
<decompose()>
Tag.decompose()
方法将当前节点移除文档树并完全销毁:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a soup.i.decompose() a_tag
# <a href="http://example.com/">I linked to</a>
<replace_with()>
PageElement.replace_with()
方法移除文档树中的某段内容,并用新tag或文本节点替代
它:
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a new_tag = soup.new_tag("b")
new_tag.string = "example.net"
a_tag.i.replace_with(new_tag) a_tag
# <a href="http://example.com/">I linked to <b>example.net</b></a>
<wrap()>
PageElement.wrap()
方法可以对指定的tag元素进行包装,并返回包装后的结果:
soup = BeautifulSoup("<p>I wish I was bold.</p>")
soup.p.string.wrap(soup.new_tag("b"))
# <b>I wish I was bold.</b> soup.p.wrap(soup.new_tag("div"))
# <div><p><b>I wish I was bold.</b></p></div>
该方法在 Beautiful Soup 4.0.5 中添加
<unwrap()>
Tag.unwrap()
方法与 wrap()
方法相反.将移除tag内的所有tag标签,该方法常被用来进行标记的解包:
6.输出
<格式化输出>
prettify()
方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行.
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
soup.prettify()
# '<html>\n <head>\n </head>\n <body>\n <a href="http://example.com/">\n...' print(soup.prettify())
# <html>
# <head>
# </head>
# <body>
# <a href="http://example.com/">
# I linked to
# <i>
# example.com
# </i>
# </a>
# </body>
# </html>
BeautifulSoup
对象和它的tag节点都可以调用 prettify()
方法.
<压缩输出>
如果只想得到结果字符串,不重视格式,那么可以对一个 BeautifulSoup
对象或 Tag
对象使用Python的 unicode()
或 str()
方法:
str(soup)
# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>' unicode(soup.a)
# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
str()
方法返回UTF-8编码的字符串,可以指定编码的设置.
还可以调用 encode()
方法获得字节码或调用 decode()
方法获得Unicode.
<输出格式>
Beautiful Soup输出是会将HTML中的特殊字符转换成Unicode,比如“&lquot;”:
soup = BeautifulSoup("“Dammit!” he said.")
unicode(soup)
# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
如果将文档转换成字符串,Unicode编码会被编码成UTF-8.这样就无法正确显示HTML特殊字符了:
str(soup)
# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
<get_text()>
如果只想得到tag中包含的文本内容,那么可以使用 get_text()
方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回:
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup) soup.get_text()
u'\nI linked to example.com\n'
soup.i.get_text()
u'example.com'
可以通过参数指定tag的文本内容的分隔符:
# soup.get_text("|")
u'\nI linked to |example.com|\n'
还可以去除获得文本内容的前后空白:
# soup.get_text("|", strip=True)
u'I linked to|example.com'
或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:
[text for text in soup.stripped_strings]
# [u'I linked to', u'example.com']
最新文章
- C# 开发windows服务的一些心得
- DataTable转List(备忘)
- [CORS:跨域资源共享] W3C的CORS Specification
- Matlab的部分文件操作
- Ubuntu jdk安装
- MVC随笔之基础数据维护(MVC4+Boostrap)
- 事件委托 documentFragment
- ubuntu 添加启动器
- Linux netstat详解
- 【面试题042】翻转单词顺序VS左旋转字符串
- Qt在VS2010的安装与配置
- IOS QuartzCore核心动画框架
- Gradle多项目配置的一个demo
- storm源码之storm代码结构【译】
- [Q]手动加载菜单方法
- CodeForces 652D Nested Segments
- js特效遮罩层(弹出层)
- 一文掌握 Linux 性能分析之 CPU 篇
- pycharm 序列号/行号 的宽度太宽了如何调整
- P1403 [AHOI2005]约数研究
热门文章
- 新手学測试----Unit Test(单元測试)
- 热烈庆祝国产编程语言R++1.8研发成功
- Datazen自己定义地图
- apt --fix-broken install
- RatingBar android:isIndicator=";true";
- 【总结】嵌入式linux内核中Makefile、Kconfig、.config的关系及增加开机Hello World【转】
- YTU 2723: 默认参数--求圆的面积
- ubuntu安装go语言
- 转 美制电线标准AWG与公制、英制单位对照
- 【高德地图API】Pivot控件中加载地图并禁止Pivot手势