【Python】100行Python代碼輕松開發(fā)個(gè)人博客
?本文示例代碼已上傳至我的
?Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes
1 簡(jiǎn)介
這是我的系列教程「Python+Dash快速web應(yīng)用開發(fā)」的第十六期,在過往所有的教程及案例中,我們所搭建的Dash應(yīng)用的訪問地址都是單一的,是個(gè)「單頁面」應(yīng)用,即我們所有的功能都排布在同一個(gè)url之下。
而隨著我們所編寫的Dash應(yīng)用功能的日趨健全和復(fù)雜,單一url的內(nèi)容組織方式無法再很好的滿足需求,也不利于構(gòu)建邏輯清晰的web應(yīng)用。
因此我們需要在Dash應(yīng)用中引入「路由」的相關(guān)功能,即在當(dāng)前應(yīng)用主域名下,根據(jù)不同的url來渲染出具有不同內(nèi)容的頁面,就像我們?nèi)粘J褂玫慕^大多數(shù)網(wǎng)站那樣。
而今天的教程,我們就將一起學(xué)習(xí)在Dash中編寫多url應(yīng)用并進(jìn)行路由控制的常用方法。

2 編寫多頁面Dash應(yīng)用
2.1 Location()的基礎(chǔ)使用
要想在Dash中實(shí)現(xiàn)url路由功能,首先我們需要捕獲到瀏覽器中地址欄對(duì)應(yīng)的url是什么,這在Dash中可以通過在app.layout中構(gòu)建一個(gè)可以持續(xù)監(jiān)聽當(dāng)前Dash應(yīng)用url信息的部件來實(shí)現(xiàn)。
我們使用官方依賴庫dash_core_components中的Location()部件來實(shí)現(xiàn)上述功能,它的核心參數(shù)或?qū)傩杂?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;overflow-wrap: break-word;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">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'當(dāng)前href為:{href}'),
html.Li(f'當(dāng)前pathname為:{pathname}'),
html.Li(f'當(dāng)前search為:{search}'),
html.Li(f'當(dāng)前hash為:{hash}'),
)
if __name__ == '__main__':
app.run_server(debug=True)

因此在Dash中編寫多url應(yīng)用的核心策略是利用埋點(diǎn)Location()捕獲到地址欄對(duì)應(yīng)信息的變化,并以這些信息作為回調(diào)函數(shù)的輸入,來輸出相應(yīng)的頁面內(nèi)容變化,讓我們從下面這個(gè)簡(jiǎn)單的例子中g(shù)et上述這一套流程的運(yùn)作方式:
?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 '當(dāng)前頁面不存在!'
if __name__ == '__main__':
app.run_server(debug=True)

2.2 利用Location()實(shí)現(xiàn)頁面重定向
在上一小節(jié)我們對(duì)dcc.Location()的基礎(chǔ)用法進(jìn)行了介紹,而它的功能可不止監(jiān)聽url變化這么簡(jiǎn)單,我們還可以利用它在Dash中實(shí)現(xiàn)「重定向」,使用方式簡(jiǎn)單一句話描述就是將Location()作為對(duì)應(yīng)回調(diào)的輸出(記住一定要定義id屬性),這樣地址欄url會(huì)在回調(diào)完成后對(duì)應(yīng)跳轉(zhuǎn)。
讓我們通過下面這個(gè)簡(jiǎn)單的例子來get這個(gè)技巧:
?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('跳轉(zhuǎn)到頁面A', id='jump-to-pageA', style={'marginRight': '10px'}),
dbc.Button('跳轉(zhuǎn)到頁面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()實(shí)現(xiàn)“無縫”頁面切換
你應(yīng)該注意到了,在Dash中利用Location()和普通的A()部件實(shí)現(xiàn)跳轉(zhuǎn)時(shí),頁面在跳轉(zhuǎn)后會(huì)整體刷新,這會(huì)一定程度上破壞整個(gè)web應(yīng)用的整體體驗(yàn)。
而dash_core_components中的Link()部件則是很好的替代,它的基礎(chǔ)屬性與A()無異,但額外的refresh參數(shù)默認(rèn)為False,會(huì)在點(diǎn)擊后進(jìn)行Dash應(yīng)用內(nèi)跳轉(zhuǎn)時(shí)無縫切換,頁面不會(huì)整體刷新:
?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 '當(dāng)前頁面不存在!'
if __name__ == '__main__':
app.run_server(debug=True)

類似的功能還有dash_bootstrap_components中的NavLink(),用法與Link()相似,這里就不再贅述。
3 動(dòng)手開發(fā)個(gè)人博客網(wǎng)站
掌握了今天的知識(shí)之后,我們來用Dash開發(fā)一個(gè)簡(jiǎn)單的個(gè)人博客網(wǎng)站,思路是在Location()監(jiān)聽url變化的前提下,后臺(tái)利用網(wǎng)絡(luò)爬蟲從我的博客園Dash主題下爬取相應(yīng)的網(wǎng)頁內(nèi)容,并根據(jù)用戶訪問來渲染出對(duì)應(yīng)的文章:
?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("http://div[@class='postTitl2']/a/@href"),
tree.xpath("http://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("http://title/text()")[0].split(' - ')[0]),
html.Em('作者:費(fèi)弗里'),
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)

按照類似的思路,你可以隨心所欲地開發(fā)自己的多頁面應(yīng)用,進(jìn)一步豐富完善你的Dash應(yīng)用功能。
以上就是本文的全部?jī)?nèi)容,歡迎在評(píng)論區(qū)發(fā)表你的意見和想法。
往期精彩回顧
本站qq群851320808,加入微信群請(qǐng)掃碼:
