# 爬蟲實作筆記
爬蟲-讓我們省去瀏覽網站的時間,自動化抓取網站上的特定內容,另外也可搭配不同功能將資料儲存成特定資料型態,方便檢視及觀看。
<div class="name">中央資工-朱禹澄著</div>
<style>
.name{
text-align:right;
}
</style>
:::success
我們需要的模組(Module):
- requests 下載網頁
- beautifulsoup4 解析網頁、獲得資料
- json 把到的資料轉換成JSON格式
- openpyxl 將df寫入excel表格中
(若有遇到任何安裝模組問題可直接詢問ChatGPT)
:::

<div class="name">圖片來源:CodeShiba程式柴</div>
<style>
.name{
text-align:right;
}
</style>
---
:::spoiler 完整程式碼(存成excel版本)
```python=
import requests
from bs4 import BeautifulSoup
import json
import pandas as pd
url = "https://www.ptt.cc/bbs/NBA/index.html"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
articles = soup.find_all("div", class_="r-ent")
data_list = []
for a in articles:
data = {}
title = a.find("div", class_="title")
if title and title.a:
title = title.a.text
else:
title = "沒有標題"
data["標題"] = title
popularity = a.find("div", class_="nrec")
if popularity and popularity.span:
popularity = popularity.span.text
else:
popularity = "N/A"
data["人氣值"] = popularity
date = a.find("div", class_="date")
if date:
date = date.text
else:
date = "N/A"
data["日期"] = date
data_list.append(data)
df = pd.DataFrame(data_list)
df.to_excel("ptt_nbaa.xlsx", index=False, engine="openpyxl")
print(df)
print("資料已成功儲存在: ptt_nbaa.xlsx 中")
```
:::
---
## 第壹部-讀取網頁
:::info
#### 1-1 黃金六行
:::
```python=
import requests
headers = {'User-Agent':"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15" }
url = "https://www.ptt.cc/bbs/NBA/index.html"
response = requests.get(url, headers=headers)
with open('output.html', 'w', encoding='utf-8') as f:
f.write(response.text)
```
:::spoiler 逐行解釋
- 一開始我們引入requests模組(需在終端機先pip install requests)
- 模仿正常人,加上request的header避免被反爬蟲機制阻擋
- 定義url這個變數為想要爬取的網站網址
- 定義response這個變數為使用request get方法爬到url資料的值
- 將response.text 的內容以 utf-8 編碼寫入到名為 output.html 的文件中。

:::
---
## 第貳步-觀察網頁
:::info
#### 2-1 右鍵->檢查->按下elements左邊兩格的圖案->觀察要爬取的資料在哪一欄裡
:::
:::spoiler 圖示步驟





:::
---
## 第參步-解析網頁
:::info
#### 3-1 變數soup-把剛剛得到的response(爬到的網站資料)丟到beautifulSoup()裡面解析
:::
```python=
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# "html.parser"用html解析器 解析response.text產生的html
articles = soup.find_all("div", class_='r-ent')
#find_all會把所有找到符合的加在articles這個list裡面
#print(articles[0]) 在終端機測試看看有沒有輸出r-ent第一大條的html
```
---
:::info
#### 3-2 一張圖解釋一切
:::
```python=
soup = BeautifulSoup(response.text, "html.parser")
# "html-parser"用html解析器 解析response.text產生的html
articles = soup.find_all("div", class_='r-ent') #find_all會把所有找到符合的加在articles這個list裡面
print(articles[0])
```

:::spoiler ### **如果要更詳細的得到某一項,那我們就要去找到該項對應到的html碼**
**(例如標題 -> div, 'title', a, "文章標題文字")**
**(一種attribute的感覺,eg:title.a)**

---
**可以觀察到整個標題條是包含在"r-ent"下(包含"nrec"對應到的人氣及"title對應到的標題**

:::
---
:::info
#### 3-3 如果我們想要萃取出所有的標題,我們可以搭配for迴圈迭代整個articles列表
:::
:::danger
- **迴圈的a跟html的a是不一樣的東西,請不要搞混**
- **變數的值可以持續不斷的被覆蓋更新**
:::

::: spoiler 三個解釋點
- 用for迴圈迭代每一個articles列表find到的元素
- 利用.text使得輸出的是純文字而不是html程式碼
- 發現找到的其中一項(articles中找到的其中一個 class="r-ent"中是沒有東西的 -> 這是因為該文章已被刪除,故所有裡面的內容皆不見了,只留下一個空殼。
:::
---
**問題:有些文章可能被刪除,只剩下空殼,無法將None轉換
解決方法:判斷該"r-ent"是否被刪除,是的話需要另做處理,避免發生錯誤**
```python=
for a in articles: #每一個文章就是一個a
title = a.find("div", class_="title")
if title and title.a: #有title且有title下面的a
title = title.a.text #如果有標題的話,我們讓title這個變數名稱的值為該標題
else:
title = "沒有標題"
print(title) #此處的a非上面的a,此為html title下面的連結a(a href=......的a)
```
::: spoiler 輸出結果

:::
::: spoiler **如果我們還想得到發佈日期、人氣值等等資訊,我們可以這麼做...**
**step1:我們先找到人氣值與日期的對應html區塊**

**step2:對症下藥**
```python=
for a in articles: #每一個文章就是一個a
title = a.find("div", class_="title")
if title and title.a: #有title且有title下面的a
title = title.a.text #如果有標題的話,我們讓title這個變數名稱的值為該標題
else:
title = "沒有標題"
# print(title) #此處的a非上面的a,此為html title下面的連結a(a href=......的a)
popularity = a.find("div", class_="nrec")
if popularity and popularity.span:
popularity = popularity.span.text # 輸出nrec下面span區塊裡面的純文字
else:
popularity = "N/A"
date = a.find("div", class_="date")
if date: #date區塊內就直接是我們要的內容了
date = date.text
else:
date = "N/A"
print(f"標題:{title} 人氣值:{popularity} 日期:{date}")
```

:::
---
## 第肆部-儲存內容
#### 儲存以便爾後重複使用
:::info
#### 4-1結構化資料-先把資料以一一對應的dictionary元素加入list中(需要想一下結構長怎麼樣)
:::
data_list內每一個元素都是一大條r-ent內的數據(包含該條的標題,人氣值及時間)
:::danger
- 字典的key重複時,value會被新進來的給覆蓋取代
:::
```python=
data_list = [] #儲存所有artcles內("r-ent"區塊內)資料的容器
for a in articles:
data = {} #儲存單一"r-ent"區塊內的標題、人氣值及日期元素
title = a.find("div", class_="title")
...
data["標題"] = title # key:"標題", value:title
popularity = a.find("div", class_="nrec")
...
data["人氣"] = popularity
date = a.find("div", class_="date")
data["時間"] = date
data_list.append(data)
# [ {k11:val11, k12:val12... }, {k21, val21, k22:val22...}, ... ]
print(data_list) #測試
print(data_list[0]) #測試
```
:::spoiler 輸出結果

:::
:::info
#### 4-2將結構化資料儲存成json格式的檔案
:::
:::success
想想看:json格式有甚麼功用? 獲取伺服器資料時所獲得的格式? 還有嗎?
:::
```python=
import json
with open("ptt_nba_data.json", "w", encoding="utf-8") as file:
json.dump(data_list, file, ensure_ascii=False, indent=4)
print("資料已經成功儲存為: ptt_nba_data.json")
```
:::spoiler 輸出結果


:::
:::info
#### 4-3將結構化資料儲存成Excel格式的檔案-使用pandas將列表data_list轉換成dataframe
:::
:::danger
- 檔名命名時盡量使用小寫英文字母及底線,不要使用大寫、空格或特殊符號
:::
```python=
df = pd.DataFrame(data_list)
df.to_excel("ptt_nba.xlsx", index=False, engine="openpyxl")
```
:::spoiler 詳細解說

- DataFrame是一個pandas中的一種資料結構,類似excel的表格或SQL表中的一個表格
- df.to_excel -> 將 DataFrame df 輸出到一個 Excel 文件中
- "ptt_nba.xlsx" -> 保存 Excel 文件的名稱
- index=False -> 表示不要將 DataFrame 的索引寫入到 Excel 文件中。如果設置為 True,索引將作為 Excel 文件中的第一列。
- engine="openpyxl" -> 指定使用 openpyxl 作為寫入 Excel 文件的引擎。openpyxl 是一個 Python 庫,用於讀取和寫入 Excel 2010 xlsx/xlsm/xltx/xltm 文件。

:::
:::spoiler 成果


:::
---
:::spoiler 完整程式碼(存成excel版本)
```python=
import requests
from bs4 import BeautifulSoup
import json
import pandas as pd
url = "https://www.ptt.cc/bbs/NBA/index.html"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
articles = soup.find_all("div", class_="r-ent")
data_list = []
for a in articles:
data = {}
title = a.find("div", class_="title")
if title and title.a:
title = title.a.text
else:
title = "沒有標題"
data["標題"] = title
popularity = a.find("div", class_="nrec")
if popularity and popularity.span:
popularity = popularity.span.text
else:
popularity = "N/A"
data["人氣值"] = popularity
date = a.find("div", class_="date")
if date:
date = date.text
else:
date = "N/A"
data["日期"] = date
data_list.append(data)
df = pd.DataFrame(data_list)
df.to_excel("ptt_nbaa.xlsx", index=False, engine="openpyxl")
print(df)
print("資料已成功儲存在: ptt_nbaa.xlsx 中")
```
:::