本文示例程式碼已上傳至我的
Github
倉庫https://github.com/CNFeffery/DataScienceStudyNotes
1 簡介
這是我的系列教程Python+Dash快速web應用開發的第十六期,在過往所有的教程及案例中,我們所搭建的Dash
應用的訪問地址都是單一的,是個單頁面應用,即我們所有的功能都排布在同一個url之下。
而隨著我們所編寫的Dash
應用功能的日趨健全和複雜,單一url的內容組織方式無法再很好的滿足需求,也不利於構建邏輯清晰的web應用。
因此我們需要在Dash
應用中引入路由的相關功能,即在當前應用主域名下,根據不同的url來渲染出具有不同內容的頁面,就像我們日常使用的絕大多數網站那樣。
而今天的教程,我們就將一起學習在Dash
中編寫多url應用並進行路由控制的常用方法。
2 編寫多頁面Dash應用
2.1 Location()的基礎使用
要想在Dash
中實現url路由功能,首先我們需要捕獲到瀏覽器中位址列對應的url是什麼,這在Dash
中可以通過在app.layout
中構建一個可以持續監聽當前Dash
應用url資訊的部件來實現。
我們使用官方依賴庫dash_core_components
中的Location()
部件來實現上述功能,它的核心引數或屬性有href
、pathname
、search
和hash
,讓我們通過下面的例子來直觀的瞭解它們各自記錄了位址列url中的哪些資訊:
app1.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = dbc.Container(
[
dcc.Location(id='url'),
html.Ul(id='output-url')
],
style={
'paddingTop': '100px'
}
)
@app.callback(
Output('output-url', 'children'),
[Input('url', 'href'),
Input('url', 'pathname'),
Input('url', 'search'),
Input('url', 'hash')]
)
def show_location(href, pathname, search, hash):
return (
html.Li(f'當前href為:{href}'),
html.Li(f'當前pathname為:{pathname}'),
html.Li(f'當前search為:{search}'),
html.Li(f'當前hash為:{hash}'),
)
if __name__ == '__main__':
app.run_server(debug=True)
因此在Dash
中編寫多url應用的核心策略是利用埋點Location()
捕獲到位址列對應資訊的變化,並以這些資訊作為回撥函式的輸入,來輸出相應的頁面內容變化,讓我們從下面這個簡單的例子中get上述這一套流程的運作方式:
app2.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = dbc.Container(
[
dcc.Location(id='url', refresh=False),
dbc.Row(
[
dbc.Col(
[
html.A('頁面A', href='/pageA'),
html.Br(),
html.A('頁面B', href='/pageB'),
html.Br(),
html.A('頁面C', href='/pageC'),
],
width=2,
style={
'backgroundColor': '#eeeeee'
}
),
dbc.Col(
html.H3(id='render-page-content'),
width=10
)
]
)
],
style={
'paddingTop': '20px',
'height': '100vh',
'weight': '100vw'
}
)
@app.callback(
Output('render-page-content', 'children'),
Input('url', 'pathname')
)
def render_page_content(pathname):
if pathname == '/':
return '歡迎來到首頁'
elif pathname == '/pageA':
return '歡迎來到頁面A'
elif pathname == '/pageB':
return '歡迎來到頁面B'
elif pathname == '/pageC':
return '歡迎來到頁面C'
else:
return '當前頁面不存在!'
if __name__ == '__main__':
app.run_server(debug=True)
2.2 利用Location()實現頁面重定向
在上一小節我們對dcc.Location()
的基礎用法進行了介紹,而它的功能可不止監聽url變化這麼簡單,我們還可以利用它在Dash
中實現重定向,使用方式簡單一句話描述就是將Location()
作為對應回撥的輸出(記住一定要定義id屬性),這樣位址列url會在回撥完成後對應跳轉。
讓我們通過下面這個簡單的例子來get這個技巧:
app3.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = dbc.Container(
[
html.Div(id='redirect-url-container'),
dbc.Button('跳轉到頁面A', id='jump-to-pageA', style={'marginRight': '10px'}),
dbc.Button('跳轉到頁面B', id='jump-to-pageB'),
],
style={
'paddingTop': '100px'
}
)
@app.callback(
Output('redirect-url-container', 'children'),
[Input('jump-to-pageA', 'n_clicks'),
Input('jump-to-pageB', 'n_clicks')],
)
def jump_to_target(a_n_clicks, b_n_clicks):
ctx = dash.callback_context
if ctx.triggered[0]['prop_id'] == 'jump-to-pageA.n_clicks':
return dcc.Location(id='redirect-url', href='/pageA')
elif ctx.triggered[0]['prop_id'] == 'jump-to-pageB.n_clicks':
return dcc.Location(id='redirect-url', href='/pageB')
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=True)
2.3 用Link()實現“無縫”頁面切換
你應該注意到了,在Dash
中利用Location()
和普通的A()
部件實現跳轉時,頁面在跳轉後會整體重新整理,這會一定程度上破壞整個web應用的整體體驗。
而dash_core_components
中的Link()
部件則是很好的替代,它的基礎屬性與A()
無異,但額外的refresh
引數預設為False,會在點選後進行Dash
應用內跳轉時無縫切換,頁面不會整體重新整理:
app4.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
app.layout = dbc.Container(
[
dcc.Location(id='url'),
dcc.Link('頁面A', href='/pageA', refresh=True),
html.Br(),
dcc.Link('頁面B', href='/pageB'),
html.Hr(),
html.H1(id='render-page-content')
],
style={
'paddingTop': '100px'
}
)
@app.callback(
Output('render-page-content', 'children'),
Input('url', 'pathname')
)
def render_page_content(pathname):
if pathname == '/':
return '歡迎來到首頁'
elif pathname == '/pageA':
return '歡迎來到頁面A'
elif pathname == '/pageB':
return '歡迎來到頁面B'
elif pathname == '/pageC':
return '歡迎來到頁面C'
else:
return '當前頁面不存在!'
if __name__ == '__main__':
app.run_server(debug=True)
類似的功能還有dash_bootstrap_components
中的NavLink()
,用法與Link()
相似,這裡就不再贅述。
3 動手開發個人部落格網站
掌握了今天的知識之後,我們來用Dash
開發一個簡單的個人部落格網站,思路是在Location()
監聽url變化的前提下,後臺利用網路爬蟲從我的部落格園Dash
主題下爬取相應的網頁內容,並根據使用者訪問來渲染出對應的文章:
app5.py
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_dangerously_set_inner_html # 用於直接渲染html原始碼字串
from dash.dependencies import Input, Output
import re
from html import unescape
import requests
from lxml import etree
app = dash.Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div(
dbc.Spinner(
dbc.Container(
[
dcc.Location(id='url'),
html.Div(
id='page-content'
)
],
style={
'paddingTop': '30px',
'paddingBottom': '50px',
'borderRadius': '10px',
'boxShadow': 'rgb(0 0 0 / 20%) 0px 13px 30px, rgb(255 255 255 / 80%) 0px -13px 30px'
}
),
fullscreen=True
)
)
@app.callback(
Output('article-links', 'children'),
Input('url', 'pathname')
)
def render_article_links(pathname):
response = requests.get('https://www.cnblogs.com/feffery/tag/Dash/',
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36'
})
tree = etree.HTML(response.text)
posts = [
(href, title.strip())
for href, title in zip(
tree.xpath("//div[@class='postTitl2']/a/@href"),
tree.xpath("//div[@class='postTitl2']/a/span/text()")
)
]
return [
html.Li(
dcc.Link(title, href=f'/article-{href.split("/")[-1]}', target='_blank')
)
for href, title in posts
]
@app.callback(
Output('page-content', 'children'),
Input('url', 'pathname')
)
def render_article_content(pathname):
if pathname == '/':
return [
html.H2('部落格列表:'),
html.Div(
id='article-links',
style={
'width': '100%'
}
)
]
elif pathname.startswith('/article-'):
response = requests.get('https://www.cnblogs.com/feffery/p/%s.html' % re.findall('\d+', pathname)[0])
tree = etree.HTML(response.text)
return (
html.H3(tree.xpath("//title/text()")[0].split(' - ')[0]),
html.Em('作者:費弗裡'),
dash_dangerously_set_inner_html.DangerouslySetInnerHTML(
unescape(etree.tostring(tree.xpath('//div[@id="cnblogs_post_body"]')[0]).decode())
)
)
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=True)
按照類似的思路,你可以隨心所欲地開發自己的多頁面應用,進一步豐富完善你的Dash
應用功能。
以上就是本文的全部內容,歡迎在評論區發表你的意見和想法。