行政院本院新聞爬蟲:進入詳細頁抓完整內文

import time

import requests
import pandas as pd
from bs4 import BeautifulSoup
from urllib.parse import urljoin


### 設定網址
base_url = "https://www.ey.gov.tw" # 行政院網站的主網址
list_url = "https://www.ey.gov.tw/Page/6485009ABEC1CB9C" # 行政院新聞列表頁網址

headers = {
    "User-Agent": "Mozilla/5.0"
} # 模擬一般瀏覽器發送請求,避免部分網站拒絕程式存取


### 建立 Session
session = requests.Session() # 建立 Session,讓後續多次 HTTP 請求可以共用連線
session.headers.update(headers) # 將 User-Agent 設定到 Session,之後每次請求都會自動帶入


### 抓取新聞詳細頁內文
def get_news_content(news_url):
    try:
        response = session.get(
            news_url,
            timeout=10
        ) # 進入新聞詳細頁,最多等待 10 秒

        response.raise_for_status() # 如果 HTTP 狀態碼為 4xx 或 5xx,直接拋出例外

        soup = BeautifulSoup(
            response.text,
            "lxml"
        ) # 將新聞詳細頁的 HTML 原始碼交給 BeautifulSoup 解析

        content_element = soup.select_one(
            ".words_content .data_left"
        ) # 使用 CSS Selector 找到新聞主要內文區域

        if not content_element:
            return "" # 如果找不到新聞內文區域,回傳空字串

        paragraphs = content_element.select("p") # 找到新聞內文區域中的所有 <p> 段落

        content_list = []

        for paragraph in paragraphs:
            text = paragraph.get_text(
                " ",
                strip=True
            ) # 取得段落文字,並移除前後及多餘的空白

            if text:
                content_list.append(text) # 排除沒有文字的空白段落

        content = "\n\n".join(content_list) # 將所有段落合併,每個段落之間空一行

        return content # 回傳整理完成的新聞完整內文

    except requests.exceptions.RequestException as error:
        print("詳細頁抓取失敗:", news_url)
        print("錯誤訊息:", error)

        return "" # 如果請求發生錯誤,回傳空字串,避免整個程式中斷


### 抓取新聞列表頁
response = session.get(
    list_url,
    timeout=10
) # 向行政院新聞列表頁發送 GET 請求

response.raise_for_status() # 如果 HTTP 狀態碼為 4xx 或 5xx,直接拋出例外

print("列表頁狀態碼:", response.status_code)


### 建立 BeautifulSoup
soup = BeautifulSoup(
    response.text,
    "lxml"
) # 將新聞列表頁的 HTML 原始碼交給 BeautifulSoup 解析


### 找到所有新聞項目
news_items = soup.select(
    "ul.list-group-item > li"
) # 找到新聞列表中所有第一層的 <li> 新聞項目

print("本頁新聞數量:", len(news_items))


### 抓取新聞資料
news_list = []

for index, item in enumerate(news_items, start=1):
    title_element = item.select_one(".title") # 找到新聞標題
    date_element = item.select_one(".date") # 找到新聞日期
    summary_element = item.select_one("p") # 找到新聞摘要
    link_element = item.select_one("a") # 找到新聞詳細頁連結

    if title_element and date_element and link_element:
        title = title_element.get_text(
            " ",
            strip=True
        ) # 取得新聞標題文字

        date = date_element.get_text(
            strip=True
        ) # 取得新聞發布日期

        if summary_element:
            summary = summary_element.get_text(
                " ",
                strip=True
            ) # 如果有摘要,就取得摘要文字
        else:
            summary = "" # 如果沒有摘要,就使用空字串

        relative_url = link_element.get("href") # 取得 <a> 標籤中的 href 網址

        news_url = urljoin(
            base_url,
            relative_url
        ) # 將相對網址與網站主網址組合成完整網址

        print(f"\n正在抓取第 {index} 則新聞")
        print("標題:", title)
        print("網址:", news_url)

        content = get_news_content(
            news_url
        ) # 呼叫函式進入新聞詳細頁,抓取完整新聞內文

        news_list.append({
            "標題": title,
            "日期": date,
            "摘要": summary,
            "完整內文": content,
            "網址": news_url
        }) # 將這一則新聞整理成字典後加入 news_list

        time.sleep(1) # 每抓完一則新聞暫停 1 秒,避免短時間內發送太多請求


### 轉換成 DataFrame
news_df = pd.DataFrame(news_list) # 將新聞資料列表轉換成 pandas DataFrame


### 顯示抓取結果
print("\n新聞資料:")

for index, news in enumerate(news_list, start=1):
    print(f"\n{index} 則新聞")
    print("標題:", news["標題"])
    print("日期:", news["日期"])
    print("完整內文:")
    print(news["完整內文"])
    print("網址:", news["網址"])


### 儲存成 CSV
news_df.to_csv(
    "行政院新聞完整資料.csv",
    index=False,
    encoding="utf-8-sig"
) # 儲存成 CSV,使用 utf-8-sig 避免 Excel 開啟中文時出現亂碼


### 執行完成
print("\n資料抓取完成")
print("共抓到:", len(news_list), "則新聞")
print("已產生:行政院新聞完整資料.csv")