【Python 網路爬蟲筆記】BeautifulSoup Library - part 3
===
目錄(Table of Contents):
[TOC]
---
感謝你點進本篇文章!!我是 LukeTseng,一個熱愛資訊的無名創作者,由於近期大學開設大數據分析程式設計這門課程,裡面談到了爬蟲概念,讓我激起一些興趣,因而製作本系列筆記。
聲明:本篇筆記僅供個人學習用途,斟酌參考。
另外本篇筆記使用 VSCode 環境進行編寫,部分模組(函式庫)需自行下載。
## 安裝 BeautifulSoup 模組
若使用 google colab 或 anaconda 環境者無須安裝。
指令:
```
pip install beautifulsoup4
```
## 引入 BeautifulSoup 模組
```
from bs4 import BeautifulSoup
```
## 為什麼我們要用 BeautifulSoup?
BeautifulSoup 的主要用途是解析 HTML 和 XML,將網頁內容轉換成結構化的樹狀格式供程式操作 。
網頁資料解析與擷取是 BeautifulSoup 最主要的用途。
在網路爬蟲的世界,無可或缺的除了 request 模組外,就是 BeautifulSoup,有了這個模組就可以進一步擷取、分析我們想要的資訊。
例如可以擷取個人部落格所有文章的總瀏覽量,可以做到的方式就是透過 sitemap 一個一個進文章,去抓取每個文章的瀏覽量資訊,最後加總起來。
## 第一支 BeautifulSoup 程式
以我的部落格網站為例:https://luketsengtw.github.io/
```python=
import requests
from bs4 import BeautifulSoup
url = 'https://luketsengtw.github.io/'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
print(soup.title)
```
Output:
```
<title>Yaoの程式小窩 - 只想好好學程式</title>
```
如果想要去掉 `<title></title>` 標籤的話,可以加上 `.string` 方法。
```python=
import requests
from bs4 import BeautifulSoup
url = 'https://luketsengtw.github.io/'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
print(soup.title.string) # 加上 .string
```
Output:
```
Yaoの程式小窩 - 只想好好學程式
```
## 解析器(Parser)
解析器是 BeautifulSoup 第二個參數,用於將 html 原始碼轉換成標籤樹好讓程式去做一些操作。
Python 內建的網頁解析器是 `html.parser`,如果要使用其它解析器需要額外安裝。
常見的解析器就有 `lxml` 跟 `html5lib`。
要安裝它們的話可以輸入指令:`pip install lxml html5lib`
以下表格可以幫各位快速閱覽這些解析器的能力:
| 解析器 | 速度 | 準確性 | 容錯能力 |
| -------- | -------- | -------- | -------- |
| html.parser | 中 | 最差 | 最差 |
| lxml | 最快 | 高 | 高 |
| html5lib | 最慢 | 最高 | 最高 |
通常會使用 lxml 作為解析器,若在學習階段,不想裝這些有的沒的話,用後續用 html.parser 就可以了。
## BeautifulSoup 常用方法
### 搜尋方法
`find()`、`find_all()` 是在 BeautifulSoup 中使用頻率最高的方法,因此先特別介紹這個。
基本上他的功能就是尋找這樣,`find()` 若有多個標籤存在的話,只會找第一個。
以下是一個範例:
```python=
from bs4 import BeautifulSoup
html = """
<html>
<body>
<div class="container">
<h2>標題</h2>
<p class="content">第一段內容</p>
<p class="content">第二段內容</p>
<a href="https://example.com">連結一</a>
<a href="https://google.com">連結二</a>
</div>
</body>
</html>
"""
soup = BeautifulSoup(html, 'lxml')
# 找於 class_ = 'content' 中第一個段落 <p> 元素
first_p = soup.find('p', class_='content')
print(first_p.text)
# 找於 class_ = 'content' 中所有段落 <p> 的元素
all_p = soup.find_all('p', class_='content')
for p in all_p:
print(p.text)
# 在 html 所有內容中找所有連結 <a>
links = soup.find_all('a', href=True)
for link in links:
print(f"連結文字: {link.text}, 網址: {link['href']}")
```
Output:
```
第一段內容
第一段內容
第二段內容
連結文字: 連結一, 網址: https://example.com
連結文字: 連結二, 網址: https://google.com
```
### CSS Selector
使用 `select()`。
以下是測試資料:
```python=
from bs4 import BeautifulSoup
html_content = """
<!DOCTYPE html>
<html>
<head>
<title>CSS 選擇器練習範例</title>
</head>
<body>
<header id="main-header" class="site-header">
<h1 class="title">網站標題</h1>
<nav class="navigation">
<a href="/home" class="nav-link active">首頁</a>
<a href="/about" class="nav-link">關於我們</a>
<a href="/contact" class="nav-link">聯絡我們</a>
</nav>
</header>
<main class="container">
<article id="article-1" class="post featured">
<h2 class="post-title">第一篇文章</h2>
<p class="post-content">這是第一篇文章的內容。</p>
<div class="meta">
<span class="author" data-name="A">作者:A</span>
<span class="date" data-published="2025-01-01">日期:2025-01-01</span>
</div>
</article>
<article id="article-2" class="post">
<h2 class="post-title">第二篇文章</h2>
<p class="post-content highlight">這是第二篇文章的重點內容。</p>
<div class="meta">
<span class="author" data-name="B">作者:B</span>
<span class="date" data-published="2025-01-02">日期:2025-01-02</span>
</div>
</article>
<aside class="sidebar">
<div class="widget recent-posts">
<h3>最新文章</h3>
<ul>
<li><a href="/post1">文章一</a></li>
<li><a href="/post2">文章二</a></li>
<li><a href="/post3">文章三</a></li>
</ul>
</div>
</aside>
</main>
<footer id="main-footer" class="site-footer">
<p>© 2025 練習網站</p>
</footer>
</body>
</html>
"""
soup = BeautifulSoup(html_content, 'lxml')
```
**標籤選擇器**:以下範例可選取 HTML 中所有的 h2 標籤及 p 段落標籤。
註:使用選擇器回傳的物件會是一個 list,所以以下的 `for h2 in h2_tags:` 會迭代 list 物件內的元素。
```python=
# 選取所有 h2 標籤
h2_tags = soup.select('h2')
print("所有 h2 標籤:")
for h2 in h2_tags:
print(f" {h2.text}")
# 選取所有段落
paragraphs = soup.select('p')
print(f"\n找到 {len(paragraphs)} 個段落")
```
Output:
```
所有 h2 標籤:
第一篇文章
第二篇文章
找到 3 個段落
```
**ID 選擇器**:`<header id="main-header" class="site-header">` 做舉例,可以選擇 `<header>` 標籤裡面的 id。
要選取特定 ID,則使用一個 `#` 井字號作為前綴。
```python=
# 選取特定 ID 的元素
header = soup.select('#main-header')
print(f"\n標頭內容: {header[0].find('h1').text}")
# 選取特定文章 ID
article1 = soup.select('#article-1')
print(f"第一篇文章標題: {article1[0].find('h2').text}")
```
Output:
```
標頭內容: 網站標題
第一篇文章標題: 第一篇文章
```
**Class 選擇器**:顧名思義,選擇 class 的值。
那 class 選擇器與 ID 選擇器不一樣,class 選擇器使用 `.` 一個半形點作為前綴。
註:以下程式碼的 `.get('href')` 是 BeautifulSoup 的方法,用於取得某個標籤的屬性值。
要取得屬性值也可直接寫 `link['href']`,與使用方法的差別在於這種方式比較不安全(會直接報錯),而 `.get()` 找不到的話會直接回傳 None。
```python=
# 選取所有有 post 類別的元素
posts = soup.select('.post')
print(f"\n找到 {len(posts)} 篇文章:")
for post in posts:
title = post.find('h2').text
print(f" {title}")
# 選取導航連結
nav_links = soup.select('.nav-link')
print(f"\n找到 {len(nav_links)} 個導航連結:")
for link in nav_links:
print(f" {link.text} -> {link.get('href')}") # get 方法取得屬性值
```
Output:
```
找到 2 篇文章:
第一篇文章
第二篇文章
找到 3 個導航連結:
首頁 -> /home
關於我們 -> /about
聯絡我們 -> /contact
```
**多重選擇器**:可以同時選擇多個選擇器。像是可以同時有兩個類別,請見範例:
```python=
# 標籤 + 類別選擇器
post_titles = soup.select('h2.post-title')
print("\n文章標題:")
for title in post_titles:
print(f" {title.text}")
# 多個類別選擇器(同時擁有兩個類別)
featured_posts = soup.select('.post.featured')
print(f"\n精選文章數量: {len(featured_posts)}")
```
Output:
```
文章標題:
第一篇文章
第二篇文章
精選文章數量: 1
```
**後代選擇器**:可以選擇一個標籤底下的其中一個標籤,假設要找 `<article>` 裡面的 `<p>`,那透過這個選擇器,就會找出 `<article>` 裡面的所有 `<p>` 標籤。
若要做到這個選擇器的功能,則在兩個標籤之間空一格。
以下是個範例:
```python=
# 選取 main 內的所有 span 標籤
meta_spans = soup.select('main span')
print("\n文章元資訊:")
for span in meta_spans:
print(f" {span.text}")
# 選取 article 內的 p 標籤
article_paragraphs = soup.select('article p')
print(f"\n文章段落數: {len(article_paragraphs)}")
```
Output:
```
文章元資訊:
作者:A
日期:2025-01-01
作者:B
日期:2025-01-02
文章段落數: 2
```
## 透過 BeautifulSoup 提取純文字
透過 `.get_text()` 可獲取標籤內的內容,而非標籤本身(如`<p>123</p>`)。
```python=
from bs4 import BeautifulSoup
html = """
<div class="article">
<h1>文章標題</h1>
<p>這是第一段落。</p>
<p>這是<strong>重要</strong>的第二段落。</p>
</div>
"""
soup = BeautifulSoup(html, 'html.parser')
# 提取純文字(包含所有子元素的文字)
article = soup.find('div', class_='article')
print(article.get_text())
```
Output:
```
文章標題
這是第一段落。
這是重要的第二段落。
```
## BeautifulSoup 結合 Requests 小應用
爬取網站:https://www.nptu.edu.tw/p/412-1000-2972.php?Lang=zh-tw
爬取國立屏東大學中的所有學術單位,含學院、以及旗下學系。
註:此爬蟲程式僅供學習用途,絕無任何其餘用途。
建議使用 colab 或 jupyter notebook 進行實作,若使用真實環境有可能會遇到 SSL Certificate 的問題。
另外可於該網站中任意處右鍵、按下檢查,開啟開發者工具介面,在左上方箭頭處可選取任意元素,選取完後會跳至該行的 HTML 原始碼。



範例程式碼:
```python=
import requests
from bs4 import BeautifulSoup
url = "https://www.nptu.edu.tw/p/412-1000-2972.php?Lang=zh-tw" # 目標網址
html = requests.get(url) # 對目標網址發送 GET 請求
html.encoding = "utf-8" # 設定正確編碼
soup = BeautifulSoup(html.text, "lxml") # 建立 BeautifulSoup 物件, 使用 lxml 解析器
main_content = soup.find("div", class_="main") # 主內容,
if main_content:
# 獲取所有文字內容並按行分割
full_text = main_content.get_text()
lines = [line.strip() for line in full_text.split('\n') if line.strip()]
# 找到學術單位列表的開始位置
start_idx = -1
for i, line in enumerate(lines):
if "學術單位列表" in line:
start_idx = i
break
if start_idx != -1:
relevant_lines = lines[start_idx+1:] # 跳過"學術單位列表"這行
current_college = None
departments = []
for line in relevant_lines:
# 如果遇到包含"學院"的行,這是新的學院
if "學院" in line and not line.endswith("系"):
# 如果之前已經有學院資料,先印出來
if current_college and departments:
print(f"{current_college}:")
for dept in departments:
print(f" - {dept}")
print() # 空行分隔
# 開始新的學院
current_college = line
departments = []
# 如果包含"學系"、"研究所"、"中心"或"學程",這是學系
elif any(keyword in line for keyword in ["學系", "研究所", "中心", "學程"]):
if current_college: # 確保目前有學院
departments.append(line)
# 如果遇到校區資訊,結束處理
elif "校區" in line:
break
# 處理最後一個學院
if current_college and departments:
print(f"{current_college}:")
for dept in departments:
print(f" - {dept}")
```
Output:
```
管理學院:
- 商業大數據學系(含碩士班)
- 行銷與流通管理學系(含碩士班)
- 休閒事業經營學系(含碩士班)
- 不動產經營學系(含碩士班)
- 企業管理學系(含碩士班)
- 國際經營與貿易學系(含碩士班)
- 財務金融學系(含碩士班)
- 會計學系
- 大數據商務應用學士學位學程(113學年停招)
資訊學院:
- 電腦與通訊學系
- 資訊工程學系(含碩士班)
- 電腦科學與人工智慧學系(含碩士班)
- 資訊管理學系(含碩士班)
- 智慧機器人學系
- 國際資訊科技與應用碩士學位學程
教育學院:
- 教育行政研究所(含博碩士班)
- 教育心理與輔導學系(含碩士班)
- 教育學系(含碩士班)
- 特殊教育學系(含碩士班)
- 幼兒教育學系(含碩士班)
- STEM教育國際碩士學位學程
- 特殊教育中心
- 社區諮商中心
- 文教事業經營碩士在職學位學程(110學年停招)
人文社會學院:
- 視覺藝術學系(含碩士班)
- 音樂學系(含碩士班)
- 文化創意產業學系(含碩士班)
- 社會發展學系(含碩士班)
- 中國語文學系(含碩士班)
- 應用日語學系
- 應用英語學系
- 英語學系(含碩士班)
- 文化發展學士學位學程原住民專班
- 文化事業發展碩士學位學程原住民專班
- 客家文化產業碩士學位學程
- 客家研究中心
- 原住民族教育研究中心
- 藝文中心
理學院:
- 科學傳播學系(含科學傳播暨教育碩士班)
- 應用化學系(含碩士班)
- 應用數學系(含碩士班)
- 體育學系(含碩士班)
國際學院:
- 東南亞發展中心
- 華語教學中心
大武山學院:
- 共同教育中心
- 博雅教育中心
- 跨領域學程中心
- EMI發展中心
- 大武山社會實踐暨永續發展中心
- 新媒體創意應用碩士學位學程
- 大武山跨領域學士學位學程
- 師資培育中心
- 師資培育中心
- 教育學程組
```
## 總結
BeautifulSoup 是 Python 中用於解析 HTML 和 XML 的函式庫,將網頁內容轉換成樹狀結構供程式操作。主要應用於網路爬蟲和網頁資料擷取。
### 第一支 BeautifulSoup 範例
```python=
import requests
from bs4 import BeautifulSoup
url = 'https://luketsengtw.github.io/'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
print(soup.title)
```
`from bs4 import BeautifulSoup` 以此來引入 BeautifulSoup 做網頁分析與資料擷取。
`soup = BeautifulSoup(html.text, 'html.parser')` 是建立 BeautifulSoup 的方法。
### 解析器介紹
| 解析器 | 速度 | 準確性 | 容錯能力 |
| -------- | -------- | -------- | -------- |
| html.parser | 中 | 最差 | 最差 |
| lxml | 最快 | 高 | 高 |
| html5lib | 最慢 | 最高 | 最高 |
### BeautifulSoup 的常用方法
* find() - 找第一個符合的元素
* find_all() - 找所有符合的元素
以下程式碼分別找出第一個 `<p>` 和找出所有的 `<p>` 標籤。
```python=
first_p = soup.find('p', class_='content')
all_p = soup.find_all('p', class_='content')
```
#### CSS 選擇器
使用 `select()` 方法:
* 一般標籤:`soup.select('h2')`
* ID 選擇器(用 `#`):`soup.select('#main-header')`
* Class 選擇器(用 `.`):`soup.select('.nav-link')`
* 多重選擇器:`soup.select('h2.post-title')`
* 後代選擇器(空格):`soup.select('main span')`
#### 文字提取
* `soup.title.string` - 取得標籤內文字
* `element.get_text()` - 取得純文字內容(去除標籤)
* `link['href'] 或 link.get('href')` - 取得屬性值
### 基本爬蟲模板
```python=
import requests
from bs4 import BeautifulSoup
url = "目標網址"
html = requests.get(url)
html.encoding = "utf-8" # 設定編碼
soup = BeautifulSoup(html.text, "lxml")
# 找到主要內容區域
main_content = soup.find("div", class_="main")
text = main_content.get_text()
```
## 參考資料
[Beautiful Soup 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/spider/beautiful-soup.html)
[Beautiful Soup Documentation — Beautiful Soup 4.13.0 documentation](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
[Implementing Web Scraping in Python with BeautifulSoup - GeeksforGeeks](https://www.geeksforgeeks.org/python/implementing-web-scraping-python-beautiful-soup/)