作者现在在香港,最近半年因为美国加息,港币暴涨,非常痛苦。在网上找了找也没有找到能够实时更新的API来调用。为了能在偶尔出现汇率下降的时候及时购汇,写了一个监测Yahoo和中银汇率的Python脚本。
结尾有整个Python代码。
要引入的包:
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
一下就行了。
本质上就是给相应页面的服务器发送Http请求获取html页面内容之后再Xpath来解析。不过先用浏览器进入F12检查一下携带的headers和参数,再用requests工具发送就可以模拟浏览器而不被拒绝了。
至于怎么获取请求头和参数,在浏览器进入检查界面后,进入带有汇率的页面,查看网络一栏下哪一个请求到了想要的数据,把url,headers和参数都记下来就行了(你也可以直接使用我的headers试试),在Python中用字典的形式来保存。
如果你需要其他货币的汇率,更换一下下面代码中的url就可以了。下面的url中都带了一个时间戳的参数防止缓存。
def get_yahoo_hk_currency():
url = 'https://finance.yahoo.com/quote/HKDCNY=X?p=HKDCNY=X&.tsrc=fin-srch×tamp=' + 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语法照着这张图估计也就差不多能明白了(//
表示的是无视层级的查找)。
跟上面的原理类似,但是中银这个页面(居然还是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
汇率能够获取到了,剩下的逻辑就可以按照自己的需求来写了。
我这里是监测到和上次记录的值不相等就发送一封电子邮件给自己。使用到了email包,要注意的是,使用QQ邮箱这种进行发送的时候,密码不是你的QQ密码,要去QQ邮箱页面开启SMTP服务(不清楚的可以百度查),使用提供的授权码作为密码。
开头部分这里接收邮箱通过命令行参数的形式来接收(第一个命令行参数是脚本的文件名,所以收集完删掉第一个元素del(to_addrs[0])
),也可以直接写死。剩下的代码就很好理解了。死循环结尾是让线程睡眠一段时间,给个随机值降低被检测出来是脚本的可能。
if __name__ == '__main__':
# sys.stdout.flush()
to_addrs = []
from_addr = "[email protected]"
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))
在服务器(或者自己电脑上)上执行nohup python3 currency.py [email protected] [email protected] &
就能够在后台持续执行了。
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×tamp=' + 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 = "[email protected]"
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))
问题1 在CentOS上运行,发不出邮件也不报错
解决 这个问题困扰我很久,网上说是没有使用SMTP_SSL
来连接,但是刚开始使用的时候 是能发出来的。然后还有一个情况是发送一阵之后就又不发送了,网上查到是需要定期quit()
重新登录才行,不然会自动退出。