社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python实战篇 | 用Flask制作港股通北水追踪器

机器学习研究会订阅号 • 5 年前 • 811 次点击  

1.该文章整理自台湾新闻,可能与实际情况有出入,请领会其文章精髓。(滑稽)

2.若手机看代码出现折行,建议点击阅读原文查看。(懒死我得了)


港股通开通近4年,现时有逾五百多只股份可以让北水买卖,而港交所网页每日均列出北水在每只股份的持股量,但要分析个别股份的持股变化及走势,却要逐日把数据下载,费时失事,于是笔者撰写了一个数据撷取程式,可以一次过到网页自行下载指定时期的数据,转化成企理的数据库。然后再用flask撰写一个数据展示程式,并以互动网页形式显示,读者可到这里或下面的显示窗内输入港股通的股份代号,程式便会输出今年至今每日的北水持股量,每日增/减持变化图,以及一个每日持股股数的对照表。读者可以由此得知北水对该个股股价走势的影响,以及看好或看淡该股。


由数据可见,北水于5月头便开始减持腾讯(0700),直到8月底才沽压稍敛。至于汇丰(0005),则自年头一轮追入后,近期才再见北水捧场,但声势远不及年头强大。一些北水持重货的股份,如新华制药(0719),虽然股价受政策影响大幅回落,但北水仍然密密吸纳,未有泄气情况。种种走势,用以下的程式便可一目了然。



Python成为近年最受欢迎的程序语言,除了易学易上手外,还因为有庞大的程式库支援,令程式员有很多捷径可以走。不过,之前示范的程式,都需要读者自行下载代码在Python的编绎器内运行,未能即时展示成果。因此今次便尝试以Python的flask应用框架,制作一个可在网上直接执行的港股通北水追踪器,令没有程式经验的读者也能看到实际应用。


读取数据的程式,主要用了我过去常用的Selenium及BeautifulSoup框架,模拟人手逐页下载数据,并将其组合成一个资料库供之后使用。相比人手逐页copy & paste,程式可以三数分钟便下载过百日的数据,而且做了基本整理,正是自动化的好处。不过,实际处理上来,仍有一些复杂的地方,如公司改名,便会出现一个股票编号、两个公司名称的情况,又如公司供股,也有可能会出现临时股票编号,而原来的股票编号所撷取的数据便出现断层。现时程式只处理首一个情况,未能处理第二个情况(笔者太懒),惟相信后者占整体数据只是少数,有兴趣的读者不妨完善之。程式northwater.py已放于文末,只是短短数十行程式。


笔者利用这程式,下载了今年至今的所有股份持股量数据,之后便要用flask来撰写一个可让网页读到及让用户互动的程式。这方面准备工夫较多,先下载flask框架,再设定一个python的虚拟环境(virtual environment),然后把程式需要用到的框架都写到一个requirements.txt的文字档案内,这是方便之后云端服务了解程式的需要,不用每次均下载一大堆无用的框架,浪费资源。这些设定都是三几行指令码的事情,读者可到flask的官方网页了解。


前期准备功夫完成后,便可以撰写程式,输入flask框架及相关数据库,其主要程序结构如下:


 1app = Flask(__name__)
2
3 @app.route("/", methods=['POST''GET'])
4
5def submit_new_profile():
6
7    if request.method == 'POST':
8
9        .
10
11        .
12
13        return render_template(...)
14
15    elif request.method == 'GET':
16
17               。
18
19               。
20
21        return  render_template(...)


首行是指定动作,第二行则是当在首页出现存取表格资料时启动submit_new_profile程序,若是用户输入资料,则request.method是'POST',按照用户输入的股票编号抽取数据库中的时序数据,然后再制作相关的图形,并更新index.html档案。


除了以上几行程式外,其余与Python一般的程式无异,详细程式看文末的main.py列表。


那么index.html又有可不同呢?其实只是加入了一些双重大括号的变数如{{name}}等,这些变数是与main.py作沟通用,并可以被main.py来动态更新的。如此一来,main.py便可以与网民互动,就算是程式中的图表,在html档中,亦只是一个简单的{{model_plot}}便搞定。可以说html档成为一道道填充题,内容交由Python处理。


 1<!DOCTYPE html>
2html >
3
4    meta  charset =  UTF = 8 ” >
5    title >北水追踪器 title >
6 head >
7
8body >
9h1 >北水追踪器 h1 >
10p >
11form  method =  POST ”  action =  {{url_for('submit_new_profile')}} ” >
12p > input  type =  text ”  style =  font-size24 px ”  name =  selected_code ”  value =  {{selected_code}} ” > button  type =  submit ”  style =   font-size24 px “  name =  submit “ >输入股票编号 button >  p >
13 p >
14
15p  style = " font-size : 24 px " >股票编号:{{selected_code}}br />股票名称:{{name}} p >
16p >  p >
17p >
18{{model_plot}}
19 p >
20p >
21{{tseries_table}}
22  p >                    
23 form >
24 body >
25 html >


各样元素齐备后,便可以在flask模拟的一个简单本地伺服器上测试,确定程式无误,便可以选择一个云端服务商把程式上线(google cloud, 亚马逊的AWS,或微软的Azure均可),笔者则选择免费的Pythonanywhere ,先登记一个帐户,然后开设一个新的web app,再把程式上载,包括requirements.txt, main.py, index.html(这档案一般会放在templates的子目录下) ,以及相关的数据库、图片及字型库。大功告成?


别想得太美了,整个过程,最令人沮丧的,并非要学习试用flask框架,而是除虫工作,以为在本地伺服器环境下运作正常的,到云端就不知为何无法再运作了,看错误讯息永远摸不着头脑,左查右查之下,才发现Pythonanywhere要求载入档案必须输入完整的档案路径,否则会发出档案不存在的警告。


另一个痛点,是广受欢迎的绘图框架matplotlib并不支援中文,若要显示中文,必须指定一个中文字库,方能正常显示。具体作法坊间有很多,但不是招招都管用,这又成为一个实验试错的过程,笔者现时用的是google开发的思源宋体。


虽然今次颇多挑战,但因为flask可以将完成品直接送到网民面前,让不懂Python的人也能直接使用,而且日后还会多多使用,倒也不算白费功夫。事实上,用flask来撰写web app,令Python应用可以直接与互联网语言html配搭,加上云端服务,便可以直接执行,连伺服器设定等繁琐工作都可以悭返。昔日写web application可能要学php, mysql等后台语言,但现在都可以由Python一手包办,这正是Python的强大之处。


 1来自 bs4 进口 BeautifulSoup
2将 pandas 导入为 pd
3导入请求
4进口重新
5进口时间
6导入日期时间
7来自 selenium import webdriver
8来自 selenium.webdriver.support.ui 导入 WebDriverWait
9从 selenium.webdriver.support 导入 expected_conditions 作为 EC
10从 selenium.webdriver.common.by 进口通过
11
12尝试:
13    table = pd.read_csv(' northwater.csv '
14除了 IOError:
15    table = pd.DataFrame( columns = [ '股票编号' , '公司名称' ])
16    table.to_csv(' northwater.csv ',index  =  False)
17
18targetpage = “ http://www.hkexnews.hk/sdw/search/mutualmarket_c.aspx?t=hk ”
19
20def  gendate(start_date,end_date):
21    dates = [start_date + datetime.timedelta(n)for n in  range(int((end_date - start_date).days)+ 1)]
22    date_string = []
23    对于我在 范围(LEN(日期)):
24        temp = f ' { dates [i]:% Y % m % d } '
25        date_string.append(TEMP)
26    return date_string
27
28#检查日期是否已加载
29def  checknewdate(date,alldate):
30    如果日期在 alldate:
31        返回 False
32    否则:
33        返回 True
34
35def  loadpage(目标页面,日期):
36    年=日期[:4 ]
37    月=日期[ 46 ]
38    day = date [ 6:]
39
40    driver.get(targetpage)
41     在 driver.title中断言“ HKEX ” 
42
43    driver.find_element_by_xpath(“ // select [@ name ='ddlShareholdingDay' ] / option [text()=' ” + day + “ '] ”)。click()
44    driver.find_element_by_xpath(“ // select [@ name ='ddlShareholdingMonth'] / option [text()=' ” + month + “ '] ”)。click()
45    driver.find_element_by_xpath(“ // select [@ name ='ddlShareholdingYear'] / option [text()=' ” + year + “ '] ”)。click()
46    driver.find_element_by_name(“ btnSearch ”)。click()
47
48    断言 “没有找到结果。” 不是 在 driver.page_source
49
50    time.sleep(2
51    page = driver.page_source
52
53    返回页面
54
55 def  readpage(页面,日期):
56    汤= BeautifulSoup(页面,' html.parser '
57    record = soup.find_all(' tr ',class_ = re.compile(r “ ^ row ”))
58
59    code_tags = []           
60    name_tags = []           
61     nostock_tags = []          
62
63    for in in  range(len(record)):
64        code_tags.append(int(record [i] .select' td ')[ 0 ] .get_text()。strip()))
65        name_tags.append(record [i] .select' td ')[ 1 ] .get_text()。strip())
66        nostock_tags.append(int(record [i] .select' td ')[ 2 ] .get_text()。strip()。replace(','' ')))
67
68    table = pd.DataFrame({
69        "股票编号" : code_tags,
70        "公司名称" : name_tags,
71        日期:nostock_tags,
72    })
73
74    返回表
75
76def  appenddata(table,temptable):
77    result = pd.merge(table, temptable, on = [ '股票编号' , '公司名称' ], how = ' outer ' )
78    返回结果
79
80START_DATE = datetime.date201891
81END_DATE    = datetime.date2018929
82dates = gendate(start_date,end_date)
83
84driver = webdriver.Firefox()
85
86对于我在 范围(LEN(日期)):
87    如果 checknewdate(dates [i],alldate):
88        page = loadpage(targetpage,dates [i])
89        temptable = readpage(页面,日期[i])
90        table = appenddata(table ,temptable)
91        alldate = list(table)[ 2:]
92
93driver.close() 
94
95table.to_csv(' northwater.csv ',index  =  False)


 1#!/ usr / bin / env python
2来自 flask 导入 Flask,render_template,flash,request,jsonify,Markup
3导入 matplotlib
4matplotlib.use(' Agg '
5将 matplotlib.pyplot 导入为 plt
6将 matplotlib.font_manager 导入为 mfm
7将 matplotlib.ticker 导入为自动收报机
8将 pandas 导入为 pd
9import io,base64
10
11font_path = “/ home / bigfish / mysys / NotoSerifCJKtc-Regular.otf ”
12 prop = mfm.FontProperties(fname = font_path,size = 30
13table = pd.read_csv('/ home / bigfish/mysite/northwater.csv '
14
15app = Flask(__name__)
16
17def  sortdate(表):
18    alldate = list(table)[ 2:]
19    alldate.sort()
20    cols = list(table)[ 02 ] + alldate
21    table = table [cols]
22    返回表
23
24result = sortdate(table
25
26def  checkcodeduplicate(代码):
27    tseries = result[result[ '股票编号' ] == code]
28    printlen (tseries))
29    如果 len(tseries)> 1
30        t1 = tseries.iloc [ 0 ]
31        t2 = tseries.iloc [ 1 ]
32        tseries.iloc [ 0 ] = t1.combine_first(t2)
33        tseries = tseries [:1 ]
34    返回 tseries
35
36
37@ app.route(“ / ”,methods = [ ' POST '' GET ' ])
38def  submit_new_profile():
39    代码= 700
40    name = “ ”
41    submit_value = “ ”
42    tseries_table = “ ”
43
44    如果 request.method ==  ' POST '
45        code =  int(request.form [ ' selected_code ' ])
46         submit_value = request.form [ ' submit ' ]
47
48        尝试:
49            tseries = checkcodeduplicate(代码)
50            name = tseries[ '公司名称' ].iat[ 0 ]
51            tseries = tseries.iloc [:,2:]
52            tseries = tseries.T
53            tseries.columns = [code]
54            tseries.index.name =  '日期'
55            change = tseries.diff()
56            pattern =  ' { :,。0f } '。 format
57
58            plt.rcParams [ “ figure.figsize ” ] =(1010
59            fig = plt.figure()
60            斧= fig.add_subplot(211
61            ax.xaxis.set_major_locator(ticker.MultipleLocator(40))
62            ax.plot(T系列)
63            plt.title(name,fontproperties  = prop)
64            AX2 = fig.add_subplot(212
65            ax2.plot(其他城市)
66            ax2.xaxis.set_major_locator(ticker.MultipleLocator(40))
67            plt.axhline(linewidth = 1,color = ' r '
68
69            img = io.BytesIO()
70            plt.savefig(img,format = ' png '
71            img.seek(0
72            plot_url = base64.b64encode(img.getvalue())。decode()
73
74            return render_template(' index.html '
75                model_plot  = Markup( '。 format(plot_url)),
76                selected_code  =  str(代码),
77                name = name,
78                 tseries_table =标记(tseries.to_html()))
79
80        除了:
81            return render_template(' index.html '
82                model_plot  =  “ ”,
83                selected_code  =  str (code), name = "你选择的股票没有数据" ,
84                tseries_table = “ ”)
85
86
87  elif request.method ==  ' GET '
88    return render_template(' index.html '
89            model_plot  =  ' '
90            selected_code  =  str(代码),
91            name = name,
92            tseries_table = “ ”)
93
94#在本地运行app时
95如果 __name__ == ' __main__ '
96    app.run(debug = False)


想要了解更多资讯,请扫描下方二维码,关注机器学习研究会

                                          


转自: 人工智能


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/h65b7DIgLl
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/24657
 
811 次点击