一、爬虫目标

本次爬虫程序的主要目标有:

1.每日自动爬取股票业绩预告(包括年报、一季报、中报、三季报);

2.爬取历史数据;

3.每日自动增量更新,免人工维护;

4.业绩预告分类分级,按照行业,概念可以查询;

5.数据条件查询,列表展示,颜色提示;

二、难点

众所周知,同花顺反爬技术还是非常厉害的,算法加密,请求频率控制等已经阻拦了95%的爬虫,本框架采用go语言编写的其中一个原因就是使用go语言更方便进行请求频率控制,更加简洁,当然更重要的原因是go语言编译生成程序的加密安全性更好,还有部署方便等优点。在本框架中,已经解决了同花顺加密hexinv,和请求频率问题。千万注意控制请求频率,否则会被封IP,我有被腾讯、新浪、东财、同花顺封IP的经历,后续编写stockgo框架后,基本没有发生过。下文会有相关代码片段。

三、效果图

效果图

选一个业绩预增的新能源牛股,效果就更加明显,2020-09-02我们的选股系统发出了买入信号(选股系统后续再讲,有兴趣的朋友可以到wx)

天赐材料

同花顺业绩预告

四、上代码

1.代码结构

代码结构非常简单:

config.go 配置文件解析器

ths_yjyg_parse.go 网页爬取及解析器

ths_yjyg_task.go 业绩预告任务

run目录下面是程序主入口(yjyg_main)

aes.min.js是同花顺hexinv生成程序

使用go调用js生成hexinv

conf下面的config.yaml 配置文件

另外还有model目录中的数据库对象文件

2.model代码

package modelimport "time"// 同花顺业绩预告type ThsYjyg struct {ID        int       `gorm:"primary_key" orm:"column(id);auto"`Code      string    `gorm:"index:code_date_jidu,unique;column:code;type:varchar(20);not null"` // 股票代码StockName string    `gorm:"column:stock_name;type:varchar(100);not null"`Date      time.Time `gorm:"index:code_date_jidu,unique;column:date;type:date;not null"` // 日期Jidu      int       `gorm:"index:code_date_jidu,unique;column:jidu;type:int(4)"`        //季度 1.一季报;2中报;3三季报;4年报Year      int       `gorm:"column:year;type:int(8)"`                                    //年//YgType    int       `gorm:"index:code_date_type,unique;column:yg_type;type:int(4);not null"` //YjLevel      int     `gorm:"column:yj_level;type:int(8);comment:业绩预告级别"`YjLevelLabel string  `gorm:"column:yj_level_label;type:varchar(100);not null;comment:业绩预估类型"`Sumary       string  `gorm:"column:sumary;type:varchar(300);not null;comment:业绩预告摘要"`JlrChange    float64 `gorm:"column:jlr_change;type:decimal(19,2);comment:净利润变动幅度%"`LastJlr      float64 `gorm:"column:last_jlr;type:decimal(19,2);comment:上年同期净利润"`Created      time.Time
}

3.网页解析器

网页爬取和解析很简单,我们通过分析同花顺原网页调用,发现每次是通过异步请求获取一段html,页面再动态渲染,参数只有两个,一个是季报日期,一个是分页参数,代码如下,网页解析使用了goquery,非常优秀的开源网页解析工具。

package yjygimport ("fmt""github.com/PuerkitoBio/goquery""stockgo/core/global""stockgo/core/util""stockgo/model""go.uber.org/zap""strconv""strings""time"
)const URL = "https://data.10jqka.com.cn/ajax/yjyg/date/%s/board/ALL/field/enddate/order/desc/page/%d/ajax/1/free/1/"var YJTYPES = map[string]int{"预计增亏":   11,"业绩大幅下降": 12,"预计续亏":   14,"业绩预亏":   16,"业绩预降":   18, // 小于20,利空消息"不确定": 20,"预计减亏": 21,"业绩预盈": 25, //这个是中性的"预计扭亏":   31, //30 以上是利好消息"业绩预增":   34,"业绩大幅上升": 35,
}func GetAndParse(date string, pageNo int, year int ,jidu int, mt *global.HttpLimit) *YjygInfo {url := fmt.Sprintf(URL, date, pageNo)global.LOG.Info("业绩预告爬取地址: ", zap.String("URL", url))doc := util.GetHtmlWithHead(url, util.GetThsHeader(), mt) //获取同花顺hexinvif doc == nil {return nil}return ParseData(doc, pageNo, year, jidu)
}func ParseData(doc *goquery.Document, pageNo int, year int ,jidu int) *YjygInfo {pageInfo := getText(doc.Find("body > div.m-page.J-ajax-page > span"))// 存在没有页码的情况,第一页不满的情况total:=1if pageInfo != ""{arr := strings.Split(pageInfo, "/")total, _ = strconv.Atoi(arr[1])}yjygInfo := YjygInfo{PageNo: pageNo,Total:  total,List:   make([]model.ThsYjyg, 0),}doc.Find("body > table > tbody > tr").Each(func(i int, selection *goquery.Selection) {yjgg := model.ThsYjyg{Code:         util.FullCode(getText(selection.Find(" td:nth-child(2) > a"))),StockName:    getText(selection.Find(" td:nth-child(3) > a")),YjLevelLabel: getText(selection.Find(" td:nth-child(4) > span")),Sumary:       getText(selection.Find("td:nth-child(5) > a")),JlrChange:    util.ParseFloatTwo(getText(selection.Find("td:nth-child(6)"))),Created:      time.Now(),Year:         year,Jidu:         jidu,}yjgg.Date, _ = time.Parse(util.Date_Format, selection.Find("td.tc.cur").Text())yjgg.YjLevel = YJTYPES[yjgg.YjLevelLabel]lastJlrStr := getText(selection.Find("td:nth-child(7)"))yjgg.LastJlr = getLastJlr(lastJlrStr)yjygInfo.List = append(yjygInfo.List, yjgg)})return &yjygInfo
}func getText(selection *goquery.Selection) string {temp := util.DecodeToGBK(selection.Text()) //处理数据编码temp = strings.TrimSpace(temp)             //去除多余空格//temp = strings.ReplaceAll(temp,"聽"," ") //替换转码导致的乱码,空格变成了聽return temp
}func getLastJlr(jlr string) float64 {if jlr == "-" {return 0.0}if strings.Contains(jlr, "亿") {jlr = strings.ReplaceAll(jlr, "亿", "")return util.ParseFloatTwo(jlr) * 10000} else if strings.Contains(jlr, "万") {jlr = strings.ReplaceAll(jlr, "万", "")return util.ParseFloatTwo(jlr)}return 0.0}type YjygInfo struct {PageNo intTotal  intList   []model.ThsYjyg
}

获取同花顺hexinv请求头部参数代码:

解决了绝大多数用户爬取同花顺的难题,使用基于chorme Driver 方式爬取太不方便了。

package utilimport ("github.com/robertkrimen/otto""io/ioutil"
)//同花顺util// 使用js生成核心V参数
func getHexinV() string {//伪造hexinv参数filePath := "aes.min.js"//先读入文件内容bytes, err := ioutil.ReadFile(filePath)if err != nil {panic(err)}vm := otto.New()_, err = vm.Run(string(bytes))if err != nil {panic(err)}value, err := vm.Call("v", nil, "")if err != nil {panic(err)}return value.String()}// 获取一个同花顺头部,每次自动生成一个hexin-v
func GetThsHeader() map[string]string {header := make(map[string]string)hexinv := getHexinV()header["hexin-v"] = hexinvheader["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36"return header
}

4.爬取任务

任务接口

type StockTaskHandler interface {GetName() stringDoTask()
}

业绩预告爬取任务

package yjygimport ("stockgo/core/global""stockgo/core/util""stockgo/model""go.uber.org/zap""strconv""time"
)var dateSuffixMap = map[int]string{1: "-03-31",2: "-06-30",3: "-09-30",4: "-12-31",
}//业绩预告
// 一季报4月15号之前发布 ,实际有4-30
//半年度业绩预告:报告期在当年7月15日前;  实际8-27
//第三季度业绩预告:报告期为当年10月15日之前 实际11-25 ,大部分在10-30
//年度业绩预告:报告期在次年1月31日前; 4-30 ;
type ThsYjygTask struct {Jidu  int    // 一季报1、中报2、三季报3、年报4Year  int    // 年,默认使用当年逻辑Mode  string // 模式[ALL 全量,UPDATE 增量],爬历史数据用ALL模式Limit *global.HttpLimit
}func (t *ThsYjygTask) GetName() string {return "同花顺业绩预告爬取任务"+t.getDate()
}func (t ThsYjygTask) DoTask() {if t.Year ==0 {t.Year=time.Now().Year()}//部分预报不需要一直查询if t.Mode != "ALL" && !needRun(t.Jidu, t.Year) {return}dateStr := t.getDate()pageNo := 1total := 1 //总页数,默认为1,先启动起来,然后爬虫修正总页数// 业务逻辑// 1.从第一页开始查询,提取出总页数,如果不是下一页,继续PageNO+1 查询// 2.和数据库历史数据对比,如果数据库有,则不继续下一页查询(全量模式下除外)for pageNo <= total {yjygInfo := GetAndParse(dateStr, pageNo, t.Year, t.Jidu, t.Limit)total = yjygInfo.Total // 每页返回的total都是一样的pageNo++// 保存 数据,并判断已存在数目repeatCount := saveYjygList(yjygInfo.List)if repeatCount > 0 && t.Mode != "ALL" { //增量模式下,如果当前页是否已经存在重复数据,就不需要再查询下一页了return}}}// 如果存在重复数据,返回count+1
func saveYjygList(list []model.ThsYjyg) int {count := 0for _, item := range list {if !saveYjyg(item) {count++}}return count
}func saveYjyg(info model.ThsYjyg) bool {oldYjyg := model.ThsYjyg{}err := global.Table(global.KLineDb, &info).Where("code=? and date=? and jidu=?", info.Code, info.Date.Format(util.Date_Format), info.Jidu).First(&oldYjyg).Errorif oldYjyg.ID > 0 {return false}err = global.Table(global.KLineDb, &info).Save(&info).Errorif err != nil {global.LOG.Error("save yjyg error : ", zap.Error(err))}return true
}//根据时间来判断是否需要爬取
func needRun(typ int, year int) bool {now := time.Now()month := now.Month()if typ == 1 {return month < 5 //一季报最多查询到4月份} else if typ == 2 {return month > 3 && month < 9 //中报3-9月} else if typ == 3 {return month >= 7 && month < 11 // 三季报7-11月} else { // 年报 全年都有可能 ,主要集中在4-30前return true //month <=4 || month >=7 //主要集中在下一年4-30前,有些票是当年就发布了}}func (t ThsYjygTask) getDate() string {if t.Year == 0 {t.Year = time.Now().Year()if time.Now().Month() < 5 && t.Jidu == 4 { //默认情况下,4月份以前的查询的是上一年的年报t.Year -= 1}}return strconv.Itoa(t.Year) + dateSuffixMap[t.Jidu]
}func InitDb() {// 初始化数据库,数据库同步global.LOG.Debug("初始化数据库表 ............")global.AutoMigrate(global.KLineDb, &model.ThsYjyg{})
}

爬取任务有两种模式,一种是爬取历史数据,一种是日常每日进行增量爬取,代码非常简单,100行左右

5.主程序

package mainimport ("stockgo/core/engine""stockgo/core/global""stockgo/core/task""stockgo/craw/ths/yjyg"
)func main() {var conf yjyg.Confengine.Start("", &conf)defer engine.Close()yjyg.InitDb() //自动初始化数据库tasks := task.NewStockTasks()for _, jidu := range conf.Config.Types {yjygTask := &yjyg.ThsYjygTask{ //同花顺业绩预告任务Mode:  conf.Config.Mode,Year:  conf.Config.Year,Jidu:  jidu,Limit: global.Limit,}tasks = append(tasks, task.CreateStockTask("同花顺数据爬取--", yjygTask))}task.RunStockTask(tasks) //并发运行}

主程序非常简洁,主要分为几步:

(1) engine引擎加载配置文件并自动连接数据库,并再主程序结束时自动关闭数据库等

(2) 自动初始化数据库,主要是自动生成数据库表

(3)根据配置文件循环创建爬虫任务,并将任务放入task运行器中并行运行

再看看配置文件

# StockGo Global Configuration# system configuration
system:name: 同花顺业绩预告数据爬取任务taskNum: 10  #并发任务数目limitSped: 3000  #网络请求限制,3000ms允许发出一个请求# 数据库配置,至少配置一个default,默认数据库
mysql:- name: defaultshowSql: falsedataSource: 'stockdev:stock123456@tcp(db.stock.com:3306)/stock_data?charset=utf8&parseTime=True&loc=Local'- name: klineshowSql: truedataSource: 'stockdev:stock123456@tcp(db.stock.com:3306)/stock_data?charset=utf8&parseTime=True&loc=Local'#业绩预告爬取数据,当补全历史数据时候,配置对应年份,mode 设置为ALL,
#其他自动爬取时候,去掉配置,使用空值
yjyg:year: 0  # 默认0使用系统时间yearmode: UPDATE   #ALL 查询所有,全量查询,其他值[update] 增量更新types: 1,2,3,4   # 1,2,3,4 季报类型# zap logger configuration(zap 日志配置)
zap:# 可使用 "debug", "info", "warn", "error", "dpanic", "panic", "fatal",level: 'info'# console: 控制台, json: json格式输出format: 'console'prefix: '[StockGo]'director: 'log'link-name: 'latest.log'show-line: true# LowercaseLevelEncoder:小写, LowercaseColorLevelEncoder:小写带颜色,CapitalLevelEncoder: 大写, CapitalColorLevelEncoder: 大写带颜色,encode-level: 'LowercaseColorLevelEncoder'stacktrace-key: 'stacktrace'log-in-console: true

config.go

package yjygimport "stockgo/core/config"type Config struct {Year  int    `mapstructure:"year" json:"year" yaml:"year"`Mode  string `mapstructure:"mode" json:"mode" yaml:"mode"`Types []int  `mapstructure:"types" json:"types" yaml:"types"`
}type Conf struct {Zap       config.Zap     `mapstructure:"zap" json:"zap" yaml:"zap"`System    config.System  `mapstructure:"system" json:"system" yaml:"system"`MysqlList []config.Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"`Config    Config         `mapstructure:"yjyg" json:"yjyg" yaml:"yjyg"`
}func (bc *Conf) GetZap() *config.Zap {return &bc.Zap
}
func (bc *Conf) GetSystem() *config.System {return &bc.System
}func (bc *Conf) GetMysql() []config.Mysql {return bc.MysqlList
}

6.运行日志

[StockGo]2021/08/27 - 15:57:55.604      info    服务启动        {"name": "同花顺业绩预告数据爬取任务"}
[StockGo]2021/08/27 - 15:57:55.605      info    数据库注册      {"Name": "default", "url": "stockdev:stock123456@tcp(db.stock.com:3306)/stock_data?charset=utf8&parseTime=True&loc=Local", "isLog": false}
[StockGo]2021/08/27 - 15:57:55.606      info    数据库注册      {"Name": "kline", "url": "stockdev:stock123456@tcp(db.stock.com:3306)/stock_data?charset=utf8&parseTime=True&loc=Local", "isLog": true}
[StockGo]2021/08/27 - 15:57:55.607      info     9 begin Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-03-31 0
[StockGo]2021/08/27 - 15:57:55.607      info     9 end Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-03-31 0
[StockGo]2021/08/27 - 15:57:55.607      info     6 begin Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-09-30 2
[StockGo]2021/08/27 - 15:57:55.607      info     4 begin Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-12-31 3
[StockGo]2021/08/27 - 15:57:55.607      info    业绩预告爬取地址:      {"URL": "https://data.10jqka.com.cn/ajax/yjyg/date/2021-09-30/board/ALL/field/enddate/order/desc/page/1/ajax/1/free/1/"}
[StockGo]2021/08/27 - 15:57:55.607      info    业绩预告爬取地址:      {"URL": "https://data.10jqka.com.cn/ajax/yjyg/date/2021-12-31/board/ALL/field/enddate/order/desc/page/1/ajax/1/free/1/"}
[StockGo]2021/08/27 - 15:57:55.607      info     3 begin Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-06-30 1
[StockGo]2021/08/27 - 15:57:55.607      info    业绩预告爬取地址:      {"URL": "https://data.10jqka.com.cn/ajax/yjyg/date/2021-06-30/board/ALL/field/enddate/order/desc/page/1/ajax/1/free/1/"}
[StockGo]2021/08/27 - 15:57:55.950      info     4 end Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-12-31 3
[StockGo]2021/08/27 - 15:57:59.605      info     6 end Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-09-30 2
[StockGo]2021/08/27 - 15:58:01.818      info     3 end Task 同花顺数据爬取--  同花顺业绩预告爬取任务2021-06-30 1
[StockGo]2021/08/27 - 15:58:01.818      info    运行总时长:46 秒

7.数据爬取之后,需要做页面进行数据展示

数据查询和展示框架基于gin-vue-admin二次开发,添加了avue,采用自动配置,自动生成的方式,基本5分钟内搞定,就是页面不够精细,不过也足够使用了,这里不进行多讲,如果有朋友有兴趣,后续我专门写一篇文章介绍。

五、最后

同花顺业绩预报数据是数据爬取中比较简单的,本人框架专注研究欧奈尔和落升,感兴趣的朋友可以一起交流,后续可能也会将当前策略筛选出来的股票分享给大家。

stockgo数据爬取-业绩预告相关推荐

  1. 搜狗·疫情数据爬取(Python)

    上周已经分享过搜狗·疫情数据爬取(R语言),这次分享一下搜狗·疫情数据爬取(Python) 不说废话,直接上代码.有什么问题,可以在留言区讨论. from urllib import request ...

  2. python手机端下载-Python3,x:如何进行手机APP的数据爬取

    Python3,x:如何进行手机APP的数据爬取 一.简介 平时我们的爬虫多是针对网页的,但是随着手机端APP应用数量的增多,相应的爬取需求也就越来越多,因此手机端APP的数据爬取对于一名爬虫工程师来 ...

  3. python爬虫案例-陶瓷公司数据爬取

    用requests爬取要注意HTTPConnectionPool(host=xxx, port=xxx): Max retries exceeded with url...异常,出现这个异常的解决方法 ...

  4. 每日一练:Python国内疫情数据爬取与地图绘制

    Python 国内疫情数据爬取与地图绘制 效果图 累计确诊疫情地图绘制 ① 时时数据抓取 ② 获取省份疫情数据 ③ 视觉配置项分段颜色数据设置 ④ 累计确诊疫情地图绘制 现存确诊疫情地图绘制 ① 获取 ...

  5. python财务报表预测股票价格_机器学习股票价格预测从爬虫到预测-数据爬取部分...

    声明:本文已授权公众号「AI极客研修站」独家发布 前言 各位朋友大家好,小之今天又来给大家带来一些干货了.上篇文章机器学习股票价格预测初级实战是我在刚接触量化交易那会,因为苦于找不到数据源,所以找的一 ...

  6. python爬虫网络数据包_Python爬虫之多线程图虫网数据爬取(十六)

    Python爬虫之多线程图虫网数据爬取(十六) 发布时间:2019-05-14 10:11, 浏览次数:289 , 标签: Python 原创不易,转载前请注明博主的链接地址:Blessy_Zhu h ...

  7. python如何爬虫股票数据_简单爬虫:东方财富网股票数据爬取(python_017)

    需求:将东方财富网行情中心的股票数据爬取下来,包括上证指数.深圳指数.上证A股.深圳A股.新股.中小板.创业板 等 一.目标站点分析 东方财富网的行情中心页面包含了所有股票信息.在左侧的菜单栏中包含了 ...

  8. 结合Selenium 和 Requests完成动态数据爬取

    Selenium 简介 Selenium是一个用于Web应用程序测试的工具.Selenium测试直接调用操作浏览器,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, 9, 10, 11),M ...

  9. 24-移动端app数据爬取

    移动端数据爬取 安装fiddler 真机安装fiddler证书 修改手机代理(改成电脑ip,端口设置为fiddler的端口) 上述设置完成后我们就可以使用fiddler抓取手机端的数据了 夜神手机模拟 ...

最新文章

  1. 安卓开发屏幕分辨率尺寸适配问题【原创】
  2. 洛谷 1379 八数码难题
  3. Java多线程的上下文切换
  4. c#后台alert出来变乱码问题解决
  5. QMap与QHash
  6. linux服务器禁止ping和允许ping的方法
  7. 【报告分享】2021年中国人工智能与教育融合应用报告.pdf(附下载链接)
  8. PHP通过SMTP实现发送邮件_包括附件
  9. 如何获取h.264码流的码率和帧率
  10. python的难点是什么,【python基础学习】基础重点难点知识汇总
  11. 事务Transaction 那点事儿
  12. verilog 3段式状态机
  13. Matlab经纬度坐标转换xy坐标,经纬度坐标系转换为UTM坐标系(matlab)
  14. 终极解锁邮件签名证书(S/MIME证书)
  15. 查找手机内所有app 包名
  16. 培训班出来的java程序员,怎么成为真正的技术大牛?
  17. mac去除dmg打开密码的方法
  18. 程序员的乐趣是什么?
  19. teamviewer v_p_n+xp v_p_n服务实现在家访问公司内部局域网
  20. 一九四六年第一台通用计算机的名字叫什么,1946年第一台计算机叫什么

热门文章

  1. 【Python绘制幸运草】见者接幸运O(∩_∩)O见者皆幸运
  2. jquery attr(“xxx“,“mmm“)修改标签属性的值
  3. python逗号怎么用_Python中逗号的三种作用实例分析
  4. mysql修改字段类型为smallint_MYSQL 字段类型之TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT
  5. Java实现阿里云视频点播的功能
  6. 什么是Portlet ?
  7. iPhone14销售惨淡,苹果已开始为清理库存做准备了
  8. squid的基本概念
  9. 微信小程序vant-weapp版本升级更新
  10. 软件工程个人阅读作业1 201521123023 戴建钊 网络1511