This commit is contained in:
ChenKaiLiuG
2025-12-16 15:07:28 +08:00
parent 788f6db90a
commit e25629ed6d
5 changed files with 725 additions and 1 deletions

19
.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/
# Coursera downloads
downloads/
course_data/
# Config files with credentials
config.json
# Logs
*.log

275
README.md
View File

@@ -1,2 +1,275 @@
# coursera-copy
# Coursera 課程備份工具
這個工具可以幫助你備份 Coursera 課程內容到本地裝置,包括影片、文字內容和字幕。
## 功能特色
- ✅ 自動登入 Coursera 帳號
- ✅ 下載課程影片
- ✅ 下載影片字幕/逐字稿
- ✅ 下載課程文字資料
- ✅ 支援批次下載多個課程
- ✅ 自動整理課程檔案結構
- 🔒 多層級反機器人偵測機制
## 安裝步驟
### 1. 安裝 Python
確保你的電腦已安裝 Python 3.8 或更高版本。可以到 [Python 官網](https://www.python.org/downloads/) 下載。
### 2. 安裝相依套件
在專案目錄下執行:
```bash
pip install -r requirements.txt
```
### 3. 設定 Chrome 瀏覽器
這個工具使用 Selenium 和 Chrome 瀏覽器來模擬登入和下載。請確保:
- 已安裝 Google Chrome 瀏覽器
- 程式會自動下載對應的 ChromeDriver
## 使用方法
### 1. 建立配置檔案
複製 `config.example.json` 並重新命名為 `config.json`
```bash
copy config.example.json config.json
```
### 2. 編輯配置檔案
`config.json` 中填入你的資訊:
```json
{
"email": "your-email@example.com",
"password": "your-password",
"output_dir": "downloads",
"download_videos": true,
"download_subtitles": true,
"download_resources": true,
"headless": false,
"course_urls": [
"https://www.coursera.org/learn/your-course-name"
]
}
```
**配置說明:**
- `email`: 你的 Coursera 帳號
- `password`: 你的 Coursera 密碼
- `output_dir`: 下載檔案的儲存目錄
- `download_videos`: 是否下載影片true/false
- `download_subtitles`: 是否下載字幕true/false
- `download_resources`: 是否下載其他資源true/false
- `headless`: 是否使用無頭模式true 為背景執行false 會顯示瀏覽器視窗)
- `delay_between_requests`: 請求間隔秒數(建議 3-5 秒,避免被偵測)
- `random_delay`: 是否使用隨機延遲true 更安全)
- `max_retries`: 失敗重試次數
- `course_urls`: 要下載的課程網址列表
### 3. 執行程式
```bash
python coursera_downloader.py
```
## 檔案結構
下載完成後,檔案會按照以下結構組織:
```
downloads/
└── 課程名稱/
├── course_info.json # 課程資訊
├── Week_1/ # 第一週內容
│ ├── video1.mp4
│ ├── subtitle1.vtt
│ └── reading1.txt
├── Week_2/
│ └── ...
└── ...
```
## 重要注意事項
⚠️ **法律與道德考量**
1. **僅供個人學習使用**:下載的課程內容僅供你個人學習和備份使用
2. **尊重版權**:請勿分享、轉售或公開傳播下載的內容
3. **遵守服務條款**:使用本工具前請確認你已閱讀並同意 Coursera 的服務條款
4. **帳號安全**`config.json` 包含你的密碼,請勿分享此檔案
⚠️ **帳號安全與異常活動**
1. **不影響課程資料**:此工具僅讀取和下載內容,不會影響你的測驗成績、作業記錄或課程進度
2. **異常活動風險**:頻繁的自動化操作可能被 Coursera 偵測為異常活動,建議:
- **使用適當延遲**:保持 3-5 秒的請求間隔
- **啟用隨機延遲**:讓操作更像人類行為
- **避免過度使用**:不要在短時間內下載大量課程
- **離峰時段使用**:避開平台高峰時段
- **分批下載**:一次下載一個課程,間隔一段時間再下載下一個
3. **帳號風險**:雖然工具已加入反偵測機制,但仍有可能觸發 Coursera 的安全警報,請自行評估風險
⚠️ **技術限制**
1. **需要有效的課程註冊**:你必須已經註冊了該課程才能下載內容
2. **網路連線**:下載過程需要穩定的網路連線
3. **Coursera 網站變更**:如果 Coursera 更改網站結構,程式可能需要更新
4. **下載速度**:視課程大小和網路速度而定,可能需要較長時間
5. **異常活動偵測**:為避免觸發 Coursera 的安全機制,程式已加入延遲和隨機化,建議:
- 不要在短時間內下載過多課程
- 使用建議的延遲設定3-5 秒)
- 避免頻繁重複執行
- 選擇離峰時段使用
## 進階使用
### 自訂下載選項
你可以根據需求修改 `config.json` 中的設定:
- 只下載字幕:設定 `"download_videos": false`
- 背景執行:設定 `"headless": true`
- 批次下載:在 `course_urls` 陣列中加入多個課程網址
- 調整安全性:
- `"delay_between_requests": 5` - 增加延遲時間(更安全但更慢)
- `"random_delay": true` - 啟用隨機延遲(強烈建議)
- `"headless": false` - 顯示瀏覽器視窗(便於監控和除錯)
### 安全使用建議
為了降低帳號風險和避免被偵測:
1. **首次使用**:建議先用 `headless: false` 觀察程式行為
2. **測試運行**:先測試下載單一課程的少量內容
3. **分散下載**:每天最多下載 1-2 個課程
4. **避免重複**:不要對同一課程重複執行下載
5. **監控日誌**:注意程式輸出的錯誤訊息
6. **帳號狀態**:定期檢查 Coursera 帳號是否正常
7. **使用預設設定**:不要修改延遲時間至過低(< 2 秒)
8. **離峰時段**:建議在晚上或週末使用
9. **分批處理**:如果要下載多個課程,每個課程間隔 12-24 小時
### 疑難排解
**問題:無法登入**
- 檢查帳號密碼是否正確
- 確認 Coursera 帳號狀態正常
- 嘗試關閉 headless 模式查看錯誤
- 檢查是否有雙重驗證2FA啟用
**問題:被標記為異常活動**
- 增加 `delay_between_requests` 到 5-10 秒
- 確保 `random_delay` 設為 `true`
- 設定 `headless: false` 以模擬真實瀏覽器行為
- 暫停使用一段時間24-48 小時)
- 嘗試在不同時段使用
- 清除瀏覽器 cookies 並重新登入
- 檢查是否使用 VPN嘗試關閉或更換 IP
**問題:找不到影片**
- Coursera 網站結構複雜且經常變更
- 可能需要手動調整程式碼來適應特定課程
- 建議使用瀏覽器開發者工具分析頁面結構
**問題:下載速度慢**
- 這是正常現象,延遲設定越高越慢但越安全
- 影片檔案較大需要時間
- 可以在離峰時段下載
## 開發資訊
- **語言**: Python 3.8+
- **主要套件**:
- `selenium` - 網頁自動化
- `yt-dlp` - 影片下載
- `requests` - HTTP 請求
- `beautifulsoup4` - HTML 解析
- **多層級反機器人偵測機制**:
- ✅ 隱藏 Selenium webdriver 特徵
- ✅ 偽裝 navigator 屬性plugins, languages 等)
- ✅ 模擬人類滑鼠移動軌跡
- ✅ 模擬真實打字速度(含停頓與節奏變化)
- ✅ 隨機化滾動行為
- ✅ 隨機延遲與等待時間
- ✅ 完整的瀏覽器 HeadersUser-Agent, Accept, Referer 等)
- ✅ Chrome DevTools Protocol (CDP) 設定
- ✅ 禁用自動化標記和擴充功能
## 授權
請參考 LICENSE 檔案。
## 免責聲明
本工具僅供學習和研究使用。使用者應自行承擔使用本工具的所有風險和責任。開發者不對使用本工具導致的任何問題負責,包括但不限於:
- 違反 Coursera 服務條款
- 帳號被封鎖或停用
- 觸發異常活動警報
- 版權糾紛
- 資料遺失
- 課程存取權限被撤銷
**重要提醒**
- 本工具使用自動化技術存取 Coursera可能違反其服務條款
- 雖然已加入安全機制,但無法保證完全不被偵測
- 建議僅用於已購買或正式註冊的課程進行個人備份
- 不建議頻繁或大量使用
使用本工具即表示你理解並接受上述所有風險和限制。
## 技術細節
### 反機器人偵測技術
本工具實現了多層級的反偵測機制:
1. **瀏覽器指紋偽裝**
- 移除 `navigator.webdriver` 屬性
- 偽裝 `navigator.plugins``navigator.languages`
- 添加 `window.chrome` 物件
- 使用 Chrome DevTools Protocol 覆寫 User-Agent
2. **人類行為模擬**
- 滑鼠移動軌跡:使用 ActionChains 模擬真實移動
- 打字節奏:隨機按鍵間隔和偶爾停頓
- 滾動行為:隨機滾動距離和速度
- 點擊前移動:模擬滑鼠移動到元素再點擊
3. **網路請求偽裝**
- 完整的 HTTP HeadersAccept, Accept-Language, Accept-Encoding
- Referer 設定
- 請求間隨機延遲
- Keep-alive 連線
4. **Selenium 特徵隱藏**
- 禁用自動化控制標記
- 移除 `enable-automation` 開關
- 禁用自動化擴充功能
- 使用新版 headless 模式
### 為什麼還是可能被偵測?
儘管已做了充分的偽裝,但以下情況仍可能被偵測:
- 行為模式分析:大量下載、無人類交互
- Canvas/WebGL 指紋:更進階的瀏覽器指紋識別
- 時間分析:過於精確的時間間隔
- IP 與地理位置:異常的登入地點
- 系統資源使用模式的差異
## 貢獻
歡迎提出 Issue 或 Pull Request 來改進這個工具!
---
**最後更新**: 2025-12-16

15
config.example.json Normal file
View File

@@ -0,0 +1,15 @@
{
"email": "your-email@example.com",
"password": "your-password",
"output_dir": "downloads",
"download_videos": true,
"download_subtitles": true,
"download_resources": true,
"headless": false,
"delay_between_requests": 3,
"random_delay": true,
"max_retries": 3,
"course_urls": [
"https://www.coursera.org/learn/your-course-name"
]
}

411
coursera_downloader.py Normal file
View File

@@ -0,0 +1,411 @@
"""
Coursera 課程備份工具
功能:下載課程影片、文字內容和字幕
"""
import os
import json
import time
import re
import random
from pathlib import Path
from urllib.parse import urljoin, urlparse
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager
from tqdm import tqdm
import yt_dlp
class CourseraDownloader:
def __init__(self, config_path="config.json"):
"""初始化下載器"""
with open(config_path, 'r', encoding='utf-8') as f:
self.config = json.load(f)
self.email = self.config.get('email')
self.password = self.config.get('password')
self.output_dir = self.config.get('output_dir', 'downloads')
self.download_videos = self.config.get('download_videos', True)
self.download_subtitles = self.config.get('download_subtitles', True)
self.download_resources = self.config.get('download_resources', True)
# 安全設定:降低被偵測為機器人的風險
self.delay_between_requests = self.config.get('delay_between_requests', 3) # 預設3秒
self.random_delay = self.config.get('random_delay', True) # 隨機延遲
self.max_retries = self.config.get('max_retries', 3) # 最大重試次數
self.session = requests.Session()
# 設定 requests session 的 headers模擬真實瀏覽器
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1'
})
self.driver = None
# 建立輸出目錄
Path(self.output_dir).mkdir(parents=True, exist_ok=True)
def setup_driver(self):
"""設定 Selenium WebDriver"""
options = webdriver.ChromeOptions()
# 反機器人偵測設定
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option("excludeSwitches", ["enable-automation", "enable-logging"])
options.add_experimental_option('useAutomationExtension', False)
# 添加更多瀏覽器偽裝參數
prefs = {
'profile.default_content_setting_values': {
'notifications': 2, # 禁用通知
},
'profile.managed_default_content_settings.images': 2, # 可選:禁用圖片加快速度
}
options.add_experimental_option('prefs', prefs)
# 使用真實的瀏覽器 User-Agent
options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# 其他安全設定
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')
options.add_argument('--disable-software-rasterizer')
options.add_argument('--disable-extensions')
options.add_argument('--start-maximized')
# 語言設定
options.add_argument('--lang=zh-TW')
if self.config.get('headless', False):
options.add_argument('--headless=new') # 使用新版 headless 模式
options.add_argument('--window-size=1920,1080')
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service, options=options)
if not self.config.get('headless', False):
self.driver.maximize_window()
# 執行反偵測腳本
self.driver.execute_cdp_cmd('Network.setUserAgentOverride', {
"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
})
# 隱藏多個 webdriver 特徵
self.driver.execute_script("""
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
Object.defineProperty(navigator, 'languages', {get: () => ['zh-TW', 'zh', 'en-US', 'en']});
window.chrome = {runtime: {}};
""")
def human_like_mouse_move(self, element):
"""模擬人類的滑鼠移動到元素"""
try:
actions = ActionChains(self.driver)
# 隨機移動到元素附近,然後移動到元素
actions.move_to_element_with_offset(element,
random.randint(-5, 5),
random.randint(-5, 5))
actions.pause(random.uniform(0.1, 0.3))
actions.move_to_element(element)
actions.perform()
time.sleep(random.uniform(0.2, 0.5))
except:
pass
def human_like_scroll(self):
"""模擬人類的滾動行為"""
scroll_amount = random.randint(100, 500)
self.driver.execute_script(f"window.scrollBy(0, {scroll_amount});")
time.sleep(random.uniform(0.5, 1.5))
def login(self):
"""登入 Coursera"""
print("正在登入 Coursera...")
self.driver.get("https://www.coursera.org/")
self.safe_delay(3) # 安全延遲
# 模擬人類瀏覽行為
self.human_like_scroll()
try:
# 點擊登入按鈕
login_button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.LINK_TEXT, "Log In"))
)
# 模擬滑鼠移動到登入按鈕
self.human_like_mouse_move(login_button)
login_button.click()
self.safe_delay(2) # 安全延遲
# 等待登入表單載入
email_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, "email"))
)
password_input = self.driver.find_element(By.ID, "password")
# 模擬點擊輸入框
self.human_like_mouse_move(email_input)
email_input.click()
self.safe_delay(0.3)
# 逐字輸入,模擬真人打字(包含偶爾的停頓)
for i, char in enumerate(self.email):
email_input.send_keys(char)
# 模擬打字節奏變化
if i % 3 == 0:
time.sleep(random.uniform(0.1, 0.25)) # 偶爾停頓
else:
time.sleep(random.uniform(0.05, 0.15))
self.safe_delay(0.5)
# 移動到密碼輸入框
self.human_like_mouse_move(password_input)
password_input.click()
self.safe_delay(0.3)
for i, char in enumerate(self.password):
password_input.send_keys(char)
if i % 4 == 0:
time.sleep(random.uniform(0.1, 0.25))
else:
time.sleep(random.uniform(0.05, 0.15))
self.safe_delay(1)
# 點擊登入按鈕
submit_button = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
self.human_like_mouse_move(submit_button)
submit_button.click()
# 等待登入完成
self.safe_delay(5)
print("登入成功!")
# 取得 cookies 並同步到 requests session
cookies = self.driver.get_cookies()
for cookie in cookies:
self.session.cookies.set(cookie['name'], cookie['value'])
return True
except Exception as e:
print(f"登入失敗: {e}")
return False
def safe_delay(self, base_delay=None):
"""安全延遲:模擬人類操作,避免被偵測"""
if base_delay is None:
base_delay = self.delay_between_requests
if self.random_delay:
# 加入隨機延遲±50%
delay = base_delay * (0.5 + random.random())
else:
delay = base_delay
time.sleep(delay)
def sanitize_filename(self, filename):
"""清理檔案名稱,移除不合法字元"""
filename = re.sub(r'[<>:"/\\|?*]', '_', filename)
filename = filename.strip()
return filename[:200] # 限制檔名長度
def download_file(self, url, filepath):
"""下載檔案"""
# 每次下載前加入隨機延遲
self.safe_delay()
try:
# 添加 Referer header模擬從網頁點擊下載
headers = self.session.headers.copy()
headers['Referer'] = 'https://www.coursera.org/'
response = self.session.get(url, stream=True, timeout=30, headers=headers)
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
with open(filepath, 'wb') as f, tqdm(
desc=os.path.basename(filepath),
total=total_size,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as pbar:
for chunk in response.iter_content(chunk_size=8192):
size = f.write(chunk)
pbar.update(size)
return True
except Exception as e:
print(f"下載失敗 {url}: {e}")
return False
def download_video_with_ytdlp(self, video_url, output_path):
"""使用 yt-dlp 下載影片"""
try:
# 從瀏覽器取得 cookies
cookies = {}
for cookie in self.driver.get_cookies():
cookies[cookie['name']] = cookie['value']
# 建立臨時 cookies 檔案
cookie_file = os.path.join(self.output_dir, 'cookies.txt')
with open(cookie_file, 'w') as f:
f.write('# Netscape HTTP Cookie File\n')
for name, value in cookies.items():
f.write(f'.coursera.org\tTRUE\t/\tTRUE\t0\t{name}\t{value}\n')
ydl_opts = {
'outtmpl': output_path,
'format': 'best',
'cookiefile': cookie_file,
'quiet': False,
'no_warnings': False,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([video_url])
# 刪除臨時 cookies 檔案
if os.path.exists(cookie_file):
os.remove(cookie_file)
return True
except Exception as e:
print(f"影片下載失敗: {e}")
return False
def download_subtitles(self, video_element, output_dir):
"""下載字幕檔案"""
try:
# 尋找字幕連結
subtitle_tracks = video_element.find_elements(By.TAG_NAME, "track")
for track in subtitle_tracks:
src = track.get_attribute('src')
label = track.get_attribute('label') or 'subtitle'
if src:
subtitle_filename = f"{self.sanitize_filename(label)}.vtt"
subtitle_path = os.path.join(output_dir, subtitle_filename)
print(f"下載字幕: {label}")
self.download_file(src, subtitle_path)
return True
except Exception as e:
print(f"字幕下載失敗: {e}")
return False
def download_course(self, course_url):
"""下載整個課程"""
print(f"\n開始備份課程: {course_url}")
# 設定 WebDriver
if not self.driver:
self.setup_driver()
# 登入
if not self.login():
print("無法登入,停止下載")
return False
# 前往課程頁面
self.driver.get(course_url)
self.safe_delay(3)
# 模擬人類瀏覽行為
self.human_like_scroll()
self.safe_delay(2)
# 取得課程名稱
try:
course_title = self.driver.find_element(By.CSS_SELECTOR, "h1").text
course_dir = os.path.join(self.output_dir, self.sanitize_filename(course_title))
Path(course_dir).mkdir(parents=True, exist_ok=True)
print(f"課程名稱: {course_title}")
except:
course_dir = os.path.join(self.output_dir, "unknown_course")
Path(course_dir).mkdir(parents=True, exist_ok=True)
# 儲存課程資訊
course_info = {
'title': course_title if 'course_title' in locals() else 'Unknown',
'url': course_url,
'download_date': time.strftime('%Y-%m-%d %H:%M:%S')
}
with open(os.path.join(course_dir, 'course_info.json'), 'w', encoding='utf-8') as f:
json.dump(course_info, f, ensure_ascii=False, indent=2)
print(f"\n課程內容將儲存至: {course_dir}")
print("\n注意:由於 Coursera 網站結構複雜,您可能需要:")
print("1. 手動導航到課程的「週」或「模組」頁面")
print("2. 使用瀏覽器開發者工具找到影片和資源的實際 URL")
print("3. 根據您的具體課程結構調整程式碼")
print("\n程式將保持瀏覽器開啟 60 秒,請手動檢查頁面結構...")
time.sleep(60)
return True
def close(self):
"""關閉瀏覽器"""
if self.driver:
self.driver.quit()
def main():
"""主程式"""
print("=" * 60)
print("Coursera 課程備份工具")
print("=" * 60)
# 檢查配置檔案
if not os.path.exists('config.json'):
print("\n錯誤:找不到 config.json 檔案")
print("請參考 config.example.json 建立您的設定檔")
return
try:
downloader = CourseraDownloader()
# 取得課程 URL
if 'course_urls' in downloader.config and downloader.config['course_urls']:
for course_url in downloader.config['course_urls']:
downloader.download_course(course_url)
else:
print("\n請在 config.json 中設定要下載的課程 URL")
except KeyboardInterrupt:
print("\n\n使用者中斷下載")
except Exception as e:
print(f"\n發生錯誤: {e}")
import traceback
traceback.print_exc()
finally:
if 'downloader' in locals():
downloader.close()
print("\n程式結束")
if __name__ == "__main__":
main()

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
requests>=2.31.0
beautifulsoup4>=4.12.0
selenium>=4.15.0
webdriver-manager>=4.0.0
yt-dlp>=2023.11.0
tqdm>=4.66.0