前往
大廳
主題 達人專欄

【爬蟲 + React + SQLite】實現無後端SQL查詢,養生寫前端

%%鼠 拒收病婿 | 2022-11-07 02:54:05 | 巴幣 2038 | 人氣 1853

製作動機

學校現有的課表查詢系統為教務處的教務資訊系統,其有幾個缺點:
  1. 無法正確回傳使用者所設定的區間的資料(例如使用者僅查詢第 7、8 節的課,系統卻會回傳 5~8 節的課)
  2. 無法合併查詢(例如使用者不能同時查詢五專部與四技部的課程)。
因此本專案特別針對該問題進行改善,架設查詢網站,除了提供使用者更好的搜尋外,也額外開放 API 與檔案存取,將校務資訊作成開放資料以方便未來開發者。
 
備註:原本是爬蟲課堂作業,後來想說乾脆延伸做個完整查詢介面

網頁API:https://lontoone.github.io/Nutc_Cls/#/api/?&(串接查詢參數)
程式一樣可以在個人網站


網站外觀


架構

下圖為本專案系統架構,包含基於React框架之前端介面、API網址查詢、Python爬蟲腳本、與彙整後的資料庫SQLite。csv檔可有可無,保留輸出與輸入csv的功能是為了(1.)方便查看爬蟲結果、(2)若csv檔案存在則優先從csv匯入資料至SQLite,就不需要進行爬蟲、(3)給老師檢查用
((使用SQLite而非真正的DB是因為我懶得架後端(x

 
下圖為本專案執行流程,為了方便做到一鍵更新資料,我在RunUpdate.py寫了cmd指令,使其自動執行爬蟲與更新至線上網站資料,若要手動爬蟲可單獨執行scrap.py。
關於自動執行程式與上傳github的指令我以前的網站用過一樣的方式,這次加點os語法讓路徑不會那麼死。

Python 爬蟲

以往我會直接從網頁傳輸(Network Log)中尋找該網頁回傳純資料的API call,但經過嘗試後仍無法理解學校教務系統所回傳的資料格式,因此只好使用URL Query。
(回傳的資料找不太到規律。)

以這串查詢網址為例:

參數說明如下:
l   sem表學期,例如1111為第111學年上學期;1112為下學期。
l   sch_type表學制,0=全部、1=五專、3=二技、4=四技、8=碩班。
l   weekday表星期,1~7分別表星期一到星期日。
l   start_section表開始節。
l   end_section表結束節。
l   _p表頁數。

網頁資料架構


除了第一排tr是表頭外,其餘tr內的td所代表之資料順序如下:

該資料有兩個問題點:(1.) 部分資料被合併顯示成一筆資料:例如 星期一第1~3節 (3708) 、(2.) 數字有的為全形:例如 3 / 3。分別需使用Regex拆資料與將全形轉半形。

取得最大頁數

為了知道爬蟲範圍,需要取得最大頁數,而教務處網站的最大頁數藏在「>>」符號的url最後一個參數中。

先透過BeautifulSoup找出頁碼最後一個元素,再用Regex得到_p參數值。

爬蟲主邏輯

寫入CSV

為了方便,我統一在物件內寫好回傳欄位屬性與值的方法:


就能直接讀取keys()或values()作為欄位與值。

寫入SQLite


初步預覽:

Web前端與SQLite

使用Sql.js套件讀取Sqlite檔案。由於前端讀取檔案需要引入fs插件,但React v5以後禁止引入node.js的fs,因此本專案前端使用React v4版開發。
使用React V5會出現這個ERROR:
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.

初步設定SQL.js

安裝

引入sql.js wasm

在上一篇Unity文章中,我們探討Assembly的使用,而Wasm(WebAssembly)概念類似,可以想像成給瀏覽器的Assembly檔案。
從官網下載wasm檔,並引入。

查詢與回傳方法

基於安全,瀏覽器禁止js程式直接讀取路徑上的資料,因此這邊使用HttpRequest,將資料以二進位的方式讀入,在瀏覽器端重建資料庫。

由於在前端重建資料庫是相當大的工程,而React一個沒設計好又會觸發頁面刷新,導致網頁崩潰,因此要盡量避免useState的使用。

React Memo

紀錄函數結果,若指定的參數一樣時就直接回傳結果,不用再執行一次複雜的函數。
語法
dependencies 有更動時就會重新執行YourFunc函式。
Memo的機制適用於函數複雜、不常更動的元件,由於Memo使用淺比較(shallow comparison)、占用記憶體,執行上會比使用React基本刷新機制複雜。因此需先衡量各自的優劣喔。

範例:我用Memo紀錄下GetDb查詢結果,只有當sql改變時才會重新執行查詢。
因為GetDb是異步程式,所以這邊傳入一個函數去接查詢結果。

UseRef

用一個變數記錄著所指定的元件。 和useState差別在於useRef不會造成頁面刷新。
一些React基礎教學可能會像下面這樣,直接在value觸發useState,這實際上會造成很大的效能問題。假設一字一字的在這個input box打了5000字,就代表這個頁面至少被刷新了5000次,每次刷新所造成的垃圾足以讓網頁crash掉。

useRef使用方式

Craco

全名是Create React App Configuration Override,顧名思義就是讓我們能覆寫webpack config檔的套件。

(webpack協助我們整合與打包專案。)
為了讓專案能載入wasm檔案,在config檔案下增加:

HashRouter

大概是React改版了,原本的<Switch>被改成<BrowserRouter>,但因為Github page不支援BrowserRouter,因此改用HashRouter。
我猜是因為BrowserRouter的url會發送request給後端,但github page不支援後端才會被擋。
範例用法:

HashRouter網址前面須加#字號,且不會發送request,例如http://localhost:3000/#/api

最後我將sql查詢做成接口,使用者可透過在網址輸入參數做查詢,範例網址如下:
(不知道是不是React在搞,?後要先接一個&,不然第一個參數會被吃掉。)
 
也因為HashRouter的網址多了#字號,導致解析網址參數的函式URLSearchParams失效,需要多一步處理。


 
雜談:
Unity資料驅動下篇再...再等一下( ̄┰ ̄*),期中考,きもち問題。



送禮物贊助創作者 !
0
留言

創作回應

樂小呈
瘋狂期中 ( ̄┰ ̄*)
2022-11-07 07:19:37
%%鼠 拒收病婿
頂樓風好大
2022-11-07 21:31:05
Eric.k
先推再讀
2022-11-07 18:58:40
%%鼠 拒收病婿
謝謝(´▽`ʃ♡ƪ)
2022-11-07 21:31:21
多古尼爾拉布拉布拉格
說起來既然是CSV檔
那為什麼不是考慮存進前端原生ㄉindexDB然後再撈出來ㄋ(?
或是直接js去filter之類ㄉ
考慮到可能是撈出來的複雜度問題嗎
2022-11-07 21:22:09
%%鼠 拒收病婿
除了SQL query比較好編輯外,還考慮到執行速度吧
來源:https://core.ac.uk/download/pdf/43558548.pdf

https://d3i71xaburhd42.cloudfront.net/4ae401bfca4442fe278cafe4da68f336ec5ae8fd/7-Figure4-1.png
2022-11-07 21:30:08
光化靜翔
我測試的結果是含有#的網址URLSearchParams還是可以處理
2022-11-08 00:57:48
%%鼠 拒收病婿
感謝分享[e16]

我當初跑不出來會是環境的因素嗎[e23]
2022-11-08 01:13:39
傑森五德
鬼欸
2023-02-03 19:09:24
追蹤 創作集

作者相關創作

更多創作