---
# System prepended metadata

title: Python爬蟲X資料視覺化X互動網頁

---

# Python爬蟲X資料視覺化X互動網頁
# 目錄
[TOC]
## 第一天上午
### 網頁概念 
在現實世界裡你想買一朵花，你得向花店老闆提出請求，然後花店老闆會給你一朵花；網路世界裡，一位客戶會用電腦、平板或手機向一個網頁的伺服器提出請求，伺服器給他一些檔案。請求與回應有一個統一的標準，以網頁來說常用的標準叫https協議。
#### http/https協議
現實世界中有許多商家，而在網路世界中，則有大量伺服器負責管理各種網站。為了讓伺服器與用戶端之間能夠順利溝通，需要一套統一的交流方式，避免出現彼此無法對接的情況。

HTTP（HyperText Transfer Protocol，超文本傳輸協定）就是這樣的一套規範，它定義了用戶端與伺服器之間請求與回應的標準格式。可以將它想像成商家與客戶之間寫信時必須遵守的格式規則。如今，大多數網站都在HTTP的基礎上加入了TLS加密，形成了更安全的HTTPS協議。

當我們在瀏覽器上輸入網址訪問網站時，瀏覽器會先使用 GET 方法向伺服器發送請求。如果需要登入，則會先載入一個表單，待使用者填寫完畢後，再透過 POST 方法提交資料，完成後續的載入與處理。

伺服器回應的內容通常包含多種檔案，例如 HTML、CSS、JavaScript 和 JSON 等檔案。

CSS 負責美化網頁，設定字體大小、顏色、排版等。

JavaScript 則處理互動功能，如偵測使用者操作或二次資料載入。

HTML 與 JSON 主要存放網站的內容資料，其中 HTML 通常還會直接內嵌部分 CSS 和 JavaScript 程式碼。

所謂的「網頁原始碼」，指的就是這些 HTML、CSS、JavaScript 檔案的組合。雖然它們通常以不同檔案存在，但也可以全部寫在同一個 HTML 檔案中。
#### 在瀏覽器上查看回應內容
在瀏覽器中可以查看請求方式與回應內容
用chrome打開威尼斯影城[https://www.venice-cinemas.com.tw/movie.php?state=&page=3]
在網頁中按下f12，或是點擊右鍵，選擇檢視，這樣就可以看到網頁背後的html檔以及其他資訊。
:::spoiler 檢查網頁原始碼畫面
![f12檢查](https://hackmd.io/_uploads/SyaFVU8Tkx.png)
:::
Element裡是網頁內容的html檔，當我們把滑鼠放在html裡一個標籤上，網頁上會有一個區塊變成淺藍，這代表該標籤內的內容顯示在淺藍色區塊內
:::spoiler 查看標籤顯示的內容
![查看標籤](https://hackmd.io/_uploads/Sk7_TIjyel.png)
:::
每個標籤最左側有個三角形，當內容太多時可以點擊以收放內容
在網頁的一段文字上點一右鍵，按下檢查可以快速跳到該段內容所屬的標籤
:::spoiler 查看特定內容所在的標籤
![檢查特定段落](https://hackmd.io/_uploads/By5TEPi1xe.png)
:::
Network裡是發出請求後，瀏覽器載入了甚麼檔案
:::spoiler 查看Network
![檢查network](https://hackmd.io/_uploads/B1k52uokee.png)
:::
| **欄位名**                        | **說明**|
|-------------------------------|-----------------|
| **Name**                   | 請求的檔名或路徑（可以點進去看詳細）
| **Status**         | HTTP 狀態碼（例如 200 OK、404 Not Found、500 Error）
| **Type** | 請求的資源類型（document, script, stylesheet, xhr（API呼叫）, image 等）|

發出請求時，伺服器會先回傳一個html檔，這個檔案被稱為網頁原始碼，這個檔案顯示在最上面，type會標示為document，依據這份html檔會再額外載入其他檔案
點擊檔案可以看到該檔案請求網址與請求方式
:::spoiler 查看檔案
![檔案請求方式與網址](https://hackmd.io/_uploads/r1fmbPnyex.png)
:::
除了看到請求方式，底下還有請求的header，header是伺服器與用戶確認彼此身分的依據
:::spoiler request headers
![header](https://hackmd.io/_uploads/rkwi1u3Jlg.png)
:::
### 靜態網頁爬蟲
#### 甚麼是靜態網頁
對一個網址發出請求時，會先得到一個html檔，此檔也被叫做網頁原始碼，如果我們在載入網頁原始碼時，就把所有資料都載入完，不額外使用javascript二次載入，我們會稱這個網頁為靜態網頁
#### 使用requests的get方法
請開啟terminal，並在裡面打上```pip install requests```
建立一個新的python檔案，並在開頭打上```import requests```
requests函式庫可以使用http的請求方式，例如：get、post
```response = requests.get(網址)```這是get方法的使用
如果我在網址裡打上威尼斯影城[https://www.venice-cinemas.com.tw/movie.php?state=&page=3]
我會取得威尼斯影城第一個載入的html檔
:::spoiler 第一個檔案
![第一個檔案](https://hackmd.io/_uploads/H1yV4w2Jgx.png)
:::
response是一個python物件，他有以下屬性
* response.status_code：http狀態代碼
* response.ok：一個布林值，顯示請求是否成功
* response.text：檔案內文字內容
* response.encoding：網頁編碼格式
以下這段程式是載入網頁原始碼後，重建一個新的html檔
```python=
import requests

url = "https://www.venice-cinemas.com.tw/movie.php?page=3"
response = requests.get(url)


with open("filename.html", "w", encoding = "utf-8") as f:
    f.write(response.text)
```
瀏覽器在使用get方法時，會傳給伺服器名稱為header的一段資料，這段資料可以在Network裡找到
:::spoiler header
![header](https://hackmd.io/_uploads/ry3_FDnkxg.png)
:::
在requests裡可以自行撰寫header，寫法為：
```r = requests.get(url, headers = 字典)```
有些伺服器會攔截爬蟲的訪問，所以我們必須偽裝自己（這不犯法），我們在get函式裡傳入header，通常網頁會檢查header裡User-Agent的值，因此我們去瀏覽器裡找到他，並複製到python程式碼的字典裡
如果傳入User-Agent後訪問失敗，可以考慮增加Authorization或Accept
```python=
import requests

url = "https://www.venice-cinemas.com.tw/movie.php?page=3"
header = {'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"}
response = requests.get(url, headers=header)
```
如果你想爬下來得檔案是json類型，你可以使用response.json()這個方法，他會回傳一個字典讓你可以更好得找到你想要的value
你可以使用[https://jsonformatter.curiousconcept.com/] 來更好觀察這個json檔，進到網頁後輸入你想觀察的json檔連結，便可以使用網站提供的縮放功能
:::spoiler json觀察網站
![json觀察網站](https://hackmd.io/_uploads/r1HfGtxWll.png)
:::
:::spoiler 觀察資料位置
![資料縮放](https://hackmd.io/_uploads/rka8fYlWel.png)
:::
以下程式碼會從網站上爬下一個json檔
```python=
import requests

url = "https://s.yimg.com/eh/prebid-config/finance-tw-desktop.json"

response = requests.get(url)

my_dict = response.json()
```
### 動態網頁爬蟲之requests library所能爬的類型
#### AJAX(Asynchronous JavaScript and XML) 
傳統爬蟲無法抓取 AJAX載入的資料。例如:例如，使用 ```requests.get(url)``` 可能只會返回初始 HTML，而不包含透過 AJAX 動態載入的資訊。
簡單來說，可以參考下圖一，**當我們使用```requests.get(url)```後，所得到的HTML只是第一階段的HTML。也就是還沒執行JavaScript的HTML，是沒有伺服器資料的(例如:購買人數、評價等)。我們在瀏覽器所看到的畫面，是由原始碼中的JavaScript，發送請求和接收請求後，才渲染出來的。**
![螢幕擷取畫面 2025-03-24 185548](https://hackmd.io/_uploads/HJfBqh0nJe.jpg)
*圖1*
#### AJAX請求種類
而在發送的請求種類中，又分為以下有API，及無API的。
| **類型**                        | **載入方式**                               | **回應類型** | **適合的爬蟲方式**               |
|-------------------------------|--------------------------------------------|--------------|-------------------------------|
| **API 方式**                   | `fetch("https://api.example.com/data")`    | JSON / XML   | `requests.get(url)` |
| **非 API：HTML 片段**         | `fetch("more-content.html")`               | HTML         | `requests.get(url)`                      |
| **非 API：JavaScript 產生內容** | `fetch("generate-content.js")`             | JavaScript   | Selenium                      |

##### 什麼是API?
>API（Application Programming Interface） 是一組用來定義不同軟體組件之間如何互動的規範。它可以被視為一個中介，使得不同的系統、應用程式或服務能夠進行互相通信和資料交換。
>
##### 為什麼需要API?
>API 使得開發者能夠利用現有的功能和服務，而無需重新開發所有東西。
>
##### 常見的 API 範例
* Google Maps API： 開發者可以將 Google 地圖嵌入到自己的網站或應用中，並利用 API 提供地圖導航、地點查詢等功能。
* Twitter API： 開發者可以使用 Twitter 的 API 來讀取推文、發送推文，甚至進行分析等操作。
##### 實際使用 API 範例
```fetch("https://api.weather.com/v3/weather/forecast?city=Taipei&days=7")```這行程式碼就是用JavaScript 向一個 API 網址發送請求，以取得資料。API會規範URL要怎麼寫，以上面的例子來說
* ```https://```：HTTP 協定
* ```api.weather.com```：天氣 API 的主機（主機名稱）
* ```/v3/weather/forecast```：API 的端點（Endpoint），表示你要的是「天氣預報」
* ```?city=Taipei&days=7```：查詢參數
* * ```city=Taipei```：代表查詢的城市是「台北」
* * ```days=7```：代表你想取得 7 天的預測

這些查詢參數（以 ? 開始，用 & 分隔）是 API 要求的規則之一，用來讓你客製化查詢內容。
你將這個 URL 作為請求發送出去，服務端就會按照 API 規範回應相應的資料（通常是 JSON 格式的天氣資料）。
#### 只用requsts library，可以爬取哪些AJAX請求?
對於回應類型不是JavaScript的類型，只要我們可以知道fetch的URL，我們就可以只用`requests.get(url)` 就可將其爬下來。
也就是說，對於回應是JSON/XML以及HTML的類型，我們可以簡單的透過用 **「開發者工具」(F12 → Network → XHR / Fetch)**，找到我們想要的資料是從哪個 AJAX 請求的 URL來的，然後用```requests.get(url)```，就可以訪問我們想要的資料。
然而，對於回應類型是JavaScript的類型，由於 requests這個library只能抓到 JavaScript 執行之前的原始 HTML 程式碼，不包含 JavaScript 渲染後的內容。
可以參考下面的例子
假設這是某個網站初始的HTML，不包含 JavaScript 渲染後的內容，也就是圖1中步驟2的所得到的HTML：
```html
<html>
  <body>
    <div id="content"></div>
    <script>
      fetch("more-content.html")  // 請求 HTML 片段
        .then(response => response.text())
        .then(data => {
          document.getElementById("content").innerHTML = data;
        });
    </script>
  </body>
</html>

```
用 ```requests.get(url)``` 抓到的 HTML，和上面的HTML是一模一樣的。
```html
<html>
  <body>
    <div id="content"></div>
    <script>
      fetch("more-content.html")  // 請求 HTML 片段
        .then(response => response.text())
        .then(data => {
          document.getElementById("content").innerHTML = data;
        });
    </script>
  </body>
</html>

```
實際上，瀏覽器載入 JavaScript 之後的網頁，可以發現```<div id="content"></div>```變成了
```< <div id="content">這是 AJAX 載入的內容</div>```
```html
<html>
  <body>
    <div id="content">這是 AJAX 載入的內容</div>
    <script>
      fetch("more-content.html")  // 請求 HTML 片段
        .then(response => response.text())
        .then(data => {
          document.getElementById("content").innerHTML = data;
        });
    </script>
  </body>
</html>

```
對於這種需要 JavaScript 發送請求、接收回應，再根據回應動態更新畫面的網站，如果我們直接用 ```requests.get(url)```，只會拿到初始的 HTML，不包含 JavaScript 渲染後的內容。
如果要取得 JavaScript 執行完、畫面已經更新後的 HTML，我們就需要用 Selenium。
Selenium 可以讓我們等待網頁渲染完成後，再把內容抓下來。
### 爬蟲禮儀
使用爬蟲可以自動的在網路上獲取許多數據，但並非所有數據都是可以被爬取，通常涉及個資的檔案不能被扒走，如果沒有學過駭客技術基本也爬不下來。也有些數據不算個資，但伺服器製作者不希望別人扒走，因此製作者設立了一個頁面規範爬蟲者。
#### 網頁規範
有些網頁會額外製作一個頁面，該頁面會寫上不能爬的分頁，或是不被允許的爬蟲程式，違反它可能會有洩漏私密檔案的嫌疑。
查詢方法為在網域後打上robots.txt，例如：```https://網域/robots.txt```
:::spoiler 爬蟲協議
![robots_txt檔案](https://hackmd.io/_uploads/BJQ4HTEele.png)
:::
該頁面沒有法律效力，遵不遵守看你有沒有素質
#### 網頁訪問頻率
伺服器同時能處理的客戶量有限，假如你的爬蟲程式訪問的過快，可能會對伺服器造成影響，該情況會導致伺服器封鎖你的ip，導致你的電腦不再能請求到資料
頻率高的訪問可能會被視為一種攻擊，經營該伺服器的公司可以依法進行提告，使用requests時，如果有複數個get或post，在兩者之間加上暫停函式
```python=
import requests
import time # 程式暫停模組

url = "https://www.venice-cinemas.com.tw/showtime.php?movie_date=&page=4"
r1 = requests.get(url) # 第一次爬取
# 請程式執行暫停5秒再繼續
time.sleep(5)
r2 = requests.get(url) # 第二次爬取
```
### 補充
ddos網路介紹影片：[https://youtu.be/7kB9-nQJR44?si=6a_l5r_WKB-Rl0EV]
http與https加密方式介紹影片：[https://youtu.be/vVbLSba6vOI?si=f06nQCfBlpNruCBe]
#### DDOS

**DDoS（Distributed Denial of Service，分散式阻斷服務攻擊）** 是一種惡意的網路攻擊，攻擊者透過大量裝置（例如殭屍網路）同時向目標伺服器發送大量請求，導致目標服務資源耗盡，進而造成網站無法正常運作或完全癱瘓。

#### 🧠 攻擊原理

攻擊者會控制大量裝置（如被駭的電腦、IoT 設備等），透過這些裝置：
- 發送海量的請求到目標伺服器
- 讓伺服器 CPU、記憶體、頻寬等資源被耗盡
- 導致正常使用者無法連線或服務異常

#### 🔥 常見的 DDoS 攻擊類型

| 類型        | 說明 |
|-------------|------|
| **HTTP Flood** | 模仿正常使用者發送大量 HTTP 請求 |
| **SYN Flood**  | 濫用 TCP 的三次握手造成資源耗盡 |
| **UDP Flood**  | 發送大量 UDP 封包干擾網路服務 |
| **DNS 放大攻擊** | 利用 DNS 伺服器將小請求轉換為大型回應攻擊目標 |

#### 🛡️ 防範方式

- 使用 CDN（如 Cloudflare）分散流量
- 設定 IP 限速與黑名單
- 使用防火牆與 DDoS 保護服務
- 監控異常流量行為，及早攔截攻擊


---
## 第一天下午
### 如何看懂 HTML?
想學好「網頁爬蟲」，會看 HTML 是非常重要的基本功，因為爬蟲的核心就是：
從 HTML 裡面找出你想要的資料
#### 為什麼要會看 HTML？
爬蟲的流程通常是這樣：
1. 進入一個網頁（用 requests 抓 HTML）
2. 用 BeautifulSoup 或 selenium 解析 HTML
3. 找到你要的資料（像是標題、時間、連結等）
3. 把這些資料抽出來存成 csv、json、Excel

那當我們要「找資料在哪裡」，就要懂 HTML 的結構和標籤意思。

####  要會看哪些 HTML 元素？
在爬蟲時我們不必學會所有的 HTML 標籤，但仍有幾個重要的標籤要去認識:


| HTML 標籤  |   意思     |        作用               |
| --------- | -------- | -------------------------- |
| div       | 區塊     | 用來分區，常見的容器標籤        |
| a         | 超連結   | 通常用在文章標題上             |
| span      | 文字容器 | 小段的文字或數字               |
| img       | 圖片     | 圖片用                      |
| class/id  | 屬性     | 幫助我們鎖定元素用             |
|   ul/li   | 列表     | 清單式的資料格式（像商品、留言） |


### Beautifulsoup + find🔍系列函式
BeautifulSoup 是 Python 爬蟲的核心工具之一，它可以**解析 HTML**並提取所需的數據。
在我們使用 BeautifulSoup 之前我們需要先安裝才能使用

#### 安裝 BeautifulSoup
``` 
pip install beautifulsoup4
```
>在 "pip install beautifulsoup4" 最後的數字4代表版本
BeautifulSoup 版本歷史
BeautifulSoup 3（BS3）：較舊的版本且已經停止維護，與 Python 3 不相容。
BeautifulSoup 4（BS4）：目前的標準版本並支援 Python 2 & 3，功能更強大。
由於 BS3 已經淘汰，現在一般只用 BS4，所以 pip install beautifulsoup4 這個名稱仍然保留了 4 來區分新舊版本。

#### 取得 HTML 內容
運用上午所學抓取 HTML 內容
```python=
response = requests.get(url)
```

#### 開始使用 BeautifulSoup
你們已經學會如何用 request 來取得網頁的 HTML 內容但這樣還遠遠不夠，面對那一大坨的程式碼看了就讓人頭痛，你不會想要自己從裡面一個個找出自己想要的內容，電腦也不例外所以會用**BeautifulSoup**把HTML內容放入一個類似**容器**的物件（BeautifulSoup 物件），然後可以用 **.find()、.find_all()** 等方法，在這個容器內搜尋我們需要的內容。

#### 建立 BeautifulSoup 物件
```python=
#respone是前面用request取得的HTML內容
soup = BeautifulSoup(response.text, "html.parser")
```
```html.parser``` 是 Python 內建的 HTML 解析器，它的功能就像是**消化 HTML 內容並轉換成可搜尋的結構**。這樣我們就將網頁的 HTML 內容消化後放入BeautifulSoup物件了!

以下是範例程式碼:
```python=
import requests
from bs4 import BeautifulSoup

url = "想要爬取網站的網址"

response = requests.get(url)

#用 html 解析器解析 response.text 產生的 html
soup = BeautifulSoup(response.text, "html.parser")
print(response.text)

#查看網頁狀態 如果找到網頁則寫出一個html檔案
if response.ok:
        with open('output.html', 'w', encoding = 'utf-8') as f:
                f.write(response.text)
        print("成功找到網頁")
else:
        print("找不到網頁")
```
執行後可以查看用程式碼寫出的 HTML 檔案

##### 可以嘗試的網站
1.[威尼斯影城-電影介紹](https://www.venice-cinemas.com.tw/movie.php?page=1)
2.[PTT NBA板](https://www.ptt.cc/bbs/nba/index.html)
3.[聯合新聞網](https://udn.com/news/breaknews/1)
4.[中央資工-成員介紹](https://www.csie.ncu.edu.tw/department/member)
5.[看漫畫繁體版-排行榜](https://tw.manhuagui.com/rank/)
6.[《寶可夢 朱/紫》全圖鑑400隻寶可夢一覽：棲息地、進化](https://pokemonhubs.com/pokemongo/22999/)
7.[巴黎奧運2024：獎牌榜上哪個國家或地區領先？](https://www.bbc.com/zhongwen/trad/world-69210979)
8.[【桃園美食】10間中壢美食名單，中壢小吃餐廳聚餐推薦](https://mecotravel.tw/zhongli-food/)
9.[台灣種菜時間表：一年四季種植指南](https://c.urcook.com/11/)
10.[KKBOX-華語新歌週榜TOP50](https://www.kkbox.com/tw/tc/playlist/LaVpNXzqVThOEXr0LJ)
11.[桃園｜中壢在地小吃全攻略：盤點39間老字號美食、銅板小吃、排隊名店，中壢人吃起來！](https://travel.yam.com/article/134292)




#### 使用find系列函式

##### .find() 是什麼？
```.find()``` 是用來「找到**第一個**符合條件的標籤」的函式。它只找第一個符合的標籤（不是全部）找到後會回傳一個 Tag 物件如果沒找到，會回傳 None

他的語法如下:
```python=
soup.find("標籤名稱", 屬性條件)
```

如果有個網站部分架構長這樣
```html=
<div class="title">
  <a href="/NCU/CSIE/M.1234567890.A.html">你學會爬蟲了嗎？</a>
</div>
```
當我們想要爬取標題時就可以運用find函式，以下是範例:
```python=
article = soup.find("div", class_="title")
print(article.a.text)

#輸出會是:
#你學會爬蟲了嗎？
```
搭配我們上面所學的程式碼會長這樣:
```python=
import requests
from bs4 import BeautifulSoup

url = "想要爬取網站的網址"

response = requests.get(url)

#用 html 解析器解析 response.text 產生的 html
soup = BeautifulSoup(response.text, "html.parser")

#使用find函式
#根據自己的需求填入標籤及屬性條件
articles = soup.find("標籤名稱", 屬性條件)
print(articles.text)

#查看網頁狀態 如果找到網頁則寫出一個html檔案
if response.ok:
        with open('output.html', 'w', encoding = 'utf-8') as f:
                f.write(response.text)
        print("成功找到網頁")
else:
        print("找不到網頁")
```

##### .find_all() 是什麼？
剛剛我們學會如何使用find()函式找出我們想要的標籤內容，但我們通常都用爬蟲爬取非常大量的資料，只靠find()函式根本不給力，所以我們需要用```.find_all()```函式，他可以用來找出所有符合條件的 HTML 標籤，它會回傳一個**列表（list）** 的形式，每個元素都是一個 Tag 物件。

他的語法跟```.find()```幾乎一樣:
```python=
soup.find_all("標籤名稱", 屬性條件)
```
如果有個網站部分架構長這樣
```html=
<div class="title">
  <a href="/NCU/CSIE/M.00000001.A.html">你學會爬蟲了嗎？</a>
</div>

<div class="title">
  <a href="/NCU/CSIE/M.00000002.A.html">怎麼使用find函式?</a>
</div>

<div class="title">
  <a href="/NCU/CSIE/M.00000003.A.html">find_all()？能吃嗎?</a>
</div>
```
當我們想要爬取所有標題時就可以運用find_all函式，以下是範例:
```python=
articles = soup.find_all("div", class_="title")
#用迴圈遍歷列表
for article in articles:
    print(article.a.text)

#輸出會是:
#你學會爬蟲了嗎？
#怎麼使用find函式?
#find_all()？能吃嗎?
```
在這個範例中我們使用**for迴圈**遍歷裝著所有標題物件的列表

再搭配我們上面所學的程式碼可以擴充成這樣:
```python=
import requests
from bs4 import BeautifulSoup

url = "想要爬取網站的網址"

response = requests.get(url)

#用 html 解析器解析 response.text 產生的 html
soup = BeautifulSoup(response.text, "html.parser")

#使用find函式
#根據自己的需求填入標籤及屬性條件
articles = soup.find_all("標籤名稱", 屬性條件)
#用迴圈遍歷列表
for article in articles:
    print(article.a.text)
#查看網頁狀態 如果找到網頁則寫出一個html檔案
if response.ok:
        with open('output.html', 'w', encoding = 'utf-8') as f:
                f.write(response.text)
        print("成功找到網頁")
else:
        print("找不到網頁")
```

當然我們可能遇到```find()```找不到東西的情況，所以最好加上判斷避免報錯:
```python=
articles = soup.find_all("標籤名稱", 屬性條件)
#用迴圈遍歷列表
for article in articles:
    title = article.find("標籤名稱", 屬性條件)
    #如果title沒有東西會回傳false
    if title and title.a:
        title = title.a.text
    else:
        title = "沒有標題"
    print(title)
```
判斷式的寫法需要注意的事情是我們不一定能直接在標籤物件找到我們要的東西，所以我們要檢查爬取的東西是否有再包一層標籤，假設網頁架構如下:
```html=
<div class="title">
  <a href="/NCU/CSIE/M.00000001.A.html">你學會爬蟲了嗎？</a>
</div>
```
此時```<a>```標籤內才是我們真的想要的內容我們需寫成:
```python=
title = article.find("div", class_="title")
    #如果title或<a>標籤內沒有東西會回傳false
    if title and title.a:
        title = title.a.text
    else:
        title = "沒有標題"
    print(title)
```

接著我們可以找出不同的東西，不僅限於標題我們可以用上面的方法找出像是發表日期或是人氣之類的東西。接著我們可以更完整我們的程式碼:
```python=
import requests
from bs4 import BeautifulSoup

url = "想要爬取網站的網址"

response = requests.get(url)

#用 html 解析器解析 response.text 產生的 html
soup = BeautifulSoup(response.text, "html.parser")

#使用find函式
#根據自己的需求填入標籤及屬性條件
articles = soup.find_all("標籤名稱", 屬性條件)
#用迴圈遍歷列表
for article in articles:
        #根據需求決定要哪些標籤內容
        "標籤物件1" = article.find("標籤名稱", 屬性條件)
        if "標籤物件1" and "標籤物件1".text:
                "物件1" = "標籤物件1".text
        else:
                "物件1" = "沒有標題"
        print("物件1 ")
        #原理同上
        "標籤物件2" = article.find("標籤名稱", 屬性條件)
        if "標籤物件2" and "標籤物件2".text:
                "物件2" = "標籤物件2".text
        else:
                "物件2" = "none"
        print("物件2 ")

        "標籤物件3" = article.find("標籤名稱", 屬性條件)
        if "標籤物件3" and "標籤物件3".text :
                "物件3" = "標籤物件3".text
        else :
                "物件3" = "未知"
        print("物件3 ")
        
#查看網頁狀態 如果找到網頁則寫出一個html檔案
if response.ok:
        with open('output.html', 'w', encoding = 'utf-8') as f:
                f.write(response.text)
        print("成功找到網頁")
else:
        print("找不到網頁")
```





### 使用 Pandas 整理爬取的資料
#### 為什麼要用 Pandas？
Pandas 是 Python 的資料處理庫，適合用來整理、分析與儲存爬取的資料，以下是他的優點：
- 表格化管理：像 Excel 一樣，能夠方便地排序、篩選、轉換資料。
- 易於儲存：能夠儲存為 CSV、Excel 或 JSON 格式，方便我們分析。
- 支援 DataFrame 操作：可以進行資料清理與分析。
#### 使用Pandas
在我們使用 Pandas 之前我們需要先安裝
``` 
pip install pandas
```
安裝完之後還需要匯入 Pandas 才能開始使用喔!
```python=
import pandas as pd  # 匯入Pandas
```
>pd只是個簡稱，方便我們使用時可以不用每次都要打上pandas，只要打pd就好了。上方的敘述可以翻譯為「匯入Pandas 並宣告在後續都以pd來表示」

#### Pandas的基本概念
- Series : 一維陣列，類似於 Python 中的列表
- DataFrame : 二維的表格結構，可以想象成一個 Excel 表格

首先我們先創建一個**列表**讓我們可以裝我們剛剛取出的東西
```python=
data = [] #創建列表
```
再來用 ```.append()``` 把我們取出的資料放進列表
```python=
# 將資料存入列表
data.append({
    "標題1": 物件1,    
    "標題2": 物件2,
    "標題3": 物件3
})
```
>這一段要放在迴圈裡面，這樣子我們才能把每個都將資料放進去

#### 使用 Pandas 的 DataFrame
接著將他們用 DataFrame 包裝起來
```python=
# 轉換為 Pandas DataFrame
df = pd.DataFrame(data)
```
用 ```print(df)``` 印出來的內容會像這樣 : 
```    
    標題1   標題2   標題3
0   物件1   物件2   物件3
1   物件1   物件2   物件3
2   物件1   物件2   物件3
```
這樣我們就成功將資料用二維表格的方式存起來了!

### 將爬取的資料存成 Excel

#### 使用 Pandas 的轉 Excel 檔工具
我們可以透過 ```pandas.DataFrame.to_excel("檔名", 索引, encoding="編碼方式")``` 把爬取的文章資訊存成 Excel。
以下是範例程式碼 : 
```python=
df.to_excel("example.xlsx", index=False, encoding="utf-8")
```
>index=False：不存入 DataFrame 的索引。Pandas 的索引是為了方便資料查詢與操作，但在 Excel 中，通常每列應該對應一個完整的資料。而且索引在 Excel 裡沒有什麼意義，所以我們通常不會存進去。
encoding="utf-8"：讓中文不會出現亂碼。

#### 讀取 Excel 檔
在剛剛我們成功把資料轉成 Excel 檔了，那我們要如何確認真的轉成功了呢?有的人會想說直接打開 Excel 檔去看，但那樣太沒效率了! 能讓程式幫你完成的就不要自己動手(超懶w)
我們可以使用 ```pd.read_excel("檔名")``` 來讀取剛才轉的 Excel 檔
```python=
df = pd.read_excel("example.xlsx")
print(df)
```












---
## 第二天上午

### What is Plotly
[Plotly Python Graphing Library](https://plotly.com/python/)
> Plotly Python 是一個互動式的開源繪圖 library ，支持超過 40 種不同類型的圖表，涵蓋統計、金融、地理、科學和三維等多種應用場景。
Plotly 基於 Plotly JavaScript 庫（plotly.js）構建，使 Python 用戶能夠創建精美的交互式 Web 視覺化圖表，這些圖表可以在 Jupyter 筆記本中顯示、保存為獨立的 HTML 文件，或通過 Dash 作為純 Python 構建的 Web 應用提供服務。為了與 JavaScript 版的 Plotly 區分，Python 版通常被稱為 "plotly.py"。
得益於與 Kaleido 圖像導出工具的深度整合，Plotly 也能很好地支持非 Web 環境，包括桌面編輯器（如 QtConsole、Spyder、PyCharm）和靜態文檔發布（如高質量向量圖導出 PDF ）。
### 安裝 Dash、Plotly
安裝 Plotly
開啟終端機，輸入下列指令
``` 
pip install plotly
```
安裝 Dash
```
pip install dash
```
引入函式庫
```python=
import plotly.express as px
```
>Plotly Express 是 Plotly 提供的高階介面，它的設計理念就是 「簡單易用」，你可以把 Plotly Express 想像成 Plotly 的「懶人包」版本，它幫我們把大部分的參數都設定好了，我們只要把資料餵進去，就能快速畫出漂亮又互動的圖。
雖然 Plotly 還有更進階、可自訂程度更高的圖表建立方式（稱為 graph objects），但我們會先從 Plotly Express 開始，打好互動式視覺化的基礎。
P.S. Plotly Express通常在程式裡縮寫成px
### 繪製基本圖表
```python=
fig = px.圖表類型(資料來源, x='X軸欄位', y='Y軸欄位', 其他設定...)
fig.show()
```
這裡會使用到 Dataframe 作為繪圖資料來源(因此要記得最上面也要`import pandas as pd`)。除了使用我們自己整理好的資料，Plotly也有內建的資料集(例如iris、carshare)可作為繪製圖表練習使用，以下將使用iris作為示範
[plotly內建資料集補充](https://hackmd.io/btfl5odkTv-bxlD0jAtNqw)

#### 折線圖 px.line
```python=
data = {
    'X': [1, 2, 3, 4, 5],
    'Y': [10, 15, 7, 12, 18]
}
df = pd.DataFrame(data)
fig = px.line(df, x='X', y='Y', title='折線圖')
fig.show()
```
![image](https://hackmd.io/_uploads/BJa9Fc62Jx.png)
```python=
data = {
    "X": [1, 2, 3, 4, 5, 1, 2, 3, 4, 5],
    "Y": [10, 15, 7, 12, 18, 5, 7, 18, 15, 9],
    "type": ["a", "a", "a", "a", "a", "b", "b", "b", "b", "b"],
}
df = pd.DataFrame(data)
fig = px.line(
    df, x="X", y="Y", title="兩條折線圖",
    color="type", line_dash="type", symbol="type",
    markers=True,
    labels={"X": "X軸", "Y": "Y軸", "type": "類型"},
)
fig.show()
```
- data中有X軸數據、Y軸數據、這些數據分別屬於哪一類(假設為a、b)
- px.line()表示現在畫的是折線圖
- title設定圖表標題
- color、line_dash、symbol分別代表兩線的顏色、線條樣式、標記符號會依照什麼來區分(這裡用type)
- markers=True會有標記點，=False則沒有(如上圖)
- labels={}裡面可以設定各個標籤的名稱，格式為"要改變的標籤": "名稱"

:::spoiler 程式結果
![image](https://hackmd.io/_uploads/ry9ITu8pye.png)
:::
:::info
💡有發現嗎？圖表右上角有功能列，由左到右分別是存成png、框選區域放大、移動、方框選取、套索選取、放大、縮小、自動縮放、重設座標軸。另外，點擊圖例也可以單獨看該圖例的圖哦！
:::
##### Pandas的groupby與聚合函數
`DataFrame.groupby("分組欄位", as_index=False)["目標欄位"].聚合函數()`
#### 長條圖 px.bar
```python=
iris = px.data.iris()
avg_petal_length = iris.groupby("species", as_index=False)["petal_length"].mean()
fig = px.bar(avg_petal_length, x="species", y="petal_length", barmode="group")
fig.show()
```
![image](https://hackmd.io/_uploads/rJVUJLUJgx.png)

- .groupby("species", as_index=False)將資料依據species來分組，並且讓species成為一般欄位而非索引值(index)
- \["petal_length"\]指要計算的欄位
- .mean()計算每組的平均值
>因此此時的資料avg\_petal\_length就被設定為有品種和花瓣平均長度兩欄、一共三列的資料
- barmode是長條圖獨有的參數設定，可以是"group"(並排)、"stack"(堆疊)、"overlay"(半透明重疊)

我們可以再加入一組平均值的資料來更清楚的理解差別，不過在此之前，要先解釋資料的寬表格與長表格的差別。
##### 寬表格vs長表格
通常我們習慣看的、覺得清楚易懂的表格形式都是寬表格(wide format)，每個觀察值同時有行和列要對照，也就是一列裡有多個觀察值，上圖的avg\_petal\_length對plotly而言就是寬表格；然而適合做數據分析、繪製圖表的形式是長表格(long format)，它的變數名稱與值被分成兩個欄位，因此一列裡只有一個觀察值，前面兩條折線圖所使用的a、b類資料整合的呈現方式就是長表格。
我們可以用`.melt()`將寬表格轉為長表格：
首先，假設我們要繪製的資料改為同時有petal_length與sepal_length
```python=
avg = iris.groupby("species", as_index=False)[["sepal_length", "petal_length"]].mean()
```
然後使用`.melt()`
```python=
avg_long = avg.melt(id_vars="species", var_name="measurement", value_name="value")
```
:::spoiler 用表格解釋
(數值僅供參考)
也就是從這樣
| species | sepal_length | petal_length |
| --- | --- | --- |
| setosa | 5.01 | 1.46 |
| versicolor | 5.94 | 4.26 |
| virginica | 6.59 | 5.55 |

變成這樣
| species | measurement | value |
| --- | --- | --- |
| setosa | sepal_length | 5.01 |
| setosa | petal_length | 1.46 |
| versicolor | sepal_length | 5.94 |
| versicolor | petal_length | 4.26 |
| ... | ... | ... |
:::
現在，我們把剛剛的長條圖Y設為"value"，color設為"measurement"，就可以看到兩筆資料的長條圖了！記得可以自己試試看`barmode="group"`、`"stack"`、`"overlay"`的差別！
:::spoiler 理論上會長這樣
![image](https://hackmd.io/_uploads/BkPZH4DTJg.png)
:::
#### 圓餅圖 px.pie
```python=
data = {
    "product": ["phone", "laptop", "tablet", "phone", "laptop", "tablet"],
    "sales": [150, 120, 80, 180, 140, 90],
}
df = pd.DataFrame(data)
fig = px.pie(
    df,
    names="product",
    values="sales",
    title="Product Distribution",
    hole=0.3,
    color_discrete_sequence=px.colors.sequential.Redor,
)
fig.show()
```
![image](https://hackmd.io/_uploads/rk_YUyDJxl.png)

- names='product' 表示用product中資料的名稱來分區
- values='sales' 則看sales有幾個資料就幾區，每一區按照數值比例分配
- hole可以調整圓餅圖圓心空白大小
##### 顏色設定
除了預設的plotly(藍、紅、綠......)及上面的Redor，plotly還有許多內建的顏色組可以使用，圖表生成時會依照該顏色組的順序自動上色

連續的顏色
`color_discrete_sequence=px.colors.sequential.顏色組名字`
:::spoiler 連續顏色組
![image](https://hackmd.io/_uploads/B19cacUp1l.png)
:::
不連續的顏色
`color_discrete_sequence=px.colors.qualitative.顏色組名字`
:::spoiler 不連續顏色組
![image](https://hackmd.io/_uploads/SJ3i65U6yl.png)
:::
#### 盒狀圖 px.box
```py=
df = pd.DataFrame({
    "Category": ["A"] * 10 + ["B"] * 10,
    "Value": [7, 8, 6, 5, 9, 10, 6, 5, 7, 8, 12, 13, 11, 10, 13, 14, 13, 12, 10, 11]
})
fig = px.box(df, x="Category", y="Value", points="all", title="Box Plot by Category")
fig.show()
```
![image](https://hackmd.io/_uploads/H1nExJ0kle.png)
- points="all"表示會顯示每個點的散佈，可加可不加
#### 散佈圖 px.scatter
```py=
iris = px.data.iris()
setosa = iris[iris["species"] == "setosa"]
fig = px.scatter(setosa, x="sepal_length", y="petal_length",
                 title="Setosa 花萼長度 vs. 花瓣長度",
                 trendline="ols")
fig.show()
```
![image](https://hackmd.io/_uploads/S1BMuVRkeg.png)

- size可設定每個點的大小要取決於什麼，這裡用petal_length來決定
- trendline可增加回歸線，"ols"表線性回歸(註：計算回歸線會用到statsmodels套件，因此要記得先到終端機`pip install statsmodels`)
#### 多維散佈圖 px.scatter_3d
```py=
iris = px.data.iris()
fig = px.scatter_3d(iris, x="sepal_length", y="sepal_width", z="petal_width", color="petal_length", symbol="species", opacity=0.7)
# 移動圖例到底下置中且水平排列
fig.update_layout(
    legend=dict(x=0.5, y=-0.1, xanchor="center", orientation="h")
)
fig.show()
```
![image](https://hackmd.io/_uploads/BJSeSrvp1e.png)
- opacity可調整透明度(0~1)

你可能想說它明明就寫著3D，為什麼叫多維不是三維？因為我們可以用連續性的顏色變化來呈現第四維，甚至用點的大小來呈現第五維！

這裡如果沒有註解後那一段，我們會發現圖例和顏色尺的預設位置重疊了，然而如果要手動調整版面配置或更多樣式，plotly express只能在建立圖表後再更新，以下會進一步說明
### 調整版面與樣式
#### fig.update_layout()
用來調整整個圖表的版面配置、標題、軸線、圖例、互動功能等
`legend`: 圖例，可以用dict()函數：dict (key1 = value1, key2 = value2) 的格式設定
> `x =`(由左到右相對0~1), `y =`(由下到上相對0~1，可負)
`xanchor =`"定位基準點" (value參考：`left`, `center`, `right`)
`yanchor =`"定位基準點" (value參考：`top`, `middle`, `bottom`)
`orientation =`"排列方向" (value參考：`h`水平, `v`垂直)

`width =`, ` height =`: 圖表長寬
`font`: 文字，用dict()可設定下列參數：
> `family =`"字型名稱"
> `size =`字體大小
> `color =`"顏色" (可以是色彩名稱表示法、HEX、rgba()等)

`hovermode`: 懸浮提示 (value參考：`closet`, `x`, `y`, `x unified`, `y unified`)

#### fig.update_traces()
用來調整圖表裡每一條trace(資料線、點、柱狀、等等)的樣式
`marker`: 資料點，適用於散點圖與折線圖等，以dict()可設定下列參數：
>`size =`點的像素大小
>`color =`"顏色"
>`symbol =`"點的樣式" (value參考：`circle`, `square`, `diamond`, `x`)

`line`: 線條，適用於折線圖，以dict()可設定下列參數：
>`color =`"顏色"
>`width =`線條寬度
>`dash =`"線條樣式" (value參考：`solid`, `dash`, `dot`, `dashdot`)

**範例架構**
```py=
df = pd.DataFrame(data)

fig = px.圖表類型(df, x='X軸欄位', y='Y軸欄位', 其他設定...)

fig.update_layout(
    legend = dict(x=0.5, y=-0.1, xanchor="center", orientation="h"),
    font = dict(family="Consolas", size=20, color="rgb(102, 51, 0)"),
    hovermode = "x",
    width = 800,
    height = 600
)

fig.update_traces(
    marker=dict(size=10, color="#ff0066", symbol="diamond")
)

fig.show()
```

[更多 traces & layout 相關參數](https://plotly.com/python-api-reference/plotly.graph_objects.html)
### 繪製多重圖表
#### 邊際分布圖 marginal plot
marginal plot顧名思義是在原本的圖表旁根據X軸或Y軸的分佈以其他圖呈現，通常用在散佈圖
```py=
iris = px.data.iris()
fig = px.scatter(iris, x="sepal_length", y="sepal_width", 
                 marginal_x="histogram", marginal_y="box")
fig.show()
```
![image](https://hackmd.io/_uploads/rJgBlbLwTke.png)
- marginal_x(y)="要加的圖表名稱"，分別會在上面、右邊加上子圖
- histogram是直方圖，一樣可以如前面所介紹的圖表以`px.histogram()`單獨繪製
#### 分面圖 facet plot
facet plot可以依照分類畫出許多張類似的子圖自動並排，方便比較不同類別的差異
```py=
iris = px.data.iris()
fig = px.scatter(
    iris, x="sepal_length", y="sepal_width",
    color="species", title="Species分面圖",
    facet_col="species", category_orders={"species": ["setosa", "virginica", "versicolor"]},
)
fig.show()
```
![image](https://hackmd.io/_uploads/B1nikIvpyx.png)
- facet_col="species"代表子圖依照品種分成不同欄的圖表(橫向)，若為facet_row則子圖間為直向排列
- category_orders={"分類依據":["類A", "類B"......]}可用來自訂排列順序，除了用在子圖排列也可以用在圖例排列
#### 多重子圖 subplot
利用make_subplots()完全自訂子圖排版，且可放不同類型的子圖
```python=
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots

df = px.data.iris()

# 建立子圖佈局
fig = make_subplots(
    rows=1, cols=2, # 建立 一列 兩欄 的佈局
    subplot_titles=("Species percentage", "Petal Length vs Width"), # 分別為1-1 1-2子圖的標題
    
    # 指定第1欄為domain類型(用於pie)，第2欄為Cartesian坐標系(用於scatter)
    specs=[[{"type": "domain"}, {"type": "xy"}]]
)

# 生成圖表
fig1 = px.pie(df, names="species", color="species")
fig2 = px.scatter(df, x="petal_length", y="petal_width", color="species")

# 將 traces 加入子圖
for trace in fig1.data:
    fig.add_trace(trace, row=1, col=1)

for trace in fig2.data:
    fig.add_trace(trace, row=1, col=2)

fig.update_layout(title_text="Iris 數據集 - 多重子圖")

fig.show()
```
![image](https://hackmd.io/_uploads/By9qWDeR1l.png)
- make_subplots()的specs預設是Cartesian坐標系的類型，用於有XY軸的圖表。其他像是圓餅圖屬於domain類型，雷達圖屬於polar類型，3D圖表則為scene類型
-  specs=[[{"type": "domain"}, {"type": "xy"}]]的外層[]是list of list的列，內層[]是欄
- 利用for迴圈可將已生成的圖表中的trace(一條線、一個圓餅、一組柱狀、一個物種的點等)加入到對應欄位的子圖中
### 補充：Plotly Graph Objects 基本架構
1. 建立圖表容器`go.Figure()`
2. 將圖形資料加入容器`fig.add_trace(go.圖表名稱(x=[X軸資料], y=[y軸資料], ......))`
3. 自訂外觀`fig.update_layout()`
4. 顯示圖表`fig.show()`
- 圖表名稱首字母大寫
- 不同於Express會引入dataframe(x、y軸只要寫"df的欄位名稱")，GO會直接把data的list放進x=、y=裡，或是要寫x=df["資料欄位名"])
---
## 第二天下午
### What is Dash
Dash 是由 Plotly 開發的開源 Python 框架，主要用於建構互動式資料視覺化網頁應用
Dash 結合了三大元素：
(1) Flask － 提供後端 Web 伺服器架構
(2) React.js － 建構前端互動式介面
(3) Plotly.js － 強大的資料視覺化庫
這些技術的整合，**使 Dash 成為全 Python 代碼即可操作的框架**，無需額外學習 HTML、CSS 或 JavaScript
**Dash的應用效果:**

-  **互動式資料視覺化:** 根據使用者的操作（如滑動條、下拉選單、按鈕等）即時變化
-  **資料篩選與動態呈現:** 透過互動式元件讓使用者選擇篩選條件
[例子](https://dash.gallery/dash-uber-rides-demo/?_gl=1*1drkun9*_gcl_au*Njc4NzE3MzIwLjE3NDE1Mjc2MDg.*_ga*NjAwNTQwODAzLjE3NDE1Mjc2MDg.*_ga_6G7EE0JNSC*MTc0Mjc0OTIwMi45LjEuMTc0Mjc0OTM3Ny41Mi4wLjA.)
[官方網站](https://plotly.com/examples/)

---
### Dash運作模式
:::spoiler 心智圖
![Dash組成 (4)](https://hackmd.io/_uploads/BJrn8Rpyxg.png)

::: 
---

### Dash程式結構
#### 1.Import packages(匯入套件)
匯入所需的函式庫和模組

```
from dash import Dash, html, dash_table, dcc, callback, Output, Input
import pandas as pd
import plotly.express as px
```
**背景知識:**
:::spoiler Import的三種方式
**(1) 引入整個模組**
直接引入整個模組，然後使用模組名稱來存取其中的內容

```
import 模組名稱
```
範例:
```
import dash
```
使用時，你需要寫 dash並加上要使用的函數名稱：
```
app = dash.Dash(__name__)
```
**(2) 引入模組中的某個具體函數**
當只需要使用模組中的某個特定函數，可以只引入這一部分
```
from 模組名稱 import 函數名稱
```
例如:
```
from dash import Dash, html, dash_table, dcc, callback, Output, Input
```
使用時，直接呼叫函數，而不用加上dash.
```
app = Dash()
```
**(3) 給引入的模組起別名**
當需要引入整個模組，並希望使用別名來簡化程式
```
import 模組名稱 as 別名
```
例如:
```
import pandas as pd
```
可以使用pd來取代pandas
```
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
```
:::

#### 2.Incorporate data(導入資料)
使用Pandas將資料匯入

```
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
```
#### 3.Initialize the app(初始化應用程式)
用來創建並初始化一個新的Dash的空框架，讓我們可以在這個框架內添加各種元素，擁有更多功能
```
app = Dash()
```
#### 4.App layout(應用程式佈局)
Layout設計就是決定「畫面上有哪些東西」和「它們怎麼排列」
```
app.layout = [
    html.Div(children='My First App with Data, Graph, and Controls'),
    html.Hr(),
    dcc.RadioItems(options=['pop', 'lifeExp', 'gdpPercap'], value='lifeExp', id='my-final-radio-item-example'),
    dash_table.DataTable(data=df.to_dict('records'), page_size=6),
    dcc.Graph(figure={}, id='my-final-graph-example')
]
```
#### 5.Callback(回呼函式)
@app.callback設定「如果發生某件事，就要做某個動作」的規則，讓你的應用可以自動更新畫面
```
@callback(
    Output(component_id='my-final-graph-example', component_property='figure'),
    Input(component_id='my-final-radio-item-example', component_property='value')
)
def update_graph(col_chosen):
    fig = px.histogram(df, x='continent', y=col_chosen, histfunc='avg')
    return fig
```
#### 6.Run the app(執行應用程式)
啟動Dash的Web伺服器
```
if __name__ == '__main__':
    app.run(debug=True)
```
---
### 放入Plotly圖表
#### 使用dcc.graph
dcc.Graph 可用來呈現任何由 Plotly 驅動的資料視覺化圖表，
只要透過 figure 參數傳入圖表
#### 使用方式
```
dcc.Graph(figure=fig) #fig為使用plolty畫出的圖表
```
#### Plotly複習
```
import plotly.express as px
import pandas as pd
data = {
    'X': [1, 2, 3, 4, 5],
    'Y': [10, 15, 7, 12, 18]
}
df = pd.DataFrame(data)
fig = px.圖表類型(資料來源, x='X軸欄位', y='Y軸欄位', 其他設定...)
```
#### 完整的程式
```
import dash
from dash import dcc
from dash import html
import plotly.express as px

df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species", hover_data=['petal_length', 'petal_width'])

app = dash.Dash(__name__)

app.layout = html.Div([
    #html
    #dcc
    #Dash支援Markdown
    dcc.Markdown('''
    # Markdown H1
    ## Markdown H2
    ### Markdown H3
    source code of the figure:
    ```python=
    fig = px.scatter(df, x="sepal_width", y="sepal_length")
    ```

    '''),
    #show figure
    dcc.Graph(figure = fig)

])

if __name__ == "__main__":
    app.run(debug=True)
```

---
### Layout (靜態網頁設置)
- 目的是設置網頁內容，為骨架。

**組成:**
- **HTML元件**: 以Python的方式撰寫HTML，例如 `html.H1('Hello Dash')`，對應HTML中的 `<h1>Hello Dash</h1>`。
- **DCC元件**:創建交互界面的UI元素

**背景知識:**
- **HTML Layout**: 構建網頁結構、排版HTML元件的位置
  
![HTML](https://hackmd.io/_uploads/r1k_luNhJl.png)

- **HTML DIV**: 將網頁的各個部分組合在一起，為HTML元件的容器

**範例程式碼:**
```
app.layout = [
    html.Div(className='row', children='My First App with Data, Graph, and Controls',
    style={'textAlign': 'center', 'color': 'blue', 'fontSize': 30})
]
```
---

### DCC元件介紹
用於創建交互界面的UI元素，使用者可以操控、點擊、滑動的東西
##### (1) Dropdown(下拉清單)
顯示一個下拉選單，讓使用者選擇一個或多個選項
參數:
* id(str) : callback中的標識符，用於指定元件
* options(list) : 定義下拉選單中的選項，包含 label 和 value。label：顯示在畫面上的文字，value：對應的選項值，callback中會使用
* value(any) : 定義當前選中的值，這個值會對應於 options 中某個選項的 value
* clearable(bool) : 設定是否可以清除選擇的選項
```
app.layout = html.Div([
    dcc.Dropdown(
        id='my-dropdown',
        options=[
            {'label': 'Option 1', 'value': 'opt1'},
            {'label': 'Option 2', 'value': 'opt2'},
            {'label': 'Option 3', 'value': 'opt3'}
        ],
        value='opt1',
        clearable=false
    ),
    html.Div(id='output-container') 
])

```
##### (2) Radio Items
用來創建單選按鈕元件，允許使用者從一組選項中選擇一個值。與Dropdown 類似，但RadioItems會直接顯示所有選項，而不是透過下拉選單選擇
參數:
* id(str) : callback中的標識符，用於指定元件
* options(list) : 定義下拉選單中的選項，包含 label 和 value。label：顯示在畫面上的文字，value：對應的選項值，callback中會使用
* value(any) : 定義當前選中的值，這個值會對應於 options 中某個選項的 value
```
app.layout = html.Div([
    dcc.RadioItems(
        id='fruit-selector',
        options=[
            {'label': 'Apple', 'value': 'apple'},
            {'label': 'Banana', 'value': 'banana'},
            {'label': 'Cherry', 'value': 'cherry'}
        ],
        value='banana'  
    ),
    html.Div(id='output-container')
])
```
##### (3) Slider(滑桿)
用來創建滑桿元件的工具，允許使用者在一個預定範圍內選擇數值
參數:
*  id(str) : callback中的標識符，用於指定元件
*  min (float):設定滑桿的最小值
* max (float):設定滑桿的最大值
* step (float):設定滑桿每次增加的大小
* value (float) :設定滑桿的初始值
* updatemode(str):控制何時觸發 callback
   'mouseup'(放開滑鼠時)'drag'(滑動的過程中)
```
app.layout = html.Div([
    dcc.Slider(
        id='my-slider',
        min=0,
        max=100,
        step=1,
        value=50
        updatemode='drag'
    ),
    html.Div(id='output-container')
])
```
##### (4)Input(輸入框)
創建輸入框元件，允許用戶輸入數據，並可用於文字輸入、數字輸入、密碼輸入
參數:
* id(str) : callback中的標識符，用於指定元件
* type(str) : 指定輸入框的類型
    * "text"（文字輸入，預設）
    * "number"（數字輸入）
* value(int/float/str):設定輸入框的預設值
* placeholder（str）:設定輸入框的提示文字
* debounce（bool）
    * True：僅在使用者輸入後，點擊其他地方或按Enter 後，才會更新 value（避免每次鍵入都觸發回調函數）
    * False（預設）：每次鍵入都會即時更新
```
app.layout = html.Div([
    dcc.Input(
        id='text-input',
        type='text',
        placeholder='Enter your name',
        value='' , 
        debounce=True
    ),
    html.Div(id='output-container')
])

```
[範例](https://dash.plotly.com/dash-core-components)


### Callback (動態更新網頁內容)

每當輸入元件的屬性改變時，Dash 都會自動呼叫這些Callback，以便更新另一個元件（輸出）中的某些屬性。  
Dash會為callback提供輸入屬性的新值作為其參數，並使用callback下方的function傳回的內容來更新輸出元件的屬性。

**背景知識:**

- **Python Decorator**: 接收 1 個函式後加工並包裝成 1 個新的函式，動態修改函式或類別的行為，而不改變原始程式碼。

**結構:**
1. `@app.callback(...)`: 告訴 Dash 這是callback函式，並設定輸入（Input）和輸出（Output）。
2. 函式 `def function(...)`: 接收使用者輸入，計算新的結果。`return` 的值會被用來更新網頁的內容。
3. 更新頁面內容: 回傳值會改變 Output 中設定的 HTML 屬性，例如 `children`（顯示的文字）、`value`（輸入框的值）等。

**範例程式碼:**
```
@app.callback(
    Output('輸出元件的ID', '屬性'),
    Input('輸入元件的ID', '屬性')
)
def callcack_function(輸入值):
    return 要更新的輸出
```


---

### 練習二的知識點
**1. pd.to_datetime(df['欄位'])**
    把原本的「文字格式」的地震時間，轉成真正的時間格式（才能取出年跟月）
    例如:"2024-05-17 14:23:00"切割成年、月、日
**2. .dt.year 和 .dt.month**
.dt.year 可以幫你從一筆時間資料中拿出「年份」
.dt.month 則是拿出「月份」
**3. df['欄位'].min()、df['欄位'].max()**
取得資料中欄位的最小值和最大值
**4. df[條件式]**
用來從 DataFrame 中選取符合條件的資料的方法






