echarts 词云_python Flask+爬虫制作股票查询、历史数据、股评词云网页

自学python的数据分析,爬虫后,花了几天时间看视频学习Flask做了一个简单的股票查询网页。本来还想着加入一些其他功能,比如财务指标分析,舆情分析,最完美的想法是做成一个股票评分系统,输入股票代码可以自动输出分析结果和最终评分以及排名。但是限于没有服务器(不想花钱买)于是先到此为止,后面计划先实现股票评分的本地系统化,然后有机会再部署到网站上。有类似想法的欢迎交流~

先放一张最终效果图。网页左上角输入股票代码,可以在下方显示实时行情、历史走势、股评词云等信息。这是本菜鸡第一次开发网页,所以是非常简单的版本。后面想要继续实现本地化的打分系统,因为虽然同花顺等炒股软件包含智能筛选功能,但是缺少一些个性化的分析指标,不知道我的这个想法是否可行?有没有价值去做?求建议!

e29da12cbffbbd697e96cfc7a896a064.png

前端页面输入股票代码传到后端,即可从网易财经、腾讯财经提供的数据接口api爬取相关股票的数据,然后利用ajax和exharts呈现到网页上。右下方的词云图是从东方财富爬取的股评信息后制作的。

具体实现过程一共花了不到一周时间,下面是全部文档信息。主要文件有四个,app.py为flask写的后端,main.html和main.css写前端,utils.py写了一些爬虫和数据处理的函数。

f297f8022c47fc8d3731cfc112348e71.png

下面分别贴出4个文档的代码,可能包括一些冗余信息(连接mysql的一些操作,之前想做财务指标呈现是用到的)。

1 app.py

flask写的后端,模式比较固定,难点在于前后台数据的交互(用到ajax)。

from flask import Flask
from flask import request
from flask import render_template
from flask import jsonify
# import pymysql
import uitls
import sys
from  jieba.analyse import extract_tags
import string

# sys.setrecursionlimit(100000)
stock_id = '600009'

app = Flask(__name__)


@app.route("/be")
def get_data():
    data = uitls.get_be_data(str(stock_id))
    return jsonify({"股票名称": data[1],"当前价格": data[3],"成交量": data[6],"涨跌幅": data[32],"流通市值": data[44]})


@app.route("/his", methods=["get", "post"])
def get_history_data():
    msg = uitls.get_history_data(str(stock_id))
    print(msg)
    print(type(msg))
    return jsonify({"日期":msg['日期'],"开盘价":msg['开盘价'],"收盘价":msg['收盘价'],"最低价":msg['最低价'],"最高价":msg['最高价']})
        # jsonify({"日期": msg['日期'][0],"开盘价":msg['开盘价'][0]})


@app.route("/gp", methods=["get", "post"])
def get_guping():
    data = uitls.get_guping(stock_id)
    d = []
    for i in data:
        k = i.rstrip(string.digits)
        v = i[len(k):]
        ks = extract_tags(k)
        # print(v)
        for j in ks:
            if not j.isdigit():
                d.append({'name': j, 'value': v})
    return jsonify({'kws':d})



@app.route("/time", methods=["get", "post"])
def get_time():
    return uitls.get_time()


@app.route("/", methods=["get", "post"])
def input_id():
    return render_template("main.html")


@app.route("/ind", methods=["get", "post"])
def get_id():
    global stock_id
    stock_id = request.values.get("股票代码")
    print(stock_id)
    return render_template("main.html")


if __name__ == '__main__':
    app.run()

2.main.html

这里包含了echarts中的K线图和词云图模板,所以看起来比较长。

<!DOCTYPE html>
<html>

   <head link rel="shortcut icon" href="#" />
      <meta charset="utf-8">
      <title>股票数据</title>
        <script src="../static/js/jquery-3.5.1.min.js"></script>
      <script src="../static/js/echarts.min.js"></script>
        <script src="../static/js/echarts-wordcloud.min.js"></script>
        <link href = "../static/css/main.css" rel = "stylesheet"/>
    </head>

   <body>
      <div id = "title">股票查询</div>
        <form action="/ind">
            股票代码 <input name = "股票代码" placeholder="请输入股票代码">
         <button>提交</button>
        </form>
        <div id = "tim">我是时间</div>
      <div id = "be">
         <div class ="tex"><h2>股票名称</h2></div>
         <div class ="tex"><h2>当前价格</h2></div>
         <div class ="tex"><h2>成交量</h2></div>
         <div class ="tex"><h2>涨跌幅</h2></div>
         <div class ="tex"><h2>流通市值</h2></div>
         <div class ="num"><h1>123</h1></div>
         <div class ="num"><h1>123</h1></div>
         <div class ="num"><h1>123</h1></div>
         <div class ="num"><h1>123</h1></div>
         <div class ="num"><h1>123</h1></div>
         </div>
      <div id="bl"  style="width: 800px;height:435px;">我是瞎做</div>
      <script>
         var hisdata = echarts.init(document.getElementById('bl'));
         var upColor = '#ec0000';
         var upBorderColor = '#8A0000';
         var downColor = '#00da3c';
         var downBorderColor = '#008F28';   
         hisdata_option = {
                title: {
                    text: '历史趋势',
                    left: 0
                },
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross'
                    }
                },
                legend: {
                    data: ['日K', 'MA5', 'MA10', 'MA20', 'MA30']
                },
                grid: {
                    left: '10%',
                    right: '10%',
                    bottom: '15%'
                },
                xAxis: {
                    type: 'category',
                    data: [],
                    scale: true,
                    boundaryGap: false,
                    axisLine: {onZero: false},
                    splitLine: {show: false},
                    splitNumber: 20,
                    min: 'dataMin',
                    max: 'dataMax'
                },
                yAxis: {
                    scale: true,
                    splitArea: {
                        show: true
                    }
                },
                dataZoom: [
                    {
                        type: 'inside',
                        start: 50,
                        end: 100
                    },
                    {
                        show: true,
                        type: 'slider',
                        top: '90%',
                        start: 50,
                        end: 100
                    }
                ],
                series: [
                    {
                        name: '日K',
                        type: 'candlestick',
                        data: [],
                        itemStyle: {
                            color: upColor,
                            color0: downColor,
                            borderColor: upBorderColor,
                            borderColor0: downBorderColor
                        },
                        markPoint: {
                            label: {
                                normal: {
                                    formatter: function (param) {
                                        return param != null ? Math.round(param.value) : '';
                                    }
                                }
                            },
                            data: [
                                {
                                    name: 'XX标点',
                                    coord: ['2013/5/31', 2300],
                                    value: 2300,
                                    itemStyle: {
                                        color: 'rgb(41,60,85)'
                                    }
                                },
                                {
                                    name: 'highest value',
                                    type: 'max',
                                    valueDim: 'highest'
                                },
                                {
                                    name: 'lowest value',
                                    type: 'min',
                                    valueDim: 'lowest'
                                },
                                {
                                    name: 'average value on close',
                                    type: 'average',
                                    valueDim: 'close'
                                }
                            ],
                            tooltip: {
                                formatter: function (param) {
                                    return param.name + '<br>' + (param.data.coord || '');
                                }
                            }
                        },
                        markLine: {
                            symbol: ['none', 'none'],
                            data: [
                                [
                                    {
                                        name: 'from lowest to highest',
                                        type: 'min',
                                        valueDim: 'lowest',
                                        symbol: 'circle',
                                        symbolSize: 10,
                                        label: {
                                            show: false
                                        },
                                        emphasis: {
                                            label: {
                                                show: false
                                            }
                                        }
                                    },
                                    {
                                        type: 'max',
                                        valueDim: 'highest',
                                        symbol: 'circle',
                                        symbolSize: 10,
                                        label: {
                                            show: false
                                        },
                                        emphasis: {
                                            label: {
                                                show: false
                                            }
                                        }
                                    }
                                ],
                                {
                                    name: 'min line on close',
                                    type: 'min',
                                    valueDim: 'close'
                                },
                                {
                                    name: 'max line on close',
                                    type: 'max',
                                    valueDim: 'close'
                                }
                            ]
                        }
                    },
                    {
                        name: 'MA5',
                        type: 'line',
                        data: '',
                        smooth: true,
                        lineStyle: {
                            opacity: 0.5
                        }
                    },
                    {
                        name: 'MA10',
                        type: 'line',
                        data: '',
                        smooth: true,
                        lineStyle: {
                            opacity: 0.5
                        }
                    },
                    {
                        name: 'MA20',
                        type: 'line',
                        data: '',
                        smooth: true,
                        lineStyle: {
                            opacity: 0.5
                        }
                    },
                    {
                        name: 'MA30',
                        type: 'line',
                        data: '',
                        smooth: true,
                        lineStyle: {
                            opacity: 0.5
                        }
                    },
            
                ]
            };
   
            
      </script>
      <div id="br" style="width: 800px;height:435px;">我是下游</div>
      <script>
         var gp = echarts.init(document.getElementById('br'));
         var ddd = [{
                     name: 'Farrah Abraham',
                     value: 366,
                     // Style of single text
                 }];
                 
         var maskResource = new Image()
         maskResource.src=image1;
         gp_option = {
             title:{
                             text: '股评词云图',
                             left:'center',
                          },
                         //数据可以点击
                         tooltip:{
                             show:false
                         },
             series: [{
                 type: 'wordCloud',
         
                 // The shape of the "cloud" to draw. Can be any polar equation represented as a
                 // callback function, or a keyword present. Available presents are circle (default),
                 // cardioid (apple or heart shape curve, the most known polar equation), diamond (
                 // alias of square), triangle-forward, triangle, (alias of triangle-upright, pentagon, and star.
         
                 shape: 'circle',
         
                 // A silhouette image which the white area will be excluded from drawing texts.
                 // The shape option will continue to apply as the shape of the cloud to grow.
         
            // maskImage: maskResource,
         
         //         // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud
         //         // Default to be put in the center and has 75% x 80% size.
         
                 left: 'center',
                 top: 'center',
                 width: '70%',
                 height: '80%',
                 right: null,
                 bottom: null,
         
                 // Text size range which the value in data will be mapped to.
                 // Default to have minimum 12px and maximum 60px size.
         
                 sizeRange: [12, 60],
         
                 // Text rotation range and step in degree. Text will be rotated randomly in range [-90, 90] by rotationStep 45
         
                 rotationRange: [-90, 90],
                 rotationStep: 45,
         
                 // size of the grid in pixels for marking the availability of the canvas
                 // the larger the grid size, the bigger the gap between words.
         
                 gridSize: 8,
         
                 // set to true to allow word being draw partly outside of the canvas.
                 // Allow word bigger than the size of the canvas to be drawn
                 drawOutOfBound: false,
         
                 // Global text style
                 textStyle: {
                     normal: {
                         fontFamily: 'sans-serif',
                         fontWeight: 'bold',
                         // Color can be a callback function or a color string
                         color: function () {
                             // Random color
                             return 'rgb(' + [
                                 Math.round(Math.random() * 160),
                                 Math.round(Math.random() * 160),
                                 Math.round(Math.random() * 160)
                             ].join(',') + ')';
                         }
                     },
                     emphasis: {
                         shadowBlur: 10,
                         shadowColor: '#333'
                     }
                 },
         
                 // Data is an array. Each array item must have name and value property.
                 data: [{
                     name: 'Farrah Abraham',
                     value: 366,
                     // Style of single text
                 }]
             }]
         }
         
         
      </script>
      <script  >
         function getatime(){
                  $.ajax({
                  url:"/time",
                  success:function(d){
                  $("#tim").html(d)
                  },
                  error:function(jqXHR, textStatus, errorThrown){
                   console.log(jqXHR.responseText);
                  }
                  })
                  }
         function get_be_data(){
                  $.ajax({
                  url:"/be",
                  success:function(data){
                  $(".num h1").eq(0).text(data['股票名称'])
                  $(".num h1").eq(1).text(data['当前价格'])
                  $(".num h1").eq(2).text(data['成交量'])
                  $(".num h1").eq(3).text(data['涨跌幅'])
                  $(".num h1").eq(4).text(data['流通市值'])
                  },
                  error:function(jqXHR, textStatus, errorThrown){
                  console.log(jqXHR.responseText);
                  }
                  })
                  }
           function get_guping(){
              $.ajax({
                 url:"/gp",
                 success:function(data){
                    gp_option.series[0].data = data.kws;
                    gp.setOption(gp_option);
                    
                 },
                 error:function(jqXHR, textStatus, errorThrown){
                 console.log(jqXHR.responseText);
                 }
              })
           }
         
         function get_his_data(){
            $.ajax({
               url:"/his",                
               success:function(msg){                    
                           var datalen = msg['日期'].length
                           k_time =[]
                            k_value = []
                               for (var i = 0; i < datalen; i++) {
                                       k_time.push(msg['日期'][i]);
                                       k_value.push([msg['开盘价'][i],
                                 msg['收盘价'][i],
                                 msg['最低价'][i],
                                 msg['最高价'][i]])                               
                                   }  
                           console.log(k_time)
                           console.log(k_value)
               function calculateMA(dayCount) {
                  var result = [];
                  for (var i = 0, len = k_value.length; i < len; i++) {
                      if (i < dayCount) {
                           result.push('-');
                           continue;
                       }
                       var sum = 0;
                  for (var j = 0; j < dayCount; j++) {
                              sum += k_value[i - j][1];
                          }
                          result.push(sum / dayCount);
                      }
                      return result;
                  }
                  
                   hisdata_option.xAxis.data = k_time;
                  hisdata_option.series[0].data = k_value;
                  hisdata_option.series[1].data = calculateMA(5);
                  hisdata_option.series[2].data = calculateMA(10);
                  hisdata_option.series[3].data = calculateMA(20);
                  hisdata_option.series[4].data = calculateMA(30);
                  hisdata.setOption(hisdata_option);
               },
               error:function(){
               console.log("获取失败");
               }
               })          
         }
      
      
         setInterval(getatime,1000)
         setInterval(get_be_data,1000)
         get_his_data()
         get_guping()
      </script>
   </body>

</html>

3 .main.css

用于网页整体布局。

body{
   margin: 0;
   background: #333;
}


#title{
   position: absolute;
   width: 40%;
   height: 10%;
   top: 0;
   left: 30%;
   /* background-color: #666666; */
   color: white;
   font-size: 30px;
   
   display: flex;
   align-items: center;
   justify-content: center;
}


#ins{
   position: absolute;
   width: 40%;
   height: 20%;
   top: 10%;
   left: 0;
   background-color: grey;
}


#tim{
   position: absolute;
   /* width: 30%; */
   height: 10%;
   top: 5%;
   right: 2%;
   color: #FFFFFF;
   font-size: 20px;
   /* background-color: green;     */
}


#be{
   position: absolute;
   width: 100%;
   height: 30%;
   top: 10%;
   left: 0;
   color: white;
   /* background-color: #777777; */
}

#bl{
   position: absolute;
   width: 50%;
   height: 60%;
   top: 40%;
   left: 0;
   background-color: #888888;
}

#br{
   position: absolute;
   width: 50%;
   height: 60%;
   top: 40%;
   left: 50%;
   background-color: #999999;
}

.num{
   width: 20%;
   float: left;
   display: flex;
   align-items: center;
   justify-content: center;
   color:yellow;
   font-size: 20px;
}

.tex{
   width: 20%;
   float: left;
   font-family: "幼圆";
   display: flex;
   align-items: center;
   justify-content: center;

4.utils.py

写了一些用于爬取数据和处理数据的函数。

import time
import pymysql
import urllib.request
import pandas as pd
import requests
import re
from bs4 import BeautifulSoup



def get_time():
    time_str = time.strftime("%Y{}%m{}%d{} %X")
    return time_str.format("年", "月", "日")


def get_conn():
    conn = pymysql.connect(host='127.0.0.1', user='root', password='', db='stock', charset='utf8')
    cursor = conn.cursor()
    return conn,cursor


def close_conn(conn,cursor):
    cursor.close()
    conn.close()


def query(sql,*args):
    conn,cursor = get_conn()
    cursor.execute(sql,args)
    res = cursor.fetchall()
    close_conn(conn,cursor)
    return res


# def get_be_data(*args):
#     sql = "SELECT * FROM hangqing where stockid = %s"
#     res = query(sql, args)
#     print(res)
#     return res[0]
def get_be_data(code):
    url = 'http://qt.gtimg.cn/q=sh' + str(code)
    content = urllib.request.urlopen(url, timeout=2).read()
    content = content.decode("gbk").encode("utf-8").decode("utf8", "ignore")
    content = content.split('~')
    return content


def get_history_data(code):
    url = 'http://quotes.money.163.com/service/chddata.html?code=0'+str(code)
    try:
        content = urllib.request.urlopen(url).read()
        content = content.decode("gbk").encode("utf-8")
        with open('E:/hisdata.csv', 'wb')as f:
            f.write(content)
        data = pd.read_csv('E:/hisdata.csv')
        # data = data.to_dict('record')
        data = data[["日期","开盘价","收盘价","最低价","最高价"]]
        # print(data)
        data = data.to_dict()
        data['日期'] = list(data['日期'].values())
        data['开盘价'] = list(data['开盘价'].values())
        data['收盘价'] = list(data['收盘价'].values())
        data['最低价'] = list(data['最低价'].values())
        data['最高价'] = list(data['最高价'].values())
        data['日期'] = data['日期'][::-1]
        data['开盘价'] = data['开盘价'][::-1]
        data['收盘价'] = data['收盘价'][::-1]
        data['最低价'] = data['最低价'][::-1]
        data['最高价'] = data['最高价'][::-1]
    except Exception as e:
        print(e)
    return data


def get_guping(id):
    max_page = 2  # input('请输入爬取页数')
    b = []
    # head = {'User-Agent':' Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'}

    for page in range(1, int(max_page) + 1):
        url = 'http://guba.eastmoney.com/list,{}_{}.html'.format(id, page)
        res = requests.get(url)
        soup = BeautifulSoup(res.text, 'html.parser')
        urllist = soup.find_all('div', {'class': 'articleh'})
        for i in urllist:
            if i.find('a') != None:
                try:
                    title = i.find('a').get_text()
                    yuedu = i.find('span',{'class':'l1 a1'}).get_text()
                    # time = i.find('span', {'class': 'l5 a5'}).get_text()
                    # a = [title + yuedu]
                    b.append(title + yuedu)
                except Exception as e:
                    print(e)
                    pass
    return b[7:]


if __name__ == '__main__':
    msg = get_guping(600002)
    print(msg)

后面想要继续实现本地化的打分系统,因为虽然同花顺等炒股软件包含智能筛选功能,但是缺少一些个性化的分析指标,不知道我的这个想法是否可行?有没有价值去做?求建议!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值