Python爬虫之Beautiful Soup用法

关于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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容