BeautifulSoup4庫

BeautifulSoup4庫

image

和 lxml 一樣,Beautiful Soup 也是一個HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 數據。

lxml 只會局部遍歷,而Beautiful Soup 是基於HTML DOM(Document Object Model)的,會載入整個文檔,解析整個DOM樹,因此時間和記憶體開銷都會大很多,所以性能要低於lxml。

Beautiful Soup 3 目前已經停止開發,推薦現在的項目使用Beautiful Soup 4。

安裝和文檔:

安裝:pip install bs4

中文文檔://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

幾大解析工具對比:

image-20220509203742373

安裝解析器

  1. pip install lxml (推薦)
  2. pip install html5lib

image-20220509205344311

推薦使用lxml作為解析器,因為效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必須安裝lxml或html5lib, 因為那些Python版本的標準庫中內置的HTML解析方法不夠穩定.

提示: 如果一段HTML或XML文檔格式不正確的話,那麼在不同的解析器中返回的結果可能是不一樣的,查看 解析器之間的區別 了解更多細節

簡單使用:

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="//example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="//example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="//example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""

soup = BeautifulSoup(html,'lxml')
print(soup.prettify())

常見的四種對象:

1.Tag:

Tag 通俗點講就是 HTML 中的一個個標籤。我們可以利用 soup 加標籤名輕鬆地獲取這些標籤的內容,這些對象的類型是bs4.element.Tag。但是注意,它查找的是在所有內容中的第一個符合要求的標籤。

2.NavigableString:

如果拿到標籤後,還想獲取標籤中的內容。那麼可以通過tag.string獲取標籤中的文字,底層繼承了str對象,可以當作字元串來使用

from bs4.element import NavigableString

3. BeautifulSoup:

BeautifulSoup 對象表示的是一個文檔的全部內容.大部分時候,可以把它當作 Tag 對象,因為底層繼承了Tag對象,它支援 遍歷文檔樹搜索文檔樹 中描述的大部分的方法.

from bs4 import BeautifulSoup

4.Comment:

Tag , NavigableString , BeautifulSoup 幾乎覆蓋了html和xml中的所有內容,但是還有一些特殊對象.容易讓人擔心的內容是文檔的注釋部分
Comment 對象是一個特殊類型的 NavigableString 對象,底層繼承了NavigableString

from bs4.element import Comment

遍歷文檔樹:

contents和children:

  1. contentschildren

    • contents:返回所有子節點的列表
    • children:返回所有子節點的迭代器

    異同:返回某個標籤下的直接子元素,其中也包括字元串。他們兩的區別是:contents返回來的是一個列表,children返回的是一個迭代器。

  2. stringsstripped_strings

    • strings:如果tag中包含多個字元串 ,可以使用 .strings 來循環獲取
    • stripped_strings:輸出的字元串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多餘空白內容

string和strings、stripped_strings屬性以及get_text方法

  1. string:獲取某個標籤下的非標籤字元串。返回來的是個字元串。如果這個標籤下有多行字元,那麼就不能獲取到了。
  2. strings:獲取某個標籤下的子孫非標籤字元串。返回來的是個生成器。
  3. stripped_strings:獲取某個標籤下的子孫非標籤字元串,會去掉空白字元。返回來的是個生成器。
  4. get_text:獲取某個標籤下的子孫非標籤字元串,以普通字元串形式返回
from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="//example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="//example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="//example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
<footer><!--注釋內容--></footer>   
"""
# 實例化
# soup=BeautifulSoup(html,'html.parser')
soup = BeautifulSoup(html,'lxml')

# print(soup)   # 自動補全
# print(soup.prettify())   # 美化輸出
'''Tag對象'''
# print(type(soup.p))   # <class 'bs4.element.Tag'> ---> Tag對象
# print(soup.p)   # 返回第一個p標籤
# print(soup.p.text)  # The Dormouse's story

# print(soup.p.name)  # 輸出p標籤的名字 ---> p
# print(soup.title.name) # 輸出title標籤的名字  --->title
# print(soup.p.attrs)  # 輸出p標籤屬性   ----->   {'class': ['title'], 'name': 'dromouse'}
# print(soup.p.get('class')) # 因為class可能有多個,所以是列表
# print(soup.p['class'])   #  輸出p標籤的屬性值  ----> ['title']

'''NavigableString對象,如果p標籤內套了標籤需要注意'''
# print(type(soup.p.string))  # <class 'bs4.element.NavigableString'>
# print(soup.p.string)   # 獲取標籤內容,當標籤只有文本或者只有一個子文本才返回,如果有多個文本或標籤返回None----->None
# print(soup.p.text)  # 當前標籤和子子孫的文本內容拼到一起   ----->HammerZeThe Dormouse's story
# print(soup.p.strings) # 把子子孫孫的文本內容放到generator  -----><generator object _all_strings at 0x0000028DDAE8FA40>
# print(list(soup.p.strings)) # --> ['HammerZe', "The Dormouse's story"]

'''BeautifulSoup對象'''
# print(type(soup))  # <class 'bs4.BeautifulSoup'>

'''Comment對象'''
from bs4.element import Comment
# print(soup.footer.string)  # --->注釋內容
# print(type(soup.footer.string))  # <class 'bs4.element.Comment'>

# 嵌套選擇
# print(soup.head.title.string)  # The Dormouse's story

# 子節點、子孫節點
# head_tag = soup.head
# print(head_tag.contents)  # 返回子節點---->[<title>The Dormouse's story</title>]
# print(soup.p.contents)  # p下所有子節點,放到列表中 --->[<b>The Dormouse's story</b>]
# print(list(soup.p.children)) # 得到一個迭代器,包含p下所有子節點,跟contents本質一樣,只是節約記憶體  ---> [<b>The Dormouse's story</b>]
# print(list(soup.p.descendants)) # 獲取子孫節點,p下所有的標籤都會選擇出來  子子孫孫  --->   [<b>The Dormouse's story</b>, "The Dormouse's story"]
# for i,child in enumerate(soup.p.children):
#     print(i,child)
# for i,child in enumerate(soup.p.descendants):
#     print(i,child)

# 父節點、祖先節點
# print(soup.a.parent) # 獲取a標籤的父節點
# print(soup.a.parents) # <generator object parents at 0x000002042AD6FA40>
# print(list(soup.a.parents)) # 找到a標籤所有的祖先節點,父親的父親,父親的父親的父親...

# 兄弟節點
print(soup.a.next_sibling) # 下一個兄弟
print(soup.a.previous_sibling) # 上一個兄弟

print(list(soup.a.next_siblings)) # 下面的兄弟們=>生成器對象
print(soup.a.previous_siblings) # 上面的兄弟們=>生成器對象

搜索文檔樹:

find、find_all的使用:

find和find_all方法:

  • 搜索文檔樹,一般用得比較多的就是兩個方法,一個是find,一個是find_all。

  • find方法是找到第一個滿足條件的標籤後就立即返回,只返回一個元素。

  • find_all方法是把所有滿足條件的標籤都選到,然後返回回去。

find與find_all的區別:

  1. find:找到第一個滿足條件的標籤就返回。說白了,就是只會返回一個元素。
  2. find_all:將所有滿足條件的標籤都返回。說白了,會返回很多標籤(以列表的形式)。

使用find和find_all的過濾條件:

  1. 關鍵字參數:將屬性的名字作為關鍵字參數的名字,以及屬性的值作為關鍵字參數的值進行過濾。
  2. attrs參數:將屬性條件放到一個字典中,傳給attrs參數。

獲取標籤的屬性:

  1. 通過下標獲取:通過標籤的下標的方式。

    href = a['href']
    
  2. 通過attrs屬性獲取:示例程式碼:

    href = a.attrs['href']
    

    demo1

#--coding:utf-8--

from bs4 import BeautifulSoup

html = """
<table class="tablelist" cellpadding="0" cellspacing="0">
    <tbody>
        <tr class="h">
            <td class="l" width="374">職位名稱</td>
            <td>職位類別</td>
            <td>人數</td>
            <td>地點</td>
            <td>發布時間</td>
        </tr>
        <tr class="even">
            <td class="l square"><a target="_blank" href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218">22989-金融雲區塊鏈高級研發工程師(深圳)</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-25</td>
        </tr>
        <tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=29938&keywords=python&tid=87&lid=2218">22989-金融雲高級後台開發</a></td>
            <td>技術類</td>
            <td>2</td>
            <td>深圳</td>
            <td>2017-11-25</td>
        </tr>
        <tr class="even">
            <td class="l square"><a target="_blank" href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218">SNG16-騰訊音樂運營開發工程師(深圳)</a></td>
            <td>技術類</td>
            <td>2</td>
            <td>深圳</td>
            <td>2017-11-25</td>
        </tr>
        <tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=31235&keywords=python&tid=87&lid=2218">SNG16-騰訊音樂業務運維工程師(深圳)</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-25</td>
        </tr>
        <tr class="even">
            <td class="l square"><a target="_blank" href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218">TEG03-高級研發工程師(深圳)</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
        <tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=34532&keywords=python&tid=87&lid=2218">TEG03-高級影像演算法研發工程師(深圳)</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
        <tr class="even">
            <td class="l square"><a target="_blank" href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218">TEG11-高級AI開發工程師(深圳)</a></td>
            <td>技術類</td>
            <td>4</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
        <tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=32218&keywords=python&tid=87&lid=2218">15851-後台開發工程師</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
        <tr class="even">
            <td class="l square"><a target="_blank" href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218">15851-後台開發工程師</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
        <tr class="odd">
            <td class="l square"><a id="test" class="test" target='_blank' href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218">SNG11-高級業務運維工程師(深圳)</a></td>
            <td>技術類</td>
            <td>1</td>
            <td>深圳</td>
            <td>2017-11-24</td>
        </tr>
    </tbody>
</table>
"""

soup = BeautifulSoup(html,'lxml')

# 1. 獲取所有tr標籤
# trs = soup.find_all('tr')  # 列表
# for tr in trs:
#     print(tr)
#     print('-'*50)

# 2. 獲取第2個tr標籤
# print(soup.find_all('tr', limit=2)[1])

# 3. 獲取所有class等於even的tr標籤
# trs = soup.find_all('tr',class_ = 'even')
# trs = soup.find_all('tr',attrs={'class':'even'})
# for tr in trs:
#     print(tr)
#     print('-'*50)

# 4. 將所有id等於test,class也等於test的a標籤提取出來。
# list = soup.find_all('a',id= 'test',class_='test')
# for a in list:
#     print(a)

# 5. 獲取所有a標籤的href屬性
# alist = soup.find_all('a')
# for a in alist:
#     #1.
#     # href = a['href']
#     # print(href)
#     #2.
#     href = a.attrs['href']
#     print(href)


# 6. 獲取所有的職位資訊(純文本)
trs = soup.find_all('tr')[1:]
# print(trs)
lists = []
for tr in trs:
    info = {}
    tds = tr.find_all('td')
    # print(tds)
    name = tds[0].string
    category = tds[1].string
    info['name']=name
    info['category']=category
    # infos = list(tr.stripped_strings)
    infos =tr.get_text()
    print(infos)

    lists.append(info)
print(lists)

demo2

from bs4 import BeautifulSoup

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" id="id_p">lqz<b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="//example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="//example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="//example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""
soup = BeautifulSoup(html_doc, 'lxml')


# 1、五種過濾器: 字元串、正則表達式、列表、True、方法
# find:找到第一個      find_all:找所有

# 字元串  --->value值是字元串
# res=soup.find_all(name='p')
# res=soup.find(id='id_p')
# res=soup.find_all(class_='story')
# res=soup.find_all(name='p',class_='story')  # and條件
# res=soup.find(name='a',id='link2').text
# res=soup.find(name='a',id='link2').attrs.get('href')
# res=soup.find(attrs={'id':'link2','class':'sister'}).attrs.get('href')
# print(res)


# 正則表達式--->value是正則表達式
# import re
#
# # res=soup.find_all(name=re.compile('^b'))
# # res=soup.find_all(href=re.compile('^http'))
# res=soup.find_all(class_=re.compile('^s'))
# print(res)


# 列表  value值是列表
# res=soup.find_all(name=['body','a'])
# res=soup.find_all(class_=['sister','story'])
# res=soup.find_all(id=['link2','link3'])
# print(res)

# True   value值是True
# res=soup.find_all(name=True)
# res=soup.find_all(id=True)
# res=soup.find_all(href=True)
# print(res)


# 方法

# def has_class_but_no_id(tag):
#     return tag.has_attr('class') and not tag.has_attr('id')
#
# print(soup.find_all(name=has_class_but_no_id))  # 有class但是沒有id的標籤


#1 html頁面中,只要有的東西,通過bs4都可以解析出來
#2 遍歷文檔樹+搜索文檔樹混用
# def has_class_but_no_id(tag):
#     return tag.has_attr('class') and not tag.has_attr('id')
# print(soup.find(name=has_class_but_no_id).a.text)

# 3 find_all的其他參數limit:限制取幾條  recursive:是否遞歸查找

# def has_class_but_no_id(tag):
#     return tag.has_attr('class') and not tag.has_attr('id')
# res=soup.find_all(name=has_class_but_no_id,limit=1)
#
# print(res)
#
# res=soup.find_all(name='a',recursive=False)  #不遞歸查找,速度快,只找一層
# print(res)

CSS選擇器:

select方法:

使用以上方法可以方便的找出元素。但有時候使用css選擇器的方式可以更加的方便。使用css選擇器的語法,應該使用select方法。以下列出幾種常用的css選擇器方法:

(1)通過標籤名查找:

print(soup.select('a'))

(2)通過類名查找:

通過類名,則應該在類的前面加一個.。比如要查找class=sister的標籤。示例程式碼如下:

print(soup.select('.sister'))

(3)通過id查找:

通過id查找,應該在id的名字前面加一個#號。示例程式碼如下:

print(soup.select("#link1"))

(4)組合查找:

組合查找即和寫 class 文件時,標籤名與類名、id名進行的組合原理是一樣的,例如查找 p 標籤中,id 等於 link1的內容,二者需要用空格分開:

print(soup.select("p #link1"))

直接子標籤查找,則使用 > 分隔:

print(soup.select("head > title"))

(5)通過屬性查找:

查找時還可以加入屬性元素,屬性需要用中括弧括起來,注意屬性和標籤屬於同一節點,所以中間不能加空格,否則會無法匹配到。示例程式碼如下:

print(soup.select('a[href="//example.com/elsie"]'))

(6)獲取內容

以上的 select 方法返回的結果都是列表形式,可以遍歷形式輸出,然後用 get_text() 方法來獲取它的內容。

soup = BeautifulSoup(html, 'lxml')
print(type(soup.select('title')))
print(soup.select('title')[0].get_text())

for title in soup.select('title'):
    print(title.get_text())

(7) 頁面複製

# 終極大招
import requests
response=requests.get('//www.runoob.com/cssref/css-selectors.html')
soup=BeautifulSoup(response.text,'lxml')
res=soup.select_one('#content > table > tbody > tr:nth-child(2) > td:nth-child(3)').text
print(res)
Tags: