iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Security

從情資收集到資安鑑識:30 天建構自動化威脅情資與鑑識平台系列 第 4 篇

[Day 04]GitHub Actions - fetchers.py 爬蟲模組說明

  • 分享至 

  • xImage
  •  

在開始前,先說明一下有幾種不同類型的資料收集,完全取決你的資料來源

  1. 靜態網頁爬蟲 (Static Web Scraper)
  2. 動態渲染爬蟲 (Dynamic Browser Scraper)
  3. API 與協定解析 (API & Protocol Parsing)

釐清後,就交給AI 幫你生成後續爬行行為,但必須注意,每次測試都必要要求AI 有過程產出。這裡的測試會需要熟悉html語法跟python,所以在每次要求gemini,chatGPT時,最好都能保有輸出,方便檢核錯誤。

下圖為GitHub Actions執行時,擷取RUN狀態的過程示範
https://ithelp.ithome.com.tw/upload/images/20260918/20184295LtoLM8U3bT.jpg

📡 fetchers.py 爬蟲模組解析

這支程式的主要任務是擔任「情報收集」,負責前往不同的資安網站與 RSS 來源,把最新的漏洞資訊與新聞抓回來,並整理成統一的格式交給主程式處理。

1. 檔案架構 (Architecture)

整支程式採用了「模組化」與「功能獨立」的設計方式,主要由四個獨立的抓取函式(Functions)構成:

  • fetch_msrc_updates():負責抓取微軟 (MSRC) 的漏洞安全更新 RSS。
  • fetch_rss_news():負責抓取 iThome 的資安與一般新聞 RSS。
  • fetch_fisac_news():負責抓取金融資安資訊分享與分析中心 (F-ISAC) 的首頁消息與 STIX 案件查詢系統。
  • fetch_nics_news():負責抓取國家資通安全研究院 (NICS) 的漏洞預警公告。
  • 統一的輸出格式:每個函式最終都會回傳一個包含 source(來源)、title(標題)、url(網址)、summary(摘要)與 raw_time(抓取時間)的字典列表 (List of Dictionaries),方便後續主程式進行標準化處理。

2. 必要條件 (Prerequisites)

要讓這支程式順利運行,你的環境必須滿足以下條件:

  • 外部套件依賴:必須安裝 requests (發送 HTTP 請求)、feedparser (解析 RSS 格式)、beautifulsoup4 (解析 HTML 結構) 以及 playwright (模擬瀏覽器行為)。
  • Playwright 環境:因為使用了 Playwright,系統中必須有安裝對應的 Chromium 瀏覽器驅動。
  • 自訂設定檔 (config.py):程式開頭引入了 config 模組,你的專案目錄下必須有這個檔案,並且裡面需要設定好 HTTP_TIMEOUT(連線超時時間)與 TZ(時區設定)這兩個變數。

3. 設計邏輯 (Design Logic)

這份程式最大的亮點,在於針對不同特性的網站,採用了不同的「戰術(抓取邏輯)」:

  • 輕重分離的抓取策略:
  • 對於 MSRC 與 iThome 這種提供標準 RSS Feed 的靜態來源,程式直接使用輕量且快速的 requests 與 feedparser 來抓取,節省系統資源。
  • 對於 F-ISAC 與 NICS 這種使用 JavaScript 動態渲染(例如 React)的現代網頁,則出動 playwright 啟動無頭瀏覽器 (Headless Browser) 來模擬真人讀取。
  • 動態翻頁處理:
  • URL 參數翻頁:在 F-ISAC 模組中,程式透過修改網址後方的 &page=1 到 &page=5 來實現前 5 頁的案件抓取。
  • 模擬按鈕點擊:在 NICS 模組中,程式利用 page.get_by_role("button", name=str(page_num)) 的方式,模擬滑鼠點擊分頁按鈕來切換頁面。
  • 即時去重機制 (Deduplication):在每個函式內部都宣告了 seen_urls 或 seen_in_batch 這種 set() (集合) 資料型態。在爬取當下就會檢查網址是否重複,確保回傳的資料清單不會有冗餘。

4. 注意事項 (Precautions)

這支程式在防呆與穩定性上做了很多細節處理,在維護時需特別留意:

  • 防阻擋與偽裝 (Anti-Scraping):在使用 Playwright 時,特別設定了 user_agent,把自己偽裝成 Windows 系統下的 Chrome 瀏覽器,這是為了避免被目標網站的 WAF (網站應用程式防火牆) 阻擋。
  • 容錯與錯誤隔離 (Error Handling):所有的抓取動作都被包覆在 try-except 區塊中。這意味著如果今天 F-ISAC 網站掛了,程式只會印出「抓取失敗」的錯誤訊息,但不會導致整支程式崩潰,其他來源 (如 NICS、MSRC) 依然可以順利完成任務。
  • 動態渲染的等待時間:抓取動態網頁最怕「畫面還沒出來,程式就開始抓」。程式中大量使用了 wait_until="networkidle"(等待網路請求靜止)、page.wait_for_timeout(2000)(強制緩衝 2 秒)以及 page.wait_for_selector(確保特定元素出現)等語法,這些都是為了解決抓到「空殼網頁」的關鍵設定。
  • 遺失模式 (Headless):Playwright 被設定為 headless=True,這代表它在執行時不會彈出真實的瀏覽器視窗,這對於要部署到 GitHub Actions 或 Linux 伺服器上自動排程執行是絕對必要的設定。

下面是我一部分的資料

# fetchers.py
import requests
import feedparser
import urllib.parse
import re
from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright
from config import HTTP_TIMEOUT, TZ
from datetime import datetime

def fetch_msrc_updates():
    """抓取 MSRC RSS"""
    items = []
    rss_url = "https://api.msrc.microsoft.com/update-guide/rss"
    try:
        print(f"[{datetime.now(TZ).strftime('%H:%M:%S')}] 正在抓取 MSRC RSS...")
        response = requests.get(rss_url, timeout=HTTP_TIMEOUT)
        if response.status_code == 200:
            feed = feedparser.parse(response.content)
            for entry in feed.entries:
                title = entry.title
                cve_match = re.search(r'CVE-\d{4}-\d+', title)
                cve_id = cve_match.group(0) if cve_match else "N/A"
                url = f"https://msrc.microsoft.com/update-guide/vulnerability/{cve_id}"
                items.append({
                    "source": "MSRC",
                    "title": title,
                    "url": url,
                    "summary": entry.get('summary', '')[:500],
                    "raw_time": datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S')
                })
    except Exception as e:
        print(f"MSRC 抓取失敗: {e}")
    return items

def fetch_rss_news():
    """抓取 iThome 資安新聞"""
    items = []
    seen_urls = set()
    feeds = [("iThome資安", "https://www.ithome.com.tw/rss/news/security"),
             ("iThome新聞", "https://www.ithome.com.tw/rss")
    ]
    for name, url in feeds:
        try:
            feed = feedparser.parse(url)
            for entry in feed.entries[:1000]:
                link = entry.link                

                if link not in seen_urls:
                    items.append({
                        "source": name,
                        "title": entry.title,
                        "url": link,
                        "summary": entry.get('summary', '')[:250],
                        "raw_time": datetime.now(TZ).strftime('%Y-%m-%d %H:%M:%S')
                    })
                seen_urls.add(link)
        except Exception as e:
            print(f"{name} 抓取失敗: {e}")
    return items

上一篇
[Day 03]GitHub Actions - .github/workflows 說明
下一篇
[Day 05]GitHub Actions - processor.py 邏輯分析說明
系列文
從情資收集到資安鑑識:30 天建構自動化威脅情資與鑑識平台 共 12 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言