本文共 6191 字,大约阅读时间需要 20 分钟。
本文是“Python + Dash 快速开发多页面Web应用”教程系列的第16篇。在前面的教程中,我们通过搭建单页面的Dash应用,掌握了如何快速开发功能丰富的Web应用。然而随着应用的功能日趋完善,单页面的内容组织方式逐渐无法满足复杂的需求,也不利于构建逻辑清晰的Web应用。
因此,我们需要在Dash应用中引入路由功能。通过在主域名下根据不同的URL渲染不同内容,就像大多数现代网站一样实现多页面应用。
本文将教你如何在Dash中编写多页面应用并进行路由控制。
要在Dash中实现URL路由,首先需要捕获浏览器地址栏中的URL信息。Dash中可以通过构建可以持续监听当前应用URL信息的组件来实现。
我们使用dash_core_components中的Location()组件。其核心属性包括href、pathname、search和hash,可以记录地址栏中的相应信息。
以下示例展示了如何通过Location()组件捕获不同URL参数:
app1.pyimport dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputapp = dash.Dash(__name__)app.layout = dcc.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) 除了捕获URL信息,Location()组件还可以用于实现页面重定向。通过将Location()作为回调函数的输出(记得设置id属性),地址栏URL会在回调完成后跳转。
以下示例展示了如何通过Location()实现重定向:
app3.pyimport dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputapp = dash.Dash(__name__)app.layout = html.Div([ 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_updateif __name__ == '__main__': app.run_server(debug=True) 除了Location()组件,dash_core_components中的Link()组件提供了更好的替代方案。Link()组件的refresh参数默认为False,实现了无缝页面切换。
以下示例展示了如何使用Link()组件实现无缝切换:
app4.pyimport dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputapp = 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开发一个简单的个人博客网站。思路是在Location监听URL变化的前提下,后台利用网络爬虫从博客园Dash主题下爬取相应网页内容,并根据用户访问渲染对应文章。
以下是一个实现示例:
app5.pyimport dashimport dash_core_components as dccimport dash_html_components as htmlimport dash_bootstrap_components as dbcimport dash_dangerously_set_inner_htmlfrom dash.dependencies import Input, Outputimport reimport requestsfrom lxml import etreeapp = 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( f'https://www.cnblogs.com/feffery/p/{re.findall("\d+", pathname)[0]}.html', 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) 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], method='text' ).decode()) ) ] return dash.no_updateif __name__ == '__main__': app.run_server(debug=True) 按照类似的方法,你可以根据需求开发自己的多页面应用,并进一步丰富功能。