Files

471 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
金融机构可爬取名单注册表
维护所有中国大陆金融机构的URL映射关系,支持增量更新
"""
import json
import re
from pathlib import Path
from typing import Dict, List, Optional
SKILL_DIR = Path(__file__).parent.parent
REGISTRY_FILE = SKILL_DIR / "data" / "institution_registry.json"
PREDEFINED_URLS: Dict[str, str] = {
# 国有大型商业银行
"中国工商银行": "https://www.icbc.com.cn",
"中国建设银行": "https://www.ccb.com",
"中国农业银行": "https://www.abchina.com",
"中国银行": "https://www.boc.cn",
"交通银行": "https://www.bankcomm.com",
"中国邮政储蓄银行": "https://www.psbc.com",
# 股份制商业银行
"招商银行": "https://www.cmbchina.com",
"浦发银行": "https://www.spdb.com.cn",
"中信银行": "https://www.citicbank.com",
"中国光大银行": "https://www.cebbank.com",
"华夏银行": "https://www.hxb.com.cn",
"中国民生银行": "https://www.cmbc.com.cn",
"广发银行": "https://www.cgbchina.com.cn",
"兴业银行": "https://www.cib.com.cn",
"平安银行": "https://bank.pingan.com",
"浙商银行": "https://www.czbank.com",
"恒丰银行": "https://www.hfbank.com.cn",
"渤海银行": "https://www.cbhb.com.cn",
# 政策性银行
"国家开发银行": "https://www.cdb.com.cn",
# 中国进出口银行(官网为政府域名,已移除)
"中国农业发展银行": "https://www.adbc.com.cn",
# 证券公司
"中信证券": "https://www.cs.ecitic.com",
"中信建投证券": "https://www.csc108.com",
"国泰君安证券": "https://www.gtja.com",
"华泰证券": "https://www.htsc.com.cn",
"广发证券": "https://www.gf.com.cn",
"招商证券": "https://www.newone.com.cn",
"海通证券": "https://www.htsec.com",
"国信证券": "https://www.guosen.com.cn",
"东方证券": "https://www.dfzq.com.cn",
"兴业证券": "https://www.xyzq.com.cn",
"中国银河证券": "https://www.chinastock.com.cn",
"长江证券": "https://www.95579.com",
"中金公司": "https://www.cicc.com",
"光大证券": "https://www.ebscn.com",
"平安证券": "https://stock.pingan.com",
"方正证券": "https://www.foundersc.com",
"中泰证券": "https://www.zts.com.cn",
"申万宏源证券": "https://www.swhysc.com",
"国元证券": "https://www.gyzq.com.cn",
"安信证券": "https://www.essence.com.cn",
"开源证券": "https://www.kysec.cn",
"华西证券": "https://www.hx168.com.cn",
"民生证券": "https://www.mszq.com",
"东北证券": "https://www.nesc.cn",
"南京证券": "https://www.njzq.com",
"长城证券": "https://www.cgws.com",
"中银证券": "https://www.bocichina.com",
"华安证券": "https://www.hazq.com",
"财达证券": "https://www.s10000.com",
"万联证券": "https://www.wlzq.com",
"山西证券": "https://www.i618.com.cn",
"西部证券": "https://www.westsecu.com",
"浙商证券": "https://www.stocke.com.cn",
"东海证券": "https://www.longone.com.cn",
"国海证券": "https://www.ghzq.com.cn",
"广州证券": "https://www.gzs.com.cn",
"大同证券": "https://www.dtsbc.com.cn",
"华融证券": "https://www.china-huarong.com",
"中天证券": "https://www.izq.com.cn",
# 基金管理公司
"易方达基金": "https://www.efunds.com.cn",
"华夏基金": "https://www.chinaamc.com",
"广发基金": "https://www.gffunds.com.cn",
"嘉实基金": "https://www.jsfund.cn",
"南方基金": "https://www.nffund.com.cn",
"博时基金": "https://www.bosera.com",
"招商基金": "https://www.cmfchina.com",
"工银基金": "https://www.icbcfs.com",
"建信基金": "https://www.ccbfund.cn",
"富国基金": "https://www.fullgoal.com.cn",
"鹏华基金": "https://www.phfund.com.cn",
"汇添富基金": "https://www.htffund.com",
"中欧基金": "https://www.zofund.com",
"兴证全球基金": "https://www.xqglobal.com",
"景林资产": "https://www.greenpeakgroup.com",
"高毅资产": "https://www.gao-yi.com",
"淡水泉投资": "https://www.downspring.com",
"明汯投资": "https://www.minghonginvestment.com",
"幻方量化": "https://www.quantizedcapital.com",
"九坤投资": "https://www.jiukuninvest.com",
"灵均投资": "https://www.lingjun-invest.com",
"天演资本": "https://www.tianyan-capital.com",
"东方港湾": "https://www.eastport.com.cn",
"汉和资本": "https://www.hanhecap.com",
"同犇投资": "https://www.tongben-invest.com",
"石锋资产": "https://www.shifengam.com",
"趣时资产": "https://www.quishi.com",
"聚鸣投资": "https://www.jumingsh.com",
"泰旸资产": "https://www.taiyangam.com",
# 保险公司
"中国人寿": "https://www.chinalife.com.cn",
"中国平安": "https://www.pingan.com.cn",
"中国太平洋保险": "https://www.cpic.com.cn",
"中国人保": "https://www.picc.com.cn",
"新华保险": "https://www.newchina-life.com",
"泰康保险": "https://www.taikang.com",
"友邦保险": "https://www.aia.com.cn",
"阳光保险": "https://www.sinosig.com",
"合众人寿": "https://www.reformlife.com",
"华夏保险": "https://www.huaxia-insurance.com",
"富德生命人寿": "https://www.sino-life.com",
"天安保险": "https://www.tiananinsurance.com",
"中华保险": "https://www.cai-insurance.com",
"大地保险": "https://www.95590.com",
"中华联合保险": "https://www.cic.cn",
"华泰保险": "https://www.ehuatai.com",
"安华农业保险": "https://www.anohua.com",
# 信托公司
"中信信托": "https://www.zxxt.com.cn",
"平安信托": "https://www.paxt.com.cn",
"中融信托": "https://www.zhongrongtrust.com",
"华润信托": "https://www.crhtrust.com.cn",
"外贸信托": "https://www.fotacn.com",
"上海信托": "https://www.shanghaitrust.com",
"中建投信托": "https://www.cctrust.com",
"兴业信托": "https://www.ciit.com.cn",
"华宝信托": "https://www.hwabaotrust.com",
"交银信托": "https://www.bankcommtrust.com",
"光大信托": "https://www.ebtrust.com",
"中粮信托": "https://www.cofco-trust.com",
"昆仑信托": "https://www.kunluntrust.com",
"五矿信托": "https://www.minmetals-trust.com",
"中铁信托": "https://www.crec-trust.com",
"江苏信托": "https://www.js-trust.com",
"粤财信托": "https://www.gdyc-trust.com",
"重庆信托": "https://www.cqitic.com",
"华融信托": "https://www.huarongtrust.com.cn",
# 银行理财子公司
"工银理财": "https://www.icbc.com.cn/ICBC/FinancialManagement",
"建信理财": "https://www.ccbfund.com.cn",
"农银理财": "https://www.abchina.com/wealth",
"中银理财": "https://www.boc.cn/financial",
"交银理财": "https://www.bankcomm.com/wealth",
"光大理财": "https://www.cebbank.com/finance",
"招银理财": "https://www.cmbc.com.cn/wealth",
"兴银理财": "https://www.cib.com.cn/wealth",
"平安理财": "https://bank.pingan.com/wealth",
"浦银理财": "https://www.spdb.com.cn/wealth",
# 第三方销售机构
"天天基金": "https://fund.eastmoney.com",
"蚂蚁基金": "https://www.fund123.cn",
"腾安基金": "https://www.tan'anfund.com",
"雪球基金": "https://danjuanfunds.com",
"京东基金": "https://jd.jrj.com",
"理财通": "https://www.lct.com",
# 外资金融机构
"汇丰银行": "https://www.hsbc.com.cn",
"渣打银行": "https://www.standardchartered.com.cn",
"花旗银行": "https://www.citibank.com.cn",
"摩根大通银行": "https://www.jpmorganchina.com.cn",
"高盛集团": "https://www.goldmansachs.com.cn",
"瑞银证券": "https://www.ubs.com/cn/zh.html",
"野村证券": "https://www.nomura.com",
"安盛保险": "https://www.axa.com.cn",
"安联保险": "https://www.allianz.com.cn",
"贝莱德资产管理": "https://www.blackrock.com.cn",
"瑞银资产管理": "https://www.ubs.com/global/en/asset-management.html",
"摩根士丹利": "https://www.morganstanley.com.cn",
"美林证券": "https://www.ml.com",
"德意志银行": "https://www.db.com.cn",
"星展银行": "https://www.dbs.com.cn",
"东亚银行": "https://www.hkbea.com.cn",
"恒生银行": "https://www.hangseng.com",
"华侨银行": "https://www.ocbc.com.cn",
# ── 城市商业银行(补充) ──
"北京银行": "https://www.bankofbeijing.com.cn",
"上海银行": "https://www.bosc.cn",
"江苏银行": "https://www.jsbchina.cn",
"南京银行": "https://www.njcb.com.cn",
"宁波银行": "https://www.nbcb.com.cn",
"杭州银行": "https://www.hzbank.com.cn",
"成都银行": "https://www.bocd.com.cn",
"长沙银行": "https://www.bankofchangsha.com",
"郑州银行": "https://www.zzbank.cn",
"青岛银行": "https://www.qdccb.com",
"西安银行": "https://www.xacbank.com",
"贵阳银行": "https://www.bankgy.cn",
"兰州银行": "https://www.lzbank.com",
"厦门国际银行": "https://www.xib.com.cn",
# ── 农商行 ──
"重庆农商行": "https://www.cqrcb.com",
"广州农商行": "https://www.grcbank.com",
"上海农商行": "https://www.shrcb.com",
"北京农商行": "https://www.bjrcb.com",
"深圳农商行": "https://www.szrcb.com",
# ── 金融科技 ──
"蚂蚁集团": "https://www.antgroup.com",
"京东科技": "https://www.jdcloud.com",
"度小满金融": "https://www.duxiaoman.com",
"陆金所": "https://www.lu.com",
"众安保险": "https://www.zhongan.com",
"微众银行": "https://www.webank.com",
"网商银行": "https://www.mybank.cn",
# ── 更多私募基金 ──
"重阳投资": "https://www.chongyang.net",
"凯丰投资": "https://www.kffund.com.cn",
"敦和资管": "https://www.dunhefund.com",
"乐瑞资产": "https://www.lowrisk.com.cn",
"千合资本": "https://www.qhcapital.com.cn",
"林园投资": "https://www.linyuaninvest.com",
"星石投资": "https://www.starrockinvest.com",
"望正资产": "https://www.wangzhengcapital.com",
# ── 另类数据/评价平台 ──
"好买基金": "https://www.howbuy.com",
"私募排排网": "https://www.simuwang.com",
"雪球": "https://xueqiu.com",
"集思录": "https://www.jisilu.cn",
"理杏仁": "https://www.lixinger.com",
"韭圈儿": "https://www.funddb.cn",
# ── 交易所 ──
"上海证券交易所": "https://www.sse.com.cn",
"深圳证券交易所": "https://www.szse.cn",
"北京证券交易所": "https://www.bse.cn",
"香港交易所": "https://www.hkex.com.hk",
# ── 期货交易所 ──
"上海期货交易所": "https://www.shfe.com.cn",
"大连商品交易所": "https://www.dce.com.cn",
"郑州商品交易所": "https://www.czce.com.cn",
"中国金融期货交易所": "https://www.cffex.com.cn",
"上海国际能源交易中心": "https://www.ine.cn",
"广州期货交易所": "https://www.gfex.com.cn",
# ── 行业协会 ──
"中国银行业协会": "https://www.china-cba.net",
"中国证券业协会": "https://www.sac.net.cn",
"中国保险行业协会": "https://www.iachina.cn",
# ── 同业市场 ──
"中国外汇交易中心": "https://www.chinamoney.com.cn",
"上海银行间同业拆放利率": "https://www.shibor.org",
"中国债券信息网(中央国债登记结算公司)": "https://www.chinabond.com.cn",
# ── 海外交易所 ──
"纳斯达克": "https://www.nasdaq.com",
"纽约证券交易所": "https://www.nyse.com",
"芝加哥商品交易所": "https://www.cmegroup.com",
"伦敦证券交易所": "https://www.londonstockexchange.com",
"东京证券交易所": "https://www.jpx.co.jp",
"新加坡交易所": "https://www.sgx.com",
# ── 其他金融机构补充 ──
"中国再保险": "https://www.chinare.com.cn",
"中投公司": "https://www.china-inv.cn",
"国家开发投资集团": "https://www.sdic.com.cn",
"中国银河资产管理": "https://www.china-galaxyam.com",
}
class ScrapableRegistry:
def __init__(self, registry_file: str = None):
self.registry_file = Path(registry_file) if registry_file else REGISTRY_FILE
self._registry = None
self._load()
def _load(self):
if self.registry_file.exists():
with open(self.registry_file, encoding="utf-8") as f:
self._registry = json.load(f)
# 解码列式格式
if self._registry.get("_f") == "c":
self._registry = self._decode_columnar(self._registry)
else:
self._registry = {}
@staticmethod
def _decode_columnar(data: dict) -> dict:
"""将列式格式还原为标准 dict"""
columns = data.get("c", [])
rows = data.get("d", [])
meta = data.get("m", data.get("_meta", {}))
items = []
for row in rows:
item = {}
for i, col_name in enumerate(columns):
if i < len(row):
item[col_name] = row[i]
items.append(item)
result = {"institutions": items}
if meta:
result["_meta"] = meta
return result
@property
def institutions(self) -> List[Dict]:
return self._registry.get("institutions", [])
@property
def total(self) -> int:
return self._registry.get("_meta", {}).get("total", len(self.institutions))
def is_scrapable(self, name: str, inst_type: str = "") -> bool:
if name in PREDEFINED_URLS:
return True
return False
def get(self, name: str, inst_type: str = "") -> Optional[Dict]:
for inst in self.institutions:
if inst["name"] == name:
result = dict(inst)
website = inst.get("website", "")
if not website and name in PREDEFINED_URLS:
website = PREDEFINED_URLS[name]
result["website"] = website
result["scrapable"] = bool(website)
return result
return None
def search(self, keyword: str) -> List[Dict]:
results = []
kw = keyword.lower()
for inst in self.institutions:
if kw in inst["name"].lower() or kw in inst.get("code", "").lower():
results.append(self.get(inst["name"]))
return results
def list_scrapable(self, inst_type: str = "") -> List[Dict]:
results = []
for inst in self.institutions:
if inst_type and inst["type"] != inst_type:
continue
info = self.get(inst["name"])
if info and info["scrapable"]:
results.append(info)
return results
def list_by_type(self, inst_type: str) -> List[Dict]:
return [self.get(i["name"]) for i in self.institutions if i["type"] == inst_type]
def add_url(self, name: str, url: str) -> bool:
for inst in self.institutions:
if inst["name"] == name:
inst["website"] = url
self._registry["_meta"]["with_website"] = sum(
1 for i in self.institutions if i.get("website")
)
self.save()
return True
return False
def get_statistics(self) -> Dict:
summary = self._registry.get("_type_summary", {})
stats = {
"total": self.total,
"scrapable": sum(
1 for i in self.institutions if self.is_scrapable(i["name"], i["type"])
),
"by_type": {},
}
for inst_type, info in summary.items():
scrapable_count = sum(
1 for i in self.institutions
if i["type"] == inst_type and self.is_scrapable(i["name"], inst_type)
)
stats["by_type"][inst_type] = {
"total": info["total"],
"with_url": info["with_website"],
"scrapable": scrapable_count,
}
return stats
def save(self):
with open(self.registry_file, "w", encoding="utf-8") as f:
json.dump(self._registry, f, ensure_ascii=False, indent=2)
def generate_report(self) -> str:
stats = self.get_statistics()
lines = [
"=" * 60,
"金融机构可爬取名单报告",
"=" * 60,
f"机构总数:{stats['total']}",
f"可爬取数:{stats['scrapable']} ({stats['scrapable']*100//stats['total']}%)",
"",
"按类型明细:",
]
for inst_type, info in sorted(stats["by_type"].items(), key=lambda x: -x[1]["total"]):
lines.append(
f" {inst_type}: {info['scrapable']}/{info['total']} 可爬取 "
f"({info['with_url']} 有URL)"
)
return "\n".join(lines)
# ── v7.0.0: API 发现引擎桥接 ────────────────────────────────
def suggest_api(self, name_or_domain: str, limit: int = 10) -> List[Dict]:
"""查询 v7.0.0 网络拦截挖掘出的 API 注册表(data/api_registry.json)。
机构名会先解析出官网域名,再返回该域名下已发现的 JSON 接口,
供降级链直接 HTTP 直连(跳过浏览器渲染)。
Args:
name_or_domain: 机构名(如 东方财富)或域名(如 eastmoney.com)
limit: 最多返回条数
"""
api_file = Path(self.registry_file).parent / "api_registry.json"
if not api_file.exists():
return []
try:
doc = json.loads(api_file.read_text(encoding="utf-8"))
except Exception:
return []
domain = name_or_domain
if "." not in domain:
inst = self.get(name_or_domain)
if not inst or not inst.get("website"):
return []
domain = inst["website"].split("//")[-1].split("/")[0]
out = [ep for ep in doc.get("endpoints", [])
if domain and domain in ep.get("url", "")]
out.sort(key=lambda e: -e.get("hit_count", 1))
return out[:limit]
def main():
import sys
registry = ScrapableRegistry()
args = sys.argv[1:]
if not args:
print(registry.generate_report())
return
cmd = args[0]
if cmd == "stat":
print(registry.generate_report())
elif cmd == "list" and len(args) > 1:
for info in registry.list_scrapable(args[1]):
print(f" {info['name']} | {info['type']} | {info.get('website', 'N/A')}")
elif cmd == "search" and len(args) > 1:
for info in registry.search(args[1]):
scrapable = "OK" if info["scrapable"] else "NO"
print(f" [{scrapable}] {info['name']} | {info['type']} | {info.get('website', '')}")
elif cmd == "add" and len(args) > 2:
name, url = args[1], args[2]
if registry.add_url(name, url):
print(f"URL已更新: {name} -> {url}")
else:
print(f"机构未找到: {name}")
elif cmd == "export":
for inst in registry.institutions:
info = registry.get(inst["name"])
if info["scrapable"]:
print(f"{info['name']}\t{info['type']}\t{info.get('website', '')}")
else:
print("用法:")
print(" python scrapable_registry.py stat")
print(" python scrapable_registry.py list <类型>")
print(" python scrapable_registry.py search <关键词>")
print(" python scrapable_registry.py add <名称> <URL>")
print(" python scrapable_registry.py export")
if __name__ == "__main__":
main()