C#通过JS变量提取天天基金API返回的基金净值
天天基金API
常见的 API 如下:
- 所有基金代码:http://fund.eastmoney.com/js/fundcode_search.js
- 基金详细信息:http://fund.eastmoney.com/pingzhongdata/001186.js
- 基金实时信息:http://fundgz.1234567.com.cn/js/001186.js
- 所有基金公司:http://fund.eastmoney.com/js/jjjz_gs.js
本文用到的API主要是前面两个,其中001186是要查询的基金代码,接口返回的数据是一个js文件。
常规方法解析js文件里面的数据会比较繁琐,所以本文使用 Jint 库来解析数据,应该算全网首创的方法了。
添加项目依赖项
使用 NuGet 安装 RestSharp、Jint 库,用途如下
- RestSharp:用于发送HTTP请求
- Jint:用于解析js文件
请求 API 数据
使用 RestSharp 库请求API,方法如下
using RestSharp;
public static string GetFundAPIData(string url)
{
var client = new RestClient();
var request = new RestRequest(url, Method.Get);
var response = client.Execute(request);
if (response.IsSuccessful)
{
return response.Content?? string.Empty;
}
else
{
throw new Exception($"Failed to fetch data from the API: {response.ErrorMessage}");
}
}
获取所有基金代码
接口返回的数据比较长,大致结构如下:
var r = [["000001","HXCZHH","华夏成长混合","混合型-灵活","HUAXIACHENGZHANGHUNHE"],["000002","HXCZHH","华夏成长混合(后端)","混合型-灵活","HUAXIACHENGZHANGHUNHE"]];
定义一个 FundInfo 类来存储基金信息:
public class FundInfo
{
public string Code { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
调用上面的请求API方法获取接口数据,并调用下面的方法解析数据:
public static List<FundInfo> ParseFundList(string js)
{
var fundList =new List<FundInfo>();
// 初始化 Jint 引擎
Engine engine = new Engine();
// 执行 JavaScript 代码
var result = engine.Execute(js).GetValue("r");
// 确保结果是 JavaScript 数组
if (result.IsArray())
{
var jsArray = result.AsArray();
// 将 JavaScript 数组转换为 C# List<List<string>> 类型
var clrList = new List<List<string>>();
foreach (var item in jsArray)
{
var sublist = new List<string>();
foreach (var subitem in item.AsArray())
{
sublist.Add(subitem.ToString());
}
clrList.Add(sublist);
}
fundList = clrList.Select(x => new FundInfo { Code = x[0], Name = x[2], Type = x[3] }).ToList();
return fundList;
}
else
{
throw new InvalidOperationException("Expected an array but got something else.");
}
}
调用上面的方法获取结果:
// 获取所有基金代码
var js=GetFundAPIData("http://fund.eastmoney.com/js/fundcode_search.js");
var list = ParseFundList(js);
获取基金净值信息
接口返回的数据比较长,分析的时候可以使用Javascript格式化在线工具处理一下,我们需要解析的数据如下:
var Data_netWorthTrend = [{
"x": 1430841600000,
"y": 1.0,
"equityReturn": 0,
"unitMoney": ""
}, {
"x": 1431014400000,
"y": 1.004,
"equityReturn": 0,
"unitMoney": ""
}];
var Data_ACWorthTrend = [
[1430841600000, 1.0],
[1431014400000, 1.004]
];
定义一个 NetWorthTrendItem 类存放基金净值信息:
public class FundValue
{
public DateTime Time { get; set; } // 时间戳
public decimal Value { get; set; } // 净值
public decimal EquityReturn { get; set; } // 涨跌幅
public string UnitMoney { get; set; } // 每份派送金
public decimal AddValue { get; set; } // 累计净值
}
直接从 Jint 获取 JavaScript 数组,并将其转换为 C# 对象:
public static List<FundValue> GetFundValues(string js)
{
var engine = new Engine();
// 定义 JavaScript 变量
engine.Execute(js);
// 获取 JavaScript 数组
// 单位净值走势
var valueArray = engine.GetValue("Data_netWorthTrend");
// 累计净值走势
var addValueArray = engine.GetValue("Data_ACWorthTrend");
// 将 JavaScript 数组转换为 C# 列表
var valueList = ConvertJsArrayToCSharpList(valueArray, addValueArray);
return valueList;
}
private static List<FundValue> ConvertJsArrayToCSharpList(JsValue valueArray, JsValue addValueArray)
{
var valueList = valueArray.AsArray();
var addValueList = addValueArray.AsArray();
var length = Math.Min(valueList.Length, addValueList.Length);
var list = new List<FundValue>();
for (int i = 0; i < length; i++)
{
var value = valueList.Get(i);
var addValue = addValueList.Get(i);
var newItem = new FundValue
{
Time = DateTimeOffset.FromUnixTimeMilliseconds((long)value.Get("x").AsNumber()).UtcDateTime.ToLocalTime(),
Value = (decimal)value.Get("y").AsNumber(),
EquityReturn = (decimal)value.Get("equityReturn").AsNumber(),
UnitMoney = value.Get("unitMoney").AsString(),
AddValue = (decimal)addValue.Get("1").AsNumber()
};
list.Add(newItem);
}
return list;
}
功能测试
测试代码如下:
static void Main()
{
var js1=GetFundAPIData("http://fund.eastmoney.com/js/fundcode_search.js");
var list1 = ParseFundList(js1);
Console.WriteLine(JsonSerializer.Serialize(list1.Last(), new JsonSerializerOptions()
{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
}));
var js2=GetFundAPIData("http://fund.eastmoney.com/pingzhongdata/001186.js");
var list2 = GetFundValues(js2);
Console.WriteLine(JsonSerializer.Serialize(list2.Last()));
Console.ReadLine();
}
测试结果如下:
{"Code":"970214","Name":"中信建投悦享6个月持有期债券C","Type":"债券型-混合一级"}
{"Time":"2024-10-15T00:00:00+08:00","Value":2.269,"EquityReturn":-1.6,"UnitMoney":""}
实际使用时可以把基金净值接口地址里面基金代码单独提取出来,这里只是为了演示方法就不做太多封装。
扩展:实现一个基金净值数据库
实现一个基金净值数据库,每天定时更新净值信息,数据库使用 LiteDB。
获取基金列表,如果数据库中没有就从API请求数据:
public static List<FundInfo> GetFundList()
{
List < FundInfo > list = new List<FundInfo>();
// 打开数据库 (如果不存在则创建)
using (var db = new LiteDatabase(@"Fund.db"))
{
// 获得 customer 集合
var col = db.GetCollection<FundInfo>("FundInfo");
if (col.Count() == 0)
{
// 创建一个唯一索引,第二个参数为true表示唯一索引
col.EnsureIndex(x => x.Code, true);
var jsFund = GetFundAPIData("http://fund.eastmoney.com/js/fundcode_search.js");
var listFund = ParseFundList(jsFund)
.Where(x => x.Type.StartsWith("指数型"))
.ToList();
foreach (var fund in listFund)
{
col.Insert(fund);
}
list =listFund;
}
else
{
list =col.FindAll().ToList();
}
}
return list;
}
更新净值信息,只更新数据库中没有的数据:
public static void UpdateFundValue(List<FundInfo> list)
{
foreach (var fund in list)
{
var jsValue = GetFundAPIData($"http://fund.eastmoney.com/pingzhongdata/{fund.Code}.js");
var listValue = GetFundValues(jsValue);
// 打开数据库 (如果不存在则创建)
using (var db = new LiteDatabase(@"Fund.db"))
{
// 获得 customer 集合
var col = db.GetCollection<FundValue>($"Fund_{fund.Code}");
var lastTime=DateTime.MinValue;
if (col.Count() == 0)
{
// 创建一个唯一索引,第二个参数为true表示唯一索引
col.EnsureIndex(x => x.Time, true);
}
else
{
lastTime = col.FindAll().OrderByDescending(x => x.Time).FirstOrDefault().Time;
}
int count = 0;
foreach (var value in listValue)
{
if (value.Time > lastTime)
{
col.Insert(value);
count++;
}
}
Console.WriteLine($"更新{fund.Name}({fund.Code})共{count}条数据,最后时间 {listValue.Last().Time}");
}
}
}
完整的更新流程如下,维护好的数据库可以用于其它业务:
static void Main()
{
var list = GetFundList();
UpdateFundValue(list);
Console.ReadLine();
}