今天有教到遞迴a_tags = soup.find_all(['a','b'],limit=4,recursive=True)

理解背後的原理和思考過程,比單純拿到一段「能用的程式碼」更重要。這樣你才能靈活應用、修改,甚至面對未來網站結構變動時,也能自己調整

1. 了解網頁結構與 HTML 元素
學會用瀏覽器的「檢查元素」工具,觀察你想抓的資料在網頁上的 HTML 標籤、class、id 等屬性。
理解標籤之間的父子關係,這樣可以用更精確的 CSS selector 或 BeautifulSoup 的方法定位目標。

2. 學會用 BeautifulSoup 的多種查找方法
find(), find_all():根據標籤、屬性查找元素。
select(): 用 CSS selector 查找元素,彈性更大。
利用文字內容、屬性值做篩選。

3. 掌握 HTTP 請求的細節
了解 headers(特別是 User-Agent)的重要性,知道為什麼要模擬瀏覽器。
學會錯誤處理,讓程式更穩定。

4. 練習並嘗試改寫
嘗試自己改變查找條件,觀察結果變化。
嘗試抓不同網站、不同區塊的資料,累積經驗。


這段 Python 程式的功能是「從網站抓取旅遊產品資訊,並寫入 travel.csv 檔案中」。下面我分段詳細說明:

🔹 1. 匯入套件

import csv import requests from bs4 import BeautifulSoup 
  • csv:用來寫入 .csv 檔案。

  • requests:發送 HTTP 請求,抓取網頁內容。

  • BeautifulSoup:解析 HTML 網頁內容。

🔹 2. 設定標頭與準備資料儲存容器

information = [] headers = { "User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) ..." } 
  • information 是用來儲存所有旅遊資訊的列表。

  • headers 模擬瀏覽器送出請求,避免被網站拒絕(網站可能會擋掉機器人)。

🔹 3. 抓取網頁內容

r = requests.get('https://www.etholiday.com/Products/Group/Search?regm_cd=0005&signup_st=&page=1', headers=headers) r.raise_for_status() soup = BeautifulSoup(r.text, 'html.parser') 
  • 向網站送出 GET 請求並接收回傳的 HTML。

  • raise_for_status() 會在請求失敗時產生錯誤。

  • BeautifulSoup 將 HTML 解析為 Python 可以操作的格式。

🔹 4. 擷取旅遊商品資訊

stories = soup.find_all('div', class_='row no-gutters') for s in stories: 
  • find_all() 找出所有商品區塊。

  • 每個 s 就是一筆商品資料,接著從裡面擷取資訊:

👉 擷取標題

title_tag = s.find('a', class_='w-100') if not (title_tag and title_tag.h4 and title_tag.h4.string): continue title = title_tag.h4.get_text(strip=True) 
  • 找不到標題就 continue 跳過。

  • .get_text(strip=True) 會去除前後空白。

👉 擷取出發日期

date_tag = s.find('div', class_='go-date') date = date_tag.get_text(strip=True) if date_tag else '' 
  • 如果有日期就取文字,否則設為空字串。

👉 擷取航空公司名稱(可能為空)

plane_tag = s.find('span', class_='plane-abbr') plane = plane_tag.get_text(strip=True) if plane_tag else '' 

👉 擷取價格

price_box = s.find('div', class_='price_box') if price_box and price_box.h4 and price_box.h4.span: price = price_box.h4.span.get_text(strip=True) else: price = '' 
  • 如果能成功找到價格標籤,就取出文字,否則設為空。

👉 加入結果列表

information.append([date, title, plane, price]) 

🔹 5. 寫入 CSV 檔案

with open('travel.csv', 'w', newline='', encoding='utf-8-sig') as csvfile: writer = csv.writer(csvfile) writer.writerow(['date', 'title', 'plane', 'price']) writer.writerows(information) 
  • 使用 utf-8-sig 避免 Excel 打開亂碼。

  • 第一行是標題,接著寫入所有資料。

(也可以改用 pandas 寫入,程式下方有備用程式碼)

🔹 6. 顯示結果

print(f"共寫入 {len(information)} 筆資料到 travel.csv") 
  • 顯示寫入的筆數。

 


下載YAHOO頭條新聞

導致失敗的觀念與操作程式碼一 分析:
import requests
from bs4 import BeautifulSoup
r = requests.get('https://tw.yahoo.com/')
if r.status_code == requests.codes.ok:
    soup = BeautifulSoup(r.text, 'html.parser')
    stories = soup.find_all('a', class_='Fz(16px) LineClamp(1,20px) Fw(700) Td(n) Td(u):h C(#324fe1) V(h) active_V(v)')    
    for s in stories:
        print("標題:", s.text)
        print("網址:", s.get('href'))

1. 加入 headers 假裝瀏覽器
錯誤範例:直接用 requests.get('https://tw.yahoo.com/') 取得網頁。
成功範例:在 requests.get 裡加入 headers,模擬瀏覽器(如 iPad)發送請求。
headers = {"User-Agent": "Mozilla/5.0 (iPad; ..."}
r = requests.get('https://tw.yahoo.com/', headers=headers)
原因:有些網站(如 Yahoo)會檢查 User-Agent,沒有瀏覽器資訊就會拒絕或回傳不完整內容。
效果:加了 headers 後,網站會回傳正常網頁內容,BeautifulSoup 才能正確解析。

2. 增加錯誤處理(try-except)
錯誤範例:沒有任何錯誤處理,遇到網路問題或網址錯誤就直接當掉。
成功範例:用 try-except 包住主要程式,捕捉 HTTPError 和 URLError。
try:
    # 主要程式
except HTTPError as e:
    print("httperror")
except URLError as e2:
    print("urlerror")
原因:網路請求常會失敗,必須捕捉例外,避免程式崩潰。
效果:即使遇到錯誤,程式會顯示錯誤訊息,不會整個中斷。

3. 結尾加上識別字串
錯誤範例:沒有特別標記程式結束。
成功範例:最後加上 print("lccnet")。
效果:方便辨識程式有正常執行到最後。

4. 其它小細節
import:成功範例多 import 了 HTTPError, URLError,但實際上這是 urllib 的錯誤型態,若用 requests 可以改用 requests.exceptions,但這裡主要是示範 try-except 結構。功能本身(爬取標題與網址)兩段都一樣,重點差在「能否順利取得網頁」和「遇到錯誤時的處理」。

 

成功案例,並形成DataFrame

import requests
from bs4 import BeautifulSoup

title = []
link_url = []

# 1. 加入 headers 模擬瀏覽器
headers = {"User-Agent": "Mozilla/5.0 (iPad; CPU OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"}

try:
    # 2. 加入 try-except 錯誤處理
    r = requests.get('https://tw.yahoo.com/', headers=headers)
    r.raise_for_status()  # 若發生 HTTP 錯誤會直接丟出例外

    if r.status_code == requests.codes.ok:
        soup = BeautifulSoup(r.text, 'html.parser')
        stories = soup.find_all('a', class_='Td(n) C(varTextPrimary) C(varTextPurpleHighlight):h Ov(h) Fw(700) Fz(16px) Lh(24px)')
        for s in stories:
            print("標題:" + s.text)
            title.append(s.text)
            print("網址:" + s.get('href'))
            link_url.append(s.get('href'))

except requests.exceptions.HTTPError as e:
    print("HTTP error:", e)
except requests.exceptions.RequestException as e:
    print("Request error:", e)
except Exception as e:
    print("Other error:", e)

# 3. 結尾提示
print("共抓到", len(title), "則新聞")
print(link_url)
print("lccnet")


成功抓取新聞頁面,請試著抓其他頁面
import requests
from bs4 import BeautifulSoup

r = requests.get('https://tw.yahoo.com/')

if r.status_code == requests.codes.ok:
    soup = BeautifulSoup(r.text, 'html.parser')
    stories = soup.find_all('a', class_='Fz(16px) LineClamp(1,20px) Fw(700) Td(n) Td(u):h C(#324fe1) V(h) active_V(v)')
    
    for s in stories:
        print("標題:", s.text)
        print("網址:", s.get('href'))

步驟 操作 背後觀念
1 選對網址 https://tw.news.yahoo.com/ 不抓 JS 動態頁面,而抓靜態的新聞列表
2 找出穩定 class 的 <a> 標籤 避免找不到或 class 變動
3 使用 BeautifulSoup 解析並找出資料 確保能從 HTML 結構中正確抓資料
4 擷取 .text.get('href') 分別拿標題與連結
5 print() 除錯與確認結果 驗證是否成功抓到你想要的內容

 

從 PTT 笑話版(joke 看板)抓取文章標題與連結,並且往前翻頁,連續抓取 5 頁的文章標題。
import requests
from bs4 import BeautifulSoup
url="https://www.ptt.cc/bbs/joke/index.html"


for i in range(5):
    r=requests.get(url)
    if r.status_code==requests.codes.ok:
        soup=BeautifulSoup(r.text,'html.parser')
        sel=soup.select("div.title a") #標題
        
        u=soup.select("div.btn-group.btn-group-paging a")
        url="https://www.ptt.cc"+u[1]["href"] #新網頁(網址)的新連結
        for s in sel:
            print(s["href"],s.text)

1.匯入套件
requests:用來發送 HTTP 請求,取得網頁內容。
BeautifulSoup:用來解析 HTML,方便抓取特定標籤內容。

2.設定起始網址
url = "https://www.ptt.cc/bbs/joke/index.html"
這是笑話版的最新文章列表頁面。

3.迴圈抓取 5 頁資料
for i in range(5):
迴圈執行 5 次,每次抓取一頁的文章標題。

4.發送請求並確認成功
r = requests.get(url)
if r.status_code == requests.codes.ok: 確認 HTTP 狀態碼是 200,代表成功取得網頁。

5.解析網頁內容
soup = BeautifulSoup(r.text, 'html.parser')
將 HTML 文字用 BeautifulSoup 解析。

6.抓取文章標題與連結
sel = soup.select("div.title a")
PTT 文章列表中,文章標題在 <div class="title"> 裡的 <a> 標籤中。
sel 是一個列表,裡面每個元素是 <a> 標籤。

7.抓取分頁按鈕
u = soup.select("div.btn-group.btn-group-paging a")
這裡抓取分頁按鈕,通常有「最舊」、「‹ 上頁」、「下頁 ›」、「最新」四個連結。
u[1] 指的是「‹ 上頁」的連結。

8.更新下一次要抓取的頁面網址
url = "https://www.ptt.cc" + u[1]["href"]
取得上一頁的網址,讓下一次迴圈抓取前一頁的文章。

9.印出每篇文章的連結與標題
for s in sel:
print(s["href"], s.text)
印出文章相對連結(例如 /bbs/joke/M.1234567890.A.html)與標題文字。

 


一、處理 PTT 的「滿 18 歲確認頁」

import requests
from bs4 import BeautifulSoup
r=requests.Session()
payload={
    "from":"bbs/Gossiping/index.html",
    "yes":"yes"}

r1=r.post("https://www.ptt.cc/ask/over18",payload)
r2=r.get("https://www.ptt.cc/bbs/Gossiping/index.html")
print(r2.text)

requests.Session():建立一個「會話」物件,可在後續請求中保留 cookie 等登入狀態。
payload:是我們要傳給 PTT 的資料,表示我們同意「已滿 18 歲」。

  • "from" 指想前往的頁面。
  • "yes": "yes" 表示我們按下「我已滿 18 歲」的按鈕。

r.post(...):模擬表單送出,通過「滿 18 歲」驗證。

🔸 為什麼要這樣做?
PTT 的 Gossiping 板需要滿 18 歲才能瀏覽,
用 requests 直接 get 的話,會只拿到驗證頁。
這裡用 Session + post() 模擬通過驗證,
後續請求才能正常取得文章。

二、抓取 Gossiping 板的 HTML 內容
r2 = r.get("https://www.ptt.cc/bbs/Gossiping/index.html")
print(r2.text)
🔸 說明:
用剛剛已經通過驗證的 r session 去抓取八卦板首頁。r2.text 是拿到的 HTML 文字內容。
此時因為 Session 已通過「滿 18 歲」驗證,所以這個 .get() 會成功拿到文章列表頁,不會再被導去問年齡的頁面。

三、後續爬多頁的程式碼
for i in range(5):
    r=requests.get(url)
    if r.status_code==requests.codes.ok:
        soup=BeautifulSoup(r.text,'html.parser')
        sel=soup.select("div.title a") #標題
        
        u=soup.select("div.btn-group.btn-group-paging a")
        url="https://www.ptt.cc"+u[1]["href"] #新網頁(網址)的新連結
        for s in sel:
            print(s["href"],s.text)

程式碼 操作 說明
for i in range(5): 重複跑 5 頁 每次都抓一頁的文章清單
soup.select("div.title a") 找出每篇文章的標題與連結 <div class="title"><a href=...> 是文章的區塊
soup.select("div.btn-group.btn-group-paging a") 找出「上一頁」的按鈕連結 u[1] 就是「上一頁」按鈕
url = "https://www.ptt.cc" + u[1]["href"] 把相對網址轉成完整網址 例如 /bbs/Gossiping/index39123.html
print(s["href"], s.text) 印出每篇文章的網址與標題 s["href"] 是文章的連結,s.text 是標題

 


find_all(class_=re.compile("^bold")) ^ 符號出現在 bold 前面的原因
尋找所有 HTML 標籤,這些標籤的 class 屬性值是以 "bold" 開頭的字串。
舉例來說,以下這些 class 屬性值都會被這個正規表達式匹配到:
bold
bold-text
boldItalic
bold_important
但是,以下這些 class 屬性值則不會被匹配到:
text-bold (因為 "bold" 不是開頭)
lightbold (因為 "bold" 不是開頭)

CSS 選擇器的優點:
在處理具有多個 CSS class 的情況時,使用 CSS 選擇器通常比使用 find_all() 搭配正規表達式或其他方式
更簡潔、更直觀,並且更符合 CSS 的語法習慣。這使得程式碼更容易閱讀和維護。
總之,css_soup.select("p.strikeout.body") 
這行程式碼精確地選取了 HTML 文件中那些同時擁有 strikeout 和 body 這兩個 CSS class 的 <p> 標籤
p_tag = css_soup.select("p.strikeout.body")
print(p_tag)

re 模組(正規表示式)進行文字比對與擷取,分兩段詳細解說

第一段程式碼解說
import re
string = '台中市南屯區埔興段35-1253號'
regex = re.compile(r'段(\d+-*\d*)(\d)(\d)')
match = regex.search(string)
print(match.group(1))

說明:
string 是目標字串:「台中市南屯區埔興段35-1253號」
正則表達式 r'段(\d+-*\d*)(\d)(\d)' 是要抓出「段」後面的數字部分。

正則式解析:
部分    意義
段    精確匹配「段」這個字
(\d+-*\d*)    第一群組:一串數字,中間可以有「-」,例如 35-1253
(\d)    第二群組:單一數字
(\d)    第三群組:單一數字

第二段程式碼解說
test_string = 'aabb abb aab dcd hab aee ab aaaaab'
pattern = 'a+b'
ans = re.findall(pattern, test_string)
print(ans)
說明:
re.findall() 是搜尋所有符合條件的子字串,並以列表回傳。
模式 'a+b' 代表:
一個或多個 a(a+)
緊接著一個 b

字串片段 符合嗎 原因
aabb O aa + b(前面 a+ 吃掉兩個 a)
abb O a + b(符合 a+b
aab O aa + b
dcd False 沒有 a 或 b
hab O ab 符合,但前面 h 被忽略
aee False 沒有符合 a+b
ab O a + b
aaaaab O aaaaa + b

['aab', 'ab', 'aab', 'ab', 'ab', 'aaaaab']
雖然看到「hab」,但 re.findall() 抓的是符合「a+b」的部分,不會包含 h。

比對單一結果    re.search()    回傳第一個匹配物件(可用 .group() 抓取群組)
搜尋所有結果    re.findall()    回傳所有符合條件的字串(回傳 list)
編譯正則表達式 re.compile() 讓樣式重複使用更方便

文章標籤
全站熱搜
創作者介紹
創作者 ky0dd 的頭像
ky0dd

阿京小站

ky0dd 發表在 痞客邦 留言(0) 人氣(0)