引言

前段时间或许由于货币政策的收紧以及十年期国债的突然上涨,理财产品市场一片哀鸿遍野,均或多或少经历了一段时间的跳水。本人所购买的二级风险理财产品本以为十分稳健也没多看,没想到前几天一看本金都受到了一定程度的亏损。而**银行绘的这个破走势绘图也基本看不出具体价格变化,等真正看出下跌趋势了早亏损惨重了。

毛都没用的趋势图

所以求人不如求己,连夜简单写了一个定时到目标网站抓取数据信息,经OCR数据提取与分析后,通过邮箱汇报产品状况的工程出来。由于我这个买的是灵活申赎型,且产品的价格走势都具有较大动量,所以及时地在下跌开始赎回便能很好的止损。

地址

我已将项目上传至GitHub:https://github.com/Z-MiCTrue/Build-personal-financial-assistant-from-OCR-and-visual-crawler-via-emailhttps://github.com/Z-MiCTrue/Build-personal-financial-assistant-from-OCR-and-visual-crawler-via-email

项目目标及流程

目标:每日实现对目标产品网站数据的抓取,并提取数据计算当前利润/亏损趋势,以及绘制趋势图。最终将结果打包发至个人邮箱,实现每日的数据汇报

流程:基于python,在设定时刻,用 webbrowser 自动打开目标网站。(由于每个网站代码都不尽相同并可能配备一些反爬措施,所以这里我考虑使用直接截图网站,用视觉实现对信息的抓取)即用 PIL 的 ImageGrab 截取打开网站的屏幕,再自动关闭该浏览器。之后使用OCR的文本提取方法或使用模板匹配的方法实现在所截图中,对目标数据区域(ROI)的切取,使用 easyocr 对切取区域进行文本识别储存于列表中,在绘图以及计算收益率之类的操作之后,最后将数据通过邮箱发送至手机。

环境准备

python3

需要安装的库有:

easyOCR, numpy, matplotlib, Pillow, opencv-python

# 其中 easyOCR 强烈建议在虚拟环境下安装,其中牵涉到 PyTorch 的安装,这里便不再赘述,具体方法百度即可

算法搭建

整体目录结构:

│  main.py
│
├─anchor
│      anchor_1.png
│      anchor_2.png
│      ...
│
└─utilsfinancial_watchdog.pymail_send.pysl_plot.py__init__.py

爬取 + 截图 + 数据分析 模块(financial_watchdog.py)

整体模块代码(细节看注释):

import os
import time
import webbrowserfrom PIL import ImageGrab
import numpy as np
import cv2
import easyocr#  模板匹配
def template_match(img, template, mask=None):# 都转成灰度图if len(img.shape) > 2:img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)if len(template.shape) > 2:template = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)# 模板匹配result = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED, mask)# 返回最大最小值及索引minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)x_1, y_1 = maxLocx_2 = x_1 + template.shape[1]y_2 = y_1 + template.shape[0]return x_1, y_1, x_2, y_2  # [左上x, 左上y, 右下x, 右下y]class Financial_Watchdog:def __init__(self, params):self.screen_roi = params.screen_roi  # 屏幕截图分辨率, 如 (0, 0, 2560, 1600)self.aim_url = params.aim_url  # 目标网站地址self.default_browser = params.default_browser  # 默认浏览器, 如 'msedge.exe'self.text_recognizer = easyocr.Reader(['ch_sim'])  # ocr 语言选择, 并生成识别器# 将匹配模板图片加载至列表, 如用 ocr 定位可忽略self.template_img = []for file_name in params.template_name:self.template_img.append(cv2.imread(file_name, 0))def grab_img(self):# 打开网站webbrowser.open(self.aim_url)time.sleep(10)  # 等待打开# 截图img = ImageGrab.grab(bbox=self.screen_roi)# 关闭浏览器进程os.system('taskkill /F /IM ' + self.default_browser)# 转为 cv2 格式img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)return imgdef detect_data(self, img, ocr_position=True):# 二值化if len(img.shape) > 2:img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)ret, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)# cv2.imwrite('test.png', img)  # 可选择记录捕获的二值化图片# 定位 (以下代码需根据需求自定义)left_x = Noneright_x = Nonetop_y = None# 用 ocr 方式定位if ocr_position:img_det = self.text_recognizer.readtext(img)for element in img_det:if element[1] == '份额净值':left_x = np.min(element[0], axis=0)[0] - 10top_y = np.max(element[0], axis=0)[1]elif element[1] == '认购价格':right_x = np.min(element[0], axis=0)[0] - 10# 用模板匹配方式定位else:match_res = template_match(img, self.template_img[0], mask=None)left_x = match_res[0] - 10top_y = match_res[3]match_res = template_match(img, self.template_img[1], mask=None)right_x = match_res[0] - 10# 切分感兴趣区域if None not in (left_x, right_x, top_y):roi_interest = img[top_y:, left_x:right_x]# OCR 提取数据img_det = self.text_recognizer.readtext(roi_interest)price_list = []for element in img_det:price_list.append(float(element[1]))price_list = np.array(price_list[::-1])  # 按时间顺序print(f'solve result: {price_list}')return price_listelse:return None

这里我抓取的的一个产品数据(示意):

黑色框选部分是定位模板;红色框选部分是我最终切取出的待识别数据图像。模板的获取一般需要亲自截图后剪裁,如:

anchor_1.png:

anchor_2.png:

这两个模板分别定位了红框区域的左右以及上边坐标,最后对该区域数据提取:

img_det = self.text_recognizer.readtext(roi_interest)

得到提取结果(例,并非图中识别数据):

[1.026289 1.026835 1.026973 1.026674 1.026487 1.026399]

Attention 1:模板分辨率必须与使用时程序获取截图部分的分辨率相同!因为OpenCV的模板匹配方法是建立于滑动窗口比对方差实现的,并不具有缩放、旋转一致性。即不匹配的分辨率可能导致相当糟糕的结果!如有该需要,可以尝试加入特征金字塔方法,或者使用CNN...

Attention 2:高分辨率的屏幕(2K+)可以使用OCR方法进行定位,但低分辨率屏幕则更建议使用模板匹配方法。因为中文字较为复杂,许多字都具有十分相似的外形,所以在低分辨率下极易出现误识别导致程序无法正常运行

Attention 3:运行时需要关闭屏幕自动关闭,否则截屏结果会不正常

测试模板匹配效果可用该函数进行(测试截屏图名为 'test.png'):

def test_position():import cv2from matplotlib import pyplot as pltfrom utils.financial_watchdog import template_matchtemplate_name = ['./anchor/anchor_1.png', './anchor/anchor_2.png']template_img = []for file_name in template_name:template_img.append(cv2.imread(file_name, 0))image = cv2.imread('test.png')# 二值化if len(image.shape) > 2:image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)ret, image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)# 模板匹配res = template_match(image, template_img[1], mask=None)# 画框cv2.rectangle(image, res[:2], res[2:4], 0, 2)# 显示plt.imshow(image, cmap='gray')plt.show()

制图模块(sl_plot.py)

整体模块代码:

import matplotlib.pyplot as plt
import numpy as npdef sl_plot(y, x=None, save_switch=False):if x is None:x = np.arange(0, len(y))else:x = np.array(x)y = np.array(y)plt.figure()plt.plot(x, y, linewidth=2, color='b', marker='o', markerfacecolor='black', markersize=2)plt.title('Trends')plt.ylabel('Value')plt.xlabel('Variable')if save_switch:plt.savefig('statistic.png')else:plt.show()

这里将图片保存为 'statistic.png' 在当前目录下,以供邮件发送模块的使用

实现效果:

邮件发送模块(mail_send.py)

整体模块代码(细节看注释):

import time
import datetime
import smtplib
from email.header import Header
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipartclass Mail_Sender:def __init__(self, params):self.mail_host = params.mail_host  # 服务器地址 'smtp.163.com'self.email_sender = params.email_sender  # 发送人邮箱 'xxx@163.com'self.mail_license = params.mail_license  # 邮箱授权码, 不是密码!self.msg = None# 编辑邮件内容: 主题, 正文, 图片路径def edit_text(self, subject_content, body_content, img_dir=None):self.msg = MIMEMultipart('related')# self.msg["From"] = ''# self.msg["To"] = ''self.msg["Subject"] = Header(subject_content, 'utf-8')  # 主题self.msg.attach(MIMEText(body_content, 'plain', 'utf-8'))  # 正文# 图片if img_dir is not None:with open(img_dir, 'rb') as img:msg_img = MIMEImage(img.read(), img_dir.split('.')[-1])self.msg.attach(msg_img)# 发送邮件: 收件人邮箱, 发送次数, 间隔时间def send_email(self, email_receiver, send_times=1, interval_time=0):stp = smtplib.SMTP()  # 创建SMTP对象stp.connect(self.mail_host, 25)  # 设置发件人邮箱的域名和端口, 端口地址为25stp.login(self.email_sender, self.mail_license)  # 登录邮箱: 邮箱地址, 邮箱授权码print(f'{datetime.datetime.now()} <-* info: Login successfully *->')# 一次/多次发送for i in range(send_times):stp.sendmail(self.email_sender, email_receiver, self.msg.as_string())print(f'{datetime.datetime.now()} <-* info: Send successfully *->')time.sleep(interval_time)stp.quit()  # 退出登录

主函数(main.py)

整体模块代码(细节看注释):

import timefrom utils.financial_watchdog import Financial_Watchdog
from utils.sl_plot import sl_plot
from utils.mail_send import Mail_Senderclass Test_Options:def __init__(self):# mailself.mail_host = 'smtp.163.com'  # 服务器地址self.email_sender = 'xxx@163.com'  # 发送人邮箱self.mail_license = 'XXXX'  # 邮箱授权码self.email_receivers = ['xxx@126.com', 'xxx@qq.com']  # 收件人邮箱, 可以为多个收件人# creeperself.target_name = '中银理财-悦享天添卓越版-YXTTZY01F'self.screen_roi = (0, 0, 2560, 1600)  # 屏幕分辨率 (0, 0, 2560, 1600); (0, 0, 1920, 1080)self.aim_url = 'https://www.bankofchina.com/sdbapp/wmpnetworth/7784/7788/8975/index_1719.html'  # 目标网站地址self.default_browser = 'msedge.exe'  # 默认浏览器# detectself.use_ocr_position = True  # True: 使用ocr定位; False: 使用模板匹配定位self.template_name = ['./anchor/anchor_1.png', './anchor/anchor_2.png']  # 模板图片地址# commonself.check_time = ('07:30', '15:00')  # 定期发送时间def main(opt):# 初始化auto_sender = Mail_Sender(opt)financial_watchdog = Financial_Watchdog(opt)with open('library.txt', encoding='utf-8') as str_book:words_list = str_book.read().split('\n\n')  # 随附的每日一句first_start = True  # 第一次运行就发送, 不需要等到特定时间段day_count = 0  # 记录运行天数# 开始工作while True:# 获取时间time_now = time.strftime("%H:%M", time.localtime())if time_now in opt.check_time or first_start:# 获取图像web_img = financial_watchdog.grab_img()# 获取识别结果res_det = financial_watchdog.detect_data(web_img, ocr_position=opt.use_ocr_position)if res_det is not None:# 绘图并保存sl_plot(res_det, x=None, save_switch=True)send_str = f'Your latest financial has arrived!\n\nTarget name: {opt.target_name}\n'value_growth = round(res_det[-1] - res_det[-2], 6)  # 相比昨天增长量 (一阶)earn_1w_per = round(1e4 / res_det[-2] * value_growth, 2)  # 直观化d_growth = round(res_det[-1] + res_det[-3] - 2 * res_det[-2], 6)  # 相比昨天增长量的增长量 (二阶)earn_1w_per_d = round(1e4 / res_det[-2] * d_growth, 2)  # 直观化send_str += f'The latest value growth is: {value_growth} (except to earn ¥{earn_1w_per} per 1w)\n'if d_growth >= 0:send_str += f'Compared to yesterday, value growth: +{d_growth} (more ¥{earn_1w_per_d} per 1w)\n'else:send_str += f'Compared to yesterday, value growth: {d_growth} (more ¥{earn_1w_per_d} per 1w)\n'send_str += f'Detail:\n{res_det}\n\nDaily tip: {words_list[day_count]}\n\nHave a nice day!'auto_sender.edit_text(subject_content='Hi! Boss', body_content=send_str, img_dir='statistic.png')else:auto_sender.edit_text(subject_content='Hi! Boss', body_content='Error!', img_dir=None)# 发送邮件auto_sender.send_email(opt.email_receivers, send_times=1, interval_time=1)day_count += 1if not first_start:time.sleep(25200)  # 睡眠7小时first_start = Falseelse:time.sleep(55)  # 必须小于一分钟这个采样周期if __name__ == '__main__':test_options = Test_Options()main(test_options)

这里我还添加了每日一句的功能(读取的是 'library.txt' 里按双换行分隔的每日一句),无需要可以删掉

整体效果

手机邮箱界面:

服务端运行效果:

后续会继续开源一个 爬取股票数据并用CNN-LSTM构建的深度学习股票预测 项目,效果还不错......

so thanks

[项目记录]通过邮箱从OCR与视觉爬虫搭建个人理财助手相关推荐

  1. ssm 项目记录用户操作日志和异常日志

    ssm 项目记录用户操作日志和异常日志 参考文章: (1)ssm 项目记录用户操作日志和异常日志 (2)https://www.cnblogs.com/mei-m/p/10231792.html (3 ...

  2. 【个人项目】项目记录:github链接、设计实现、单元测试、性能分析与改进、PSP完成表格、总结反思

    项目记录 一.github链接 链接:https://github.com/LLFKirito/SudokuWork-BIT1120161918/ 二.设计实现 总体设计 程序流程图如下 程序分为ma ...

  3. 微信读书项目记录(1)

    微信读书项目记录 一.项目需求及阅读器引擎介绍 1.1epubjs阅读器引擎介绍,常用的类 Book,完成阅读器解析 Rendition,完成阅读器定位 Navigation,存储目录信息 View ...

  4. STM32对接涂鸦wifi模块项目记录(智能插座完善版本)

    应项目需求,客户需要对接涂鸦平台,从了解平台到样品实际落地,还是挺方便的, 做过的一个项目,人体感应智能插座项目,对接涂鸦云 : 硬件平台:STM32F103 WIFI模块: 涂鸦WiFi(型号见文章 ...

  5. vue+element项目 手机号、邮箱校验 保姆级教程

    vue+element项目 手机号.邮箱校验 保姆级教程 (包含注意点) 先看案例:在vue+element项目中给表单中的手机号和邮箱做校验 标题先说注意点 prop黄色框框起来的一定要有 废话不多 ...

  6. 项目记录: 3DOF+实时渲染 之 虚拟视点合成

    项目记录: 3DOF+实时渲染 之 虚拟视点合成 总结一下实现3DOF+渲染的一些细节. 虚拟视点合成 3DOF+实时渲染方案 0. 3DOF+概念 关于 3DOF+ 概念 可以参考我之前的博文. 3 ...

  7. 简单记录一下fabric版本1.4的环境搭建,

    简单记录一下fabric版本1.4的环境搭建,运行环境为Ubuntu18.04,其中一些内容是根据官方文档整理的,如有错误欢迎批评指正. 本文只介绍最简单的环境搭建方法,具体的环境搭建解析在这里深入解 ...

  8. 9.9元进群项目源码/付费进群源码含搭建教程以及微擎工具

    9.9元进群项目源码/付费进群源码 功能简介: 源码的功能其实就是一个单独的页面,在上面你可以在后台自定义你的内容,底部会显示9.9(可自定义)入群.当然也可以加价,也可以换别的,不只是进群. 还可以 ...

  9. 【记录】饥荒联机版+个人云服务器搭建(2020.6.19)

    [记录]饥荒联机版+个人云服务器搭建(至2020.6.19有效) 写在前面 所需材料 开始做菜! 1.进入属于自己服务器 2.安装依赖 3.安装SteamCMD及Steam客户端 4.安装饥荒联机版服 ...

最新文章

  1. spring session 退出登录 清理session
  2. 如何获取本地安装的Python模块列表?
  3. 【安全研究】从mimikatz学习万能密码——上
  4. c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值
  5. pcm 降采样_Android_android downsample降低音频采样频率代码,使用Android AudioRecord 录制PCM文 - phpStudy...
  6. 强烈推荐:给去美国的新生说几句(转载),超实用
  7. Python魔法方法(magic method)细解几个常用魔法方法(下)
  8. java todo error_Java全局异常处理(TODO)
  9. pycharm安装javascript插件_IDEA必备插件系列-Rainbow
  10. MSI/MSI-X Capability结构 (转)
  11. 问答式验证码源码贡献(待续.......)
  12. linux服务器测试接口命令,Linux 下 TCP/UDP 端口测试及验证方法说明
  13. 【老生谈算法】matlab实现Dijkstra最短路算法源码——Dijkstra算法
  14. crontab 每天凌晨12点定时器_每天爆卖10000只生蚝!西安这家烧烤界扛把子,吃到凌晨2点都不尽兴!...
  15. iOS开发-常用第三方开源框架介绍
  16. 神器——写Markdown来画流程图、时序图
  17. 太阳表面的重力加速度
  18. java Exchanger原理
  19. android手机来电自动报名字,读短信来电报姓名
  20. TOP 10专属代工厂,建了多少晶圆厂?

热门文章

  1. 【收藏】Proteus仿真LCD12864液晶显示极详细的教程
  2. Excel 函数 - VLookup 常见问题和使用技巧(超详细)
  3. 实验二、8位流水灯电路设计
  4. android 自定义下拉菜单
  5. K8S部署单节点zookeeper服务
  6. 2021-10-21 统计学-基于R(第四版)第五章课后习题记录及总结
  7. JAVA应用服务器都有那些?
  8. 解决Pycharm不能自动生成函数注释
  9. FFT变换前后的幅值对应关系
  10. 自建SuSE Yast安装源