Feature Specification: Semantic Scholar + OpenAlex Scraper
Feature Branch: feat/semantic_scholar
Created: 2026-06-04
Updated: 2026-06-05 (rev 2 — 新增 US5/US6、FR-018~FR-023、bug fix 記錄)
Status: In Progress
Input: User description: "新增 Semantic Scholar scraper 至 scraping pipeline,以解決 arXiv rate limit 問題,同時擴大論文來源涵蓋範圍。實作後發現 Semantic Scholar 免費 API 無法個人申請 key,且首次執行即 429;改以 OpenAlex 作為主要免費學術論文 API 並同步實作。"
User Scenarios & Testing (mandatory)
User Story 1 - 設定 Semantic Scholar 來源並自動抓取論文 (Priority: P1)
身為管理員,我希望能在 Scraper Settings 頁面為某個 topic 啟用 Semantic Scholar 來源,設定關鍵字與抓取頻率,讓系統自動定期從 Semantic Scholar 搜尋並收集相關論文。
Why this priority: 這是核心功能。若無此功能,其餘設計均無意義。Semantic Scholar 的搜尋 API 覆蓋 ArXiv、ACM、IEEE 等多個來源,能大幅減少因 ArXiv rate limit 造成的抓取失敗。
Independent Test: 可在管理頁面新增一個 Semantic Scholar 設定卡片並加入 keyword,手動觸發 scrape 後確認資料庫中出現新論文,並確認這些論文顯示在前端論文列表中。
Acceptance Scenarios:
- Given 管理員已登入且選取一個 topic,When 在 Scraper Settings 點擊「啟用 Semantic Scholar」並設定 keyword 與頻率後儲存,Then 系統應新增一筆 Semantic Scholar 設定,並在下次排程週期自動執行搜尋。
- Given Semantic Scholar 設定已啟用且有有效 keyword,When scraper 執行 discover,Then 應回傳符合 keyword 的論文清單(含標題、摘要、作者、發表日期)。
- Given 系統抓到一篇 Semantic Scholar 論文,When 該論文已存在於資料庫(無論是從 ArXiv 還是 Semantic Scholar 抓取),Then 系統應跳過該論文不重複儲存。
User Story 2 - 開放取用論文取得全文供 LLM 分析 (Priority: P2)
身為管理員,我希望系統對有開放取用 PDF 的論文能自動下載並解析全文段落,以提供 LLM 更豐富的分析素材,而不僅限於摘要。
Why this priority: 全文分析的品質顯著優於純摘要分析,尤其對研究方法、實驗結果等段落的標籤提取。
Independent Test: 抓取一篇已知有開放取用 PDF 的論文(例如 ArXiv 上的論文透過 Semantic Scholar 取得),確認其 LLM 分析結果中包含 Introduction、Methods 等段落資訊,而非僅有摘要。
Acceptance Scenarios:
- Given Semantic Scholar 回傳的論文有開放取用 PDF 連結,When scraper 執行 fetch,Then 系統應下載 PDF 並解析出各章節文字,存入論文 metadata。
- Given 論文沒有開放取用 PDF(僅限訂閱),When scraper 執行 fetch,Then 系統應退回使用摘要作為分析內容,不應因缺少 PDF 而中斷流程。
- Given 有 PDF 且解析出多個章節,When LLM 進行分析,Then 應使用完整章節文字(上限 15,000 字)作為分析輸入。
User Story 3 - ArXiv 設定精簡為只使用分類訂閱 (Priority: P3)
身為管理員,我希望 ArXiv 的設定介面只允許設定分類(category)而非關鍵字搜尋,因為 ArXiv 關鍵字搜尋是導致 rate limit 的主因,且這項功能已由 Semantic Scholar 承接。
Why this priority: 此變更是 rate limit 解決方案的配套措施。Semantic Scholar 負責關鍵字搜尋,ArXiv 只負責分類訂閱,兩者各司其職。
Independent Test: 在 ArXiv 設定介面確認「新增 keyword」的輸入區塊已消失,只剩「新增 category」的區塊,且現有 ArXiv category 設定仍正常運作。
Acceptance Scenarios:
- Given 管理員開啟 ArXiv 設定頁面,When 查看 ArXiv 卡片,Then 應只看到 category 管理區塊,keyword 管理區塊已移除。
- Given 資料庫中有舊的
arxiv_keyword資料,When ArXiv scraper 執行,Then 舊的 keyword 應被忽略不使用(不產生查詢),只使用 category。 - Given ArXiv 只設定了 categories(無 keywords),When scraper 執行,Then 應正常依 category 抓取論文,不受影響。
User Story 4 - 以 OpenAlex 抓取學術論文(Priority: P1)
身為管理員,我希望能在 Scraper Settings 頁面為某個 topic 啟用 OpenAlex 來源,設定關鍵字與抓取頻率,讓系統自動定期從 OpenAlex 搜尋並收集相關論文。
Why this priority: Semantic Scholar 免費 API 無法個人申請 key(需機構帳號),且首次請求即觸發 429(IP 層級封鎖)。OpenAlex 為完全免費的學術論文索引 API,無需任何 API key,透過 polite pool(User-Agent 帶 mailto)可穩定取得 10 req/sec。資料來源同等豐富(含 ArXiv、DOI、開放取用 PDF URL、引用數、摘要)。
Independent Test: 在 admin UI 啟用 OpenAlex + 加入 keyword,手動觸發 make scrape SOURCE=openalex LIMIT=3,確認資料庫出現 source = 'openalex' 的論文並顯示於前端。
Acceptance Scenarios:
- Given 管理員已登入且選取一個 topic,When 在 Scraper Settings 點擊「啟用 OpenAlex」並設定 keyword 與頻率後儲存,Then 系統應新增一筆 OpenAlex 設定,並在下次排程週期自動執行搜尋。
- Given OpenAlex 設定已啟用且有有效 keyword,When scraper 執行 discover,Then 應回傳符合 keyword 的論文清單(含標題、摘要、作者、發表日期)。
- Given 系統抓到一篇 OpenAlex 論文且其有 ArXiv ID,When 比對已存在資料庫的論文,Then 系統應使用 ArXiv URL 去重,跳過重複論文。
- Given OpenAlex abstract 以 inverted index 格式回傳,When client 解析論文,Then 應正確還原為可讀純文字。
User Story 5 - 文章來源標示(原始出處 + 聚合器標籤)(Priority: P2)
身為讀者,當我瀏覽文章卡片時,我希望看到文章的真實原始出處(如 "arxiv"、"Nature"、"IEEE Transactions on..."),而非聚合器本身的名稱("openalex" 或 "semantic_scholar"),並在旁邊附上小標籤說明「via OpenAlex」,讓我清楚知道論文的出處與抓取管道。
Why this priority: DOI URL 顯示為 "doi" 毫無意義。讀者需要知道論文來自哪個期刊或 preprint 伺服器,而不只是聚合器。
Independent Test: 觀察 OpenAlex 抓到的 arXiv 論文,卡片應顯示 "arxiv" badge 加上 "via OpenAlex" 小標;期刊論文應顯示期刊名稱(如 "Nature Neuroscience")加上 "via OpenAlex"。
Acceptance Scenarios:
- Given 一篇透過 OpenAlex 抓取且有 ArXiv ID 的論文,When 顯示文章卡片,Then source badge 應顯示 "arxiv",並加上 "via OpenAlex" 小標籤。
- Given 一篇透過 OpenAlex 抓取且無 ArXiv ID(只有 DOI)的論文,When 顯示文章卡片,Then source badge 應顯示期刊名稱(從
primary_location.source.display_name取得),並加上 "via OpenAlex" 小標籤。 - Given 一篇透過 Semantic Scholar 抓取且有 ArXiv ID 的論文,When 顯示文章卡片,Then source badge 應顯示 "arxiv",並加上 "via Semantic Scholar" 小標籤。
- Given 非聚合器來源的論文(
source = "arxiv"直接抓取),When 顯示文章卡片,Then source badge 正常顯示 "arxiv",不顯示 "via" 標籤。
User Story 6 - 文章列表聚合器篩選(Priority: P2)
身為讀者,我希望在文章列表頁能透過聚合器(OpenAlex / Semantic Scholar)來篩選文章,讓我專門檢視某一聚合器抓到的論文。這與現有 Source 篩選是獨立的維度。
Why this priority: 讀者可能想評估 OpenAlex vs Semantic Scholar 的論文品質,或追蹤特定聚合器的覆蓋範圍。
Independent Test: 在 Filter Bar 點選「Aggregator: OpenAlex」,確認列表只顯示 source = 'openalex' 的文章。URL 中出現 aggregator=openalex query param。
Acceptance Scenarios:
- Given 文章列表頁已有 Filter Bar,When 展開 filter panel,Then 應看到「Aggregator」選項,固定提供 "openalex" 與 "semantic_scholar" 兩個選項。
- Given 選擇了 Aggregator: OpenAlex,When 套用篩選,Then URL 加入
aggregator=openalex,文章列表只顯示source = 'openalex'的文章。 - Given 同時選擇了 Aggregator 和 Source 篩選,When 套用,Then 兩者 AND 組合,只顯示同時符合的文章。
- Given Knowledge Graph 頁面,When 展開 Filter Bar,Then Aggregator 篩選同樣出現並正常運作。
Edge Cases
Semantic Scholar:
- 若 Semantic Scholar API 回傳的論文同時有 ArXiv ID,系統應使用 ArXiv URL(
https://arxiv.org/abs/{id})作為去重依據。 - 若 Semantic Scholar API 回傳 HTTP 429,系統應記錄警告並跳過本次執行,不應中斷整條 pipeline。
- 若 keyword 搜尋無結果,系統應正常結束本次 discover,記錄 info log,不視為錯誤。
- 若 PDF 下載失敗或解析失敗,系統應退回使用摘要,不應因此丟棄該論文。
- 若未設定 Semantic Scholar API key,系統應以免費層運作(受限於 IP 配額),不阻斷功能。
OpenAlex:
- OpenAlex 的 abstract 以 inverted index 格式(word → position list)儲存,client 必須在解析時還原為純文字。
- 若論文有 ArXiv ID(
ids.arxiv),系統應使用 ArXiv URL 作為正規化 URL;若有 DOI 則使用 DOI URL;否則使用 OpenAlex URL(https://openalex.org/W...)。 - 若 OpenAlex API 回傳 HTTP 429,系統應記錄警告並跳過本次執行,不應中斷 pipeline。
- polite pool 需在 User-Agent 帶上
mailto:電子信箱(從環境變數OPENALEX_MAILTO讀取);未設定時仍可運作但速率較低。 - OpenAlex 預設
Accept-Encoding不可包含br(Brotli),因requests套件未安裝brotli時無法解碼,會導致 JSON 解析失敗。 - OpenAlex 搜尋結果 MUST 套用基礎過濾器(
type:article,has_abstract:true,is_retracted:false)以排除非期刊文章;排序應使用relevance_score:desc而非純日期排序,避免回傳與查詢無關的近期論文。
Article Source Display:
- 前端文章卡片 MUST 顯示「真實原始出處」(
original_source),而非聚合器的source欄位名稱。 original_source於 scraping 時由後端解析並存入metadata_JSONB;前端直接讀取,不靠 URL heuristics。- 若
original_source不存在(舊資料),前端應 fallback 至 URL hostname 解析(arxiv.org → "arxiv" 等)。 via_source欄位儲存聚合器名稱("openalex" / "semantic_scholar");非聚合器來源的文章via_source為 null,不顯示標籤。
Requirements (mandatory)
Functional Requirements
Semantic Scholar:
- FR-001: 系統 MUST 支援
semantic_scholar作為一個獨立的 scraper 來源類型,可在 Scraper Settings 中針對每個 topic 進行設定。 - FR-002: Semantic Scholar 設定 MUST 以 singleton 模式運作(每個 topic 最多一個),介面設計與 ArXiv 設定卡片平行對稱。
- FR-003: 管理員 MUST 能在 Semantic Scholar 設定中管理 topic 層級的關鍵字清單(新增、刪除),這些關鍵字用於 Semantic Scholar API 搜尋。
- FR-004: 系統 MUST 支援為 Semantic Scholar 設定
max_results(每次最多回傳幾篇)與days_back(只搜尋最近 N 天的論文)兩個參數。 - FR-005: 系統 MUST 在有開放取用 PDF 時自動下載並解析全文,無 PDF 時退回摘要,兩種情況都不中斷流程。
- FR-006: 系統 MUST 對 Semantic Scholar 回傳的論文進行 URL 去重:若論文有 ArXiv ID 則使用 ArXiv URL 作為正規化 URL,否則使用 Semantic Scholar 論文頁 URL。
- FR-007: 系統 MUST 支援透過環境變數
SEMANTIC_SCHOLAR_API_KEY設定 API key;未設定時應退回免費層運作(受 IP 配額限制)。 - FR-008: LLM 分析流程 MUST 對
semantic_scholar來源的論文採用與 ArXiv 相同的內容擷取邏輯(有段落則用全文截斷為上限,無段落則用摘要)。 - FR-009: ArXiv scraper MUST 在系統層面忽略
arxiv_keyword類型的設定,只使用arxiv_category類型的設定進行查詢。 - FR-010: ArXiv Scraper Settings 介面 MUST 移除 keyword 管理區塊,只保留 category 管理區塊;現有 category 功能不受影響。
OpenAlex:
- FR-011: 系統 MUST 支援
openalex作為一個獨立的 scraper 來源類型,可在 Scraper Settings 中針對每個 topic 進行設定。 - FR-012: OpenAlex 設定 MUST 以 singleton 模式運作(每個 topic 最多一個),介面設計與 Semantic Scholar 設定卡片平行對稱。
- FR-013: 管理員 MUST 能在 OpenAlex 設定中管理 topic 層級的關鍵字清單(
openalex_keyword類型,新增、刪除)。 - FR-014: 系統 MUST 支援為 OpenAlex 設定
max_results與days_back兩個參數;max_results上限為 200(OpenAlex API 單次最大值)。 - FR-015: OpenAlexClient MUST 還原 abstract inverted index 格式為純文字後儲存,並從
ids.arxiv/doi正規化 URL;有開放取用 PDF 時同樣走 PdfParser 解析全文。搜尋時 MUST 套用基礎過濾器(type:article,has_abstract:true,is_retracted:false)並以relevance_score:desc排序。 - FR-016: 系統 MUST 透過環境變數
OPENALEX_MAILTO讓 OpenAlex client 在 User-Agent 帶 mailto,以進入 polite pool(10 req/sec);未設定時仍可運作但速率受預設限制。HTTP clientAccept-EncodingMUST NOT 包含br(Brotli)。 - FR-017: LLM 分析流程 MUST 對
openalex來源的論文採用與 ArXiv、Semantic Scholar 相同的內容擷取邏輯。
Article Source Attribution (US5):
- FR-018: 聚合器 scraper(SS / OA)在 discover/fetch 時 MUST 將
via_source(聚合器名稱)與original_source(原始出處,如 "arxiv" 或期刊名稱)存入 Articlemetadata_JSONB 欄位;無需 DB schema 變更。 - FR-019:
original_source解析規則:若論文有 ArXiv ID → "arxiv";否則取 OpenAlexprimary_location.source.display_name(期刊名稱)或 Semantic Scholar 直接為 "semanticscholar"。 - FR-020: 後端
ArticleOut/ArticleDetailOutMUST 暴露via_source: Optional[str]與original_source: Optional[str]欄位(從metadata_讀取)。 - FR-021: 前端文章卡片與文章詳情 dialog MUST 顯示
original_source(而非 scrapersource欄位)作為主要來源 badge;若via_source存在則額外顯示 "via OpenAlex" / "via Semantic Scholar" 小標籤。
Aggregator Filter (US6):
- FR-022: 後端
/articlesAPI MUST 支援aggregator: List[str]query 參數,過濾Article.source IN (aggregator)的文章;與現有source篩選獨立,可同時使用。 - FR-023: 前端文章列表 Filter Bar MUST 新增 Aggregator 篩選(固定選項:openalex、semantic_scholar),透過
aggregatorURL query param 傳遞;Knowledge Graph 頁面的 Filter Bar 同步支援。
Admin UI (US1/US4 更新):
- FR-024: Scraper Settings 頁面 MUST 將 Semantic Scholar 與 OpenAlex 兩個設定卡片整合於同一「Aggregator」accordion section 下,新增聚合器時以 dialog 選擇類型(SS / OA)後再展開對應設定卡片。
Key Entities
- SemanticScholarSetting:Semantic Scholar 的抓取設定,包含啟用狀態、頻率、max_results、days_back,隸屬於某個 topic。每個 topic 最多一筆(singleton)。
- SemanticScholarKeyword:用於 Semantic Scholar API 搜尋的關鍵字,屬於 topic 層級,一個 topic 可有多個 keyword。
- SemanticScholarPaper:從 Semantic Scholar API 取得的論文資料,包含 paper ID、標題、摘要、作者、發表日期、開放取用 PDF URL、DOI、ArXiv ID、引用數。此為中間資料,最終儲存為系統統一的 Article 格式。
- OpenAlexSetting:OpenAlex 的抓取設定,與 SemanticScholarSetting 結構相同,每個 topic 最多一筆(singleton)。
- OpenAlexKeyword:用於 OpenAlex search API 的關鍵字,
keyword_type = "openalex_keyword",屬於 topic 層級。 - OpenAlexWork:從 OpenAlex API 取得的論文資料,包含 work ID、標題(需從 abstract inverted index 還原摘要)、作者、發表日期、開放取用 PDF URL、DOI、ArXiv ID、引用數。此為中間資料,最終儲存為系統統一的 Article 格式。
- ArticleSourceMetadata:儲存於
Article.metadata_JSONB 中的來源相關欄位:via_source(聚合器名稱,如 "openalex")、original_source(原始出處,如 "arxiv"、"Nature Neuroscience")、primary_topic(OpenAlex primary topic 名稱)、primary_field(OpenAlex field 名稱)。
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: 管理員可在 3 分鐘內完成啟用 Semantic Scholar 或 OpenAlex 來源、設定關鍵字、儲存設定的完整流程。
- SC-002: 單次 Semantic Scholar scrape 執行(20 篇論文)應在 60 秒內完成(不含 PDF 下載)。
- SC-003: 在多來源(SS / OpenAlex / ArXiv)同時啟用的情況下,系統應確保同一篇論文不重複出現在論文列表中(去重率 100%)。
- SC-004: ArXiv scraper 在移除 keyword 搜尋後,rate limit 錯誤(HTTP 429)應降低至每週 0 次(category-only 查詢量遠低於 rate limit 閾值)。
- SC-005: 有開放取用 PDF 的論文,其 LLM 分析結果的標籤豐富度(tag 數量)應高於純摘要分析的同類論文。
- SC-006: OpenAlex scrape 在無 API key 情況下(僅 mailto polite pool)應能穩定執行,不出現 HTTP 429(rate limiter 設定 5 RPM,遠低於 polite pool 10 req/sec 上限)。
- SC-007: 透過聚合器抓取的 arXiv 論文,文章卡片 source badge 應正確顯示 "arxiv"(而非 "doi" 或聚合器名稱),正確率 100%。
- SC-008: OpenAlex 回傳結果應為期刊文章(非書籍章節、資料集等),且有摘要,且未撤稿;
_BASE_FILTERS確保過濾品質。
Assumptions
- 系統已有 ArXiv scraper 運作,本功能為並行新增,不取代 ArXiv。
- Semantic Scholar:免費 API 無法個人申請 key(需機構帳號),且未驗證 IP 的速率限制極為嚴格(首次執行即 HTTP 429)。保留 Semantic Scholar 實作,但以 OpenAlex 作為主要免費學術論文來源。若未來取得 API key,SS 實作可直接啟用。
- OpenAlex:完全免費,無需 API key;透過 mailto polite pool 可穩定取得 10 req/sec,rate limiter 設定 5 RPM 確保安全邊際。abstract 以 inverted index 格式儲存,client 層負責還原。
- 現有資料庫中已存在的
arxiv_keyword資料不需清除,系統層忽略即可(無需資料遷移)。 - 前端 Scraper Settings 頁面採用 AccordionSection 區塊佈局;Semantic Scholar 與 OpenAlex 合併為單一「Aggregator」accordion,新增時透過 dialog 選擇類型。
- 論文 PDF 解析邏輯(PdfParser)現有實作可直接複用,無需修改。
- 去重機制(UrlHash)現有實作可直接複用;URL 正規化邏輯在各 scraper client 內處理(ArXiv URL 優先 → DOI URL → 來源 URL)。
- 本功能不包含論文引用關係(citation graph)的收集或展示。
- 本功能只使用 keyword search API,不使用個人化推薦 API。