ceiba-dl
ceiba-dl copied to clipboard
NTU CEIBA 資料下載工具
= NTU CEIBA 資料下載工具
自動下載所有儲存在 https://ceiba.ntu.edu.tw/[臺大 CEIBA 網站] 上的個人資料與課程資料。
== 專案簡介 我在 2016 年 8 月底辦理離校手續之前,走到博雅教學館五樓的教務處教學發展中心服務 臺,詢問關於畢業後 CEIBA 資料保存的問題。當天得到的回覆是,儲存在 CEIBA 上的所 有資料都會永久保留,但是畢業一年後帳號就禁止登入,所以建議在畢業一年內自行備份 資料,因為以後要存取資料就只能向教務處申請了。由於 CEIBA 網站上存放許多課程重要 資料,大多數的課程我在修課時並沒有認真整理和備份資料,為了避免哪一天需要使用時 無法登入,我在辦理離校手續之前就曾經打算使用 https://github.com/shouko/node-ceiba-sdk[NTU CEIBA Node.js SDK] 寫一個自動下載資料的程式,但是當時只接好登入功能就沒有再繼續實作其他功能, 程式碼也沒有公開過。直到 2017 年 5 月底,我才感覺到時間有限,該認真處理備份資料 的事情了。為了節省時間、加快開發速度,我捨棄了之前幾乎沒有功能的 Node.js 程式碼,改用我比較熟悉的 Python 語言和之前 https://github.com/ntu-infoplat/nolcrawler[爬臺大課程網] 資料時使用的 PycURL 和 lxml 來做。這也代表目前這個版本沒有使用到前面提到的非官方 SDK 專案,而是直接 呼叫至今仍未公佈文件的 CEIBA API。然而 CEIBA API 提供的資料不一定完整也 不一定正確,有些課程甚至無法用 CEIBA API 查詢和操作,因此仍有許多功能是直接爬 CEIBA 網頁做出來的。
== 功能區分
. ceiba-dl
,讓使用者方便操作的命令列前端程式。
. ceiba_dl
,所有讀寫設定、登入、下載、解析網頁的類別和函式都在這裡。
. helpers
,讓使用者可以透過帳號密碼輕鬆登入,而不需要手動填寫瀏覽器的 cookie 值。
== 執行需求
- https://www.python.org/[Python 3],編寫時使用的版本是 3.6。
- https://freedesktop.org/wiki/Software/pyxdg/[PyXDG], 用於取得存放設定檔和登入輔助程式的路徑。
- http://pycurl.io/[PycURL],用於連線 CEIBA 網站與下載資料。
- http://lxml.de/[lxml],用於讀取 HTML 文件。
如果你使用 pip
管理 Python 套件,可以執行 pip install -r requirements.txt
來安裝以上的函式庫。
== 選擇性需求
- https://webkitgtk.org/[WebKitGTK], 只有支援 Unix-like 作業系統,用來在登入輔助程式顯示網頁。
- 任何一個 Autoconf 和 Automake 支援使用的 C 編譯器, 這只有在編譯登入輔助程式的時候會用到。
== 開發需求
- https://www.gnu.org/software/autoconf/[Autoconf],用於產生
configure
。 - https://www.gnu.org/software/automake/[Automake],用於產生
Makefile.in
。 - https://www.gnu.org/software/autoconf-archive/[Autoconf Archive],
需要
AX_PYTHON_MODULE
和AX_COMPILER_FLAGS
。
== 安裝說明 目前支援兩種使用方式:
. 用很常見的三個步驟 ./configure
、make
、make install
來將程式安裝到系統中。
. 直接執行 python3 ceiba-dl.py
,但注意這樣不會編譯登入輔助程式,
可能會需要手動編譯,或在執行時手動輸入 cookie 值。
如果是 Arch Linux 的使用者,可以直接從 AUR 安裝 https://aur.archlinux.org/packages/ceiba-dl-git/[ceiba-dl-git] 套件。
== 使用方式
執行 ceiba-dl --help
可以看到程式的版本編號、支援的子指令和選項。
. 首先我們必須執行 ceiba-dl login
登入 CEIBA 網站。登入完成後就會將連線 CEIBA
網站所需要的 cookie 值,連同程式本身各項設定的預設值寫入設定檔中。因為呼叫
CEIBA API 和一般登入 CEIBA 網站所使用的網址不一樣,所以會需要登入兩次。如果
需要使用多個帳號,可以用 -p
參數指定設定檔名稱,預設的設定檔名稱是
default
。注意 CEIBA 網站 cookie 的有效期限通常只有幾個小時,若長時間
未使用,很可能下次使用時會出現錯誤訊息而必須重新登入。
. 接著我們可以執行 ceiba-dl ls
看看有哪些資料想要下載。這個子指令後面可以接
其他參數,例如 ceiba-dl ls 課程
可以列出所有學期的名稱,
而 ceiba-dl ls 課程/104-2
可以看到 104-2
學期的課程列表。
如果不想一個一個資料夾慢慢查看,可以加上 -r
參數把子資料夾的內容一併列出。
我個人目前測試在校外執行 ceiba-dl ls -l -r
大約可以在七分鐘內列完
四年、八個學期的課程、教師、學生資料。
. 決定好要下載的資料就可以執行 ceiba-dl get
了,如果後面沒有接要下載的資料夾
名稱,就代表是下載所有資料。注意下載時如果遇到磁碟上已經有同名的檔案,
會直接被覆寫,不會顯示任何確認或提示訊息,因此建議先開一個空資料夾再開始下載。
重複執行 ceiba-dl get
只會下載有變動過的檔案,因此可能會看到有很長一段時間
程式都沒有顯示下載進度訊息,這代表目前正在處理的檔案和資料夾與上次下載時相同,
不需要再次下載。
. 雖然程式本身會用檔案大小和內容之類的資訊減少重複下載所需的時間,但仍然要注意 很多時候程式並沒有辦法檢查 CEIBA 網站是否因為功能故障導致回傳錯誤資訊。 如果大部分資料都已經下載過,重複執行時卻有許多不應該有變動的資料被重複下載, 則應該先暫停下載工作,檢查下載到的資料是否正確,再決定是否繼續下載。為了避免 重複下載時因為 CEIBA 網站故障導致已下載的正確資料被取代成網站上的錯誤資料, 可以考慮和第一次下載時一樣,先在空資料夾內下載,完成後用其他程式比較差異。
== 設定檔存放位置
預設情況下設定檔會存放在 ~/.config/ceiba-dl
資料夾下。設定檔的格式類似 INI
檔案,你可以用文字編輯器修改檔案內容,但建議不要加上註解,因為註解會在下次執行
ceiba-dl login
寫入設定檔時被刪除,設定值本身則沒有這個問題,可以放心修改。
若要更改設定檔存放位置,可以設定 XDG_CONFIG_HOME
環境變數,詳細說明請參考
https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html[
XDG Base Directory Specification]。
如果不知道目前使用的設定檔位置,可在執行時加上 --log-level INFO
來查看。
== 登入輔助程式搜尋路徑
預設情況下會依序搜尋
~/.local/share/ceiba-dl/helpers
、 $PREFIX/share/ceiba-dl/helpers
、
/usr/local/share/ceiba-dl/helpers
、 /usr/share/ceiba-dl/helpers
等資料夾,
其中 $PREFIX
代表的是安裝時傳給 ./configure
的 --prefix
參數,
也就是安裝路徑。如果你沒有使用 ./configure
而是直接執行
python3 ceiba-dl.py
,也會搜尋 ceiba-dl.py
所在資料夾下的 helpers
資料夾。
若要更改搜尋路徑,可以設定 XDG_DATA_HOME
或 XDG_DATA_DIRS
環境變數,
詳細說明仍請參考
https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html[
XDG Base Directory Specification]。
如果想查看目前使用的搜尋路徑和搜尋過程,
可以在執行時加上 --log-level INFO
參數。
== 登入輔助程式輸入輸出格式
登入輔助程式與 ceiba-dl
程式使用標準輸入輸出來溝通。
. 首先要從標準輸入讀入兩行字,第一行表示登入網站的網址,
第二行表示登入成功以後會重導向到的網址開頭。
. 登入輔助程式連上第一行指定的網址,跟隨重導向到登入頁面讓使用者輸入帳號密碼。
. 登入成功以後又會有多次重導向,當目前的網址開頭等於第二行網址時,
表示登入完成,要在標準輸出上印出 OK
。
- 注意第一行網址的開頭可能就是第二行網址,所以必須經過至少一次重導向後 才能開始檢查網址是否和第二行相同。 . 接著要從標準輸入讀入 cookie 查詢請求,每行會有一個 cookie 名稱, 收到以後要在標準輸出上印出 cookie 值。
- 一行輸入對應一行輸出,如果找不到要求的 cookie 就印空白行。 . 讀到空白行或檔案結尾 (EOF) 表示工作完成,結束登入輔助程式。
== ceiba_dl 函式庫操作範例 [source,python]
import ceiba_dl, ceiba_dl.config, ceiba_dl.vfs config = ceiba_dl.config.Config() config.load() True request = ceiba_dl.Request(config.api_cookies, config.web_cookies) vfs = ceiba_dl.vfs.VFS(request, config.strings, config.edit) current_semester_link = vfs.open('課程/目前') vfs.is_regular(current_semester_link) False vfs.is_directory(current_semester_link) False vfs.is_internal_link(current_semester_link) True current_semester_link.read_link() '104-2'
== CEIBA API 簡易操作說明 . 首先要使用 CEIBA API 專用的網址登入: https://ceiba.ntu.edu.tw/course/f03067/app/info_web.php?api_version=2 。 . 登入成功後會被重導向至一個不明的網址: app://index 。 . 接著只要在傳送 HTTP 請求時有包含剛才取得的 cookie 就能使用 CEIBA API 了。 . CEIBA API Endpoint: https://ceiba.ntu.edu.tw/course/f03067/app/login.php?api=1 。
== CEIBA API 參考文件
所有參數都是透過網址的 query string 來傳送,其中 mode
參數表示要使用的功能,
其他參數則要依照各功能使用方式填寫。
-
mode=semester
,用來查詢學號、可用的學期名稱,還有該學期修習的課程。
-
semester
參數表示要查詢的學期名稱,省略則使用 CEIBA 預設的學期。
-
mode=course
,用來查詢與指定課程相關的資訊。
-
semester
參數表示課程所在的學期別,必填。 -
course_sn
參數表示課程在 CEIBA 的代號,必填。 -
class_no
參數表示課程班次,必填,即使是空字串也是要填。
-
mode=read_board
,列出課程討論看板清單。
-
semester
參數表示課程所在的學期別,必填。 -
course_sn
參數表示課程在 CEIBA 的代號,必填。 -
board
參數固定為 0。
-
mode=read_board_post
,下載指定討論看板中的所有文章。
-
semester
參數表示課程所在的學期別,必填。 -
course_sn
參數表示課程在 CEIBA 的代號,必填。 -
board
參數表示看板序號,可用mode=read_board
取得,必填。
除了 mode=semester
以外,其他的功能都要求操作的課程所在學期別要和最近一次呼叫
mode=semester
時選擇的學期別相同。如果沒有遵守這個規則,很可能拿到空白回應、
錯誤資料,或是缺少部份項目的資料。我並不知道為什麼 CEIBA API 在設計時會有這種
限制,這讓 CEIBA 下載工具不容易平行化,而且還需要送出多餘的 mode=semester
來確保目前選定的學期別正確。CEIBA 網頁同樣有類似的限制,只是從選定學期變成
選定課程而已。
CEIBA API 並不只有這幾個,這裡只列出我在 ceiba-dl
中有使用到的。目前 CEIBA API
並沒有官方文件,如果想知道更多操作方法,可以用 Android 手機到 Google Play 安裝
官方應用程式,再從手機中取出 APK 檔案。目前官方應用程式是用 HTML 和 JavaScript
寫成,可以直接從 APK 檔案中取得原始碼,程式碼沒有被混亂過。
== 問答集
=== 這是什麼
這是個把 CEIBA 上的資料轉換成機器和人類都容易讀取的格式,並用檔案系統的形式
呈現的程式。最初的想法是接上 FUSE 成為一個能正常在作業系統中操作的檔案系統,
讓使用者能直接利用現有的備份工具來備份資料。可惜因為太晚開始實作,我已經沒有
時間做掛載功能了。程式內部依然有一棵樹串起所有的資料,但只能透過
ceiba-dl ls
和 ceiba-dl get
之類的指令來存取。
=== 這不是什麼 這不是 CEIBA 作業上傳工具、討論看板發文工具、刷資源分享點閱數工具,也不是 https://ntu-infoplat.github.io/[InfoPlat] 網頁上的「CEIBA 雲端大硬碟」。 這個程式提供的所有功能對 CEIBA 上的資料都是唯讀的,只能下載不能上傳。 刷點閱數是確實發生的事情,但這並不是故意的:因為要下載資源分享頁的資料就必須 點進去查看完整資料,這樣的操作就已經改變點閱數了。它確實可以用來做投影片下載 功能,但仍然需要使用者自己想辦法維持登入 cookie 有效並設定排程自動下載。
=== 我的 pycurl
在 macOS 裝不起來,顯示 ImportError: pycurl: libcurl link-time ssl backend (openssl) is different from compile-time ssl backend (none/other)
簡單來說,請先把 pycurl
解除安裝後再加上環境變數並重裝。如果你的 python
套件管理器是 pip
的話:
[source,sh]
pip uninstall pycurl export PYCURL_SSL_LIBRARY=openssl pip install pycurl
=== 這個程式會將資料快取到檔案嗎?
不會,每次執行都是重新向 CEIBA 下載資料。所有資料都只會快取在記憶體中,
程式結束就自動消失。如果網路狀況穩定,每次執行相同的 ceiba-dl ls
指令花費的時間應該會差不多。
=== 如何查看送出了哪些 HTTP 請求?
執行 ceiba-dl
時加上 --log-level DEBUG
就會全部顯示了。
=== 為什麼一直在送重複的 HTTP 請求?
原因就如同「CEIBA API 參考文件」一節所說,很多操作都必須依照一定的先後順序才能
拿到正確的資料。但問題是,當初設計 ceiba-dl
時是想要提供一個可以隨機存取的檔
案系統,考慮到使用者會用各種不同的順序存取資料,所以很多地方都加上了非必要的
CEIBA API 或 CEIBA 網頁請求以確保之後真正用來下載資料的請求可以成功。我知道很多
HTTP 請求都還是可以透過記錄上一次使用過的參數來避免,但是目前 ceiba_dl.Request
沒有辨識特殊網址並記錄的功能,而我也覺得在沒有請求數量限制的情況下,這並不是個
必須立即解決的問題。
=== 可以同時執行兩個 ceiba-dl
嗎?
只有在兩個 ceiba-dl
使用不同的 cookie 登入時才可以。這也代表著你必須先用 -p
指定不同的設定檔名稱,執行兩次 ceiba-dl -p <設定檔名稱> login
取得兩組不同的
cookie 以後,才能同時執行兩個 ceiba-dl
。執行時也要記得使用 -p
指定不同的
設定檔。 會有這樣的限制同樣是因為「CEIBA API 參考文件」一節所提到的問題,若兩個
ceiba-dl
使用同一組 cookie,很可能因為兩個 ceiba-dl
正在下載的資料屬於不同
學期或不同課程,而導致下載失敗或資料內容錯誤。
=== 伺服器回傳非 JSON 格式資料
這通常表示目前使用的 cookie 已經失效了,必須執行 ceiba-dl login
再次登入才能
繼續使用。如果你有使用 ceiba-dl api
指令手動操作 CEIBA API,也有可能是因為在
使用 mode=course
之前沒有先使用對應的 mode=semester
所造成。
=== 伺服器回傳 HTTP 狀態 302 (Found)
這通常也是表示 cookie 失效,嘗試存取網頁時因為沒有登入,而被重導向到登入頁面,
必須用 ceiba-dl login
重新登入才能繼續操作。我知道 302 Found 在大多數地方
都不會被當成錯誤訊息,但因為正常情況下所有 ceiba-dl
送出的 HTTP 請求都不會
遇到重導向,所以只要不是 200 OK 就會回報成錯誤。
=== 出現 AssetionError
了
這通常代表你找到 ceiba-dl
的 bug 了!為了節省在開發過程中人工測試的時間,我在
程式裡的很多地方加上 assert
來確保下載到的資料和我預期的相符。例如必要的欄位
都存在、頁面中確實包含我想找的表格、表格標題正確之類的。我知道這對使用者來說
可能造成不方便,但這是我在開發過程中很重要的找 bug 方法,如果你遇到了,建議
可以加上 --log-level DEBUG
找出造成問題的課程資料,再回報給我或是自己寫
patch 修正它。
=== 為什麼每個 JSON 檔案都是包含兩個項目的陣列? 這是為了讓使用者可以比較容易知道每個欄位的資料是從哪裡來的。第一項是真正的資料, 第二項則是表示資料來源。如果是從 CEIBA API 取得的資料,資料來源會填上對應 CEIBA API 的欄位名稱;如果是從 CEIBA 網頁爬下來的,資料來源會填上網頁的網址。
=== 有些課程沒有顯示
目前已知有些課程,像是 101-1 學期的「網路與系統管理訓練」,無法透過 CEIBA API
查詢到,也無法使用 CEIBA API 取得課程資訊。由於在我的帳號中就只有這一門課
有這樣的狀況,我也沒猜出發生問題的原因,目前只能由使用者自行 workaround。
如果想要下載這類無法顯示的課程資料,必須先手動找出課程的 CEIBA 代號,
通常可以從網址中的 csn
參數找到。
接著再手動修改設定檔 edit
區段中 add_courses
項目的值:
[source,ini]
[edit] add_courses = [('101-1', 'ce1293'), ('102-1', '38c9db')]
由於手動加入的課程會在第一次操作 ceiba-dl
內部的虛擬檔案系統的時候被加進去,
所以即使是原本不需要連上 CEIBA 就可以使用的 ceiba-dl ls -l /
,現在也會因為需要
先處理手動加入的課程而必須連網,使得許多較簡單的操作處理時間變長。而且因為有些
類型的資料,例如公佈欄、課程內容、討論看板,只有實作利用 CEIBA API 下載,沒有
爬網頁版本,因此手動加入的課程可能會有部份資料沒有辦法下載。
=== 手動加入的課程被放在錯誤的資料夾
如果確認過設定檔填寫的學期別沒有錯誤,卻仍然看到這個警告訊息,通常你在表示同時間
執行了兩個共用同一組 cookie 值的 ceiba-dl
。同時執行多個 ceiba-dl
的時候
一定要用不同的 cookie 值才不會發生互相干擾的狀況。
=== 有些檔案無法下載
目前已知有些在 CEIBA 上有連結的檔案可能因為檔案遺失或權限設定錯誤而無法下載。
如果在手動使用瀏覽器連上 CEIBA 網站下載檔案時依然出現 404 Not Found 或
403 Forbidden 之類無法下載的訊息,則可以手動修改設定檔,在 edit
區段中加入
delete_files
項目以避免在下載時因為少數檔案無法下載使 ceiba-dl
提前結束。
[source,ini]
[edit] delete_files = [ '/課程/101-2/<課程名稱>/討論看板/<看板名稱>/<討論串名稱>/檔案/00867058 101_2校外教學.rar', '/學生/<學號>/<學號>_<照片檔名>.jpg']
=== 有些檔案總是重複下載 這可能有兩種原因:
. 比較常見的一種是,因為「資源分享」功能的點閱數在每次下載時都會不一樣, 造成檔案大小或內容不相同而重複下載。 . 另一種則是下載作業檔案時,可能因為當時上傳的檔案已經遺失,所以雖然 CEIBA 回傳 200 OK,但下載到的只是一個純文字檔,裡面寫著不太有用的錯誤訊息, 告知使用者檔案讀取失敗。
=== 「教師」資料夾是空的
CEIBA 網站不會提供所有教師的帳號列表,因此「教師」資料夾的內容是在存取「課程」
資料夾的過程中,根據教師資訊欄位填出來的。這也是為什麼執行 ceiba-dl ls
時,
「教師」資料夾永遠排在「課程」資料夾之後,因為如果沒有使用過「課程」資料夾,
「教師」資料夾就一定是空白的。不過因為 CEIBA 並沒有限制只能下載和自己課程相關的
教師資料,所以即使 ceiba-dl ls -l -r 課程 教師
沒有顯示你想下載的教師的帳號,
也可以自己手動用 ceiba-dl get /教師/<帳號>
來下載。
=== 「學生」資料夾是空的
CEIBA 網站不會提供所有學生的帳號列表,因此「學生」資料夾的內容也是在存取「課程」
資料夾的過程中,由助教資訊、修課學生、討論看板、作業評語、作業觀摩、資源分享等
功能填出來的。只要沒有存取過「課程」資料夾,「學生」資料夾就一定是空白的。和
「教師」資料夾不同的是,學生資料只能在有開放查詢修課學生名單的課程中查到。只有
在已經存取過有開放查詢修課學生名單的「修課學生」資料夾以後,「學生」資料夾才會
有內容。如果想要下載不在修課學生名單中的學生資料,可以使用
ceiba-dl get /課程/<學期>/<課程>/修課學生 /學生/<學號>
。
其中 <課程> 表示有開放查詢修課學生名單的課程名稱。