golang学习: 基金数据爬取分析过滤(一)

从过年在微信投了几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
}

代码github传送

    原文作者:最近不在
    原文地址: https://www.jianshu.com/p/764a21e4efb8
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞