美文网首页
golang学习: 基金数据爬取分析过滤(一)

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

作者: 最近不在 | 来源:发表于2018-05-04 18:08 被阅读18987次

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

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/hnqjrftx.html