最近有小伙伴问我能不能抓取同花顺的数据,最近股票行情还不错,想把数据抓下来自己分析分析。我大A股,大家都知道的,一个概念火了,相应的股票就都大涨。

如果能及时获取股票涨跌信息,那就能在刚开始火起来的时候杀进去,小赚一笔。但是股票那么多,小伙伴也盯不过来,于是就微信问我,能不能抓取同花顺的板块下的股票信息存到数据库里?他就能根据数据库里的数据,制定一些策略。

俗话说:哪里有痛点,哪里就有编程!不就是个同花顺嘛,办他!

调研背景

于是我点开了同花顺的板块页面:http://q.10jqka.com.cn/gn/

发现有好268个概念:

分析概念板块的网页HTML发现,268个概念的URL就在HTML中:

打开其中的“阿里巴巴概念”,发现网页又有分页:

分页的数据,是根据接口实时获取的,接口中注入了一些Cooki信息和其他标识,同花顺的反爬虫策略一直比较强,使用模拟接口的方式可能难度会比较大,所以使用selenium模拟浏览器操作这种方式比较完美。

设计方案

技术方向有了,再简单整理一下思路:

  • 根据http://q.10jqka.com.cn/gn/,获取板块网页的源码HTML,用Jsoup解析HTML获取每个概念的url信息放到List中
  • 遍历List,根据概念的url获取概念网页源码HTML,解析股票信息
    • 再递归点击执行“下一页”操作,获取每一页的股票数据,直至尾页
  • 把股票信息存储到数据库

配置环境

先介绍下工程所需要的环境:

编码工具:idea

语言:java

依赖:jdk1.8、maven、chrome、ChromeDriver

我们使用的方案是模拟浏览器的操作,所以我们需要在电脑安装chrome浏览器和chromedriver驱动。chrome的安装这里就不说了,百度下载个浏览器就行。

关键是安装 ChromeDriver ,需要安装和当前chrome版本一致的驱动才写。

查看chrome版本:chrome浏览器输入:Chrome://version

在根据版本下载对于的驱动,版本最好要一致,比如我的是:79.0.3945.117 (正式版本) (64 位),我下载的就是 79.0.3945.36。

ChromeDriver各版本的下载地址:

淘宝镜像:https://npm.taobao.org/mirrors/chromedriver

谷歌下载(需要翻墙,不推荐):https://sites.google.com/a/chromium.org/chromedriver/downloads

下面这一步可做可不做,不做也能启动工程,只是需要修改代码中的一个配置即可。

配置方式:

将下载好的ChromeDriver文件放到/usr/local/bin/目录下:

cp chromedriver /usr/local/bin/

检测是否安装成功

chromedriver --version

如果不配置,只需要记得修改ChromeDriver在代码中配置的路径,你只需要将路径改为你自己的ChromeDriver路径即可,比如我的是:

System.setProperty(
"webdriver.chrome.driver",
"/Users/admin/Documents/selenium/chrome/79.0.3945.36/chromedriver"
);

记得修改代码里ChromeDriver的路径。

记得修改代码里ChromeDriver的路径。

记得修改代码里ChromeDriver的路径。

验证方案

首先完成设计方案中的三步

package com.ths.controller;

import com.ths.service.ThsGnCrawlService;
import com.ths.service.ThsGnDetailCrawlService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap;
import java.util.List; @Controller
public class CrawlController { @Autowired
private ThsGnCrawlService thsGnCrawlService; @Autowired
private ThsGnDetailCrawlService thsGnDetailCrawlService; @RequestMapping("/test")
@ResponseBody
public void test() {
// 抓取所有概念板块的url
List<HashMap<String, String>> list = thsGnCrawlService.ThsGnCrawlListUrl();
// 放入阻塞队列
thsGnDetailCrawlService.putAllArrayBlockingQueue(list);
// 根据url多线程抓取
thsGnDetailCrawlService.ConsumeCrawlerGnDetailData(1);
} }

先看看thsGnCrawlService.ThsGnCrawlListUrl();方法,如何抓取所有概念板块的url?

package com.ths.service.impl;

import com.ths.parse.service.ThsParseHtmlService;
import com.ths.service.ThsGnCrawlService;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit; @Service
public class ThsGnCrawlServiceImpl implements ThsGnCrawlService {
private final static Logger LOGGER = LoggerFactory.getLogger(ThsGnCrawlServiceImpl.class); /**
* 同花顺全部概念板块url
*/
private final static String GN_URL = "http://q.10jqka.com.cn/gn/"; @Autowired
private ThsParseHtmlService thsParseHtmlService; @Override
public List<HashMap<String, String>> ThsGnCrawlListUrl() {
System.setProperty("webdriver.chrome.driver", "/Users/admin/Documents/selenium/chrome/79.0.3945.36/chromedriver");
ChromeOptions options = new ChromeOptions();
//是否启用浏览器界面的参数
//无界面参数
// options.addArguments("headless");
//禁用沙盒 就是被这个参数搞了一天
// options.addArguments("no-sandbox");
WebDriver webDriver = new ChromeDriver(options);
try {
// 根据网速设置,网速慢可以调低点
webDriver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
webDriver.get(GN_URL);
Thread.sleep(1000L);
String gnWindow = webDriver.getWindowHandle();
// 获取同花顺概念页面的HTML
String thsGnHtml = webDriver.getPageSource();
LOGGER.info("获取同花顺url:[{}]的html为:/n{}", GN_URL, thsGnHtml);
return thsParseHtmlService.parseGnHtmlReturnGnUrlList(thsGnHtml);
} catch (Exception e) {
LOGGER.error("获取同花顺概念页面的HTML,出现异常:", e);
} finally {
webDriver.close();
webDriver.quit();
}
return null;
}
}

这里使用了上文说的ChromeDriver,我们需要根据自己的配置,修改对应的地址(重复第四遍!)。

根据代码可以看到String thsGnHtml = webDriver.getPageSource();方法获取页面的HTML,再解析HTML就能获取各大概念板块的url。

解析HTML我使用的是Jsoup,简单易上手,api也很简单,解析HTML获取各大板块的url的代码如下:

package com.ths.parse.service.impl;

import com.ths.parse.service.ThsParseHtmlService;
import org.jsoup.Jsoup;
import org.jsoup.helper.StringUtil;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; @Service
public class ThsParseHtmlServiceImpl implements ThsParseHtmlService { /**
* 解析同花顺概念板块的Html页面:http://q.10jqka.com.cn/gn/
* 返回所有概念板块的url地址
*/
public List<HashMap<String, String>> parseGnHtmlReturnGnUrlList(String html) {
if (StringUtil.isBlank(html)) {
return null;
}
List<HashMap<String, String>> list = new ArrayList<>();
Document document = Jsoup.parse(html);
Elements cateItemsFromClass = document.getElementsByClass("cate_items");
for (Element element : cateItemsFromClass) {
Elements as = element.getElementsByTag("a");
for (Element a : as) {
String gnUrl = a.attr("href");
String name = a.text();
HashMap<String, String> map = new HashMap<>();
map.put("url", gnUrl);
map.put("gnName", name);
list.add(map);
}
}
return list;
}
}

可以看到,只要在html中有的数据,定位到标签就能获取对应的数据。

然后放到阻塞队列:

		/**
* 阻塞队列
*/
private ArrayBlockingQueue<HashMap<String, String>> arrayBlockingQueue = new ArrayBlockingQueue<>(1000); @Override
public void putAllArrayBlockingQueue(List<HashMap<String, String>> list) {
if (!CollectionUtils.isEmpty(list)) {
arrayBlockingQueue.addAll(list);
}
}

再开启多个线程,从阻塞队列里获取url,分别抓取概念板块的股票数据,如果页面有分页,就循环点击下一页,再获取数据,直到尾页,代码如下:

package com.ths.service.impl;

import com.ths.dao.StockThsGnInfoDao;
import com.ths.domain.StockThsGnInfo;
import com.ths.service.ThsGnDetailCrawlService;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit; @Service
public class ThsGnDetailCrawlServiceImpl implements ThsGnDetailCrawlService {
private final static Logger LOGGER = LoggerFactory.getLogger(ThsGnDetailCrawlServiceImpl.class); /**
* 阻塞队列
*/
private ArrayBlockingQueue<HashMap<String, String>> arrayBlockingQueue = new ArrayBlockingQueue<>(1000); @Autowired
private StockThsGnInfoDao stockThsGnInfoDao; @Override
public void putAllArrayBlockingQueue(List<HashMap<String, String>> list) {
if (!CollectionUtils.isEmpty(list)) {
arrayBlockingQueue.addAll(list);
}
} @Override
public void ConsumeCrawlerGnDetailData(int threadNumber) {
for (int i = 0; i < threadNumber; ++i) {
LOGGER.info("开启线程第[{}]个消费", i);
new Thread(new crawlerGnDataThread()).start();
}
LOGGER.info("一共开启线程[{}]个消费", threadNumber);
} class crawlerGnDataThread implements Runnable { @Override
public void run() {
try {
while (true) {
Map<String, String> map = arrayBlockingQueue.take();
String url = map.get("url");
String gnName = map.get("gnName");
String crawlerDateStr = new SimpleDateFormat("yyyy-MM-dd HH:00:00").format(new Date());
//chromederiver存放位置
System.setProperty("webdriver.chrome.driver", "/Users/admin/Documents/selenium/chrome/79.0.3945.36/chromedriver");
ChromeOptions options = new ChromeOptions();
//无界面参数
// options.addArguments("headless");
//禁用沙盒 就是被这个参数搞了一天
// options.addArguments("no-sandbox");
WebDriver webDriver = new ChromeDriver(options);
try {
webDriver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
webDriver.get(url);
Thread.sleep(1000L);
String oneGnHtml = webDriver.getPageSource();
LOGGER.info("当前概念:[{}],html数据为[{}]", gnName, oneGnHtml);
LOGGER.info(oneGnHtml);
// TODO 解析并存储数据
parseHtmlAndInsertData(oneGnHtml, gnName, crawlerDateStr);
clicktoOneGnNextPage(webDriver, oneGnHtml, gnName, crawlerDateStr);
} catch (Exception e) {
LOGGER.error("用chromerDriver抓取数据,出现异常,url为[{}],异常为[{}]", url, e);
} finally {
webDriver.close();
webDriver.quit();
}
}
} catch (Exception e) {
LOGGER.error("阻塞队列出现循环出现异常:", e);
}
}
} public void parseHtmlAndInsertData(String html, String gnName, String crawlerDateStr) {
Document document = Jsoup.parse(html);
// Element boardElement = document.getElementsByClass("board-hq").get(0);
// String gnCode = boardElement.getElementsByTag("h3").get(0).getElementsByTag("span").get(0).text(); Element table = document.getElementsByClass("m-pager-table").get(0);
Element tBody = table.getElementsByTag("tbody").get(0);
Elements trs = tBody.getElementsByTag("tr");
for (Element tr : trs) {
try {
Elements tds = tr.getElementsByTag("td");
String stockCode = tds.get(1).text();
String stockName = tds.get(2).text();
BigDecimal stockPrice = parseValueToBigDecimal(tds.get(3).text());
BigDecimal stockChange = parseValueToBigDecimal(tds.get(4).text());
BigDecimal stockChangePrice = parseValueToBigDecimal(tds.get(5).text());
BigDecimal stockChangeSpeed = parseValueToBigDecimal(tds.get(6).text());
BigDecimal stockHandoverScale = parseValueToBigDecimal(tds.get(7).text());
BigDecimal stockLiangBi = parseValueToBigDecimal(tds.get(8).text());
BigDecimal stockAmplitude = parseValueToBigDecimal(tds.get(9).text());
BigDecimal stockDealAmount = parseValueToBigDecimal(tds.get(10).text());
BigDecimal stockFlowStockNumber = parseValueToBigDecimal(tds.get(11).text());
BigDecimal stockFlowMakertValue = parseValueToBigDecimal(tds.get(12).text());
BigDecimal stockMarketTtm = parseValueToBigDecimal(tds.get(13).text());
// 存储数据
StockThsGnInfo stockThsGnInfo = new StockThsGnInfo();
stockThsGnInfo.setGnName(gnName);
stockThsGnInfo.setGnCode(null);
stockThsGnInfo.setStockCode(stockCode);
stockThsGnInfo.setStockName(stockName);
stockThsGnInfo.setStockPrice(stockPrice);
stockThsGnInfo.setStockChange(stockChange);
stockThsGnInfo.setStockChangePrice(stockChangePrice);
stockThsGnInfo.setStockChangeSpeed(stockChangeSpeed);
stockThsGnInfo.setStockHandoverScale(stockHandoverScale);
stockThsGnInfo.setStockLiangBi(stockLiangBi);
stockThsGnInfo.setStockAmplitude(stockAmplitude);
stockThsGnInfo.setStockDealAmount(stockDealAmount);
stockThsGnInfo.setStockFlowStockNumber(stockFlowStockNumber);
stockThsGnInfo.setStockFlowMakertValue(stockFlowMakertValue);
stockThsGnInfo.setStockMarketTtm(stockMarketTtm);
stockThsGnInfo.setCrawlerTime(crawlerDateStr);
stockThsGnInfo.setCrawlerVersion("同花顺概念板块#" + crawlerDateStr);
stockThsGnInfo.setCreateTime(new Date());
stockThsGnInfo.setUpdateTime(new Date());
stockThsGnInfoDao.insert(stockThsGnInfo);
} catch (Exception e) {
LOGGER.error("插入同花顺概念板块数据出现异常:", e);
} }
} public BigDecimal parseValueToBigDecimal(String value) {
if (StringUtils.isEmpty(value)) {
return BigDecimal.ZERO;
} else if ("--".equals(value)) {
return BigDecimal.ZERO;
} else if (value.endsWith("亿")) {
return new BigDecimal(value.substring(0, value.length() - 1)).multiply(BigDecimal.ONE);
}
return new BigDecimal(value);
} public boolean clicktoOneGnNextPage(WebDriver webDriver, String oneGnHtml, String key, String crawlerDateStr) throws InterruptedException {
// 是否包含下一页
String pageNumber = includeNextPage(oneGnHtml);
if (!StringUtils.isEmpty(pageNumber)) {
WebElement nextPageElement = webDriver.findElement(By.linkText("下一页"));
webDriver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
nextPageElement.click();
Thread.sleep(700);
String nextPageHtml = webDriver.getPageSource();
LOGGER.info("下一页:");
LOGGER.info(nextPageHtml);
// TODO 解析并存储数据
parseHtmlAndInsertData(nextPageHtml, key, crawlerDateStr);
clicktoOneGnNextPage(webDriver, nextPageHtml, key, crawlerDateStr);
}
return true;
} public String includeNextPage(String html) {
Document document = Jsoup.parse(html);
List<Element> list = document.getElementsByTag("a");
for (Element element : list) {
String a = element.text();
if ("下一页".equals(a)) {
String pageNumber = element.attr("page");
return pageNumber;
}
}
return null;
}
}

最后对,概念板块的页面数据进行解析入库。

数据展示

抓取数据入库,验证成功! 源码地址:Java爬取同花顺股票数据,源码地址

如果有什么不懂的或是遇到问题,可以关注我的公众号:java之旅或扫描下方二维码,回复【加群】,加我个人微信询问我

Java爬取同花顺股票数据(附源码)的更多相关文章

  1. java抓取东方财富股票数据(附源码)

    背景 前段时间给朋友写了一个自动抓取同花顺股票数据的程序,不少人觉得不错. 这几天后台有粉丝给我留言让我也抓一下东方财富的数据,说东方财富的数据特别难抓,我还真不一定能搞得定. 本来我是一个德艺双磬且 ...

  2. Python爬虫一爬取B站小视频源码

    如果要爬取多页的话 在最下方循环中 填写好循环的次数就可以了 项目源码 from fake_useragent import UserAgent import requests import time ...

  3. JAVA模拟Spring实现IoC过程(附源码)

    前言:本人大四学生,第一次写博客,如果有写得不好的地方,请大家多多指正 一.IoC(Inversion of Control)反转控制 传统开发都是需要对象就new,但这样做有几个问题: 效率低下,创 ...

  4. 用 Java 实现人脸识别功能(附源码)

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...

  5. 干货:Java多线程详解(内附源码)

      线程是程序执行的最小单元,多线程是指程序同一时间可以有多个执行单元运行(这个与你的CPU核心有关). 在java中开启一个新线程非常简单,创建一个Thread对象,然后调用它的start方法,一个 ...

  6. SpringBoot整合Redis、mybatis实战,封装RedisUtils工具类,redis缓存mybatis数据 附源码

    创建SpringBoot项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redis 引 ...

  7. 十大经典排序算法(java实现、配图解,附源码)

    前言: 本文章主要是讲解我个人在学习Java开发环境的排序算法时做的一些准备,以及个人的心得体会,汇集成本篇文章,作为自己对排序算法理解的总结与笔记. 内容主要是关于十大经典排序算法的简介.原理.动静 ...

  8. 使用Java生成word文档(附源码)

    当我们使用Java生成word文档时,通常首先会想到iText和POI,这是因为我们习惯了使用这两种方法操作Excel,自然而然的也想使用这种生成word文档.但是当我们需要动态生成word时,通常不 ...

  9. 利用js编写一个简单的html表单验证,验证通过时提交数据(附源码)

    <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8 ...

随机推荐

  1. algorithm入门算法中的常见问题

    KMP算法(next数组) 二分查找(非递归) /** * 二分查找(非递归) * @param arr 从小到大的排序数组 * @param target 目标查找值 * @return */ pu ...

  2. asp.net中的日志添加和未处理异常的记录

    1.第一次写博客如有错误欢迎纠正.邮箱:Jiangwenyuan0217@163.com: 2.此博客可能对初学者有些帮助,对哪些骨灰级的程序员来说都是分分钟的事了,所以就不用在这里费时间了. 环境说 ...

  3. python链表从尾到头的顺序返回一个ArrayList

    思路:获取链表的值,添加入列表中,反转列表即可获得ArrayList # -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): ...

  4. hystrix文档翻译之概述

    Hystrix是什么 在一个大型的分布式系统中,难免有些依赖服务会失败.hystrix通过容错逻辑来控制不同服务间的交互.hystrix通过隔离各服务交互节点来防止连级错误,并且提供降级功能,最终保证 ...

  5. 12.扩展:向量空间模型算法(Vector Space Model)

  6. 面试官:分库分表之后,id 主键如何处理?

    面试题 分库分表之后,id 主键如何处理? 面试官心理分析 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个全 ...

  7. Docker数据卷和数据卷容器

    是什么 数据卷设计的目的,在于数据的永久化,他完全独立于容器的生存周期,因此,Docker不会在容器删除时删除其挂载的数据卷,也不会存在类似的垃圾收集机制对容器引用的数据卷进行处理.类似我们Redis ...

  8. 我的Keras使用总结(5)——Keras指定显卡且限制显存用量,常见函数的用法及其习题练习

    Keras 是一个高层神经网络API,Keras是由纯Python编写而成并基于TensorFlow,Theano以及CNTK后端.Keras为支持快速实验而生,能够将我们的idea迅速转换为结果.好 ...

  9. ISP-OB, pedestal 以及ISP概述

    网上的直接参考资料 1. https://zhuanlan.zhihu.com/p/36896537 2. https://blog.csdn.net/m0_38049850/article/deta ...

  10. C#怎么从List集合中随机取出其中一个值

    1.首先在该命名空间下创建一个实体,和在Main方法下List集合,为后续做准备: /// <summary> /// 实体 /// </summary> public cla ...