Django 爬蟲自動化:Docker 獨立運行 scraper.py
更新日期: 2025 年 3 月 13 日
本文為 Docker 指令細談,第 4 篇:
在 Django 開發模式 (runserver
) 中,開發者經常會使用 Django 自帶的自動重新載入機制 (StatReloader
) 來提升開發效率。
然而,這個便捷的功能卻可能干擾背景執行緒,例如自動化爬蟲 (scraper.py
) 的運行,導致爬蟲異常終止或無法正常啟動。
本篇文章將介紹如何在不改動 Dockerfile
與不影響 Django 開發模式的前提下,使用 docker-compose.yml
將爬蟲腳本獨立為一個服務,確保爬蟲可以穩定運行。
為什麼啟動時爬蟲沒有運行?
在 Django 開發模式中,當你使用指令 python manage.py runserver
啟動伺服器時,Django 會自動啟用 StatReloader 進行檔案變更監控。
然而,StatReloader 的工作原理其實有點複雜,這導致某些情況下爬蟲腳本(例如在 AppConfig.ready()
中啟動的背景執行緒)可能無法在啟動時正常運行。
sequenceDiagram participant Dev as 開發者 participant StatReloader as Django StatReloader participant Django as Django 服務 (web) participant Scraper as 爬蟲腳本 (scraper) Dev->>StatReloader: 啟動 runserver StatReloader->>Django: 啟動初始子進程 (Django) Django--XScraper: 子進程中爬蟲未啟動 Note over Scraper: 爬蟲腳本沒有執行 Dev->>StatReloader: 修改檔案 (如 scraper.py) StatReloader->>Django: 重啟 Django 服務 Django->>Scraper: 重新載入,爬蟲腳本啟動成功 Note over Django, Scraper: 重新載入後爬蟲正常運行
StatReloader 的核心運作機制
- 創建主進程 (
watcher
):StatReloader 會先啟動一個監控進程,這個進程的主要工作是:- 監聽專案目錄中的檔案變更,例如
.py
、.html
、.js
檔案的修改。 - 當檔案變更時,觸發 Django 服務重啟,讓開發者看到最新的代碼效果。
- 監聽專案目錄中的檔案變更,例如
- 啟動子進程 (
worker
):- 主進程不會直接運行 Django 服務,而是創建一個子進程來運行你的應用程式 (
manage.py runserver
)。 - 這個子進程會加載你的 Django 專案設定 (
settings.py
) 並初始化所有應用程式(AppConfig.ready()
也在此時調用)。
- 主進程不會直接運行 Django 服務,而是創建一個子進程來運行你的應用程式 (
- 首次啟動時的特殊行為:
- 空白子進程:在應用首次啟動時,StatReloader 創建的這個子進程其實是「準備用於監聽變更的占位進程」,而不是真正的「應用服務進程」。
- Django 在這個進程中可能不會完整初始化應用程式,尤其是在
AppConfig.ready()
中啟動的非同步任務(例如threading.Thread
),可能會被跳過。
潛在問題:為什麼爬蟲腳本沒有啟動?
1️⃣ 子進程初始化不完全,ready()
方法可能被跳過
假設你的爬蟲腳本是在 apps.py
中透過 ready()
方法啟動的,例如:
# apps.py 範例
from django.apps import AppConfig
from threading import Thread
from music_events.scripts.scraper import start_scheduler
class MusicEventsConfig(AppConfig):
name = 'music_events'
def ready(self):
Thread(target=start_scheduler).start()
在這個設定中,ready()
方法會在應用初始化時啟動一個執行緒來運行爬蟲排程 (start_scheduler()
)。
然而,在 Django 的 首次啟動進程 中,這個子進程處於「佔位」狀態,可能不會執行 ready()
中的邏輯,導致爬蟲腳本沒有啟動。
2️⃣ 執行緒 (Thread
) 啟動失敗
即使 ready()
被調用,因為這個進程僅是佔位進程,所有的 非同步任務(例如 Thread
)可能在進程被真正啟動前就被中止,導致爬蟲腳本無法啟動。
3️⃣ 主進程與子進程的重啟機制
Django 在第一次啟動時,子進程處於「偽運行」狀態,僅僅是在等待主進程的重新載入指令。
這也解釋了為什麼爬蟲腳本只有在檔案變更(例如修改 views.py
或 models.py
)後,StatReloader
觸發真正的「應用服務進程」啟動時,爬蟲才會正常運行。
實際情境再現:啟動與重啟的不同行為
- 啟動階段:子進程是「佔位進程」
- Django 服務看似啟動,但背景任務並未真正啟動。
AppConfig.ready()
可能沒有被調用,或即使調用,啟動的Thread
也可能因進程狀態不穩定而終止。
- 文件變更觸發重啟:進入真正的應用服務進程
- 當你修改專案中的任何
.py
文件,StatReloader
會終止「佔位進程」,並啟動一個全新的「應用服務進程」。 - 這個過程中,Django 會完整調用
AppConfig.ready()
,此時爬蟲腳本才會正確啟動,背景執行緒 (Thread
) 也能正常運行。
- 當你修改專案中的任何
解決方案:讓 scraper.py
獨立運行
在 Django 開發模式中,為了確保爬蟲腳本 (scraper.py
) 在 Docker 中穩定運行,我們的解決方案目標如下:
- 獨立服務運行:讓
scraper.py
在 Docker 中作為一個獨立的服務運行,不再依賴 Django 服務 (web
) 啟動。 - 避免受到影響:當 Django 使用自動重新載入機制 (
StatReloader
) 重啟時,爬蟲服務不會被中斷。 - 保持開發便捷性:仍然可以使用 Django 的開發模式 (
python manage.py runserver
),即時查看應用變更效果。
sequenceDiagram participant Dev as 開發者 participant Docker as Docker participant Django as Django 服務 (web) participant Scraper as 獨立爬蟲服務 (scraper) Dev->>Docker: 修改 docker-compose.yml,新增 scraper 服務 Docker->>Django: 啟動 Django 服務\n(manage.py runserver) Docker->>Scraper: 啟動獨立爬蟲服務\n(scraper.py) Note over Scraper: 爬蟲服務獨立於 Django,\n不受 Django 重啟影響 Dev->>Django: 修改 Django 專案檔案 (例如 views.py) Django-->>Django: StatReloader 自動重啟 Django 服務 Note over Django: Django 服務成功重啟,\n但不影響爬蟲服務 Dev->>Scraper: 使用 docker logs -f scraper 查看爬蟲日誌 Scraper->>Dev: 顯示爬蟲運行狀況\n"✅ 每日 01:00 執行爬蟲排程" Dev->>Scraper: 爬蟲持續執行,自動化任務不中斷 Scraper->>Scraper: 爬蟲服務穩定運行
流程說明:從配置到穩定運行
- 開發者配置 Docker 服務
- 動作:開發者首先修改
docker-compose.yml
,新增一個名為 scraper 的服務,確保爬蟲腳本在 Docker 中獨立運行。 - 目的:避免爬蟲腳本依賴於 Django 服務 (
web
),進而避免受StatReloader
重啟機制影響。
- 動作:開發者首先修改
- Docker 啟動兩個獨立服務
- Django 服務 (
web
) 啟動:執行manage.py runserver
,進入 開發模式,啟用自動重新載入 (StatReloader
) 功能,方便開發者即時查看專案變更效果。 - 爬蟲服務 (
scraper
) 啟動:Docker 同時啟動scraper.py
,這個服務獨立於 Django 之外,確保爬蟲腳本在後台持續執行,不受 Django 重啟影響。
- Django 服務 (
services:
web:
command: ["python3", "manage.py", "runserver", "0.0.0.0:8000"]
scraper:
command: ["python3", "/app/music_events/scripts/scraper.py"]
- 開發者修改 Django 應用程式檔案
- 動作:開發者修改專案中的任意 Django 檔案(例如
views.py
或models.py
),此時StatReloader
會自動偵測變更,重啟 Django 服務。 - 結果:Django 服務順利重啟,但因為爬蟲服務是獨立運行的,所以不會受到影響。
- 動作:開發者修改專案中的任意 Django 檔案(例如
- 檢視爬蟲服務日誌
- 操作:開發者透過指令
docker logs -f scraper
查看爬蟲服務的執行狀態。 - 回饋:爬蟲服務日誌顯示出:
- 操作:開發者透過指令
✅ 每日 01:00 執行爬蟲排程
- 這意味著爬蟲腳本已成功設定每日自動執行的排程,並且在 Django 重啟的情況下依然保持穩定運行。
- 持續運行,穩定無中斷
- 爬蟲服務保持獨立運行,即使開發過程中頻繁修改 Django 專案,爬蟲腳本仍能每日按時運行,確保自動化數據抓取任務不中斷。
- 關鍵優勢:透過服務分離實現「高開發效率」與「自動化穩定性」的雙贏效果。
為什麼這樣設計更好?
- 避免相互干擾:Django 重啟不影響爬蟲腳本,讓開發與自動化任務彼此獨立。
- 提升專案穩定性:無需擔心每次應用程式變更都會中斷爬蟲執行。
- 高效監控與排錯:可單獨透過
docker logs -f scraper
查看爬蟲服務日誌,問題排查更精確。
原本的錯誤配置:爬蟲依賴 Django 服務
以下是 原始的 docker-compose.yml
配置,在這個配置中,並沒有將爬蟲腳本 (scraper.py
) 設置為獨立的服務:
services:
web:
build: .
environment:
- TZ=Asia/Taipei
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- mongodb
mongodb:
image: mongo:latest
container_name: mongo_db
restart: always
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
volumes:
mongodb_data:
錯誤點分析:為什麼這樣配置會失敗?
1. 爬蟲服務沒有獨立,依賴 Django 啟動
- 爬蟲腳本 (
scraper.py
) 只是作為web
服務的一部分運行,與 Django 服務 (manage.py runserver
) 共享同一個容器。 - 這意味著 爬蟲的運行完全依賴於 Django,需要等到
web
服務啟動後,爬蟲腳本才會執行。
2. Django 重啟會導致爬蟲中斷
- 在 Django 開發模式中,
StatReloader
會監聽檔案變更,並自動重啟web
服務。 - 當
web
服務重啟時,所有屬於該服務的進程(包括爬蟲腳本)都會被中斷,導致爬蟲排程無法持續運行。 - 自動化任務(例如每日定時數據抓取)可能因此無法按時執行,影響專案的穩定性。
正確解法:將爬蟲服務獨立運行,避免相互影響
在 docker-compose.yml
中新增 scraper
服務
為了解決上述問題,我們可以透過修改 docker-compose.yml
,將爬蟲腳本 (scraper.py
) 獨立出來作為單獨的 Docker 服務,與 Django (web
) 分離,達到兩者互不影響的效果。
更新後的配置:docker-compose.yml
version: "3.8"
services:
web:
build: .
environment:
- TZ=Asia/Taipei
ports:
- "8000:8000"
volumes:
- .:/app
depends_on:
- mongodb
command: ["python3", "manage.py", "runserver", "0.0.0.0:8000"]
scraper:
build: .
environment:
- TZ=Asia/Taipei
depends_on:
- mongodb
command: ["python3", "/app/music_events/scripts/scraper.py"]
mongodb:
image: mongo:latest
container_name: mongo_db
restart: always
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
volumes:
mongodb_data:
正確配置的關鍵點:分工明確,保持穩定
web
服務:負責運行 Django (manage.py runserver
),保持開發模式的便捷性,讓開發者可以即時看到應用變更效果。scraper
服務:獨立運行爬蟲腳本 (scraper.py
),避免受到 Django 重啟 (StatReloader
) 影響。mongodb
服務:資料庫支持部分保持不變,為 Django 和爬蟲服務提供數據存取功能,確保應用數據一致性。
後續操作:重新啟動 Docker 容器,應用新配置
在修改完成後,請按照以下步驟重啟 Docker 容器,確保服務正確啟動並應用最新的設定:
docker compose down
docker compose build
docker compose up -d
docker logs -f scraper
- 關閉舊容器 (
down
):清理資源,避免舊服務影響新配置。 - 重新構建服務 (
build
):構建最新的映像檔,確保代碼與設定都正確應用。 - 背景啟動 (
up -d
):讓服務在後台運行,保持伺服器命令行簡潔。 - 查看爬蟲日誌 (
logs -f scraper
):監控爬蟲腳本的運行情況,確保自動化任務正確啟動。
確認服務成功啟動:爬蟲腳本正常運行的指標
當執行上述指令後,若配置正確,應該會在終端機中看到以下日誌輸出:
✅ [DEBUG] start_scheduler() 被呼叫
✅ 爬蟲排程已啟動,每日 01:00 執行
📈 這表示:
- 爬蟲服務已經正確啟動:
start_scheduler()
正常運行,背景任務已經被成功安排。 - 自動化排程設定完成:爬蟲腳本會在每日 01:00 自動執行,確保數據抓取任務穩定進行,不受 Django 服務重啟影響。
總結
- 解耦 Django 與爬蟲腳本:確保開發模式下 Django 能自動重新載入,爬蟲卻保持連續運行。
- 提升開發與運行效率:不管 Django 應用如何頻繁變更,爬蟲腳本始終如一,每天 01:00 自動執行。
- 實現自動化任務的穩定性:這樣的設計能讓你的應用程式在開發與生產環境中,都能保持高度的穩定性和自動化效率!
這樣的架構設計不僅符合開發者在開發模式下的快速迭代需求,還能確保數據爬蟲任務自動化地穩定運行,是構建 Django + 爬蟲專案的最佳實踐!😊