【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>&copy; 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 原始碼。 ![image](https://hackmd.io/_uploads/rJU4rmOngl.png) ![image](https://hackmd.io/_uploads/BJySH7d2lg.png) ![image](https://hackmd.io/_uploads/r1_HBXunlx.png) 範例程式碼: ```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/)