从过年在微信投了几k到基金,后来发现支付宝手续费低很多,就转到支付宝开始开始买基金,投了数额不多,每个几百到几千不等,或者几十固定定投。最终手上持有20多支基金。数据看不过来了,正好最近在学习go,没有实际项目经验,便用来练练手。
痛点
- 1.基金数目太多,如何选出可以抄底的基金
- 2.单个基金如何直接看到最近连续涨跌多少
- 3.持有的基金数据太多,如何一次展示出来重点
寻找可用的基金接口
1.今天估值 http://fund.eastmoney.com/pingzhongdata/000001.js
返回数据如下
jsonpgz(
{
"fundcode": "000001",
"name": "华夏成长",
"jzrq": "2018-05-03",
"dwjz": "1.0950",
"gsz": "1.0958",
"gszzl": "0.07",
"gztime": "2018-05-04 15:00"
}
);
解析代码如下
func getOneFund(code string) *types.FundItem {
data, err := httputils.HttpGet2(fmt.Sprintf(fundGzURL, code))
if err != nil || len(data) < 20 {
return nil
}
fi := &types.FundItem{}
//text := string(data[8:len(data)-2])
//fmt.Println(text)
err = json.Unmarshal(data[8:len(data)-2], fi)
if err != nil {
//fmt.Println(err.Error())
return nil
}
return fi
}
2.历史净值 http://fund.eastmoney.com/pingzhongdata/000001.js
**
* 测试数据
* @type {arry}
*/
/*2018-05-04 16:57:01*/
var ishb=false;
/*基金或股票信息*/
var fS_name = "华夏成长";
var fS_code = "000001";
/*原费率*/
var fund_sourceRate="1.50";
/*现费率*/
var fund_Rate="0.15";
//最小申购金额
var fund_minsg="100";
/*基金持仓股票代码*/
var stockCodes=
var Data_fundSharesPositions = Long Long 数组对象
var Data_netWorthTrend = Long Long 数组
/*同类排名百分比*/
var Data_rateInSimilarPersent= Long Long 数组对象
这个接口吐出来数据接近600k,非常大,是一个js文件,但是能取到的信息非常丰富。
当时第一直觉是找个go的js库,一搜索还真有:”github.com/robertkrimen/otto”
取出上面的类型没什么问题,但是下面的复杂对象数组,根本不知道从js转go的类型。
浪费了很长时间。最终直接strings.Index解决,最高效最简单。😰
解析代码如下
func parseFundDetail(code string) ([]float64, []byte, error) {
url := fmt.Sprintf("http://fund.eastmoney.com/pingzhongdata/%s.js", code)
data, err := utils.HttpGET2(url)
if err != nil {
return nil, nil, err
}
text := string(data)
pos := strings.Index(text, "var Data_netWorthTrend =")
if pos == -1 {
//fmt.Printf("string index fail")
return nil, nil, err
}
text = text[pos+24:]
pos = strings.Index(text, "}];")
if pos == -1 {
//fmt.Printf("string index fail")
return nil, nil, err
}
text = text[:pos+2]
fwiArray := []FundWorthItem{}
err = json.Unmarshal([]byte(text), &fwiArray)
arr := make([]float64, len(fwiArray))
for i, fwi := range fwiArray {
arr[i] = fwi.Y
}
data, _ = json.Marshal(arr)
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
defer gz.Close()
gz.Write(data)
gz.Flush()
return arr, buf.Bytes(), err
}
这里有个误区,因为这个数量很大,想着要给http请求加上gzip压缩,正好搜索到了
Golang http.NewRequest GET带Header获取远程网页,并解析gzip压缩
但是严格来说,在接收流的时候,可以先预读2字节,看是否是gzip,是用gzipreader读取,否则用普通reader读取。但是http接口返回的response不支持seek…找了好久没解决。
后面断点调试net/http代码发现在net/http/ransport下 DisableCompression 相关代码有指定gzip处理,默认开启了gzip,并且没有判断是否返回了的是gzip流,完全信任http协议。
requestedGzip := false
if !pc.t.DisableCompression &&
req.Header.Get("Accept-Encoding") == "" &&
req.Header.Get("Range") == "" &&
req.Method != "HEAD" {
// Request gzip only, not deflate. Deflate is ambiguous and
// not as universally supported anyway.
// See: http://www.gzip.org/zlib/zlib_faq.html#faq38
//
// Note that we don't request this for HEAD requests,
// due to a bug in nginx:
// http://trac.nginx.org/nginx/ticket/358
// https://golang.org/issue/5522
//
// We don't request gzip if the request is for a range, since
// auto-decoding a portion of a gzipped document will just fail
// anyway. See https://golang.org/issue/8923
requestedGzip = true
req.extraHeaders().Set("Accept-Encoding", "gzip")
}
3.获取所有的基金列表 http://fund.eastmoney.com/js/fundcode_search.js
解析代码如下:
//"000001","HXCZ","华夏成长","混合型","HUAXIACHENGZHANG"
func GetAllFundList() (map[string] []string, error) {
if len(fundListMap) > 0 {
return fundListMap, nil
}
var data []byte
var err error
if !utils.IsFileExist(jsonpath) {
data, err = getFundListFromServer()
if err != nil {
return nil, err
}
ioutil.WriteFile(jsonpath, data, 0)
} else {
data, err = ioutil.ReadFile(jsonpath)
if err != nil {
return nil, err
}
}
fundListMap = map[string] []string {}
var fss [][]string
err = json.Unmarshal(data, &fss)
if err != nil {
return nil, err
}
for _, v := range fss {
if v[3] == "分级杠杆"{
continue
}
fundListMap[v[0]] = v
}
return fundListMap, err
}
func getFundListFromServer() ([]byte, error) {
data, err := httputils.HttpGet2("http://fund.eastmoney.com/js/fundcode_search.js")
if err != nil || len(data) < 1000 {
return nil, err
}
return data[10:len(data)-1], nil
}
这里有做一个缓存,毕竟这个接口数量比较大
4.精简版历史净值 http://fund.eastmoney.com/f10/F10DataApi.aspx?type=lsjz&code=000001&page=1&per=50
上面那个接口数量太大,如果分析大量基金,需要很长时间,需要一个简略的接口
通过github code 搜索 fund.eastmoney.com域名找到了这个。
var apidata={ content:"
净值日期 单位净值 累计净值 日增长率 申购状态 赎回状态 分红送配
2018-05-03 1.0950 3.5060 0.92% 开放申购 开放赎回
2018-05-02 1.0850 3.4960 0.28% 开放申购 开放赎回
2018-04-27 1.0820 3.4930 0.19% 开放申购 开放赎回
2018-04-26 1.0800 3.4910 -2.00% 开放申购 开放赎回
2018-04-25 1.1020 3.5130 0.55% 开放申购 开放赎回
",records:3967,pages:794,curpage:1};
开始以为用空格,直接index + split。后来才发现这是被浏览器渲染过了,通过html source发现里面有html标签。
解析代码如下,把table给解析出来
func parseHtmlFundDetail(code string) ([]*types.FundItem) {
url := fmt.Sprintf(fundDetailURL, code)
data, err := httputils.HttpGet2(url)
if err != nil {
return nil
}
text := string(data)
pos := strings.Index(text, "<tbody>")
if pos == -1 {
return nil
}
text = text[pos + len("<tbody>"):]
fundArr := []*types.FundItem{}
//解析table
for {
pos = strings.Index(text, "<tr>")
if pos == -1 {
break
}
text = text[pos + len("<tr>"):]
fi := &types.FundItem{}
for i := 0; i < 4; i++ {
if strings.Index(text, "<td") < 0 {
break
}
pos = strings.Index(text, ">")
text = text[pos + len(">"):]
pos = strings.Index(text, "</td>")
if pos < 4 {
break
}
text2 := text[:pos]
text = text[pos + len("</td>"):]
switch i {
case 0:
//fi.Time = text2
case 1:
fi.Dwjz = utils.ParseFloat(text2)
case 2:
//fi.Ljjz = fundutils.ParseFloat(text2)
case 3:
//fi.Rzzl = fundutils.ParseFloat(text2)
}
}
fundArr = append(fundArr, fi)
}
return fundArr
}