【java爬虫】获取个股详细数据并用echarts展示

前言

前面一篇文章介绍了获取个股数据的方法,本文将会对获取的接口进行一些优化,并且添加查询数据的接口,并且基于后端返回数据编写一个前端页面对数据进行展示。

具体的获取个股数据的接口可以看上一篇文章

【java爬虫】基于springboot+jdbcTemplate+sqlite+OkHttp获取个股的详细数据-CSDN博客

下面是操作演示,首先是爬虫获取股票数据

接着是进行获取个股详细数据并且进行数据展示

数据图表还可以下载下来,下面是下载下来的图片,不过下载下来的图片就不能查看每个点的详细数据了

后端接口

相对于前文,后端接口进行了一定优化,每年的数据分3次获取,时间段分别是0101-0501,0501-0901和0901-1231,并且每次请求都是从2023年开始逐年往前获取,一旦发现没有数据了就停止获取。

服务类的详细代码如下

@Slf4j
@Service
public class StockService {// 没有数据对应的返回private final String NO_DATA_RESPONSE1 = "historySearchHandler({})";private final String NO_DATA_RESPONSE2 = "history({})";@Autowiredprivate SQLiteStockDao sqLiteStockDao;// 获取一个OKHttp实例private OkHttpClient client = new OkHttpClient().newBuilder().connectTimeout(1000, TimeUnit.SECONDS).build();public void clearAll() {sqLiteStockDao.clearAll();}public void createTbaleIfNotExist() {sqLiteStockDao.createTbaleIfNotExist();}// 查询所有的数据public List<StockEntity> queryAllByCode(String code) {return sqLiteStockDao.queryAllByCode(code);}// 获取数据并且存入数据库// 三个参数分别是:股票代码,开始时间和结束时间// 开始时间和结束时间都填年份,代码中会自动补全具体时间public int getDataByYear(String code, String start, String end) {String url = "https://q.stock.sohu.com/hisHq?";Request request = null;Response response = null;int num = 0;// 一年的数据分三次请求String[] startTime = {"0101", "0501", "0901"};String[] endTime = {"0501", "0901", "1231"};try {for (int i = Integer.parseInt(end); i >= Integer.parseInt(start); i--) {for (int j = startTime.length-1; j >=0; j--) {HttpUrl.Builder httpBuiler = HttpUrl.parse(url).newBuilder();String starttime = i + startTime[j];String endtime = i + endTime[j];log.info("开始计算时间段[" + starttime + "," + endtime + "]内数据");httpBuiler.addQueryParameter("code", "cn_" + code);httpBuiler.addQueryParameter("start", starttime);httpBuiler.addQueryParameter("end", endtime);httpBuiler.addQueryParameter("stat", "1");httpBuiler.addQueryParameter("order", "D");httpBuiler.addQueryParameter("period", "d");httpBuiler.addQueryParameter("callback", "history");httpBuiler.addQueryParameter("rt", "jsonp");request = new Request.Builder().url(httpBuiler.build()).get()   //默认就是GET请求,可以不写.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36").build();response = client.newCall(request).execute();String res = response.body().string();log.info("请求得到的数据:" + res);if (res.contains(NO_DATA_RESPONSE1) || res.contains(NO_DATA_RESPONSE2)) {// 如果返回为空就认为后面没有数据了log.info("时间段[" + starttime + "," + endtime + "]没有数据");return num;} else {List<StockEntity> entities = parseStrToArr(res, code);sqLiteStockDao.insertItems(entities);log.info("时间段[" + starttime + "," + endtime + "]内有" + entities.size() + "条数据");num += entities.size();}}}} catch (IOException e) {e.printStackTrace();}return num;}// 将string数据解析成List列表private List<StockEntity> parseStrToArr(String res, String code) {if (res.contains(NO_DATA_RESPONSE1) || res.contains(NO_DATA_RESPONSE2)) {return new ArrayList<>();}List<StockEntity> entities = new ArrayList<>();res = res.split("\\(\\[")[1].split("]\\)")[0];JSONObject jsonObject = JSON.parseObject(res);// 获取 hq 字段的值Object hq = jsonObject.get("hq");// 判断 hq 的值是否为数组if (hq instanceof JSONArray) {// 遍历数组for (Object arr : (JSONArray) hq) {JSONArray jsonArray = (JSONArray) arr;StockEntity entity = new StockEntity();entity.setRecord_date((String) jsonArray.get(0));Double open_price = Double.parseDouble((String) jsonArray.get(1));Double close_price = Double.parseDouble((String) jsonArray.get(2));Double change_amend = Double.parseDouble((String) jsonArray.get(3));Double change_range = Double.parseDouble(((String) jsonArray.get(4)).split("%")[0]);Double max_price = Double.parseDouble((String) jsonArray.get(5));Double min_price = Double.parseDouble((String) jsonArray.get(6));Double volume = Double.parseDouble((String) jsonArray.get(7));Double turnover = Double.parseDouble((String) jsonArray.get(8));Double turnover_rate = Double.parseDouble(((String) jsonArray.get(9)).split("%")[0]);entity.setOpen_price(open_price);entity.setClose_price(close_price);entity.setChange_amend(change_amend);entity.setChange_range(change_range);entity.setMax_price(max_price);entity.setMin_price(min_price);entity.setVolume(volume);entity.setTurnover(turnover);entity.setTurnover_rate(turnover_rate);entity.setCode(code);entity.setId(entity.getCode() + "_" + (String) jsonArray.get(0));entities.add(entity);}}return entities;}}

Dao层新增了查询某一只股票详细数据的方法,详细代码如下

@Slf4j
@Repository
public class SQLiteStockDao implements StockDao {private final String TABLE_NAME = "stock_table";@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void clearAll() {String sql = "DELETE FROM " + TABLE_NAME;jdbcTemplate.batchUpdate(sql);log.info("成功清空数据表" + TABLE_NAME);}@Overridepublic void createTbaleIfNotExist() {Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name = ?", Integer.class, TABLE_NAME);if (count == 0) {String sql = "CREATE TABLE " + TABLE_NAME + "(" +"id VARCHAR(50) PRIMARY KEY," +"code VARCHAR(20)," +           // 股票代码"record_date VARCHAR(20)," +    // 记录的时间"open_price float," +           // 开盘价"close_price float," +           // 收盘价"change_ament float," +          // 涨跌额"change_range float," +          // 涨跌幅"max_price float," +             // 最高价格"min_price float," +             // 最低价格"volume float," +                // 成交量(手)"turnover float," +              // 成交额(万)"turnover_rate float)";               // 换手率jdbcTemplate.execute(sql);log.info(TABLE_NAME + "建表成功");} else {log.info("建表失败,表格已存在");}}@Overridepublic void insertItems(List<StockEntity> entityList) {String sql = "INSERT OR IGNORE INTO " + TABLE_NAME + " (id, code, record_date," +"open_price, close_price, change_ament," +"change_range, max_price, min_price," +"volume, turnover, turnover_rate) values (?,?,?,?,?,?,?,?,?,?,?,?)";// 将列表转为Object数组List<Object[]> arr = new ArrayList<>();for(int i=0; i<entityList.size(); i++) {arr.add(entityList.get(i).changeToArray());}jdbcTemplate.batchUpdate(sql, arr);}@Overridepublic List<StockEntity> queryAllByCode(String code) {String sql = "SELECT open_price, close_price, record_date, volume FROM " + TABLE_NAME +" WHERE code=? ORDER BY record_date DESC";log.info("执行sql:" + sql);List<StockEntity> stockEntities = jdbcTemplate.query(sql, new Object[]{code}, new BeanPropertyRowMapper<>(StockEntity.class));return stockEntities;}}

Dao层对应的实体类如下

@Data
@NoArgsConstructor
@AllArgsConstructor
public class StockEntity {private String id;private String code;private String record_date;private Double open_price;private Double close_price;private Double change_amend;private Double change_range;private Double max_price;private Double min_price;private Double volume;private Double turnover;private Double turnover_rate;// 将数据转换为Object数组public Object[] changeToArray() {Object[] arr = new Object[]{id,code,record_date,open_price.toString(),close_price.toString(),change_amend.toString(),change_range.toString(),max_price.toString(),min_price.toString(),volume.toString(),turnover.toString(),turnover_rate.toString()};return arr;}}

最后就是提供给前端调用的接口了,主要是获取某一只股票的数据,只需要传入股票代码就能开始获取数据,还有查询的接口,同样是输入股票代码进行查询,控制类的详细代码如下

@Controller
@CrossOrigin
@RequestMapping("/stock")
public class StockController {private final String START_YEAR = "1985";private final String END_YEAR = "2023";@Autowiredprivate StockService stockService;@RequestMapping("/clear")@ResponseBodypublic String clear() {stockService.clearAll();return "success";}@RequestMapping("/createTable")@ResponseBodypublic String getData() {stockService.createTbaleIfNotExist();return "success";}@RequestMapping("/getDataByYear/{code}/{start}/{end}")@ResponseBodypublic String getDataByYear(@PathVariable("code") String code,@PathVariable("start") String start,@PathVariable("end") String end) {Integer num = stockService.getDataByYear(code, start, end);return num.toString();}@RequestMapping("/getData/{code}")@ResponseBodypublic String getData(@PathVariable("code") String code) {Integer num = stockService.getDataByYear(code, START_YEAR, END_YEAR);List<StockEntity> stockEntityList = stockService.queryAllByCode(code);return JSON.toJSONString(stockEntityList);}@RequestMapping("/queryData/{code}")@ResponseBodypublic String queryData(@PathVariable("code") String code) {List<StockEntity> stockEntityList = stockService.queryAllByCode(code);return JSON.toJSONString(stockEntityList);}
}

前端页面

下面来说一下前端页面的编写,前端页面一共分为三个大块,

  • 沪深300成分股数据和操作按钮,通过按钮可以进行数据获取或者数据展示
  • 个股详细数据,这一个表格的内容会在你选定具体的股票后变更
  • 数据展示,选定个股后会动态生成展示的数据

前端主要用了vue+element-plus+axios+echarts进行编写,echarts表格参数参考了官方示例,由于数据量比较大,所以选用了大数据量的图表,参考的地址如下

Examples - Apache ECharts

下面展示页面的详细代码

<template><div><el-row class="container"><div class="left-grid"><el-card class="box-card"><template #header><div class="card-header"><span>沪深300成分股</span></div></template><el-table:data="table_data":show-header="true":max-height="250"stripe><el-table-columntype="index"label="序号"width="65%"></el-table-column><el-table-columnprop="code"label="股票代码"width="85%"></el-table-column><el-table-columnprop="name"label="公司简称"width="85%"></el-table-column><el-table-column prop="industry" label="操作"><template #default="scope"><el-buttontype="primary"size="small"@click="queryData(scope.row)">查询</el-button><el-buttontype="primary"size="small"@click="getData(scope.row)">获取</el-button></template></el-table-column></el-table></el-card><el-card><template #header><div class="card-header"><span>{{ table_title }}</span></div></template><el-tablev-loading="loading":data="stock_data":show-header="true":max-height="220"stripe><el-table-column prop="record_date" label="时间"></el-table-column><el-table-column prop="open_price" label="开盘价"></el-table-column><el-table-columnprop="close_price"label="收盘价"></el-table-column><el-table-columnprop="volume"label="成交量(手)"></el-table-column></el-table></el-card></div><div class="right-grid" ref="myChart"></div></el-row></div>
</template><script>
import axios from "axios";
import { ElMessage } from "element-plus";
import { getCurrentInstance } from "vue";
export default {data() {return {update_status: "未开始",loading: true,table_title: "个股数据",// 沪深300成分股数据table_data: [],// 个股详细数据stock_data: [],echarts: getCurrentInstance().appContext.config.globalProperties.$echarts,};},mounted() {this.init();},methods: {init() {var url = "http://localhost:9001/queryAll";axios.get(url).then((response) => {this.table_data = response.data;console.log(response);this.loading = false;}).catch((error) => {console.log(error);this.loading = false;});},// 绘制折线图create_axis() {//3.初始化实例对象 echarts.init(dom容器)var data_xAxis = [];var data_yAxis = [];for (var i = this.stock_data.length - 1; i >= 0; i--) {data_xAxis.push(this.stock_data[i].record_date);data_yAxis.push(this.stock_data[i].close_price);}console.log(data_xAxis);console.log(data_yAxis);var dom = this.$refs["myChart"]; // 获取dom节点var myChart = this.echarts.init(dom);//4.指定配置项和数据var option = {tooltip: {trigger: "axis",position: function (pt) {return [pt[0], "10%"];},},title: {left: "center",text: this.table_title,},toolbox: {feature: {dataZoom: {yAxisIndex: "none",},restore: {},saveAsImage: {},},},xAxis: {type: "category",boundaryGap: false,data: data_xAxis,},yAxis: {type: "value",boundaryGap: [0, "100%"],},dataZoom: [{type: "inside",start: 0,end: 10,},{start: 0,end: 10,},],series: [{name: this.table_title,type: "line",symbol: "none",sampling: "lttb",itemStyle: {color: "rgb(135,206,235)",},areaStyle: {color: new this.echarts.graphic.LinearGradient(0, 0, 0, 1, [{offset: 0,color: "rgb(135,206,250)",},{offset: 1,color: "rgb(135,206,235)",},]),},data: data_yAxis,},],};//5.将配置项设置给echarts实例对象,使用刚指定的配置项和数据显示图表。myChart.setOption(option);},// 查询数据queryData(row) {var url = "http://localhost:9001/stock/queryData/" + row.code;this.loading = true;this.table_title = row.code + " " + row.name;ElMessage("开始查询 " + this.table_title + " 的数据");axios.get(url).then((response) => {this.stock_data = response.data;console.log(response);this.loading = false;ElMessage({message: "查询 " + this.table_title + " 的数据成功",type: "success",});// 绘制数据this.create_axis();}).catch((error) => {console.log(error);this.loading = false;ElMessage.error("查询 " + this.table_title + " 的数据失败");});},// 获取数据getData(row) {var url = "http://localhost:9001/stock/getData/" + row.code;this.loading = true;this.table_title = row.code + " " + row.name;ElMessage("开始获取 " + this.table_title + " 的数据");axios.get(url).then((response) => {this.stock_data = response.data;console.log(response);this.loading = false;ElMessage({message: "获取 " + this.table_title + " 的数据成功",type: "success",});// 绘制数据this.create_axis();}).catch((error) => {console.log(error);this.loading = false;ElMessage.error("获取 " + this.table_title + " 的数据失败");});},},
};
</script><style scoped>
.card-header {display: flex;justify-content: space-between;align-items: center;
}
.container {display: grid;grid-template-columns: 35% 65%;width: 100%;height: 80vh;
}
.left-grid {background-color: #f0f0f0;border-radius: 2%;padding: 10px;height: 95%;
}
.right-grid {background-color: #f9ecc3;border-radius: 2%;padding: 10px;height: 95%;
}
</style>

前端页面有一个问题,就是数据量非常非常大,页面会很卡,这个问题的其中一个解决办法就是在获取数据的时候颗粒度可以小一点,比如一个星期获取一个数据之类的,因为一张图表也不可能展示出所有的数据,大家可能也只是想看一个总体的走势图,不过本文没有进行相关的优化,因为个人自用的话这点卡顿是可以接受的。 

结语

本文展示了通过网络爬虫获取个股详细数据,并且进行数据展示的方法,通过这个方法可以查询个股数据,并且用图表的方式将股票价格展示出来,这样可以非常直观地观察某一只股票的价格走势,由于获取到的数据量非常大,后期还可以进行一定的数据分析,如果你有什么想法欢迎和我交流,下面展示一下获取到的股票走势图。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/823361.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

开源radishes高仿网易云音乐完整源码,可试听和下载“灰色”歌曲,跨平台的无版权音乐平台

源码介绍 Radishes是项目名称&#xff0c;是由萝卜翻译而来。可以在这里试听和下载“灰色”歌曲&#xff0c;是一个可以跨平台的无版权音乐平台。 萝卜音乐界面和功能参考 windows 网易云音乐界面和 ios 的网易云音乐 安装依赖 cd radishes/ yarn bootstrap 运行项目 web:…

nodejs+vue+ElementUi摄影预约服务网站系统91f0v

本系统提供给管理员对首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;员工管理&#xff0c;摄影套餐管理&#xff0c;套餐系列管理&#xff0c;客片欣赏管理&#xff0c;摄影预约管理&#xff0c;摄影订单管理&#xff0c;取片通知管理&#xff0c;摄影评价管理&…

智能硬件(8)之蜂鸣器模块

学好开源硬件&#xff0c;不仅仅需要会编程就可以了&#xff0c;电路基础是很重要的&#xff1b;软件和硬件都玩的溜&#xff0c;才是高手&#xff0c;那么小编为了方便大家的学习&#xff0c;特别画了一块智能传感器板子&#xff0c;来带领大家学习电路基础&#xff0c;玩转智…

平台无关性和语言无关性的记录

目录 背景 平台无关性 语言无关性 背景 最近在学习Java虚拟机&#xff08;JVM: Java Virtual Machine&#xff09;,在学习过程中&#xff0c;再一次学习了JVM的平台无关性这一特性&#xff0c;此外也了解到了虚拟机的另外一种中立特性 --- 语言无关性&#xff0c;下面进行简单…

【网络安全】upload靶场pass1-10思路

目录 Pass-1 Pass-2 Pass-3 Pass-4 Pass-5 Pass-6 Pass-7 Pass-8 Pass-9 Pass-10 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1…

GBASE南大通用-GBase 8s分片表操作 提升大数据处理性能

目录 一、GBase 8s分片表的优势 二、六种分片方法 轮转 1.轮转法 基于表达式分片 2.基本表达式 3.Mod运算表达式 4.Remainder关键字方式 5.List方式 6.interval 固定间隔 三、分片表的索引 1.创建索引的注意事项 2.detach索引替代delete功能展现 3.在现有分片表上增加一个新…

PostgreSQL 作为向量数据库:入门和扩展

PostgreSQL 拥有丰富的扩展和解决方案生态系统&#xff0c;使我们能够将该数据库用于通用人工智能应用程序。本指南将引导您完成使用 PostgreSQL 作为向量数据库构建生成式 AI 应用程序所需的步骤。 我们将从pgvector 扩展开始&#xff0c;它使 Postgres 具有特定于向量数据库…

Spring Boot学习随笔- 集成MyBatis-Plus(二)条件查询QueryWrapper、聚合函数的使用、Lambda条件查询

学习视频&#xff1a;【编程不良人】Mybatis-Plus整合SpringBoot实战教程,提高的你开发效率,后端人员必备! 查询方法详解 普通查询 // 根据主键id去查询单个结果的。 Test public void selectById() {User user userMapper.selectById(1739970502337392641L);System.out.print…

Games101作业5

1.实现Renderer.cpp 中的 Render()&#xff1a;为每个像素生成光线 这里你需要为每个像素生成一条对应的光 线&#xff0c;然后调用函数 castRay() 来得到颜色&#xff0c;最后将颜色存储在帧缓冲区的相 应像素中。 我们要做的就是将屏幕空间下的坐标最后转换到世界空间的坐标…

Linux CPU 数据 Metrics 指标解读

过去从未仔细了解过使用 top 和 htop 等命令时显式的CPU信息&#xff0c;本文我们详解解读和标注一下各个数据项的含义&#xff0c;同时和 Ganglia 显式的数据做一个映射。开始前介绍一个小知识&#xff0c;很多查看CPU的命令行工具都是 cat /proc/stat 里的数据&#xff0c;所…

城市自贸区/经开区/产业园基于EasyCVR视频技术的可视化、移动化、智能化视频监管方案

一、背景需求 移动互联网的发展激发了用户对轻应用的使用习惯。4G、5G使得无线带宽快速提升&#xff0c;令大流量视频数据流逐渐从PC往手持终端转移。借助智能手持终端也可以实时查看、远程控制、存储录像、抓拍图像&#xff0c;能方便快捷地掌握所关注区域的视频动态。 随着…

【面试】 Maven 的八大核心概念

Maven 的八大核心概念 在这里&#xff0c;举出这个标题&#xff0c;自然大家知道Maven是干啥的&#xff0c;就不过多进行赘述&#xff01;我们主要对于Maven的八大核心概念做一个解释补充&#xff0c;这也是我自己的一个学习历程&#xff0c;我们一起共勉&#xff01; 文章概述…

【代码随想录】刷题笔记Day42

前言 这两天机器狗终于搞定了&#xff0c;一个控制ROS大佬&#xff0c;一个计院编程大佬&#xff0c;竟然真把创新点这个弄出来了&#xff0c;牛牛牛牛&#xff08;菜鸡我只能负责在旁边喊加油&#xff09;。下午翘了自辩课来刷题&#xff0c;这次应该是元旦前最后一刷了&…

【教程】Typecho Joe主题开启并修复壁纸相册不显示问题

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 背景说明 Joe主题本身支持“壁纸”功能&#xff0c;其实就是相册。当时还在网上找了好久相册部署的开源项目&#xff0c;太傻了。 但是网上教程很少&#xff0c;一没说如何开启壁纸功能&#xff0c;二没说开启后为…

【数据结构复习之路】查找(严蔚敏版)万字详解

专栏&#xff1a;数据结构复习之路 复习完上面四章【线性表】【栈和队列】【串】【数组和广义表】【树和二叉树】【图】&#xff0c;我们接着复习 查找&#xff0c;这篇文章我写的非常详细且通俗易懂&#xff0c;看完保证会带给你不一样的收获。如果对你有帮助&#xff0c;看在…

鸿蒙HarmonyOS-带笔锋手写板(三)

笔者用ArkTS 写了一个简单的带笔锋的手写板应用&#xff0c;并且可以将手写内容保存为图片。 一、效果图 手写效果如下&#xff08;在鸿蒙手机模拟器上运行&#xff0c;手写时反应可能会有点慢&#xff09; 二、实现方法 参考文章&#xff1a; 支持笔锋效果的手写签字控件_a…

精品Nodejs实现的校园疫情防控管理系统的设计与实现健康打卡

《[含文档PPT源码等]精品Nodejs实现的校园疫情防控管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 操作系统&#xff1a;Windows 10、Windows 7、Win…

【Linux操作系统】探秘Linux奥秘:操作系统的入门与实战

&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《操作系统实验室》&#x1f516;诗赋清音&#xff1a;柳垂轻絮拂人衣&#xff0c;心随风舞梦飞。 山川湖海皆可涉&#xff0c;勇者征途逐星辉。 目录 &#x1fa90;1 初识Linux OS …

共享单车之数据存储

文章目录 第1关&#xff1a;获取工作簿中的数据第2关&#xff1a;保存共享单车数据 第1关&#xff1a;获取工作簿中的数据 相关知识 获取工作簿中的信息&#xff0c;我们可以使用Java POI&#xff08;POI是一个提供API给Java程序对Microsoft Office格式档案读和写的功能&#…

用通俗易懂的方式讲解大模型:HugggingFace 推理 API、推理端点和推理空间使用详解

接触 AI 的同学肯定对HuggingFace[1]有所耳闻&#xff0c;它凭借一个开源的 Transformers 库迅速在机器学习社区大火&#xff0c;为研究者和开发者提供了大量的预训练模型&#xff0c;成为机器学习界的 GitHub。 在 HuggingFace 上我们不仅可以托管模型&#xff0c;还可以方便…