部落格觀察 API
公開取用最新文章、部落格目錄、正文提及關係與話題排行;資料只涵蓋已收錄且驗證的部落格。
使用規則
- 基底網址
https://blog.observe.tw。公開查詢使用GET(也接受HEAD),回傳 UTF-8 JSON。 - API 狀態頁:
/api/status。瀏覽器以 HTML 顯示;以Accept: application/json取得 JSON,只顯示 API 狀態與端點用途。狀態包含ok或degraded,以有界可用性檢查確認公開查詢的資料依賴可連線,最多快取 5 秒;不是每個端點逐筆測試。JSON 欄位為status、version、endpoints(每項path、summary)與updatedAt;這是狀態回應產生時間,不是文章資料更新時間。此回應為no-store,不提供內部主機或爬蟲維運資訊。 - 公開查詢不需要金鑰。每個 IP 每分鐘最多 60 次,超過回
429;回應帶x-ratelimit-limit、x-ratelimit-remaining、x-ratelimit-reset。登入後可在/my/建立個人 API 金鑰(每人最多 3 把),以x-api-key: blog_…傳送,每把每分鐘 1000 次;伺服器只存 SHA-256 摘要,完整金鑰只顯示一次。無效或撤銷的金鑰回401。為防止隨機金鑰探測,首次出現或未快取的金鑰須先通過每 IP 每分鐘 60 次的匿名/驗證桶;若該桶已用完會先回429,請等窗口重設後重試。已快取且有效的金鑰不受此 IP 桶限制,每把每分鐘 1000 次額度不變。金鑰不能用來登入、管理或讀取私人資料,Authorization不是本 API 的金鑰認證方式。 - 允許跨網域:
Access-Control-Allow-Origin: *,不接受跨域 cookie 認證。瀏覽器可讀取限流標頭與Retry-After;遇429請依Retry-After秒數退避。限流標頭表示產生回應時的額度;來源站對實際到站請求計額。 - 時間一律使用 UTC ISO 8601(例如
2026-10-12T00:00:00.000Z);「一天」指台北時間(UTC+8)的日曆日,hours則是連續經過的小時數。未知日期回null,不以抓取日期冒充發文日期。 - 部落格
slug與話題id是穩定 API 識別碼,不因網頁網址改名而變動。已建立可讀網頁網址時,項目另附選用的canonicalPath,文章附blogCanonicalPath;以基底網址加上此相對路徑即可分享。欄位暫缺時仍可使用原識別碼網址,網頁會 301 到正式網址;不要拿可讀 slug 取代 API 參數。 - 匿名成功回應帶
cache-control: no-store。文章依各站 RSS/抓取排程分批更新,查詢不保留跨請求結果快取,讓隱藏或刪除內容可即時反映;低頻部落格不因久未發文而失效。請至少間隔 120 秒輪詢,關係以 5 分鐘以上為宜。更新與歷史回補有延遲,不保證每輪都有新資料。 - 帶
x-api-key或Authorization的請求與錯誤回應為private, no-store或no-store,不得進共享快取。快取標頭不代表 CDN 已快取該端點;目前 Cloudflare 的專用 HTML 規則只涵蓋首頁。 - 錯誤格式為
{"error":"..."}:400參數/游標錯誤、401無效金鑰、404找不到、410話題證據已全部撤銷、405不支援的方法、429太頻繁、503暫時無法提供服務。未知參數、重複參數與不合法的時間窗口會回400。 - 路徑參數請 URL 編碼(例如話題
rss:1寫成rss%3A1),路徑結尾不要加/。文章與目錄使用limit、cursor/nextCursor;游標是不透明字串,請原樣傳回並保持原查詢條件。nextCursor: null表示這個結果已到尾頁。 - 變更紀錄(2026-10-12,已核准的破壞性變更):
GET /api/v1/blogs的items[].url與GET /api/v1/blogs/{slug}的blog.url改為本站可讀網頁絕對網址;原站首頁移至同物件的sourceUrl(可為 null)。原先使用 blog.url 前往原站的程式請改讀 sourceUrl。GET /api/v1/topics的items[].url及GET /api/v1/topics/{id}的topic.url為新增的本站網頁網址。ID/slug/favicon 識別碼不變,文章 url 仍是原文;內部 /api/* 的原站 url 不改。缺少 registry 時,本站 url 暫用可轉址的舊識別碼路徑;純標點話題不提供網址。不提供 pageUrl 系列欄位。 - 除上述明列的已核准變更外,
v1內只做向後相容的變更(新增欄位、新增端點);移除或改名會先公告。客戶端應忽略未知的新欄位。既有站內/api/*介面保留給網站使用,不是本文件的穩定公開契約;本站沒有舊版 PHP API。 - 部落格文章、標題與圖片著作權屬原作者。API 只提供標題、原文連結、統計及最多 500 字元的短摘要,不提供全文;私人追蹤、個人 RSS、候選證據、管理及 AI 原始輸入不在公開 API。使用統計請註明「資料來源:部落格觀察 blog.observe.tw」,閱讀文章請連回原站。
- 不寫程式也能追:訂閱各部落格原站提供的 RSS/Atom;登入
/my/可管理追蹤與匯出/me/opml給閱讀器,OPML 只包含已成功抓取的 RSS/Atom,沒有可用 feed 的站會略過,請到原站確認訂閱方式。/me/feed.xml是需要瀏覽器登入 session 的私人摘要 RSS,不是可公開分享或以 API 金鑰讀取的 feed。 - 變更紀錄(2026-10-13):話題詳情新增選用 history 累積歷史清單與 historyCursor 分頁。話題曾有有效比對證據就保留,不因退出每日詞庫窗口消失;只公開標題、原文連結及觀測狀態,絕不提供全文。全部證據明確撤銷時回 410,合併網頁一次 301 到最終話題。
- 正文提及是交流訊號,不是抄襲或背書判斷。引用占比的分母為來源站在窗口內所有已解析、指向已收錄目標的「文章—目標部落格」提及數;一篇提及三站會計三筆,不是占全站文章的百分比。覆蓋與截斷欄位應與排行一起閱讀。
- 話題詞條以新文易數維護的標籤池為唯一來源;目前 observed-article-window 僅代表觀測窗口內出現的詞,不代表完整詞庫。NFKC 與大小寫正規化後合併,顯示文字取同快照中上游文章出現次數最多的寫法。只比對標題與清理後正文;同篇取標題權重 2 或正文權重 1,依權重由高到低排列後,同站後續文章貢獻依序乘上 0.5。只命中正文時即 1、0.5、0.25…;2 篇標題加 1 篇正文合計 3.25。排除詞保留原始資料與原因,可透過話題詳情查詢;RSS 分類不另建排行詞庫。
- 排行另使用新文易數 broad 門檻(至少三站、兩站各兩篇且一站三篇),配合既有文件頻率門檻。若詞在查詢窗口內超過 2% 公開文章命中,排行只計標題證據;原詞、正文命中與歷史話題頁保留。
matchingPolicy.displayGate記錄門檻與來源,matchingReason=high-window-document-frequency-title-only可查核正文降權原因;此規則不改原 matcher/policy 版本,也不是另建通用詞黑名單。 - 若跨站詞在長期基準中少於三分之一命中文章出現在標題,熱門排行另要求至少兩個公開部落格的文章原站標籤/分類明確使用該詞;否則僅退出排行和文字雲,保留詞庫、比對、話題頁與歷史證據。
matchingPolicy.displayGate.background記錄門檻。這是全詞統一的背景詞品質規則,不含硬編的排除詞清單。 - coverage.matchedPosts 是依既有高頻詞標題/正文規則認定的有效比對文章數,包含因跨站不足或背景詞品質退出熱門排行的文章;不是所有原始 topic_matches 紀錄數。高頻詞僅正文命中不計入此有效比對統計,但原索引與歷史證據仍保留。
快速開始
最新文章與游標分頁(curl + jq)
BASE='https://blog.observe.tw'
page=$(curl --fail-with-body -sS "$BASE/api/v1/articles?hours=720&limit=10")
printf '%s\n' "$page" | jq '.items[] | {title, url, blogName, publishedAt}'
cursor=$(printf '%s\n' "$page" | jq -r '.nextCursor // empty')
if [ -n "$cursor" ]; then
curl --fail-with-body -sS -G "$BASE/api/v1/articles" --data-urlencode "hours=720" --data-urlencode "limit=10" --data-urlencode "cursor=$cursor" | jq '.items'
fi某部落格的文章與提及關係(curl + jq)
BASE='https://blog.observe.tw'
slug=$(curl --fail-with-body -sS "$BASE/api/v1/blogs?limit=1" | jq -r '.items[0].slug // empty')
if [ -n "$slug" ]; then
encoded=$(printf '%s' "$slug" | jq -sRr @uri)
curl --fail-with-body -sS "$BASE/api/v1/blogs/$encoded/articles?hours=2160&limit=10" | jq '.items'
curl --fail-with-body -sS "$BASE/api/v1/blogs/$encoded/relations?hours=2160" | jq '.outgoing, .incoming, .coverage'
fi誰提到誰、熱門話題與部落格目錄(curl + jq)
curl --fail-with-body -sS 'https://blog.observe.tw/api/v1/relations?hours=720' | jq '.nodes, .edges, .coverage'
curl --fail-with-body -sS 'https://blog.observe.tw/api/v1/topics?hours=168&limit=10' | jq '.items, .vocabulary'
curl --fail-with-body -sS -G 'https://blog.observe.tw/api/v1/blogs' --data-urlencode 'q=旅行' --data-urlencode 'limit=20' | jq '.items, .nextCursor'JavaScript:有上限的文章分頁(瀏覽器或 Node 24)
const endpoint = 'https://blog.observe.tw/api/v1/articles';
let cursor = null;
for (let page = 0; page < 3; page++) {
const query = new URLSearchParams({ hours: "720", limit: "50" });
if (cursor) query.set("cursor", cursor);
const response = await fetch(endpoint + "?" + query);
if (!response.ok) throw new Error("HTTP " + response.status + ": " + await response.text());
const result = await response.json();
for (const article of result.items) console.log(article.blogName, article.title, article.url);
cursor = result.nextCursor;
if (!cursor) break;
await new Promise(resolve => setTimeout(resolve, 1100));
}
// 這是同一輪分頁;開始下一輪輪詢前至少等 120 秒。端點一覽
- GET /api/v1/articles最新文章與標題搜尋
- GET /api/v1/blogs已驗證部落格目錄
- GET /api/v1/blogs/{slug}單一部落格資料
- GET /api/v1/blogs/{slug}/articles某部落格的文章
- GET /api/v1/relations誰提到誰與互相提及排行
- GET /api/v1/blogs/{slug}/relations某部落格提到誰、被誰提到
- GET /api/v1/topics熱門話題排行
- GET /api/v1/topics/{id}某話題有哪些部落格在寫
- GET /api/v1/openapi.json機器可讀 OpenAPI 規格
最新文章與搜尋
GETHEAD/api/v1/articles
最新文章與標題搜尋
依發文時間(未知時用首次收錄時間)再依 ID 由新到舊;publishedAt 仍保留 null。游標固定第一頁窗口與篩選條件,後續請保持相同參數。
| 參數 | 型別 | 說明 |
|---|---|---|
limit | integer | 每頁筆數,1–200,預設 50 |
cursor | string | 上一頁的 nextCursor,不可修改或跨篩選條件使用 |
hours | integer | 最近幾小時;不得與 since/until 同時使用,1–2160,預設 720 |
q | string | 名稱或標題的純文字包含搜尋;不是 SQL/正規表示式 |
since | string (ISO 時間) | 窗口起點(UTC ISO 8601,秒或三位毫秒且以 Z 結尾),必須與 until 成對,跨度最多 90 天 |
until | string (ISO 時間) | 窗口終點(UTC ISO 8601,秒或三位毫秒且以 Z 結尾),必須與 since 成對 |
blog | string | 限制部落格代碼 |
curl -s 'https://blog.observe.tw/api/v1/articles'在瀏覽器開啟這個範例 ↗回應欄位18 個
| 欄位 | 型別 | 說明 |
|---|---|---|
items | object[] | |
items[].id | integer | 文章 ID |
items[].title | string | 原文標題 |
items[].url | string | 原文網址 |
items[].author | string | null | 原站署名,不代表跨站作者身分相同 |
items[].publishedAt | string (ISO 時間) | null | 原站發文時間 |
items[].updatedAt | string (ISO 時間) | null | RSS/原站提供的文章更新時間 |
items[].imageUrl | string | null | 圖片網址;沒有圖片時為 null |
items[].blogSlug | string | 部落格代碼 |
items[].blogName | string | 部落格名稱 |
items[].summary | string | 純文字短摘要;不提供全文 |
items[].observedAt | string (ISO 時間) | null | 本站首次收錄時間 |
items[].iconUrl | string | null | 部落格圖示 |
items[].blogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
nextCursor | string | null | 下一頁游標;null 表示末頁 |
window | object | 此輪固定窗口;游標保留窗口與篩選條件 |
window.since | string (ISO 時間) | |
window.until | string (ISO 時間) |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
GETHEAD/api/v1/blogs/{slug}/articles
某部落格的文章
| 參數 | 型別 | 說明 |
|---|---|---|
slug路徑 | string | 從目錄取得的部落格代碼 |
limit | integer | 每頁筆數,1–200,預設 50 |
cursor | string | 上一頁的 nextCursor,不可修改或跨篩選條件使用 |
hours | integer | 最近幾小時;不得與 since/until 同時使用,1–2160,預設 720 |
q | string | 名稱或標題的純文字包含搜尋;不是 SQL/正規表示式 |
since | string (ISO 時間) | 窗口起點(UTC ISO 8601,秒或三位毫秒且以 Z 結尾),必須與 until 成對,跨度最多 90 天 |
until | string (ISO 時間) | 窗口終點(UTC ISO 8601,秒或三位毫秒且以 Z 結尾),必須與 since 成對 |
curl -s 'https://blog.observe.tw/api/v1/blogs/skyhong-tw/articles'在瀏覽器開啟這個範例 ↗回應欄位18 個
| 欄位 | 型別 | 說明 |
|---|---|---|
items | object[] | |
items[].id | integer | 文章 ID |
items[].title | string | 原文標題 |
items[].url | string | 原文網址 |
items[].author | string | null | 原站署名,不代表跨站作者身分相同 |
items[].publishedAt | string (ISO 時間) | null | 原站發文時間 |
items[].updatedAt | string (ISO 時間) | null | RSS/原站提供的文章更新時間 |
items[].imageUrl | string | null | 圖片網址;沒有圖片時為 null |
items[].blogSlug | string | 部落格代碼 |
items[].blogName | string | 部落格名稱 |
items[].summary | string | 純文字短摘要;不提供全文 |
items[].observedAt | string (ISO 時間) | null | 本站首次收錄時間 |
items[].iconUrl | string | null | 部落格圖示 |
items[].blogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
nextCursor | string | null | 下一頁游標;null 表示末頁 |
window | object | 此輪固定窗口;游標保留窗口與篩選條件 |
window.since | string (ISO 時間) | |
window.until | string (ISO 時間) |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
部落格目錄
GETHEAD/api/v1/blogs
已驗證部落格目錄
依 ID 由小到大,使用游標續頁;不公開未核准的探索候選。
| 參數 | 型別 | 說明 |
|---|---|---|
limit | integer | 每頁筆數,1–200,預設 50 |
cursor | string | 上一頁的 nextCursor,不可修改或跨篩選條件使用 |
q | string | 名稱或標題的純文字包含搜尋;不是 SQL/正規表示式 |
curl -s 'https://blog.observe.tw/api/v1/blogs'在瀏覽器開啟這個範例 ↗回應欄位15 個
| 欄位 | 型別 | 說明 |
|---|---|---|
items | object[] | |
items[].id | integer | 部落格 ID |
items[].slug | string | 部落格代碼 |
items[].name | string | 部落格名稱 |
items[].url | string | 本站部落格可讀網頁絕對網址;2026-10-12 起原站網址改存 sourceUrl |
items[].sourceUrl | string | null | 原站部落格首頁;原 blog.url 的值 |
items[].category | string | 原始分類,不代表權威分類 |
items[].description | string | 公開短簡介 |
items[].iconUrl | string | null | 本站圖示網址 |
items[].discoveredAt | string (ISO 時間) | null | 收錄時間 |
items[].lastCrawlSuccessAt | string (ISO 時間) | null | 最近抓取成功時間 |
items[].lastPublishedAt | string (ISO 時間) | null | 最近發文時間 |
items[].expectedIntervalHours | integer | 預期更新間隔,單位小時 |
items[].canonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
nextCursor | string | null | 下一頁游標 |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
GETHEAD/api/v1/blogs/{slug}
單一部落格資料
| 參數 | 型別 | 說明 |
|---|---|---|
slug路徑 | string | 從目錄取得的部落格代碼 |
curl -s 'https://blog.observe.tw/api/v1/blogs/skyhong-tw'在瀏覽器開啟這個範例 ↗回應欄位17 個
| 欄位 | 型別 | 說明 |
|---|---|---|
blog | object | |
blog.id | integer | 部落格 ID |
blog.slug | string | 部落格代碼 |
blog.name | string | 部落格名稱 |
blog.url | string | 本站部落格可讀網頁絕對網址;2026-10-12 起原站網址改存 sourceUrl |
blog.sourceUrl | string | null | 原站部落格首頁;原 blog.url 的值 |
blog.category | string | 原始分類,不代表權威分類 |
blog.description | string | 公開短簡介 |
blog.iconUrl | string | null | 本站圖示網址 |
blog.discoveredAt | string (ISO 時間) | null | 收錄時間 |
blog.lastCrawlSuccessAt | string (ISO 時間) | null | 最近抓取成功時間 |
blog.lastPublishedAt | string (ISO 時間) | null | 最近發文時間 |
blog.expectedIntervalHours | integer | 預期更新間隔,單位小時 |
blog.canonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
blog.feeds | object[] | |
blog.feeds[].url | string | 原站 feed 位址 |
blog.feeds[].type | string | rss/atom/sitemap |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
部落格提及關係
GETHEAD/api/v1/relations
誰提到誰與互相提及排行
只計正文且雙方均為已驗證部落格。全站圖最多 500 條邊,排行有獨立上限;請檢查 coverage 截斷標記。
| 參數 | 型別 | 說明 |
|---|---|---|
hours | 168 | 720 | 2160 | 最近 7/30/90 天,預設 720 |
curl -s 'https://blog.observe.tw/api/v1/relations'在瀏覽器開啟這個範例 ↗回應欄位44 個
| 欄位 | 型別 | 說明 |
|---|---|---|
hours | integer | |
days | integer | |
generatedAt | string (ISO 時間) | |
nodes | object[] | |
nodes[].id | integer | |
nodes[].slug | string | |
nodes[].name | string | |
nodes[].iconUrl | string | null | |
nodes[].canonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
edges | object[] | 全站最多 500 條圖形邊 |
edges[].sourceBlogId | integer | |
edges[].targetBlogId | integer | |
edges[].count | integer | 不同來源文章提及此目標的篇數 |
edges[].share | number | count ÷ 來源站已解析的文章—目標站提及數,不是文章總數占比 |
edges[].firstMentionAt | string (ISO 時間) | null | |
edges[].lastMentionAt | string (ISO 時間) | null | |
edges[].mutual | boolean | 窗口內是否也有反向正文提及 |
edges[].sourceBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
edges[].targetBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
mostMentioned | object[] | |
mostMentioned[].blogId | integer | |
mostMentioned[].mentions | integer | |
mostMentioned[].incomingBlogs | integer | |
mutual | object[] | |
mutual[].sourceBlogId | integer | |
mutual[].targetBlogId | integer | |
mutual[].forwardCount | integer | |
mutual[].reverseCount | integer | |
coverage | object | 解析覆蓋統計;並非所有文章皆已分析 |
coverage.observedPosts | integer | |
coverage.totalPosts | integer | |
coverage.failedPosts | integer | |
coverage.noBodyPosts | integer | |
coverage.unknownDatePosts | integer | |
coverage.unresolvedLinks | integer | |
coverage.truncatedPosts | integer | |
coverage.graphTruncated | boolean | |
coverage.aggregationTruncated | boolean | |
coverage.sampledAt | string (ISO 時間) | null | |
coverage.method | string | |
coverage.nonBodyLinks | object | |
coverage.nonBodyLinks.sidebar | integer | |
coverage.nonBodyLinks.blogroll | integer | |
coverage.nonBodyLinks.comment | integer |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
GETHEAD/api/v1/blogs/{slug}/relations
某部落格提到誰、被誰提到
單站關係不受全站圖形 500 邊上限影響,最多 500 邊及 20 筆公開連結證據;不提供正文片段。
| 參數 | 型別 | 說明 |
|---|---|---|
slug路徑 | string | 從目錄取得的部落格代碼 |
hours | 168 | 720 | 2160 | 最近 7/30/90 天,預設 720 |
curl -s 'https://blog.observe.tw/api/v1/blogs/skyhong-tw/relations'在瀏覽器開啟這個範例 ↗回應欄位76 個
| 欄位 | 型別 | 說明 |
|---|---|---|
hours | integer | |
days | integer | |
generatedAt | string (ISO 時間) | |
nodes | object[] | |
nodes[].id | integer | |
nodes[].slug | string | |
nodes[].name | string | |
nodes[].iconUrl | string | null | |
nodes[].canonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
edges | object[] | 全站最多 500 條圖形邊 |
edges[].sourceBlogId | integer | |
edges[].targetBlogId | integer | |
edges[].count | integer | 不同來源文章提及此目標的篇數 |
edges[].share | number | count ÷ 來源站已解析的文章—目標站提及數,不是文章總數占比 |
edges[].firstMentionAt | string (ISO 時間) | null | |
edges[].lastMentionAt | string (ISO 時間) | null | |
edges[].mutual | boolean | 窗口內是否也有反向正文提及 |
edges[].sourceBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
edges[].targetBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
mostMentioned | object[] | |
mostMentioned[].blogId | integer | |
mostMentioned[].mentions | integer | |
mostMentioned[].incomingBlogs | integer | |
mutual | object[] | |
mutual[].sourceBlogId | integer | |
mutual[].targetBlogId | integer | |
mutual[].forwardCount | integer | |
mutual[].reverseCount | integer | |
coverage | object | 解析覆蓋統計;並非所有文章皆已分析 |
coverage.observedPosts | integer | |
coverage.totalPosts | integer | |
coverage.failedPosts | integer | |
coverage.noBodyPosts | integer | |
coverage.unknownDatePosts | integer | |
coverage.unresolvedLinks | integer | |
coverage.truncatedPosts | integer | |
coverage.graphTruncated | boolean | |
coverage.aggregationTruncated | boolean | |
coverage.sampledAt | string (ISO 時間) | null | |
coverage.method | string | |
coverage.nonBodyLinks | object | |
coverage.nonBodyLinks.sidebar | integer | |
coverage.nonBodyLinks.blogroll | integer | |
coverage.nonBodyLinks.comment | integer | |
blogId | integer | |
outgoing | object[] | |
outgoing[].sourceBlogId | integer | |
outgoing[].targetBlogId | integer | |
outgoing[].count | integer | 不同來源文章提及此目標的篇數 |
outgoing[].share | number | count ÷ 來源站已解析的文章—目標站提及數,不是文章總數占比 |
outgoing[].firstMentionAt | string (ISO 時間) | null | |
outgoing[].lastMentionAt | string (ISO 時間) | null | |
outgoing[].mutual | boolean | 窗口內是否也有反向正文提及 |
outgoing[].sourceBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
outgoing[].targetBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
incoming | object[] | |
incoming[].sourceBlogId | integer | |
incoming[].targetBlogId | integer | |
incoming[].count | integer | 不同來源文章提及此目標的篇數 |
incoming[].share | number | count ÷ 來源站已解析的文章—目標站提及數,不是文章總數占比 |
incoming[].firstMentionAt | string (ISO 時間) | null | |
incoming[].lastMentionAt | string (ISO 時間) | null | |
incoming[].mutual | boolean | 窗口內是否也有反向正文提及 |
incoming[].sourceBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
incoming[].targetBlogCanonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
evidence | object[] | |
evidence[].postId | integer | |
evidence[].postTitle | string | |
evidence[].postUrl | string | |
evidence[].targetUrl | string | |
evidence[].anchor | string | |
evidence[].publishedAt | string (ISO 時間) | null | |
evidence[].sourceBlogId | integer | |
evidence[].targetBlogId | integer | |
evidence[].location | "body" | |
profileTruncated | boolean |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
話題與關鍵字
GETHEAD/api/v1/topics
熱門話題排行
只列至少兩個部落格寫過的話題。依 limit 最多回應 200 筆排行;truncated 表示仍有其他結果,此端點不提供 cursor。
| 參數 | 型別 | 說明 |
|---|---|---|
hours | 168 | 720 | 2160 | 最近 7/30/90 天,預設 720 |
limit | integer | 每頁筆數,1–200,預設 50 |
curl -s 'https://blog.observe.tw/api/v1/topics'在瀏覽器開啟這個範例 ↗回應欄位79 個
| 欄位 | 型別 | 說明 |
|---|---|---|
items | object[] | |
items[].id | string | tag:<NFKC/大小寫正規化詞條 SHA-256>;舊 tag/rss ID 可在詳情解析 |
items[].label | string | |
items[].source | "rss-category" | "tag-vocabulary" | |
items[].score | number | 同篇取標題 2 或正文 1;同站按權重排序後依序減半加總 |
items[].posts | integer | |
items[].blogs | integer | |
items[].titlePosts | integer | |
items[].bodyOnlyPosts | integer | |
items[].recentShare | number | null | |
items[].baselineShare | number | null | |
items[].lift | number | null | 近期文章占比 ÷ 長期基準占比;無基準時為 null,不代表零 |
items[].matchingRule | "title-only" | "title-and-body" | |
items[].matchingReason | string | null | |
items[].canonicalPath | string | 可讀網頁的相對 canonical 路徑;選用,不取代原 API 識別碼 |
items[].url | string | 本站話題網頁絕對網址;選用,純標點話題不提供 |
nextCursor | | null | 排行僅提供有界快照,不提供游標續頁 |
hours | integer | |
vocabulary | object | |
vocabulary.status | "ready" | "unavailable" | |
vocabulary.source | string | |
vocabulary.version | string | null | |
vocabulary.coverage | string | null | |
vocabulary.syncedAt | string (ISO 時間) | null | |
vocabulary.window | object | null | |
vocabulary.window.since | string (ISO 時間) | |
vocabulary.window.until | string (ISO 時間) | |
vocabulary.invalidTagCount | integer | null | |
vocabulary.normalizedTagCount | integer | null | |
vocabulary.tagNormalization | string | null | |
vocabulary.upstreamSnapshotConsistent | boolean | |
vocabulary.excludedTermCount | integer | 保留但不參與比對的 canonical 詞條數 |
vocabulary.exclusionReasons | {鍵: integer} | 排除原因與詞條數 |
vocabulary.frequencyBaseline | object | null | |
vocabulary.frequencyBaseline.since | string (ISO 時間) | |
vocabulary.frequencyBaseline.until | string (ISO 時間) | |
vocabulary.frequencyBaseline.totalPosts | integer | |
vocabulary.frequencyBaseline.coveredPosts | integer | |
vocabulary.frequencyBaseline.thresholdRatio | number | |
vocabulary.frequencyBaseline.version | string | |
vocabulary.totalSourceTerms | integer | null | 保留的上游原始詞數,包含未參與比對的詞 |
vocabulary.activeSet | object | null | |
vocabulary.activeSet.status | string | |
vocabulary.activeSet.version | string | null | |
vocabulary.activeSet.asOf | string (ISO 時間) | null | |
vocabulary.activeSet.eligibleTerms | integer | |
vocabulary.activeSet.activeTerms | integer | |
vocabulary.activeSet.termLimit | integer | |
vocabulary.activeSet.codePointLimit | integer | |
vocabulary.activeSet.memoryBudgetBytes | integer | |
vocabulary.activeSet.estimatedBytes | integer | |
vocabulary.activeSet.inactiveReasons | {鍵: integer} | low-frequency 或 memory-budget;原詞仍保留 |
matchingPolicy | object | |
matchingPolicy.version | string | |
matchingPolicy.policyVersion | string | |
matchingPolicy.titleWeight | number | |
matchingPolicy.bodyWeight | number | |
matchingPolicy.sameBlogDecay | number | |
matchingPolicy.sources | "title" | "body"[] | |
matchingPolicy.recentBackfillHours | integer | |
matchingPolicy.frequency | {鍵: object} | |
matchingPolicy.publicationNoiseVersion | string | 公開呈現的純標點雜訊規則版本;不改原始詞條 ID |
matchingPolicy.displayGate | object | 選用的排行展示品質門檻 |
matchingPolicy.displayGate.version | string | 展示門檻版本;不取代 matcher/policy 版本 |
matchingPolicy.displayGate.upstream | string | 移植的 tag 跨站 broad 門檻來源 |
matchingPolicy.displayGate.minimumSources | integer | 至少三個部落格 |
matchingPolicy.displayGate.minimumTwiceSources | integer | 至少兩站各兩篇 |
matchingPolicy.displayGate.minimumPeakPosts | integer | 至少一站三篇 |
matchingPolicy.displayGate.documentFrequencyRatio | number | 查詢窗口內高文件頻率門檻,目前 0.02 |
matchingPolicy.displayGate.reason | string | high-window-document-frequency-title-only |
matchingPolicy.displayGate.effect | string | 只影響排行的正文貢獻;保留來源池及歷史證據 |
matchingPolicy.displayGate.background | object | 選用的背景詞原站分類佐證門檻 |
matchingPolicy.displayGate.background.maximumTitleShare | number | 長期基準標題命中占比門檻,目前三分之一 |
matchingPolicy.displayGate.background.minimumBaselinePosts | integer | 至少三篇基準命中文章 |
matchingPolicy.displayGate.background.minimumNativeBlogs | integer | 至少兩個公開部落格的原站分類/標籤 |
matchingPolicy.displayGate.background.reason | string | body-dominant-without-native-label-consensus |
minimumBlogs | integer | |
generatedAt | string (ISO 時間) | |
truncated | boolean |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable
GETHEAD/api/v1/topics/{id}
某話題有哪些部落格在寫
近期最多 100 個部落格與 100 篇文章;請檢查 truncated。history 是累積曾有比對證據的公開文章,獨立於 hours 與每日來源窗口;近期為空時仍可讀歷史。pending 僅表示過去觀測、待重新確認,不代表現在仍命中。全部證據明確撤銷回 410;不存在或暫不可公開回 404。
| 參數 | 型別 | 說明 |
|---|---|---|
id路徑 | string | 從話題排行取得的 ID,請 URL 編碼 |
hours | 168 | 720 | 2160 | 最近 7/30/90 天,預設 720 |
historyCursor | string | history.nextCursor;累積歷史文章續頁,保持同一話題 ID |
curl -s 'https://blog.observe.tw/api/v1/topics/rss%3A1'在瀏覽器開啟這個範例 ↗回應欄位108 個
| 欄位 | 型別 | 說明 |
|---|---|---|
hours | integer | |
generatedAt | string (ISO 時間) | |
topic | object | |
topic.id | string | |
topic.label | string | |
topic.source | string | |
topic.canonicalId | string | null | |
topic.excludedReason | string | null | too-short、pure-number、pure-date、tag-noise、no-equal、upstream-noise、upstream-no-equal、invalid-label、empty-label、punctuation-only 或 not-in-tag-vocabulary |
topic.inactiveReason | string | null | low-frequency 或 memory-budget;與雜訊排除分開 |
topic.matchingStatus | "active" | "inactive" | "excluded" | |
topic.articleCount | integer | null | |
topic.lastPublishedAt | string (ISO 時間) | null | |
topic.matchingRule | "title-only" | "title-and-body" | |
topic.matchingReason | string | null | |
topic.canonicalPath | string | 本站可讀網頁相對路徑;選用 |
topic.url | string | 本站話題網頁絕對網址;選用 |
vocabulary | object | |
vocabulary.status | "ready" | "unavailable" | |
vocabulary.source | string | |
vocabulary.version | string | null | |
vocabulary.coverage | string | null | |
vocabulary.syncedAt | string (ISO 時間) | null | |
vocabulary.window | object | null | |
vocabulary.window.since | string (ISO 時間) | |
vocabulary.window.until | string (ISO 時間) | |
vocabulary.invalidTagCount | integer | null | |
vocabulary.normalizedTagCount | integer | null | |
vocabulary.tagNormalization | string | null | |
vocabulary.upstreamSnapshotConsistent | boolean | |
vocabulary.excludedTermCount | integer | 保留但不參與比對的 canonical 詞條數 |
vocabulary.exclusionReasons | {鍵: integer} | 排除原因與詞條數 |
vocabulary.frequencyBaseline | object | null | |
vocabulary.frequencyBaseline.since | string (ISO 時間) | |
vocabulary.frequencyBaseline.until | string (ISO 時間) | |
vocabulary.frequencyBaseline.totalPosts | integer | |
vocabulary.frequencyBaseline.coveredPosts | integer | |
vocabulary.frequencyBaseline.thresholdRatio | number | |
vocabulary.frequencyBaseline.version | string | |
vocabulary.totalSourceTerms | integer | null | 保留的上游原始詞數,包含未參與比對的詞 |
vocabulary.activeSet | object | null | |
vocabulary.activeSet.status | string | |
vocabulary.activeSet.version | string | null | |
vocabulary.activeSet.asOf | string (ISO 時間) | null | |
vocabulary.activeSet.eligibleTerms | integer | |
vocabulary.activeSet.activeTerms | integer | |
vocabulary.activeSet.termLimit | integer | |
vocabulary.activeSet.codePointLimit | integer | |
vocabulary.activeSet.memoryBudgetBytes | integer | |
vocabulary.activeSet.estimatedBytes | integer | |
vocabulary.activeSet.inactiveReasons | {鍵: integer} | low-frequency 或 memory-budget;原詞仍保留 |
matchingPolicy | object | |
matchingPolicy.version | string | |
matchingPolicy.policyVersion | string | |
matchingPolicy.titleWeight | number | |
matchingPolicy.bodyWeight | number | |
matchingPolicy.sameBlogDecay | number | |
matchingPolicy.sources | "title" | "body"[] | |
matchingPolicy.recentBackfillHours | integer | |
matchingPolicy.frequency | {鍵: object} | |
matchingPolicy.publicationNoiseVersion | string | 公開呈現的純標點雜訊規則版本;不改原始詞條 ID |
matchingPolicy.displayGate | object | 選用的排行展示品質門檻 |
matchingPolicy.displayGate.version | string | 展示門檻版本;不取代 matcher/policy 版本 |
matchingPolicy.displayGate.upstream | string | 移植的 tag 跨站 broad 門檻來源 |
matchingPolicy.displayGate.minimumSources | integer | 至少三個部落格 |
matchingPolicy.displayGate.minimumTwiceSources | integer | 至少兩站各兩篇 |
matchingPolicy.displayGate.minimumPeakPosts | integer | 至少一站三篇 |
matchingPolicy.displayGate.documentFrequencyRatio | number | 查詢窗口內高文件頻率門檻,目前 0.02 |
matchingPolicy.displayGate.reason | string | high-window-document-frequency-title-only |
matchingPolicy.displayGate.effect | string | 只影響排行的正文貢獻;保留來源池及歷史證據 |
matchingPolicy.displayGate.background | object | 選用的背景詞原站分類佐證門檻 |
matchingPolicy.displayGate.background.maximumTitleShare | number | 長期基準標題命中占比門檻,目前三分之一 |
matchingPolicy.displayGate.background.minimumBaselinePosts | integer | 至少三篇基準命中文章 |
matchingPolicy.displayGate.background.minimumNativeBlogs | integer | 至少兩個公開部落格的原站分類/標籤 |
matchingPolicy.displayGate.background.reason | string | body-dominant-without-native-label-consensus |
blogs | object[] | |
blogs[].id | integer | |
blogs[].slug | string | |
blogs[].name | string | |
blogs[].posts | integer | |
blogs[].score | number | |
blogs[].titlePosts | integer | |
blogs[].bodyOnlyPosts | integer | |
blogs[].canonicalPath | string | 本站可讀網頁相對路徑;選用 |
articles | object[] | |
articles[].id | integer | |
articles[].title | string | |
articles[].url | string | |
articles[].publishedAt | string (ISO 時間) | null | |
articles[].blogSlug | string | |
articles[].blogName | string | |
articles[].blogCanonicalPath | string | 本站可讀網頁相對路徑;選用 |
truncated | boolean | |
history | object | |
history.articles | object[] | |
history.articles[].id | integer | |
history.articles[].title | string | |
history.articles[].url | string | 原文連結;不提供全文 |
history.articles[].publishedAt | string (ISO 時間) | null | |
history.articles[].blogSlug | string | |
history.articles[].blogName | string | |
history.articles[].blogCanonicalPath | string | 可讀部落格路徑 |
history.articles[].evidenceState | "confirmed" | "pending" | confirmed 為已確認;pending 為過去觀測、待重新確認 |
history.articles[].observedAt | string (ISO 時間) | 本站最後觀測到提及的時間 |
history.nextCursor | string | null | 歷史清單下一頁游標;傳入 historyCursor,綁定話題不可跨用 |
history.confirmed | integer | 累積已確認的公開文章數 |
history.pending | integer | 累積待重新確認的公開文章數 |
history.lastObservedAt | string (ISO 時間) | null | |
history.total | integer | 累積不同公開文章數 |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;410 gone(累積證據已全部撤銷);503 unavailable
API 本身
GETHEAD/api/v1/openapi.json
機器可讀 OpenAPI 規格
curl -s 'https://blog.observe.tw/api/v1/openapi.json'在瀏覽器開啟這個範例 ↗回應欄位4 個
| 欄位 | 型別 | 說明 |
|---|---|---|
openapi | string | |
info | object | |
paths | object | |
components | object |
錯誤:400 invalid_query/invalid_cursor;401 invalid_api_key;404 not_found;405 method_not_allowed;503 unavailable