关于bs4,官方文档的介绍已经非常详细了,传送:Beautifulsoup 4官方文档,这里我把它组织成自己已经消化的笔记,你们最好看官方的,官方的全些可视化更强。本文从解释器,DOM树创建,遍历DOM树,修改DOM树,Beautiful Soup3和4的区别,Soup结合Requests 这几个主题整理。ps,其实如果掌握了javascript的DOM树的话,会更好理解soup。
Beautiful Soup
将复杂HTML文档转换成一个复杂的树形结构DOM,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment .
A、DOM树的解释器:
推荐使用lxml作为解析器,因为效率更高,解析器的好处是可以容错比如没有结束标签。
B、创建DOM树的三种类型:
1)打开本地文件with open("foo.html","r") as foo_file:soup_foo = BeautifulSoup(foo_file)
2)手动创建soup = BeautifulSoup(“hello world”,编码类型选填)
3)打开外部文件url = "http://www.packtpub.com/books" page = urllib.urlopen(url)soup_packtpage = BeautifulSoup(page,'lxml')
C.DOM树介绍:
节点【元素,属性,内容(string,NavigableString,contents,Comment】,还有节点的家庭【父节点,同辈邻接兄弟节点,同辈往右邻接所有兄弟节点,同辈往左邻接所有兄弟节点,直接子节点,所有子节点】
1)节点tag:类似javascript的元素-
访问元素:soup = BeautifulSoup(html_tag1,'lxml')tag1 = soup.a
访问元素的名字:tagname = tag1.name
访问元素的属性:tag1['class']或tag1.attrs
修改元素的属性访问: tag['class'] = 'verybold'赋予多个属性rel_soup.a['rel'] = ['index', 'contents']
是否有元素的属性:tag1.has_attr('class')
获取所有属性:比如获取a所有标签的链接,for link in soup.find_all('a'): print(link.get('href'))
获取元素的内容:soup.p.string .如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None. 如果tag中包含多个字符串 [2] ,可以使用 .strings 来循环获取.for string in soup.strings: print(repr(string))
获取元素的内容含编码过的:head_tag.contents,#[u'Hello', u' there']
获取元素带标签的内容:head_tag.NavigableString,#<b>Hello there.</b>
获取元素带注释的内容Comment:创建对象时用Comment,输出对象时用soup.b.prettify()
获取所有文字内容:soup.get_text()
2)节点的家庭
访问直接子节点:.children() 和.contents()
.children() 不返回文本节点,如果需要获得包含文本和注释节点在内的所有子节点,请使用 .contents()。
遍历所有子节点:.descendants
遍历子节点的内容:.strings 和.stripped_strings(去除空格或空行)
访问父节点:.parent
遍历所有父节点:.parents
获得匹配元素集合中所有元素的同辈元素:.next_siblings .previous_siblings
获得匹配元素集合中的下或上一个同辈元素.next_element .previous_element 属性:
获得匹配元素集合中的往后所有同辈或者往前所有元素:.next_elements .previous_elements
D、遍历DOM树
可以通过方法或者css选择器
方法:find_all( name , attrs , recursive , string, **kwargs )
参数:支持可以使用的参数值包括 字符串 , 正则表达式 , 列表, True,自定义包含一个参数的方法Lambda表达式(这个我放在正则表达式那篇文章了)。
如支持字符串soup.find_all('b'),正则表达式soup.find_all(re.compile("^b")),列表soup.find_all(["a","b"]),True值如soup.find_all(True),自定义包含一个参数的方法如soup.find_all(has_class_but_no_id),而方法def has_class_but_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id')。
name:是元素名,可以是一个元素find('head'),也可以是多个元素,多个元素可以用列表find(['a','b']),字典find({'head':True, 'body':True}),或者lambda表达式find(lambda name: if len(name) == 1) 搜索长度为1的元素,或者正则表达式匹配结果find(re.compile('^p'))来表示,find(True) 搜索所有元素
attrs:是元素属性。
搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True
按照属性值搜索tag:一个属性如find(id='xxx') ,soup.find_all("a", class_="sister"),soup.find_all("a", attrs={"class": "sister"});也可以是多个属性,比如正则表达式:find(attrs={id=re.compile('xxx'), p='xxx'})或者soup.find_all(class_=re.compile("itl")),或者true方法:find(attrs={id=True, algin=None}),或者列表方法find_all(attrs={"data-foo": "value"})。
recursive和limit参数
recursive=False表示只搜索直接儿子,否则搜索整个子树,默认为True。当使用findAll或者类似返回list的方法时,limit属性用于限制返回的数量,如findAll('p', limit=2): 返回首先找到的两个tag.
soup.html.find_all("title", recursive=False)
string 参数
通过 string参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表, 自定义方法,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)
关于Beautiful Soup树的操作具体可以看官方文档
另外还有和find_all类似的方法:
find( name , attrs , recursive , text , **kwargs )
和find_all区别:返回第一个节点
find_parents() find_parent()
find_next_siblings() find_next_sibling()
find_previous_siblings() find_previous_sibling()
find_all_next() find_next()
find_all_previous() 和 find_previous()
除了find方法遍历外,还有CSS选择器
CSS选择器:soup.select()
元素查找,比如元素名soup.select('a'),类名soup.select('.sister'),ID名.soupselect('#link1')或者soup.select("#link1,#link2"),组合查,比如soup.select('p #link1'),或者直接子元素选择器soup.select("head > title"),所有子元素soup.select("body a"),筛选soup.select("p nth-of-type(3)"),soup.select("p > a:nth-of-type(2)")。
属性查找soup.select('a[class="sister"]'),soup.select('p a[href="http://example.com/elsie"]'),soup.select('a[href^="http://example.com/"]'),soup.select('a[href$="tillie"]')
兄弟节点查找:所有后面的兄弟soup.select("#link1 ~ .sister"),直接后面兄弟soup.select("#link1 + .sister"),soup.select('a[href*=".com/el"]')
查找到的元素的第一个soup.select_one(".sister")
E、修改DOM树:
修改tag的名称和属性:tag.name = "blockquote" tag['class'] = 'verybold'
修改tag的内容:.string tag = soup.a tag.string = "New link text."
添加节点内容:append()或者NavigableString构造对象,new_string构造comment注释
append():soup.a.append("Bar"),输出内容时soup.a.contents,结果从"<a>Foo</a>变成<a>FooBar</a>"
NavigableString方法:new_string = NavigableString(" there") tag.append(new_string)
new_string构造comment注释:from bs4 import Comment new_comment = soup.new_string("Nice to see you.", Comment) tag.append(new_comment)
添加子节点:new_tag(),第一个参数作为tag的name,是必填,其它参数选填
soup = BeautifulSoup("<b></b>") original_tag = soup.b
new_tag = soup.new_tag("a", href="http://www.example.com") original_tag.append(new_tag)
#<b><a href="http://www.example.com"></b>
插入子节点或内容:Tag.insert(指定位置索引,节点或内容),Tag.insert_before(节点或内容) 和 Tag.insert_after(节点或内容)
tag = soup.p
insert():tag.insert(1, "but did not")#从"<p>haha<a href='www.baidu.com'>i like <i>fish</i></a></p>"变成"<p>haha but did not <a href='www.baidu.com'></p>"
insert_before():如tag = soup.new_tag("i") tag.string = "Don't" soup.b.string.insert_before(tag)
insert_after():如soup.b.i.insert_after(soup.new_string(" ever "))
删除当前节点内容:tag = soup.a tag.clear()
删除当前节点并返回删除后的内容:tag=soup.i.extract(),注意这个tag是新创建的,和dom树soup有区别。
删除当前tag并完全销毁,不会创建新的:soup.i.decompose()
删除当前节点并替换新的节点或内容:a_tag = soup.a new_tag = soup.new_tag("b") new_tag.string = "example.net" a_tag.i.replace_with(new_tag)
对指定节点进行包装,添加标签如div:soup.p.wrap(soup.new_tag("div"))
删除指定节点的标签如a标签:tag.a.unwrap()
打印DOM树或DOM树的节点:
含标签:soup.prettify() ,soup.p.prettify() #<p>i will<p>
不含标签:unicode() 或 str() 方法:#i will
特殊字符如ldquo转换成‘\’:soup = BeautifulSoup("“Dammit!” he said.") unicode(soup)
获取本文内容并以字符隔开:get_text(隔开字符可选填,去除空白符开关可选填),soup.get_text(),soup.get_text("|")如‘i like | fish’,soup.get_text("|", strip=True)
判断节点内容是否相同:first_b == second_b
判断节点是否相同,即指向同一地址:first_b is second_b
判断节点的类型是否是已知类型:isinstance(1, int)
复制节点:p_copy = copy.copy(soup.p)
F、Python3和Python2版本的Beautiful Soup区别:
G、Soup与Requests
附上可靠的网络连接代码:
把这段代码
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page1.html")
bsObj = BeautifulSoup(html.read())
print(bsObj.h1)
改成以下代码
from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
try:
html = urlopen(url)
except HTTPError as e:
return None
try:
bsObj = BeautifulSoup(html.read())
title = bsObj.body.h1
except AttributeError as e:
return None
return title
title = getTitle("http://www.pythonscraping.com/pages/page1.html")
if title == None:
print("Title could not be found")
else:
print(title)
不过,最终选择了xpath