Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- app.py +183 -22
- news_sources.json +3 -19
app.py
CHANGED
|
@@ -4,8 +4,9 @@ import random
|
|
| 4 |
import re
|
| 5 |
import time
|
| 6 |
import xml.etree.ElementTree as ET
|
| 7 |
-
from typing import List, Dict, Optional
|
| 8 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
| 9 |
|
| 10 |
import requests
|
| 11 |
from fastapi import FastAPI
|
|
@@ -540,18 +541,24 @@ def fetch_html_articles(source: Dict) -> List[Dict]:
|
|
| 540 |
|
| 541 |
|
| 542 |
def fetch_articles_from_source(source: Dict) -> List[Dict]:
|
| 543 |
-
"""統一入口:根據來源類型呼叫對應的擷取函式。"""
|
|
|
|
| 544 |
try:
|
| 545 |
if source["type"] == "rss":
|
| 546 |
-
|
| 547 |
elif source["type"] == "html":
|
| 548 |
-
|
| 549 |
else:
|
| 550 |
print(f"⚠️ 未知來源類型: {source['type']}", flush=True)
|
| 551 |
-
return []
|
| 552 |
except Exception as e:
|
| 553 |
print(f"❌ [{source['name']}] 擷取異常: {e}", flush=True)
|
| 554 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
|
| 556 |
|
| 557 |
# ─────────────────────────────────────────────────────────────────
|
|
@@ -610,6 +617,146 @@ SYSTEM_PROMPT = """你是一個專業的繁體中文新聞編輯秘書。你將
|
|
| 610 |
- 回傳的 JSON 陣列長度必須等於輸入的文章數量"""
|
| 611 |
|
| 612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
def summarize_by_deepseek(articles: List[Dict]) -> List[Dict]:
|
| 614 |
"""將文章餵給 DeepSeek 進行摘要處理,回傳結構化 JSON 陣列。"""
|
| 615 |
if not articles:
|
|
@@ -748,10 +895,11 @@ def home():
|
|
| 748 |
def get_news():
|
| 749 |
"""
|
| 750 |
主新聞端點:
|
| 751 |
-
1. 從
|
| 752 |
-
2.
|
| 753 |
-
3.
|
| 754 |
-
4.
|
|
|
|
| 755 |
"""
|
| 756 |
start_time = time.time()
|
| 757 |
all_articles: List[Dict] = []
|
|
@@ -783,22 +931,35 @@ def get_news():
|
|
| 783 |
"articles": [],
|
| 784 |
}
|
| 785 |
|
| 786 |
-
# ── 步驟 2:
|
| 787 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 788 |
BATCH_SIZE = 30
|
| 789 |
-
final_articles: List[Dict] =
|
| 790 |
|
| 791 |
-
if
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
|
| 800 |
total_time = time.time() - start_time
|
| 801 |
-
print(f"🏁 全部完成:{len(final_articles)} 篇新聞(總耗時 {total_time:.1f}s)", flush=True)
|
| 802 |
print(f"{'='*60}\n", flush=True)
|
| 803 |
|
| 804 |
return final_articles
|
|
|
|
| 4 |
import re
|
| 5 |
import time
|
| 6 |
import xml.etree.ElementTree as ET
|
| 7 |
+
from typing import List, Dict, Optional, Tuple
|
| 8 |
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 9 |
+
from urllib.parse import urlparse
|
| 10 |
|
| 11 |
import requests
|
| 12 |
from fastapi import FastAPI
|
|
|
|
| 541 |
|
| 542 |
|
| 543 |
def fetch_articles_from_source(source: Dict) -> List[Dict]:
|
| 544 |
+
"""統一入口:根據來源類型呼叫對應的擷取函式;失敗時自動切換 HTML 備用爬蟲。"""
|
| 545 |
+
articles = []
|
| 546 |
try:
|
| 547 |
if source["type"] == "rss":
|
| 548 |
+
articles = fetch_rss_articles(source)
|
| 549 |
elif source["type"] == "html":
|
| 550 |
+
articles = fetch_html_articles(source)
|
| 551 |
else:
|
| 552 |
print(f"⚠️ 未知來源類型: {source['type']}", flush=True)
|
|
|
|
| 553 |
except Exception as e:
|
| 554 |
print(f"❌ [{source['name']}] 擷取異常: {e}", flush=True)
|
| 555 |
+
|
| 556 |
+
# ── 若正常抓取失敗(0 篇),自動啟動 HTML 備用爬蟲 ──
|
| 557 |
+
if not articles:
|
| 558 |
+
print(f"🔄 [{source['name']}] 正常抓取取得 0 篇,觸發 HTML 備用爬蟲...", flush=True)
|
| 559 |
+
articles = fetch_html_fallback(source)
|
| 560 |
+
|
| 561 |
+
return articles
|
| 562 |
|
| 563 |
|
| 564 |
# ─────────────────────────────────────────────────────────────────
|
|
|
|
| 617 |
- 回傳的 JSON 陣列長度必須等於輸入的文章數量"""
|
| 618 |
|
| 619 |
|
| 620 |
+
# ── HTML 備用爬蟲專用提示詞 ──
|
| 621 |
+
HTML_FALLBACK_SYSTEM_PROMPT = """你是一個精準的網頁新聞提煉專家。我將提供一段從網站首頁擷取下來的 HTML 純文字內容(已移除 script/style 標籤)。
|
| 622 |
+
這段文字混雜了選單、廣告、頁尾與真正的新聞條目。
|
| 623 |
+
|
| 624 |
+
你的任務是:
|
| 625 |
+
1. 從這段雜亂的文字中,精確找出**最新的 2 篇重要新聞**
|
| 626 |
+
2. 自行判斷原文語言,遵循中英雙語對照規則(與主新聞秘書相同):
|
| 627 |
+
- 外語原文 → 上半段繁體中文 / 下半段英文對照
|
| 628 |
+
- 中文原文 → 僅繁體中文
|
| 629 |
+
3. 繁體中文摘要 80~200 字,英文摘要 40~70 字
|
| 630 |
+
4. 為每篇新聞指定一個 category(從:科技 | 財經 | 國際 | 旅遊 | 生活 | 科學 | 材料工業 | 冷知識 | 熱門趨勢 中挑選)
|
| 631 |
+
5. 圖片欄位(image_url)若無法從文本中取得,請留空字串 ""
|
| 632 |
+
|
| 633 |
+
你必須**嚴格回傳一個 JSON 物件**,內含 "articles" 陣列:
|
| 634 |
+
{
|
| 635 |
+
"articles": [
|
| 636 |
+
{
|
| 637 |
+
"title": "...",
|
| 638 |
+
"summary": "...",
|
| 639 |
+
"category": "...",
|
| 640 |
+
"image_url": "",
|
| 641 |
+
"source": "提供的網站名稱"
|
| 642 |
+
},
|
| 643 |
+
...
|
| 644 |
+
]
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
注意:只回傳真正的新聞內容,忽略導覽選單、頁尾連結、廣告、社交媒體按鈕等雜訊。"""
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
def fetch_html_fallback(source: Dict) -> List[Dict]:
|
| 651 |
+
"""
|
| 652 |
+
HTML 備用爬蟲:
|
| 653 |
+
當 RSS/正常爬蟲失敗時,抓取網站首頁並請 DeepSeek 直接從雜亂文本中提取新聞。
|
| 654 |
+
回傳的文章已具備最終格式(含 title, summary, category, image_url, source)。
|
| 655 |
+
"""
|
| 656 |
+
name = source["name"]
|
| 657 |
+
url = source["url"]
|
| 658 |
+
|
| 659 |
+
# 從 URL 提取首頁網址
|
| 660 |
+
try:
|
| 661 |
+
parsed = urlparse(url)
|
| 662 |
+
base_url = f"{parsed.scheme}://{parsed.netloc}"
|
| 663 |
+
except Exception:
|
| 664 |
+
base_url = url
|
| 665 |
+
|
| 666 |
+
print(f"🔄 [{name}] RSS 失敗,啟動 HTML 備用爬蟲 → {base_url}", flush=True)
|
| 667 |
+
|
| 668 |
+
try:
|
| 669 |
+
resp = requests.get(base_url, headers=HEADERS, timeout=MAX_FETCH_SECONDS)
|
| 670 |
+
resp.raise_for_status()
|
| 671 |
+
resp.encoding = resp.apparent_encoding or "utf-8"
|
| 672 |
+
except Exception as e:
|
| 673 |
+
print(f" ❌ [{name}] HTML 備用連線失敗: {e}", flush=True)
|
| 674 |
+
return []
|
| 675 |
+
|
| 676 |
+
# ── 提取 body 純文字,限制 10000 字元 ──
|
| 677 |
+
try:
|
| 678 |
+
soup = BeautifulSoup(resp.text, "html.parser")
|
| 679 |
+
# 移除 script / style / nav / footer
|
| 680 |
+
for tag in soup(["script", "style", "nav", "footer", "header", "aside"]):
|
| 681 |
+
tag.decompose()
|
| 682 |
+
if soup.body:
|
| 683 |
+
text = soup.body.get_text(separator="\n", strip=True)
|
| 684 |
+
else:
|
| 685 |
+
text = soup.get_text(separator="\n", strip=True)
|
| 686 |
+
# 壓縮空白
|
| 687 |
+
text = re.sub(r"\n\s*\n", "\n", text)
|
| 688 |
+
text = re.sub(r" +", " ", text)
|
| 689 |
+
text = text[:10000]
|
| 690 |
+
print(f" 📄 [{name}] 提取純文字 {len(text)} 字元,餵給 DeepSeek...", flush=True)
|
| 691 |
+
except Exception as e:
|
| 692 |
+
print(f" ❌ [{name}] HTML 文本提取失敗: {e}", flush=True)
|
| 693 |
+
return []
|
| 694 |
+
|
| 695 |
+
if len(text) < 100:
|
| 696 |
+
print(f" ⚠️ [{name}] HTML 文本過短,跳過", flush=True)
|
| 697 |
+
return []
|
| 698 |
+
|
| 699 |
+
# ── 餵給 DeepSeek 提取新聞 ��─
|
| 700 |
+
try:
|
| 701 |
+
response = client.chat.completions.create(
|
| 702 |
+
model=DEEPSEEK_MODEL,
|
| 703 |
+
messages=[
|
| 704 |
+
{"role": "system", "content": HTML_FALLBACK_SYSTEM_PROMPT},
|
| 705 |
+
{"role": "user", "content": f"網站名稱:{name}\n網址:{base_url}\n\nHTML 文本內容:\n{text}"},
|
| 706 |
+
],
|
| 707 |
+
response_format={"type": "json_object"},
|
| 708 |
+
temperature=0.3,
|
| 709 |
+
max_tokens=4096,
|
| 710 |
+
)
|
| 711 |
+
|
| 712 |
+
raw = response.choices[0].message.content
|
| 713 |
+
result = json.loads(raw)
|
| 714 |
+
|
| 715 |
+
# 提取 articles 陣列
|
| 716 |
+
articles = []
|
| 717 |
+
if isinstance(result, dict):
|
| 718 |
+
for key in ["articles", "news", "items", "results", "data"]:
|
| 719 |
+
if key in result and isinstance(result[key], list):
|
| 720 |
+
articles = result[key]
|
| 721 |
+
break
|
| 722 |
+
# 若只有單個 key 是 list
|
| 723 |
+
if not articles:
|
| 724 |
+
for val in result.values():
|
| 725 |
+
if isinstance(val, list):
|
| 726 |
+
articles = val
|
| 727 |
+
break
|
| 728 |
+
elif isinstance(result, list):
|
| 729 |
+
articles = result
|
| 730 |
+
|
| 731 |
+
# ── 補完欄位 ──
|
| 732 |
+
completed = []
|
| 733 |
+
for item in articles[:5]: # 最多取 5 篇
|
| 734 |
+
if not isinstance(item, dict):
|
| 735 |
+
continue
|
| 736 |
+
completed.append({
|
| 737 |
+
"title": str(item.get("title", "未知標題")).strip(),
|
| 738 |
+
"summary": str(item.get("summary", item.get("content", "暫無摘要"))).strip(),
|
| 739 |
+
"category": str(item.get("category", guess_category(name))).strip(),
|
| 740 |
+
"image_url": item.get("image_url") or get_fallback_image(str(item.get("title", ""))),
|
| 741 |
+
"source": name,
|
| 742 |
+
"_fallback": True, # 標記為備用爬蟲產出(已含摘要,不需再次 summarize)
|
| 743 |
+
})
|
| 744 |
+
|
| 745 |
+
if completed:
|
| 746 |
+
print(f" ✅ [{name}] HTML 備用爬蟲成功提取 {len(completed)} 篇新聞", flush=True)
|
| 747 |
+
else:
|
| 748 |
+
print(f" ⚠️ [{name}] HTML 備用爬蟲未找到新聞", flush=True)
|
| 749 |
+
|
| 750 |
+
return completed
|
| 751 |
+
|
| 752 |
+
except json.JSONDecodeError as e:
|
| 753 |
+
print(f" ❌ [{name}] DeepSeek JSON 解析失敗: {e}", flush=True)
|
| 754 |
+
except Exception as e:
|
| 755 |
+
print(f" ❌ [{name}] HTML 備用爬蟲 DeepSeek 呼叫失敗: {e}", flush=True)
|
| 756 |
+
|
| 757 |
+
return []
|
| 758 |
+
|
| 759 |
+
|
| 760 |
def summarize_by_deepseek(articles: List[Dict]) -> List[Dict]:
|
| 761 |
"""將文章餵給 DeepSeek 進行摘要處理,回傳結構化 JSON 陣列。"""
|
| 762 |
if not articles:
|
|
|
|
| 895 |
def get_news():
|
| 896 |
"""
|
| 897 |
主新聞端點:
|
| 898 |
+
1. 從 news_sources.json 載入的所有來源平行抓取最新新聞
|
| 899 |
+
2. RSS/HTML 失敗時自動切換 HTML 備用爬蟲(DeepSeek 直接提取)
|
| 900 |
+
3. 確保每篇都有圖片
|
| 901 |
+
4. 一般文章餵給 DeepSeek 做繁體中文摘要(備用文章已含摘要則略過)
|
| 902 |
+
5. 回傳統一 JSON 陣列
|
| 903 |
"""
|
| 904 |
start_time = time.time()
|
| 905 |
all_articles: List[Dict] = []
|
|
|
|
| 931 |
"articles": [],
|
| 932 |
}
|
| 933 |
|
| 934 |
+
# ── 步驟 2:分離「備用爬蟲文章」(已含 DeepSeek 摘要)與「一般文章」──
|
| 935 |
+
regular_articles: List[Dict] = []
|
| 936 |
+
fallback_articles: List[Dict] = []
|
| 937 |
+
|
| 938 |
+
for a in all_articles:
|
| 939 |
+
if a.pop("_fallback", False):
|
| 940 |
+
fallback_articles.append(a)
|
| 941 |
+
else:
|
| 942 |
+
regular_articles.append(a)
|
| 943 |
+
|
| 944 |
+
fb_count = len(fallback_articles)
|
| 945 |
+
reg_count = len(regular_articles)
|
| 946 |
+
print(f"📊 一般文章 {reg_count} 篇 + 備用爬蟲文章 {fb_count} 篇(已摘要,略過處理)", flush=True)
|
| 947 |
+
|
| 948 |
+
# ── 步驟 3:一般文章餵給 DeepSeek 摘要 ──
|
| 949 |
BATCH_SIZE = 30
|
| 950 |
+
final_articles: List[Dict] = list(fallback_articles) # 備用文章直接加入
|
| 951 |
|
| 952 |
+
if regular_articles:
|
| 953 |
+
if len(regular_articles) <= BATCH_SIZE:
|
| 954 |
+
final_articles.extend(summarize_by_deepseek(regular_articles))
|
| 955 |
+
else:
|
| 956 |
+
for i in range(0, len(regular_articles), BATCH_SIZE):
|
| 957 |
+
batch = regular_articles[i:i + BATCH_SIZE]
|
| 958 |
+
print(f"📦 處理批次 {i // BATCH_SIZE + 1}/{(len(regular_articles) + BATCH_SIZE - 1) // BATCH_SIZE} ({len(batch)} 篇)...", flush=True)
|
| 959 |
+
final_articles.extend(summarize_by_deepseek(batch))
|
| 960 |
|
| 961 |
total_time = time.time() - start_time
|
| 962 |
+
print(f"🏁 全部完成:{len(final_articles)} 篇新聞(一般 {reg_count} + 備用 {fb_count})(總耗時 {total_time:.1f}s)", flush=True)
|
| 963 |
print(f"{'='*60}\n", flush=True)
|
| 964 |
|
| 965 |
return final_articles
|
news_sources.json
CHANGED
|
@@ -1,29 +1,13 @@
|
|
| 1 |
[
|
| 2 |
{"name": "科技新報", "type": "rss", "url": "https://technews.tw/feed/", "category": "科技/AI"},
|
| 3 |
-
{"name": "
|
| 4 |
-
{"name": "數位時代", "type": "rss", "url": "https://www.bnext.com/rss", "category": "科技/商業"},
|
| 5 |
{"name": "HKEPC", "type": "html", "url": "https://www.hkepc.com/", "category": "科技/硬體"},
|
| 6 |
-
{"name": "QbitAI 機器之心", "type": "rss", "url": "https://www.jiqizhixin.com/
|
| 7 |
-
{"name": "Inside 科技趨勢", "type": "rss", "url": "https://www.inside.com.tw/
|
| 8 |
{"name": "明日科學", "type": "rss", "url": "https://tomorrowsci.com/feed/", "category": "科學/未來"},
|
| 9 |
{"name": "Gizmodo", "type": "rss", "url": "https://gizmodo.com/rss", "category": "科技/極客"},
|
| 10 |
{"name": "Hackaday", "type": "rss", "url": "https://hackaday.com/feed/", "category": "科技/DIY"},
|
| 11 |
-
{"name": "地球圖輯隊", "type": "rss", "url": "https://world.yam.com/rss.php", "category": "國際/圖文"},
|
| 12 |
-
{"name": "冷知識", "type": "rss", "url": "https://misstwocm.com/feed/", "category": "生活/趣味"},
|
| 13 |
{"name": "鉅亨網 國際政經", "type": "rss", "url": "https://news.cnyes.com/rss/div/global_macro", "category": "財經/國際"},
|
| 14 |
-
{"name": "阿斯達克財經網", "type": "rss", "url": "https://www.aastocks.com/tc/resources/rss.ashx?type=1", "category": "財經/港股"},
|
| 15 |
-
{"name": "RTHK 財經新聞", "type": "rss", "url": "https://rthk.hk/rthk/news/rss/c_expressnews_cfinance.xml", "category": "財經/香港"},
|
| 16 |
-
{"name": "RTHK 本地新聞", "type": "rss", "url": "https://rthk.hk/rthk/news/rss/c_expressnews_clocal.xml", "category": "時事/香港"},
|
| 17 |
-
{"name": "RTHK 國際新聞", "type": "rss", "url": "https://rthk.hk/rthk/news/rss/c_expressnews_cinternational.xml", "category": "國際/時事"},
|
| 18 |
-
{"name": "Plastics Today", "type": "rss", "url": "https://www.plasticstoday.com/rss.xml", "category": "材料工業/塑膠"},
|
| 19 |
-
{"name": "Metal Miner", "type": "rss", "url": "https://agmetalminer.com/feed/", "category": "材料工業/金屬"},
|
| 20 |
-
{"name": "AZoM 材料科學", "type": "rss", "url": "https://www.azom.com/azom-news-feed.xml", "category": "材料工業/科學"},
|
| 21 |
-
{"name": "Wave 流行潮流", "type": "rss", "url": "https://www.wavetv.tw/feed/", "category": "生活/潮流"},
|
| 22 |
-
{"name": "U Travel 旅遊", "type": "rss", "url": "https://utravel.com.hk/rss", "category": "旅遊/香港"},
|
| 23 |
-
{"name": "Yahoo 旅遊 港台", "type": "rss", "url": "https://travel.yahoo.com.tw/rss/headline/", "category": "旅遊/港台"},
|
| 24 |
-
{"name": "Yahoo 旅遊 香港", "type": "rss", "url": "https://hk.news.yahoo.com/rss/travel", "category": "旅遊/香港"},
|
| 25 |
-
{"name": "港生活 北上", "type": "rss", "url": "https://hk.ulifestyle.com.hk/rss/travel-main", "category": "旅遊/深圳"},
|
| 26 |
-
{"name": "香港01 大灣區", "type": "rss", "url": "https://www.hk01.com/rss/category/877", "category": "旅遊/大灣區"},
|
| 27 |
{"name": "Google Trends 香港","type": "rss", "url": "https://trends.google.com.hk/trending/rss?geo=HK", "category": "熱門趨勢/香港"},
|
| 28 |
{"name": "Google Trends 美國","type": "rss", "url": "https://trends.google.com/trending/rss?geo=US", "category": "熱門趨勢/全球"}
|
| 29 |
]
|
|
|
|
| 1 |
[
|
| 2 |
{"name": "科技新報", "type": "rss", "url": "https://technews.tw/feed/", "category": "科技/AI"},
|
| 3 |
+
{"name": "數位時代", "type": "rss", "url": "https://www.bnext.com/", "category": "科技/商業"},
|
|
|
|
| 4 |
{"name": "HKEPC", "type": "html", "url": "https://www.hkepc.com/", "category": "科技/硬體"},
|
| 5 |
+
{"name": "QbitAI 機器之心", "type": "rss", "url": "https://www.jiqizhixin.com/", "category": "科技/AI"},
|
| 6 |
+
{"name": "Inside 科技趨勢", "type": "rss", "url": "https://www.inside.com.tw/", "category": "科技/趨勢"},
|
| 7 |
{"name": "明日科學", "type": "rss", "url": "https://tomorrowsci.com/feed/", "category": "科學/未來"},
|
| 8 |
{"name": "Gizmodo", "type": "rss", "url": "https://gizmodo.com/rss", "category": "科技/極客"},
|
| 9 |
{"name": "Hackaday", "type": "rss", "url": "https://hackaday.com/feed/", "category": "科技/DIY"},
|
|
|
|
|
|
|
| 10 |
{"name": "鉅亨網 國際政經", "type": "rss", "url": "https://news.cnyes.com/rss/div/global_macro", "category": "財經/國際"},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
{"name": "Google Trends 香港","type": "rss", "url": "https://trends.google.com.hk/trending/rss?geo=HK", "category": "熱門趨勢/香港"},
|
| 12 |
{"name": "Google Trends 美國","type": "rss", "url": "https://trends.google.com/trending/rss?geo=US", "category": "熱門趨勢/全球"}
|
| 13 |
]
|