python
crawler
beautifulsoup
Beautiful Soup是Python中用來解析HTML、XML標籤文件的模組,並能修復含有未閉合標籤等錯誤的文件(此種文件常被稱為tag soup);解析後會為這個頁面建立一個BeautifulSoup
物件,這個物件中包含了整個頁面的結構樹,透過這個BeautifulSoup
物件的結構樹,就可以輕鬆的提取頁面內任何有興趣的資料了。
Python 3.x版本可以使用 pip3
安裝:
pip3 install bs4
如果是Python 2.x版本,可以這樣安裝:
pip install bs4
下面範例為建立一個解析HTML文件的BeautifulSoup
物件,解析完成後會產生一個soup
物件,這個就是該HTML的結構樹物件,之後所有資料的搜尋、提取等操作都是透過這個物件來進行。
# 引入Beautiful Soup模組
from bs4 import BeautifulSoup
# 原始HTML原始碼
html_doc = """
<html>
<head>
<title>這是HTML文件標題</title>
</head>
<body>
<h1 id="article" class="banner">網頁標題</h1>
<p data-author='aaron'>文章段落</p>
<a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
<a href="https://www.aaronlife.com/ref2">參考資料連結2</a>
<p>這是一份<b class="boldtext">HTML文件</b>。</p>
<h2 id="article">網頁標題2</h2>
</body>
</html>
"""
# 建立BeautifulSoup物件解析HTML文件
soup = BeautifulSoup(html_doc, 'html.parser')
解析器名稱 | 使用範例 | 說明 |
---|---|---|
Python’s html.parser | BeautifulSoup(markup, "html.parser") |
Python內建 |
lxml’s HTML parser | BeautifulSoup(markup, "lxml") |
速度較快的HTML解析器 |
lxml’s XML parser | BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml") |
速度較快的XML解析器 |
html5lib | BeautifulSoup(markup, "html5lib") |
解析後建立HTML 5物件 |
補充
lxml解析器需要另外安裝:
pip3 install lxml
prettify()
呼叫BeautifulSoup
物件的prettify
方法可以輸出排版過後的HTML文件,在開發時可以用來觀察其文件的整個輪廓:
print(soup.prettify())
會輸出:
<html>
<head>
<title>
這是HTML文件標題
</title>
</head>
<body>
<h1 id="article" class="banner">
網頁標題
</h1>
<p data-author='aaron'>
文章段落
</p>
<a href="https://www.aaronlife.com/ref1">
參考資料連結1
</a>
<a href="https://www.aaronlife.com/ref2">
參考資料連結2
</a>
<p>
這是一份
<b class="boldtext">
HTML文件
</b>
。
</p>
</body>
</html>
直接指定網頁標題標籤的名稱(例如:title
),即可將該標籤的節點抓出來:
print(soup.title) # 取得第一個title標籤
print(soup.html.body.h1) # 取得指定位置的第一個h1標籤
print(soup.a) # 取得文件內第一個a標籤
會輸出包含標籤的文字:
<title>這是HTML文件標題</title>
<h1 id="article" class="banner">網頁標題</h1>
<a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
透過 string
屬性,可以取得標籤內的純文字:
print(soup.title.string)
會輸出:
這是HTML文件標題
find_all()
透過 find_all
可以在文件內找出指定的所有標籤,find_all
會以list
傳回所有找到的標籤:
tag_p = soup.find_all('p')
for p in tag_p:
print(p.getText())
會輸出:
文章段落
這是一份HTML文件。
注意:
因為第二個p標籤內含有子標籤,所以如果直接使用
soup.string
只會得到None
,這時就必須使用getText()
方法來取得文字。
get()
方法透過 get()
方法,可以取得標籤內的屬性值,下面透過get()
方法來取得a
標籤內的href
屬性:
tag_a = soup.find_all('a')
for a in tag_a:
print(a.get('href'))
會輸出:
https://www.aaronlife.com/ref1
https://www.aaronlife.com/ref2
attrs
也可以透過attrs
屬性可以拿到標籤內全部的屬性,該屬性為一個list
,例:
print(tag.attrs['data-table'])
會得到data-table
的屬性值
如果要同時搜尋多種HTML標籤,可以使用 list 來指定所有要搜尋的標籤名稱:
tags = soup.find_all(['a', 'p'])
for tag in tags:
print(tag)
會輸出:
<p data-author='aaron'>文章段落</p>
<a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
<a href="https://www.aaronlife.com/ref2">參考資料連結2</a>
<p>這是一份<b class="boldtext">HTML文件</b>。</p>
find_all
預設會搜尋所有符合條件的節點,如果遇到文件較大或是標籤數量很多的時候,就會執行比較多的時間,如果在應用上不需要用到全部標籤,可以用 limit
參數來限制搜尋的標籤上限值,這樣一來,就只會找出前幾個符合條件的標籤:
tags = soup.find_all(['a', 'p'], limit=2)
for tag in tags:
print(tag)
會輸出:
<p data-author='aaron'>文章段落</p>
<a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
如果只需要抓出第一個符合條件的節點,可以直接使用 find()
方法即可:
tag = soup.find('a') # 搜尋第一個a標籤
print("第一個搜尋結果:", tag)
tag = soup.find(['a', 'p']) # 在多個標籤名稱下搜尋第一個符合條件的標籤
print("第二個搜尋結果:", tag)
會輸出:
第一個搜尋結果: <a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
第二個搜尋結果: <p data-author='aaron'>文章段落</p>
在預設的狀況下,find_all()
方法會以遞迴的方式 往下尋找所有的子標籤,例如搜尋html
標籤下所有h1標籤,包含子標籤下的標籤:
tags = soup.html.find_all('h1')
會輸出:
print(tags)
上面程式碼,加上recursive=False
後,表示只會搜尋在html
標籤下第一層的標籤:
tags = soup.html.find_all('h1', recursive=False)
print(tags)
會輸出:
[]
因為
h1
標籤是在html
標籤下的body
標籤內,也就是底下兩層,所以不會被搜尋到。
透過指定標籤內的屬性名稱,可以將所有符合屬性值的所有標籤找出來,例如搜尋 id
屬性為 article
的節點:
tags = soup.find_all(id='article')
print(tags)
會輸出:
[<h1 id="article" class="banner">網頁標題</h1>, <h2 id="article">網頁標題2</h2>]
可以透過結合標籤名稱名稱與屬性名稱進行更精確的搜尋,例如搜尋 id
屬性為 article
的 h2
節點:
tag = soup.find_all('h2', id='article')
print(tag)
會輸出:
[<h2 id="article">網頁標題2</h2>]
補充:
以屬性做為條件來搜尋時,也可以一次給多個屬性條件來篩選,例如:
`tags = soup.find_all(id='article', name='title')
在HTML5中有些屬性名稱中含有符號,例如 data-
開頭的屬性名稱如果直接寫在Python的方法中的話會出現錯誤:
tag = soup.find(data-attr='aaron')
會出現下面錯誤:
SyntaxError: expression cannot contain assignment
解決方法為,將屬性名稱與值放進一個字典(dict)中,再將此字典設定給 attrs
參數即可,例如:
condition = {'data-author': 'aaron'}
tag = soup.find_all(attrs=condition)
以正規表示法來搜尋:
import re
links = soup.find_all(href=re.compile('^https://www.aaronlife.com'))
print(links)
說明:
只要href屬性開頭為
https://www.aaronlife.com
都會被搜尋出來。
因為class
是Python程式語言的保留字,所以在Beautiful Soup內改用 class_
這個名稱來代表HTML標籤內的class屬性,例如要搜尋class為banner
的標籤:
tags = soup.find_all(class_='banner')
print(tags)
會輸出:
[<h1 class="banner" id="article">網頁標題</h1>]
補充:
- 一個class屬性可以有多個屬性值,在做
class_
屬性執比對時,只要有一個符合就算成功。- 也可以直接拿完整的class屬性值字串比對,但如果順序不對就會失敗,例如:
"banner primary"
和"primary banner"
會是不一樣的屬性值,這個情況下會建議用CSS選擇器來進行比對,例如:soup.select('h1.banner.primary')
或soup.select('*.banner.primary')
搜尋多個屬性值:
tags = soup.find_all(class_=['banner', 'primary'])
搜尋多個屬性值需要使用list將所有要搜尋的值放在一起。
使用 find_all
配合 string
參數可以根據文字進行來搜尋特定的標籤內容,例如:
contents = soup.find_all(string='網頁標題')
print(contents)
會輸出:
['網頁標題']
注意:
輸出結果只會有內容,而不是完整的標籤。
find_all
和find
方法都是從外層向下搜尋子標籤,但我們也可以由下往上來搜尋上層標籤(或稱父標籤),方法find_parents
(如同find_all
網上搜尋全部符合條件的標籤)和find_parent
(如同find
指搜尋第一個符合的標籤)例如:
tag_b = soup.find('b')
parent_p = tag_a.find_parent('p')
print(parent_p)
會輸出:
<p>這是一份<b class="boldtext">HTML文件</b>。</p>
如果想要搜尋的標籤在同一層,則可以使用:
find_previous_siblings()
(全部符合的)和 find_previous_sibling()
(第一個符合的):往前搜尋。find_next_siblings()
(全部符合的)和 find_next_sibling()
(第一個符合的):往後搜尋。例如:
tag.find_previous_siblings('a') # 往前搜尋同一層的全部a標籤
tag.find_previous_sibling('a') # 搜尋前一個a標籤
tag.find_next_siblings('a') # 往後搜尋全部的a標籤
tag.find_next_sibling('a') # 搜尋後面第一個a標籤
方法名稱 | 說明 |
---|---|
select() |
以CSS選擇器的方式來找出所有符合條件的元素 |
select_one() |
以CSS選擇器的方式來定位第一筆符合條件的元素 |
select()
from bs4 import BeautifulSoup
# 原始HTML原始碼
html_doc = '''
<html>
<head>
<title>這是HTML文件標題</title>
</head>
<body>
<h1 id="article" class="banner">網頁標題1</h1>
<p data-author='aaron' class="reqular text-normal">文章段落1</p>
<a class="link no btn" href="https://www.aaronlife.com/ref1">參考<b>資料</b>連結1</a>
<a class="link btn" href="http://www.aaronlife.org/ref2">參考<b>資料</b>連結2</a>
<a class="link btn" href="http://www.aaronlife.edu/ref2">參考資料連結3</a>
<a class="link btn" href="https://www.aaronlife.com/ref2">參考資料連結4</a>
<p>這是一份<b class="boldtext">HTML文件</b>。</p>
<h2 id="article1" class="banner">網頁標題2</h2>
<p data-author='andy' class="reqular text-normal">文章段落2</p>
<h2 id="article2" class="title normal">網頁標題3</h2>
<p data-author='william' class="reqular text-normal">文章段落3</p>
</body>
</html>
'''
# 建立BeautifulSoup物件解析HTML文件
soup = BeautifulSoup(html_doc, 'html.parser')
# 透過標籤來定位元素
result = soup.select('a')
print('select a:', result)
result = soup.select('body p b')
print('select p b:', result)
# 透過id來定位元素
result = soup.select('#article')
print('select #article:', result)
# 多重選擇(只要一個符合即可)
result = soup.select('#article, p, b')
print('select #article, p, b:', result)
# 全部class都要符合才會被定位到
result = soup.select('.no.link.btn')
print('select .no.link.btn:', result)
# 透過class來定位元素
result = soup.select('.banner')
print('select .banner:', result)
# 透過是否存在某個屬性來定位元素
result = soup.select('a[href]')
print('select a[href]:', result)
# 透過指定的屬性值來定位元素
result = soup.select('a[href="http://wwww.aaronlife.com"]')
print('select a[href=http://wwww.aaronlife.com]:', result)
# 透過指定的屬性值「開頭字串」來定位元素
result = soup.select('a[href^="http"]')
print('select a[href^=http]:', result)
# 透過指定的屬性值「結束字串」來定位元素
result = soup.select('a[href$="com"]')
print('select a[href$=com]:', result)
# 透過指定的屬性值「有包含的字串」來定位元素
result = soup.select('a[href*="com"]')
print('select a[href*=com]:', result)
select_one()
使用方式與select()一樣,直接回傳符合條件的元素,而非list。
如果我們想要用 Beautiful Soup 解析已經下載的 HTML 檔案,可以直接將開啟的檔案交給 BeautifulSoup
處理:
from bs4 import BeautifulSoup
# 從檔案讀取HTML原始碼來進行解析
with open("index.html") as html_file:
soup = BeautifulSoup(html_doc)
print(html_doc)
會輸出:
<html>
<head>
<title>這是HTML文件標題</title>
</head>
<body>
<h1 id="article" class="banner">網頁標題</h1>
<p data-author='aaron'>文章段落</p>
<a href="https://www.aaronlife.com/ref1">參考資料連結1</a>
<a href="https://www.aaronlife.com/ref2">參考資料連結2</a>
<p>這是一份<b class="boldtext">HTML文件</b>。</p>
</body>
</html>
備註:
須先將最上面的HTML文件範例存成index.html檔案,並和這個範例的程式碼檔案放在同一層目錄下。
要直接從網路抓取html文件,可以使用Python內建的request
模組:
import requests
from bs4 import BeautifulSoup
url = "https://www.ptt.cc/bbs/Python/index.html" # PTT Python看板
response = requests.get(url) # 使用requests的get方法把網頁抓下來
html_doc = response.text # text屬性就是html文件原始碼
soup = BeautifulSoup(response.text, "lxml") # 指定lxml作為解析器來建立Beautiful物件