第一部這六天,講的都是「怎麼把爬蟲這一層設計得好維護」,但還沒明講一件事:爬蟲層的責任邊界劃在哪裡,超過這條線的事它不該碰。
回顧這六天:Template Method 解決「不同來源結構不同,共用流程要怎麼共用」;Factory 解決「該用哪個實作,這個判斷要放哪」;正規表達式的案例解決「撈出來的資料要不要交給對應的解析器處理」;例外情境的案例解決「假設不成立時要有明確退路」;抽象化取捨的案例解決「這些設計值不值得現在做」。
這些全部指向同一件事:爬蟲層的責任,是把充滿不確定性的外部頁面,轉換成一份確定、結構化、可以被下一層信任的資料(在這個案例裡,就是「內容項目清單 + 對應的 M3U8 位置」)。
❌ 反例:爬蟲層順手做了下載
class Crawler:
async def collect_and_download(self, url):
items = await self.__parse_items(url)
for item in items:
await self.__download_m3u8(item.m3u8_url) # 越界了
這樣寫,「解析頁面失敗」跟「下載失敗」這兩種完全不同性質的錯誤會混在同一個方法的例外裡,呼叫端沒辦法分開處理,測試也沒辦法只針對「解析邏輯對不對」單獨驗證,因為每次測試都得連帶處理下載這個外部依賴。
✅ 正例:爬蟲層只到「產出確定的資料結構」為止
class Crawler:
async def collect(self, url) -> list[Item]:
return await self.__parse_items(url) # 只做到這裡
# 下載是另一層的責任
async def process(url, crawler: Crawler, downloader):
items = await crawler.collect(url)
for item in items:
await downloader.download(item.m3u8_url)
一旦爬蟲層越界去做下載,這一層的測試就會被迫連帶處理網路下載這個外部依賴,原本「驗證解析邏輯對不對」這種可以很快跑完的單元測試,會被拖慢、變得不穩定。把「轉換」跟「使用轉換結果」分成兩層,兩層才能各自被獨立測試——這正是第四部要講的測試策略能夠成立的前提。
第二部開始:進入 HLS 協定本身,先從索引清單(M3U8 playlist)的基本結構講起。