【练习/Python】监测汇率脚本

1 背景

作者现在在香港,最近半年因为美国加息,港币暴涨,非常痛苦。在网上找了找也没有找到能够实时更新的API来调用。为了能在偶尔出现汇率下降的时候及时购汇,写了一个监测Yahoo和中银汇率的Python脚本。

结尾有整个Python代码。

2 环境

  • 建议可以跑在服务器上可以一直运行
  • Python3

要引入的包:

import random
import sys

import requests
from lxml import etree
from hyper.contrib import HTTP20Adapter
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import datetime
import time

哪个没有直接pip install一下就行了。

3 代码

本质上就是给相应页面的服务器发送Http请求获取html页面内容之后再Xpath来解析。不过先用浏览器进入F12检查一下携带的headers和参数,再用requests工具发送就可以模拟浏览器而不被拒绝了。

至于怎么获取请求头和参数,在浏览器进入检查界面后,进入带有汇率的页面,查看网络一栏下哪一个请求到了想要的数据,把url,headers和参数都记下来就行了(你也可以直接使用我的headers试试),在Python中用字典的形式来保存。

如果你需要其他货币的汇率,更换一下下面代码中的url就可以了。下面的url中都带了一个时间戳的参数防止缓存。

3.1 Yahoo汇率(内地IP可以跳过)

def get_yahoo_hk_currency():
    url = 'https://finance.yahoo.com/quote/HKDCNY=X?p=HKDCNY=X&.tsrc=fin-srch&timestamp=' + str(time.time())
    headers = {':method': 'GET', ':scheme': 'https', ':authority': 'finance.yahoo.com',
               ':path': '/quote/HKDCNY=X?p=HKDCNY=X&.tsrc=fin-srch',
               'Cookie': 'A1=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII; A1S=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII&j=WORLD; A3=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII; B=1g7u7l9hj7rph&b=3&s=sc; PRF=t%3DCNY%253DX; maex=%7B%22v2%22%3A%7B%7D%7D; __gpi=UID=00000894058aa89c:T=1666772728:RT=1666772728:S=ALNI_Mbzrr-RS-qdoaBtUlK4qMHoR4fTOw; GUC=AQEBBwFjWjZjg0IdJwR0; cmp=t=1666772723&j=0&u=1---; GUCS=ATweDAh7',
               'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
               'Host': 'finance.yahoo.com',
               'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15',
               'Accept-Language': 'zh-CN,zh-Hans;q=0.9', 'Accept-Encoding': 'gzip, deflate, br',
               'Connection': 'keep-alive'}
    params = {'p': 'HKDCNY=X', '.tsrc': 'fin-srch'}

    sessions = requests.session()
    sessions.mount(url, HTTP20Adapter())
    res = sessions.get(url, headers=headers, params=params)

    dom = etree.HTML(res.text)
    cur = dom.xpath('/html/body//div[@class="D(ib) Mend(20px)"]/fin-streamer')
    return float(cur[0].text) if len(cur) > 0 else -1

下面是Yahoo带有汇率的html,蓝色部分是我们想要的汇率信息。
在这里插入图片描述
函数中用到的Xpath语法照着这张图估计也就差不多能明白了(//表示的是无视层级的查找)。

3.2 中银汇率

跟上面的原理类似,但是中银这个页面(居然还是jsp)请求更加简单,不需要请求头就行。

def get_boc_hk_currency():
    url = 'https://srh.bankofchina.com/search/whpj/search_cn.jsp'
    params = {'erectDate': None, 'nothing': None, 'pjname': '港币', 'head': 'head_620.js', 'bottom': 'bottom_591.js',
              't': random.random()}

    sessions = requests.session()
    sessions.mount(url, HTTP20Adapter())
    res = sessions.get(url, params=params)

    dom = etree.HTML(res.text)
    tds = dom.xpath('/html/body//div[@class="BOC_main publish"]//tr[2]/td')

    # 购汇
    buy_currency = round(float(tds[3].text) / 100, 4)
    # 结汇
    sale_currency = round(float(tds[1].text) / 100, 4)
    return buy_currency, sale_currency

3.3 监测

汇率能够获取到了,剩下的逻辑就可以按照自己的需求来写了。

我这里是监测到和上次记录的值不相等就发送一封电子邮件给自己。使用到了email包,要注意的是,使用QQ邮箱这种进行发送的时候,密码不是你的QQ密码,要去QQ邮箱页面开启SMTP服务(不清楚的可以百度查),使用提供的授权码作为密码。

开头部分这里接收邮箱通过命令行参数的形式来接收(第一个命令行参数是脚本的文件名,所以收集完删掉第一个元素del(to_addrs[0])),也可以直接写死。剩下的代码就很好理解了。死循环结尾是让线程睡眠一段时间,给个随机值降低被检测出来是脚本的可能。

if __name__ == '__main__':
    # sys.stdout.flush()
    to_addrs = []
    from_addr = "?@163.com"
    for email in sys.argv:
        to_addrs.append(email)
    del (to_addrs[0])

    # 记录上一次yahoo购汇发送的汇率和时间
    last_yahoo_currency = -1
    last_boc_buy_currency = -1
    last_boc_sale_currency = -1
    last_time = '初始化运行无数据,已记录本次'

    while True:
        try:
            yahoo_currency = get_yahoo_hk_currency()
            boc_buy_currency, boc_sale_currency = get_boc_hk_currency()
            print(str(datetime.datetime.now()) + ' ' + str(yahoo_currency) + ' ' + str(boc_buy_currency) + ' ' + str(
                boc_sale_currency))
            if last_boc_buy_currency == -1 or (last_yahoo_currency != -1 and last_yahoo_currency != yahoo_currency) \
                    or last_boc_buy_currency != boc_buy_currency:
                print("监测到变化。")
                cur_time = str(datetime.datetime.now())
                msg_str = f'''
                {cur_time}
                [yahoo]\t\t{yahoo_currency}
                [boc购汇]\t{boc_buy_currency}
                [boc结汇]\t{boc_sale_currency}
                -------------------
                (上一次中银购汇汇率)
                {last_time}
                {last_boc_buy_currency}
                '''
                # 创建 SMTP 对象
                mail_server = "smtp.163.com"
                smtp = smtplib.SMTP_SSL(mail_server)
                smtp.connect(mail_server, port=465)
                # 登录,需要:登录邮箱和授权码
                smtp.login(user=from_addr, password="?")

                msg = MIMEText(msg_str, 'plain', 'utf-8')
                msg['From'] = Header('URAC')
                msg['Subject'] = Header('港币汇率更新提醒', 'utf-8')

                smtp.sendmail(from_addr=from_addr,
                              to_addrs=to_addrs, msg=msg.as_string())
                smtp.quit()
                smtp.close()
                last_yahoo_currency = yahoo_currency
                last_boc_buy_currency = boc_buy_currency
                last_boc_sale_currency = boc_sale_currency
                last_time = str(cur_time)
        except BaseException as e:
            traceback.print_exc()
        finally:
            sys.stdout.flush()
            time.sleep(30 - random.randint(-9, 9))

3.4 运行

在服务器(或者自己电脑上)上执行nohup python3 currency.py 1@qq.com 2@qq.com &就能够在后台持续执行了。

3.5 整体代码

import random
import sys

import requests
from lxml import etree
from hyper.contrib import HTTP20Adapter
import smtplib
from email.mime.text import MIMEText
from email.header import Header
import datetime
import time
import traceback


def get_yahoo_hk_currency():
    url = 'https://finance.yahoo.com/quote/HKDCNY=X?p=HKDCNY=X&.tsrc=fin-srch&timestamp=' + str(time.time())
    headers = {':method': 'GET', ':scheme': 'https', ':authority': 'finance.yahoo.com',
               ':path': '/quote/HKDCNY=X?p=HKDCNY=X&.tsrc=fin-srch',
               'Cookie': 'A1=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII; A1S=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII&j=WORLD; A3=d=AQABBDHvM2MCECI2mmBLq9Wjg_CpwqrHHxgFEgEBBwE2WmODY1iia3sB_eMBAAcIMe8zY6rHHxg&S=AQAAAkClup8wRI-xsJF1yi6vqII; B=1g7u7l9hj7rph&b=3&s=sc; PRF=t%3DCNY%253DX; maex=%7B%22v2%22%3A%7B%7D%7D; __gpi=UID=00000894058aa89c:T=1666772728:RT=1666772728:S=ALNI_Mbzrr-RS-qdoaBtUlK4qMHoR4fTOw; GUC=AQEBBwFjWjZjg0IdJwR0; cmp=t=1666772723&j=0&u=1---; GUCS=ATweDAh7',
               'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
               'Host': 'finance.yahoo.com',
               'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15',
               'Accept-Language': 'zh-CN,zh-Hans;q=0.9', 'Accept-Encoding': 'gzip, deflate, br',
               'Connection': 'keep-alive'}
    params = {'p': 'HKDCNY=X', '.tsrc': 'fin-srch'}

    sessions = requests.session()
    sessions.mount(url, HTTP20Adapter())
    res = sessions.get(url, headers=headers, params=params)

    dom = etree.HTML(res.text)
    cur = dom.xpath('/html/body//div[@class="D(ib) Mend(20px)"]/fin-streamer')
    return float(cur[0].text) if len(cur) > 0 else -1


def get_boc_hk_currency():
    url = 'https://srh.bankofchina.com/search/whpj/search_cn.jsp'
    params = {'erectDate': None, 'nothing': None, 'pjname': '港币', 'head': 'head_620.js', 'bottom': 'bottom_591.js',
              't': random.random()}

    sessions = requests.session()
    sessions.mount(url, HTTP20Adapter())
    res = sessions.get(url, params=params)

    dom = etree.HTML(res.text)
    tds = dom.xpath('/html/body//div[@class="BOC_main publish"]//tr[2]/td')

    # 购汇
    buy_currency = round(float(tds[3].text) / 100, 4)
    # 结汇
    sale_currency = round(float(tds[1].text) / 100, 4)
    return buy_currency, sale_currency


if __name__ == '__main__':
    # sys.stdout.flush()
    to_addrs = []
    from_addr = "?@163.com"
    for email in sys.argv:
        to_addrs.append(email)
    del (to_addrs[0])

    # 记录上一次yahoo购汇发送的汇率和时间
    last_yahoo_currency = -1
    last_boc_buy_currency = -1
    last_boc_sale_currency = -1
    last_time = '初始化运行无数据,已记录本次'

    while True:
        try:
            yahoo_currency = get_yahoo_hk_currency()
            boc_buy_currency, boc_sale_currency = get_boc_hk_currency()
            print(str(datetime.datetime.now()) + ' ' + str(yahoo_currency) + ' ' + str(boc_buy_currency) + ' ' + str(
                boc_sale_currency))
            if last_boc_buy_currency == -1 or (last_yahoo_currency != -1 and last_yahoo_currency != yahoo_currency) \
                    or last_boc_buy_currency != boc_buy_currency:
                print("监测到变化。")
                cur_time = str(datetime.datetime.now())
                msg_str = f'''
                {cur_time}
                [yahoo]\t\t{yahoo_currency}
                [boc购汇]\t{boc_buy_currency}
                [boc结汇]\t{boc_sale_currency}
                -------------------
                (上一次中银购汇汇率)
                {last_time}
                {last_boc_buy_currency}
                '''
                # 创建 SMTP 对象
                mail_server = "smtp.163.com"
                smtp = smtplib.SMTP_SSL(mail_server)
                smtp.connect(mail_server, port=465)
                # 登录,需要:登录邮箱和授权码
                smtp.login(user=from_addr, password="?")

                msg = MIMEText(msg_str, 'plain', 'utf-8')
                msg['From'] = Header('URAC')
                msg['Subject'] = Header('港币汇率更新提醒', 'utf-8')

                smtp.sendmail(from_addr=from_addr,
                              to_addrs=to_addrs, msg=msg.as_string())
                smtp.quit()
                smtp.close()
                last_yahoo_currency = yahoo_currency
                last_boc_buy_currency = boc_buy_currency
                last_boc_sale_currency = boc_sale_currency
                last_time = str(cur_time)
        except BaseException as e:
            traceback.print_exc()
        finally:
            sys.stdout.flush()
            time.sleep(30 - random.randint(-9, 9))

4 运行效果

问题1 在CentOS上运行,发不出邮件也不报错
解决 这个问题困扰我很久,网上说是没有使用SMTP_SSL来连接,但是刚开始使用的时候 是能发出来的。然后还有一个情况是发送一阵之后就又不发送了,网上查到是需要定期quit()重新登录才行,不然会自动退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值