Files

212 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
"""
财联社数据爬虫 v1.0
提供 7x24 小时电报快讯、热门文章等。
数据源: cls.cn(中国最重要的金融实时快讯平台)
注意:
- 财联社有反爬机制,需设置合理的 User-Agent 和 Referer
- 建议请求频率不超过 2 次/秒(由 http_utils 自动限流)
"""
import json
import logging
from typing import Dict, List, Optional
try:
from .http_utils import http_get, http_get_json, http_post
except ImportError:
from http_utils import http_get, http_get_json, http_post
log = logging.getLogger(__name__)
CLS_BASE = "https://www.cls.cn"
CLS_API = "https://www.cls.cn/api"
DEFAULT_CLS_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
"Referer": "https://www.cls.cn/telegraph",
"Content-Type": "application/json",
}
def get_telegraph(limit: int = 20) -> List[Dict]:
"""获取财联社 7x24 电报快讯
Args:
limit: 返回条数,默认20,最多50
Returns:
[{id, title, content, ctime, level, type, ...}, ...]
level: "A"(重要) / "B"(中等) / "C"(一般)
"""
try:
url = f"{CLS_API}/sw"
payload = {
"app": "CailianpressWeb",
"os": "web",
"sv": "8.4.6",
"type": "telegram",
}
resp = http_post(url, json_body=payload, headers=DEFAULT_CLS_HEADERS, timeout=20)
if resp is None:
log.warning("财联社电报请求失败")
return []
data = resp.json()
items = data.get("data", {}).get("roll_data", []) if isinstance(data, dict) else []
results = []
for item in items[:limit]:
results.append({
"id": item.get("id", ""),
"title": item.get("title", "") or item.get("brief", ""),
"content": item.get("content", "") or item.get("brief", ""),
"ctime": item.get("ctime", 0),
"level": item.get("level", "C"),
"type": item.get("type", ""),
"url": f"{CLS_BASE}/telegraph/detail/{item.get('id', '')}" if item.get("id") else "",
})
return results
except Exception as e:
log.error(f"获取财联社电报失败: {e}")
# v5.0.0: 降级到 akshare 经济新闻(接口被封时不至于无数据)
try:
from scripts.akshare_fallback import fallback_akshare
return fallback_akshare(
"news_economic_cls", {"limit": limit},
{"title": ["标题", "title"], "url": ["链接", "url", "link"],
"publish_time": ["发布时间", "time", "ctime"],
"content": ["摘要", "content"]},
)
except Exception:
return []
def get_hot_articles(limit: int = 10) -> List[Dict]:
"""获取财联社热门文章
Args:
limit: 返回条数,默认10
Returns:
[{id, title, summary, url, publish_time, source}, ...]
"""
try:
url = f"{CLS_API}/sw"
payload = {
"app": "CailianpressWeb",
"os": "web",
"sv": "8.4.6",
"type": "recommend",
}
resp = http_post(url, json_body=payload, headers=DEFAULT_CLS_HEADERS, timeout=20)
if resp is None:
return []
data = resp.json()
items = data.get("data", {}).get("roll_data", []) if isinstance(data, dict) else []
results = []
for item in items[:limit]:
results.append({
"id": item.get("id", ""),
"title": item.get("title", ""),
"summary": item.get("brief", ""),
"url": f"{CLS_BASE}/detail/{item.get('id', '')}" if item.get("id") else "",
"publish_time": item.get("ctime", 0),
"source": "财联社",
})
return results
except Exception as e:
log.error(f"获取财联社文章失败: {e}")
# v5.0.0: 降级到 akshare 经济新闻
try:
from scripts.akshare_fallback import fallback_akshare
return fallback_akshare(
"news_economic_cls", {"limit": limit},
{"title": ["标题", "title"], "url": ["链接", "url", "link"],
"publish_time": ["发布时间", "time", "ctime"],
"summary": ["摘要", "content"]},
)
except Exception:
return []
def search_articles(keyword: str, limit: int = 10) -> List[Dict]:
"""按关键词搜索财联社文章
Args:
keyword: 搜索关键词,如 "降息"、"茅台"
limit: 返回条数
Returns:
[{title, url, publish_time}, ...]
"""
try:
url = f"{CLS_API}/search"
payload = {
"app": "CailianpressWeb",
"os": "web",
"sv": "8.4.6",
"keyword": keyword,
"type": "article",
}
resp = http_post(url, json_body=payload, headers=DEFAULT_CLS_HEADERS, timeout=20)
if resp is None:
return []
data = resp.json()
items = data.get("data", {}).get("list", []) if isinstance(data, dict) else []
results = []
for item in items[:limit]:
results.append({
"id": item.get("article_id", item.get("id", "")),
"title": item.get("title", ""),
"url": f"{CLS_BASE}/detail/{item.get('article_id', '')}" if item.get("article_id") else "",
"publish_time": item.get("time", ""),
"source": "财联社",
})
return results
except Exception as e:
log.error(f"搜索财联社文章失败: {e}")
# v5.0.0: akshare 无财联社搜索接口,降级到经济新闻近似
try:
from scripts.akshare_fallback import fallback_akshare
return fallback_akshare(
"news_economic_cls", {"limit": limit},
{"title": ["标题", "title"], "url": ["链接", "url", "link"],
"publish_time": ["发布时间", "time", "ctime"]},
)
except Exception:
return []
# ── CLI 测试入口 ──────────────────────────────────────
if __name__ == "__main__":
import sys
cmd = sys.argv[1] if len(sys.argv) > 1 else "telegraph"
if cmd == "telegraph":
print("📡 财联社 7x24 电报快讯:\n")
items = get_telegraph(limit=10)
for i, item in enumerate(items, 1):
level_icon = {"A": "🔴", "B": "🟡", "C": "🟢"}.get(item.get("level", "C"), "⚪")
print(f" {i}. {level_icon} {item.get('title', '')[:80]}")
elif cmd == "search":
keyword = sys.argv[2] if len(sys.argv) > 2 else "降息"
print(f"🔍 搜索: {keyword}\n")
articles = search_articles(keyword, limit=10)
for i, a in enumerate(articles, 1):
print(f" {i}. {a.get('title', '')}")
else:
print("用法: python cls_scraper.py [telegraph|search <关键词>]")