今年,我只赚了一点点

news2024/11/17 8:33:15

大家好,我是 Jack。

之前一直有小伙伴问我,有没有免费的股票信息查询的 API 接口?

我看了一圈,很多免费的 API 接口都年久失修,失效了。

那好吧,咱自己写一个

想要玩量化交易,第一步,那得有稳定的股票数据来源。

然后再谈什么量化策略,怎么选股、何时买股。

怎么稳定的获取数据呢?

只能是抄起我的老板行,写个网络爬虫,自动抓取数据

玩股票、玩基金的,应该多多少听过一款股票交流 APP 雪球。

这里面的数据很全,就它了!

前方提醒:使用网络爬虫,请控制好访问频率。

在雪球上,想要获得各种股票信息,那需要携带身份信息,也就是要有 Cookie。

没有 Cookie,很多信息是获取不到的。

2017 年的时候,我就写过关于 Cookie 的文章。

一些基础知识忘记的小伙伴,可以重温下我这个系列的文章。

网络爬虫教程(2020年)

想要获取 Cookie,那就需要进行模拟登录。

模拟登录 - 准备篇

模拟登录,顾名思义,就是模拟人类的行为,登录这个网站。

登录之后,我们就可以用保存身份信息的 Cookie,获取我们想要的各种数据:股票信息、基金信息等。

我们先手动登录,体验一下整个登录流程。

手动登录

第一步:点击登录按钮。

第二步:输入帐号和密码,并点击登录。

第三步:解锁滑块。

第四步:登录成功。

模拟登录

接下来,就是需要写个代码,让代码替我们完成上述操作

这里我使用 Selenium,它是一款自动化测试工具。

不过说实话,Selenium 这东西挺老了。

现在有不少更好的工具,不过对于模拟登录的知识储备,我还停留在 2017 年,也只会用它了。

有更好更好的方法的话,欢迎小伙伴们提交 PR。

不过,好在 Selenium 虽然老了点,但还能勉强胜任获取 Cookie 这项工作的。

Selenium 不会的小伙伴,可以看我从前的教程:

https://jackcui.blog.csdn.net/article/details/72331737

想要使用 Selenium,首先需要下载浏览器驱动,这里以 Chrome 浏览器为例。

打开 Chrome 浏览器,查看 Chrome 版本号。

然后根据这个版本号,下载相同大版本的驱动。

http://chromedriver.storage.googleapis.com/index.html

根据自己的操作系统,选择对应的版本。

我的是 Windows 电脑,选择 Win32 的版本。

下载好后,解压备用。

最后安装 Selenium 第三方依赖库。

python -m pip install selenium==3.4 --user

注意,需要安装 3.4 的版本,Selenium 的新版本改动较多,用我的代码会存在接口不兼容的情况。

模拟登录 - 实战篇

我们先睹为快,看下让代码自动登录雪球的效果:

https://cuijiahua.com/wp-content/uploads/2022/12/1.mp4

(PS:录屏时间 12.2,由于大家都知道的原因,页面为黑白)

其实模拟登录的思路很简单,就是根据审查元素,找到各个元素的位置。

比如登录按钮,右键审查元素,然后选择 Copy Xpath。

就能拷贝路径地址。

使用这种方法,找到帐号输入框、密码输入框的位置,然后点击登录即可。

这里的难点在于验证码。

不过好在,GEETEST 验证码的破解,我还是有些经验的,17 年的时候,就写过相关内容。

很多代码,直接复用即可

整体思路就是:

  • 使用Selenium打开页面。

  • 匹配到输入框,输入账号密码,点击登录。

  • 读取验证码图片,并做缺口识别。

  • 根据缺口位置,计算滑动距离。

  • 根据滑动距离,拖拽滑块到需要匹配的位置。

直接放代码:

from selenium import webdriver
from selenium.webdriver import ActionChains
from io import BytesIO
import json
import base64
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from time import sleep
from PIL import Image
from selenium import webdriver

# 账号
USERNAME = '***'
# 密码
PASSWORD = '***'
BORDER = 6


class Login(object):
    def __init__(self):
        self.url = 'https://xueqiu.com/'

        opt = webdriver.ChromeOptions()
        opt.add_experimental_option('w3c', False)
        self.browser = webdriver.Chrome("chromedriver.exe", chrome_options=opt)
        self.browser.maximize_window()#第一处修复,设置浏览器全屏
        self.username = USERNAME
        self.password = PASSWORD
        self.wait = WebDriverWait(self.browser, 20)

    def __del__(self):
        print("close")

    def open(self):
        self.browser.get(self.url)
        ele = self.browser.find_element_by_xpath('//*[@id="app"]/nav/div[1]/div[2]/div/div')#第二处修复,改xpath
        ele.click()
        username = self.wait.until(EC.presence_of_element_located((By.XPATH, '//input[@name="username"]')))
        pwd = self.wait.until(EC.presence_of_element_located((By.XPATH, '//input[@name="password"]')))
        username.send_keys(self.username)
        time.sleep(2)
        pwd.send_keys(self.password)

    # 获取验证码按钮
    def get_yzm_button(self):
        button = self.wait.until(EC.presence_of_element_located((By.XPATH, '/html/body/div[2]/div[1]/div/div/div/div[2]/div[2]/div[2]')))#第三处修复,改xpath
        return button

    # 获取验证码图片对象
    def get_img_element(self):
        element = self.wait.until(EC.presence_of_element_located((By.XPATH, '//cavas[@name="geetest_canvas_bg geetest_absolute"]')))
        return element

    def get_position(self):
        # 获取验证码位置
        element = self.get_img_element()
        sleep(2)
        location = element.location
        size = element.size
        top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
            'width']
        return left, top, right, bottom

    def get_geetest_image(self):
        """
        获取验证码图片
        :return: 图片对象
        """
        '''
        <canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260"></canvas>
        '''
        # 带阴影的图片
        # im = self.wait.until(EC.presence_of_element_located((By.XPATH, '/html/body/div[4]/div[2]/div[6]/div/div[1]/div[1]/div/a/div[1]/div/canvas[1]')))
        im = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.geetest_canvas_bg')))
        time.sleep(2)
        im.screenshot('captcha.png')
        # 执行 JS 代码并拿到图片 base64 数据
        JS = 'return document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'  # 不带阴影的完整图片
        im_info = self.browser.execute_script(JS)  # 执行js文件得到带图片信息的图片数据
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        captcha1 = base64.b64decode(im_base64)
        # 将图片保存在本地
        with open('captcha1.png', 'wb') as f:
            f.write(captcha1)

        JS = 'return document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'
        # 执行 JS 代码并拿到图片 base64 数据ng  # 带阴影的图片
        im_info = self.browser.execute_script(JS)  # 执行js文件得到带图片信息的图片数据
        # 拿到base64编码的图片信息
        im_base64 = im_info.split(',')[1]
        # 转为bytes类型
        captcha2 = base64.b64decode(im_base64)
        # 将图片保存在本地
        with open('captcha2.png', 'wb') as f:
            f.write(captcha2)

        captcha1 = Image.open('captcha1.png')
        captcha2 = Image.open('captcha2.png')
        return captcha1, captcha2

    # 获取网页截图
    def get_screen_shot(self):
        screen_shot = self.browser.get_screenshot_as_png()
        screen_shot = Image.open(BytesIO(screen_shot))
        return screen_shot

    def get_yzm_img(self, name='captcha.png'):
        #  获取验证码图片
        left, top, right, bottom = self.get_position()
        print('验证码位置', top, bottom, left, right)
        screen_shot = self.get_screen_shot()
        captcha = screen_shot.crop((left, top, right, bottom))
        captcha.save(name)
        return captcha

    def get_slider(self):
        # 获取滑块
        # :return: 滑块对象
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_track')))
        return slider

    def get_gap(self, image1, image2):
        """
        获取缺口偏移量
        :param image1: 不带缺口图片
        :param image2: 带缺口图片
        :return:
        """
        left = 62
        for i in range(left, image1.size[0]):
            for j in range(image1.size[1]):
                if not self.is_pixel_equal(image1, image2, i, j):
                    left = i
                    return left
        # return left

    def is_pixel_equal(self, image1, image2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点
        pixel1 = image1.load()[x, y]
        pixel2 = image2.load()[x, y]
        threshold = 60
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        else:
            return False

    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 初速度
        v = 0
        # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
        t = 0.3
        # 位移/轨迹列表,列表内的一个元素代表0.2s的位移
        tracks = []
        # 当前的位移
        current = 5
        # 到达mid值开始减速
        mid = distance * 3 / 5
        while current < distance:
            if current < mid:
                # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
                a = 2
            else:
                a = -3
            # 初速度
            v0 = v
            # 0.2秒时间内的位移
            s = v0 * t + 0.4 * a * (t ** 2)
            # 当前的位置
            current += s
            # 添加到轨迹列表
            tracks.append(round(s))
            # 速度已经达到v,该速度作为下次的初速度
            v = v0 + a * t
        return tracks

    def move_to_gap(self, slider, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()
        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
        time.sleep(0.5)
        ActionChains(self.browser).release().perform()

    def shake_mouse(self):
        """
        模拟人手释放鼠标抖动
        :return: None
        """
        ActionChains(self.browser).move_by_offset(xoffset=-2, yoffset=0).perform()
        ActionChains(self.browser).move_by_offset(xoffset=2, yoffset=0).perform()

    def operate_slider(self, track):
        '''
        拖动滑块
        '''
        # 获取拖动按钮
        back_tracks = [-1,-1, -1, -1]
        slider_bt = self.browser.find_element_by_xpath('//div[@class="geetest_slider_button"]')

        # 点击拖动验证码的按钮不放
        ActionChains(self.browser).click_and_hold(slider_bt).perform()

        # 按正向轨迹移动
        for i in track:
            ActionChains(self.browser).move_by_offset(xoffset=i, yoffset=0).perform()

        time.sleep(1)
        ActionChains(self.browser).release().perform()

    def get_cookies(self):
        try:
            cookie_list = self.browser.get_cookies()
            cookie_dict = {i['name']: i['value'] for i in cookie_list}
            with open('xueqiu_cookies', 'w', encoding='utf8')as f:
                cookie_dict = json.dumps(cookie_dict)
                f.write(cookie_dict)

            return cookie_dict
        except:
            print("cookie 获取失败")
            return None

    # 读取cookie
    def return_cookie(self):
        cookies = ''
        with open('xueqiu_cookies', 'r')as f:
            cookie = f.read()[1:-1]
            cookie = cookie.split(', ')
            for i in cookie:
                cook = i.split(': ')
                cookies += cook[0][1:-1] + '=' + cook[1][1:-1] + ';'
            return cookies

    def run(self):
        # 破解入口
        self.open(), sleep(3)
        self.get_yzm_button().click(), sleep(2)# 点击验证按钮
        # 点按呼出缺口
        slider = self.get_slider()
        # slider.click()
        # 获取带缺口的验证码图片
        image1, image2 = self.get_geetest_image()
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)
        track = self.get_track(gap)
        print('滑动轨迹', track)
        self.operate_slider(track)
        # 判定是否成功
        time.sleep(8)
        try:
            elem = self.wait.until(
                EC.text_to_be_present_in_element((By.CLASS_NAME, 'nav__btn--longtext'), '发帖'))
            if elem:
                cookie = self.get_cookies()
            else:
                print("get cookies errors")
        except Exception as e:
            print(e, 'fail! ')
            time.sleep(3)
            self.run()
        finally:
            self.browser.quit()


if __name__ == '__main__':
    crack = Login()
    crack.run()

代码我也上传到 Github 上了,代码的后续更新维护会放在这里建议 Star 收藏下

https://github.com/Jack-Cherish/quantitative

数据获取

等待模拟登录完成后,会保存一个名为 xueqiu_cookies 的文件。

这里保存的是帐号的 Cookie,使用这个 Cookie 就能获取雪球的数据了。

比如,获取一下股票实时行情和现金流量表,就可以这样写:

#-*- coding:utf-8 -*-
import requests
import json

def fetch(url, xq_a_token):
    headers = {'Host': 'stock.xueqiu.com',
        'Accept': 'application/json',
        'Cookie': 'xq_a_token={};'.format(xq_a_token),
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36',
        'Accept-Language': 'zh-Hans-CN;q=1, ja-JP;q=0.9',
        'Accept-Encoding': 'br, gzip, deflate',
        'Connection': 'keep-alive'}
    response = requests.get(url, headers = headers)

    if response.status_code != 200:
        raise Exception(response.content)

    return json.loads(response.content)

if __name__ == '__main__':
    # 获取股票 SH600000 实时行情
    url = "http://stock.xueqiu.com/v5/stock/quote.json?extend=detail&symbol=SH600000&count=10"
    with open("xueqiu_cookies", "r") as f:
        cookies_info = json.load(f)
    res = fetch(url, cookies_info['xq_a_token'])
    print(res)
    # 获取股票 SH600000 现金流量表
    url = "http://stock.xueqiu.com/v5/stock/finance/cn/cash_flow.json?symbol=SH600000&count=10"
    with open("xueqiu_cookies", "r") as f:
        cookies_info = json.load(f)
    res = fetch(url, cookies_info['xq_a_token'])
    print(res)

运行结果:

有了 Cookie,很多接口数据都能获取,实时行情、实时分笔、业绩预告、机构评级、资金流向趋势、资金流向历史、资金成交分布、大宗交易、融资融券、业绩指标、利润表、资产负债表、现金流量表、主营业务构成、F10 十大股东、F10 主要指标等等。

这些数据,都能获取。

絮叨

篇幅有限,今天就是带大家小小实战下。

后续我会完善各个常用查询接口,方便大家获取各类数据,用于量化分析。

万事开头难,先弄好数据,再看量化策略~

如果喜欢这类的内容,记得点赞,喜欢的人多的话,我会快速加更的~

最后必须提醒一下各位:

获取数据,请温柔,请勿高并发获取,且用且珍惜。

对了,还有不少小伙伴问我,我的量化策略收益如何。

去年的五万元实验,最后浮盈不到 10%,清仓之后就换新的策略实验了。

6月份的时候,又用上了新策略,新的策略一直跑到今年 10 月份,也就这样:

实验没放多少钱,随便玩玩,你觉得,这点收益如何?

好了,今天就聊这么多吧,我是 Jack,我们下期见~

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

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

相关文章

015 | 乡村振兴背景下田园综合体建设对农户生计的影响 | 大学生创新训练项目申请书 | 极致技术工厂

研究目的 本项目旨在研究xx市xx区田园综合体建设对农户生计的影响。以农户生计资本和生计恢复力为切入点&#xff0c;以塘坝镇、崇龛镇、太安镇等六个地级镇田园综合体建设前后作对比&#xff0c;研究其田园综合体建设对农户生计的影响&#xff0c;以期找到乡村振兴背景下田园综…

前端岗位编写代码注意规范

&#x1f525;&#x1f525;&#x1f525;关注前端小王hs&#x1f525;&#x1f525;&#x1f525; &#x1f525;&#x1f525;&#x1f525;加底部微信&#x1f525;&#x1f525;&#x1f525; &#x1f525;&#x1f525;&#x1f525;学习指导/前端练手项目推荐/毕设选题/…

基于元胞自动机森林火灾模型及应用(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

Allegro如何打印光绘层操作指导export模式

Allegro如何打印光绘层操作指导export模式 Allegro支持把光绘层面输出成pdf格式,下面介绍export模式如何输出,具体操作如下 打开光绘绘设置 确保光绘设置都是正确的 选择File-Export-PDF 弹出allegro PDF publisher,选择page setup Paper size选择A4,A4纸张 orientati…

SimKD

又搬来一个简单高效的知识蒸馏技术哦~~直接复用教师分类器还能显著减小性能差距的~ 在分类器的上一层通过特征对齐来训练学生模型&#xff0c;并直接复用教师分类器到学生模型中&#xff0c;再使用L2损失进行特征对齐。来自浙江大学的复用教师模型的方法哦~~ 浙大好厉害~~ 论…

2023年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

文章有点长&#xff0c;请耐心看完&#xff0c;绝对有收获&#xff01;不想听我BB直接进入面试分享&#xff1a; 准备过程 蚂蚁金服面试分享 拼多多面试分享 字节跳动面试分享 总结 说起来开始进行面试是年前倒数第二周&#xff0c;上午9点&#xff0c;我还在去公司的公交…

ERD Online 4.0.5 在线数据库建模、元数据管理(免费、私有部署)

4.0.5版本来袭❝ fix(erd): 增加数据库数据查询功能&#xff0c;支持多数据源切换查询&#xff0c;查看sql执行计划fix(erd): 数据查询功能&#xff0c;保留历史查询记录&#xff0c;格式化sql&#xff0c;多级树结构保存历史查询fix(erd): 依赖ERD加密手段&#xff0c;导出保留…

vdbench测试SSD快速入门

介绍 vdbench是一个I/O工作负载生成器&#xff0c;通常用于验证数据完整性和度量直接附加&#xff08;或网络连接&#xff09;存储性能。它可以运行在windows、linux环境&#xff0c;可用于测试文件系统或块设备基准性能。我们下面主要以块设备为介绍对象。 下载及安装 下载…

Linux 在过去几年发生的六种变化

随着时间的推移&#xff0c;Linux 桌面已经发生了变化&#xff0c;这种变化是逐渐发生的&#xff0c;因此这里汇总了过去十年中 Linux 桌面体验发生变化的一些具体方式。资深用户知道 Linux 桌面已经走过了漫长的道路。从前端应用程序设计到后端 Linux 组件&#xff0c;近年来发…

驱动无模块注入dll

文章目录实现效果三环无模块注入的方案反射型dll注入方式的改进零环无模块注入方案petoshellcode无模块注入流程实现代码Xenos注入方案研究IT_MMap注入IT_Thread注入IT_Apc注入火绒的注入思路实现效果 可以看到dll已经成功执行&#xff0c;但是在内存区域里面并没有我们的模块&…

《野球少年》:投捕搭档·棒球联盟

中文名 野球少年 原版名称 バッテリー 别 名 棒球伙伴、Battery 动画制作 ZERO-G 类 型 青春、运动、棒球 剧情简介 身为一名投手&#xff0c;原田巧是位拥有着拔群棒球才能的少年。在初中入学时移居的山间城镇新田市&#xff0c;巧与接住自己全力投球的捕手永仓豪相遇了。…

13 个你应该知道的 Webpack 优化技巧

Webpack 是目前前端开发最重要的构建工具。无论是自己的日常开发&#xff0c;还是准备面试&#xff0c;都应该掌握一些关于 Webpack 的优化技巧。 在这篇文章中&#xff0c;我将从三个方面分享一些我常用的技巧&#xff1a; 提高优化速度 压缩打包文件的大小 改善用户体验。…

[附源码]Python计算机毕业设计SSM基于框架的动漫设计(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MarkDown语法浅析(基础语法)

本篇学习笔记简述MarkDown基础语法。掌握了“MarkDown基本语法简单HTML5标签”的综合运用&#xff0c;就可以把CSDN博文搞得美美哒✌ (本文获得CSDN质量评分【92】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经…

SpringMVC笔记

文章目录一、SpringMVC简介1、什么是MVC2、什么是SpringMVC3、SpringMVC的特点二、HelloWorld1、开发环境2、创建maven工程a>添加web模块b>打包方式&#xff1a;warc>引入依赖3、配置web.xmla>默认配置方式b>扩展配置方式4、创建请求控制器5、创建springMVC的配…

Android开发中的服务发现技术

我们的日常开发中充满了InterfaceRegistry这种模式的代码&#xff0c;其中&#xff1a; Interface为定义的服务接口&#xff0c;可能是业务功能服务也可能是日志服务、数据解析服务、特定功能引擎等各种抽象层&#xff08;abstract layer&#xff09;&#xff1b;Registry为特…

线性表→顺序表→链表 逐个击破

一. 线性表 1. 前言 线性表&#xff0c;全名为线性存储结构。使用线性表存储数据的方式可以这样理解&#xff0c;即 “ 把所有(一对一逻辑关系的)数据用一根线儿串起来&#xff0c;再存储到物理空间中 ”。这根线有两种串联形式&#xff0c;如下图&#xff0c;即顺序存储(集中…

【收藏级】MySQL基本操作的所有内容(常看常新)

文章目录前言一、ER模型二、数据类型三、字段命名规范四、数据库创建与管理4.1、创建数据库4.2、删除数据库4.3、列出数据库4.4、备份数据库4.5、还原数据库4.6、使用某个数据库五、数据表创建与管理5.1、创建表、结构5.2、查看表结构5.3、查看数据表5.4、复制表结构5.5、复制表…

m基于PSO粒子群算法的重采样算法仿真,对比随机重采样,多项式重采样,分层重采样,系统重采样,残差重采样,MSV重采样

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 重采样的主要方法有随机重采样,多项式重采样,分层重采样,系统重采样,残差重采样,MSV重采样等。 a.随机采样是一种利用分层统计思想设计出来的&#xff0c;将空间均匀划分&#xff0c;粒子打点后…

Lecture6:激活函数、权值初始化、数据预处理、批量归一化、超参数选择

目录 1.最小梯度下降&#xff08;Mini-batch SGD&#xff09; 2.激活函数 2.1 sigmoid 2.2 tanh 2.3 ReLU 2.4 Leaky ReLU 2.5 ELU 2.6 最大输出神经元 2.7 建议 3.数据预处理 4. 如何初始化网络的权值 5. 批量归一化 6.超参数的选择 1.最小梯度下降&#xf…