今年,我只赚了一点点

大家好,我是 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: 图片对象
        """
        '''
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;<canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260">
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;'''
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x5E26;&#x9634;&#x5F71;&#x7684;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;im&#xA0;=&#xA0;self.wait.until(EC.presence_of_element_located((By.XPATH,&#xA0;'/html/body/div[4]/div[2]/div[6]/div/div[1]/div[1]/div/a/div[1]/div/canvas[1]')))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im&#xA0;=&#xA0;self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,&#xA0;'.geetest_canvas_bg')))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;time.sleep(2)
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im.screenshot('captcha.png')
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x6267;&#x884C;&#xA0;JS&#xA0;&#x4EE3;&#x7801;&#x5E76;&#x62FF;&#x5230;&#x56FE;&#x7247;&#xA0;base64&#xA0;&#x6570;&#x636E;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;JS&#xA0;=&#xA0;'return&#xA0;document.getElementsByClassName("geetest_canvas_fullbg")[0].toDataURL("image/png");'&#xA0;&#xA0;#&#xA0;&#x4E0D;&#x5E26;&#x9634;&#x5F71;&#x7684;&#x5B8C;&#x6574;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im_info&#xA0;=&#xA0;self.browser.execute_script(JS)&#xA0;&#xA0;#&#xA0;&#x6267;&#x884C;js&#x6587;&#x4EF6;&#x5F97;&#x5230;&#x5E26;&#x56FE;&#x7247;&#x4FE1;&#x606F;&#x7684;&#x56FE;&#x7247;&#x6570;&#x636E;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x62FF;&#x5230;base64&#x7F16;&#x7801;&#x7684;&#x56FE;&#x7247;&#x4FE1;&#x606F;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im_base64&#xA0;=&#xA0;im_info.split(',')[1]
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x8F6C;&#x4E3A;bytes&#x7C7B;&#x578B;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha1&#xA0;=&#xA0;base64.b64decode(im_base64)
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x5C06;&#x56FE;&#x7247;&#x4FDD;&#x5B58;&#x5728;&#x672C;&#x5730;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;with&#xA0;open('captcha1.png',&#xA0;'wb')&#xA0;as&#xA0;f:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;f.write(captcha1)

&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;JS&#xA0;=&#xA0;'return&#xA0;document.getElementsByClassName("geetest_canvas_bg")[0].toDataURL("image/png");'
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x6267;&#x884C;&#xA0;JS&#xA0;&#x4EE3;&#x7801;&#x5E76;&#x62FF;&#x5230;&#x56FE;&#x7247;&#xA0;base64&#xA0;&#x6570;&#x636E;ng&#xA0;&#xA0;#&#xA0;&#x5E26;&#x9634;&#x5F71;&#x7684;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im_info&#xA0;=&#xA0;self.browser.execute_script(JS)&#xA0;&#xA0;#&#xA0;&#x6267;&#x884C;js&#x6587;&#x4EF6;&#x5F97;&#x5230;&#x5E26;&#x56FE;&#x7247;&#x4FE1;&#x606F;&#x7684;&#x56FE;&#x7247;&#x6570;&#x636E;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x62FF;&#x5230;base64&#x7F16;&#x7801;&#x7684;&#x56FE;&#x7247;&#x4FE1;&#x606F;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;im_base64&#xA0;=&#xA0;im_info.split(',')[1]
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x8F6C;&#x4E3A;bytes&#x7C7B;&#x578B;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha2&#xA0;=&#xA0;base64.b64decode(im_base64)
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x5C06;&#x56FE;&#x7247;&#x4FDD;&#x5B58;&#x5728;&#x672C;&#x5730;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;with&#xA0;open('captcha2.png',&#xA0;'wb')&#xA0;as&#xA0;f:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;f.write(captcha2)

&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha1&#xA0;=&#xA0;Image.open('captcha1.png')
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha2&#xA0;=&#xA0;Image.open('captcha2.png')
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;captcha1,&#xA0;captcha2

&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x83B7;&#x53D6;&#x7F51;&#x9875;&#x622A;&#x56FE;
&#xA0;&#xA0;&#xA0;&#xA0;def&#xA0;get_screen_shot(self):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;screen_shot&#xA0;=&#xA0;self.browser.get_screenshot_as_png()
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;screen_shot&#xA0;=&#xA0;Image.open(BytesIO(screen_shot))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;screen_shot

&#xA0;&#xA0;&#xA0;&#xA0;def&#xA0;get_yzm_img(self,&#xA0;name='captcha.png'):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#xA0;&#x83B7;&#x53D6;&#x9A8C;&#x8BC1;&#x7801;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;left,&#xA0;top,&#xA0;right,&#xA0;bottom&#xA0;=&#xA0;self.get_position()
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;print('&#x9A8C;&#x8BC1;&#x7801;&#x4F4D;&#x7F6E;',&#xA0;top,&#xA0;bottom,&#xA0;left,&#xA0;right)
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;screen_shot&#xA0;=&#xA0;self.get_screen_shot()
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha&#xA0;=&#xA0;screen_shot.crop((left,&#xA0;top,&#xA0;right,&#xA0;bottom))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;captcha.save(name)
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;captcha

&#xA0;&#xA0;&#xA0;&#xA0;def&#xA0;get_slider(self):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x83B7;&#x53D6;&#x6ED1;&#x5757;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;:return:&#xA0;&#x6ED1;&#x5757;&#x5BF9;&#x8C61;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;slider&#xA0;=&#xA0;self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,&#xA0;'geetest_slider_track')))
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;slider

&#xA0;&#xA0;&#xA0;&#xA0;def&#xA0;get_gap(self,&#xA0;image1,&#xA0;image2):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"""
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x83B7;&#x53D6;&#x7F3A;&#x53E3;&#x504F;&#x79FB;&#x91CF;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;image1:&#xA0;&#x4E0D;&#x5E26;&#x7F3A;&#x53E3;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;image2:&#xA0;&#x5E26;&#x7F3A;&#x53E3;&#x56FE;&#x7247;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:return:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"""
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;left&#xA0;=&#xA0;62
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;for&#xA0;i&#xA0;in&#xA0;range(left,&#xA0;image1.size[0]):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;for&#xA0;j&#xA0;in&#xA0;range(image1.size[1]):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;if&#xA0;not&#xA0;self.is_pixel_equal(image1,&#xA0;image2,&#xA0;i,&#xA0;j):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;left&#xA0;=&#xA0;i
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;left
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;return&#xA0;left

&#xA0;&#xA0;&#xA0;&#xA0;def&#xA0;is_pixel_equal(self,&#xA0;image1,&#xA0;image2,&#xA0;x,&#xA0;y):
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"""
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#x5224;&#x65AD;&#x4E24;&#x4E2A;&#x50CF;&#x7D20;&#x662F;&#x5426;&#x76F8;&#x540C;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;image1:&#xA0;&#x56FE;&#x7247;1
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;image2:&#xA0;&#x56FE;&#x7247;2
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;x:&#xA0;&#x4F4D;&#x7F6E;x
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:param&#xA0;y:&#xA0;&#x4F4D;&#x7F6E;y
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;:return:&#xA0;&#x50CF;&#x7D20;&#x662F;&#x5426;&#x76F8;&#x540C;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;"""
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x53D6;&#x4E24;&#x4E2A;&#x56FE;&#x7247;&#x7684;&#x50CF;&#x7D20;&#x70B9;
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;pixel1&#xA0;=&#xA0;image1.load()[x,&#xA0;y]
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;pixel2&#xA0;=&#xA0;image2.load()[x,&#xA0;y]
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;threshold&#xA0;=&#xA0;60
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;if&#xA0;abs(pixel1[0]&#xA0;-&#xA0;pixel2[0])&#xA0;< 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 ="&#xA0;0"         # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移         t ="&#xA0;0.3"         # 位移 轨迹列表,列表内的一个元素代表0.2s的位移         tracks ="&#xA0;[]"         # 当前的位移         current ="&#xA0;5"         # 到达mid值开始减速         mid ="&#xA0;distance&#xA0;*&#xA0;3&#xA0;/&#xA0;5"         while current < distance:             if current < mid:                 # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细                 a ="&#xA0;2"             else:             # 初速度             v0 ="&#xA0;v"             # 0.2秒时间内的位移             s ="&#xA0;v0&#xA0;*&#xA0;t&#xA0;+&#xA0;0.4&#xA0;*&#xA0;a&#xA0;*&#xA0;(t&#xA0;**&#xA0;2)"             # 当前的位置             current +="&#xA0;s"             # 添加到轨迹列表             tracks.append(round(s))             # 速度已经达到v,该速度作为下次的初速度             v ="&#xA0;v0&#xA0;+&#xA0;a&#xA0;*&#xA0;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,&#xA0;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,&#xA0;yoffset=0).perform()"     def operate_slider(self, track):         '''         拖动滑块         # 获取拖动按钮         back_tracks ="&#xA0;[-1,-1,&#xA0;-1,&#xA0;-1]"         slider_bt ="&#xA0;self.browser.find_element_by_xpath('//div[@class="geetest_slider_button"]')"         # 点击拖动验证码的按钮不放         actionchains(self.browser).click_and_hold(slider_bt).perform()         # 按正向轨迹移动         for i in track:         time.sleep(1)     def get_cookies(self):         try:             cookie_list ="&#xA0;self.browser.get_cookies()"             cookie_dict ="&#xA0;{i['name']:&#xA0;i['value']&#xA0;for&#xA0;i&#xA0;in&#xA0;cookie_list}"             with open('xueqiu_cookies', 'w', encoding="utf8" )as f:                 cookie_dict ="&#xA0;json.dumps(cookie_dict)"                 f.write(cookie_dict)             return cookie_dict         except:             print("cookie 获取失败")             return none     # 读取cookie     def return_cookie(self):         cookies ="&#xA0;''"         with open('xueqiu_cookies', 'r')as f:             cookie ="&#xA0;f.read()[1:-1]"             for i in cookie:                 cook ="&#xA0;i.split(':&#xA0;')"                 cookies +="&#xA0;cook[0][1:-1]&#xA0;+&#xA0;'='&#xA0;+&#xA0;cook[1][1:-1]&#xA0;+&#xA0;';'"             return cookies     def run(self):         # 破解入口         self.open(), sleep(3)         self.get_yzm_button().click(), sleep(2)# 点击验证按钮         # 点按呼出缺口         slider ="&#xA0;self.get_slider()"         # slider.click()         # 获取带缺口的验证码图片         image1, image2 ="&#xA0;self.get_geetest_image()"         gap ="&#xA0;self.get_gap(image1,&#xA0;image2)"         print('缺口位置', gap)         track ="&#xA0;self.get_track(gap)"         print('滑动轨迹', track)         self.operate_slider(track)         # 判定是否成功         time.sleep(8)             elem ="&#xA0;self.wait.until("                 ec.text_to_be_present_in_element((by.class_name, 'nav__btn--longtext'), '发帖'))             if elem:                 cookie ="&#xA0;self.get_cookies()"                 print("get cookies errors")         except exception as e:             print(e, 'fail! ')             time.sleep(3)             self.run()         finally:             self.browser.quit() if __name__ ="=&#xA0;'__main__':"     crack ="&#xA0;Login()"     crack.run() < code></ threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(></canvas class="geetest_canvas_bg geetest_absolute" height="160" width="260">

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

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

数据获取

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

今年,我只赚了一点点

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

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

#-*-&#xA0;coding:utf-8&#xA0;-*-
import&#xA0;requests
import&#xA0;json

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

&#xA0;&#xA0;&#xA0;&#xA0;if&#xA0;response.status_code&#xA0;!=&#xA0;200:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;raise&#xA0;Exception(response.content)

&#xA0;&#xA0;&#xA0;&#xA0;return&#xA0;json.loads(response.content)

if&#xA0;__name__&#xA0;==&#xA0;'__main__':
&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x83B7;&#x53D6;&#x80A1;&#x7968;&#xA0;SH600000&#xA0;&#x5B9E;&#x65F6;&#x884C;&#x60C5;
&#xA0;&#xA0;&#xA0;&#xA0;url&#xA0;=&#xA0;"http://stock.xueqiu.com/v5/stock/quote.json?extend=detail&symbol=SH600000&count=10"
&#xA0;&#xA0;&#xA0;&#xA0;with&#xA0;open("xueqiu_cookies",&#xA0;"r")&#xA0;as&#xA0;f:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;cookies_info&#xA0;=&#xA0;json.load(f)
&#xA0;&#xA0;&#xA0;&#xA0;res&#xA0;=&#xA0;fetch(url,&#xA0;cookies_info['xq_a_token'])
&#xA0;&#xA0;&#xA0;&#xA0;print(res)
&#xA0;&#xA0;&#xA0;&#xA0;#&#xA0;&#x83B7;&#x53D6;&#x80A1;&#x7968;&#xA0;SH600000&#xA0;&#x73B0;&#x91D1;&#x6D41;&#x91CF;&#x8868;
&#xA0;&#xA0;&#xA0;&#xA0;url&#xA0;=&#xA0;"http://stock.xueqiu.com/v5/stock/finance/cn/cash_flow.json?symbol=SH600000&count=10"
&#xA0;&#xA0;&#xA0;&#xA0;with&#xA0;open("xueqiu_cookies",&#xA0;"r")&#xA0;as&#xA0;f:
&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;cookies_info&#xA0;=&#xA0;json.load(f)
&#xA0;&#xA0;&#xA0;&#xA0;res&#xA0;=&#xA0;fetch(url,&#xA0;cookies_info['xq_a_token'])
&#xA0;&#xA0;&#xA0;&#xA0;print(res)

运行结果:

今年,我只赚了一点点

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

这些数据,都能获取。

絮叨

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

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

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

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

最后必须提醒一下各位:

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

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

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

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

今年,我只赚了一点点

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

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

Original: https://blog.csdn.net/c406495762/article/details/128267619
Author: Jack-Cui
Title: 今年,我只赚了一点点

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/769001/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球