100行Python代码轻松开发个人博客

简介: 100行Python代码轻松开发个人博客

1 简介

这是我的系列教程「Python+Dash快速web应用开发」的第十六期,在过往所有的教程及案例中,我们所搭建的Dash应用的访问地址都是单一的,是个「单页面」应用,即我们所有的功能都排布在同一个url之下。

而随着我们所编写的Dash应用功能的日趋健全和复杂,单一url的内容组织方式无法再很好的满足需求,也不利于构建逻辑清晰的web应用。

因此我们需要在Dash应用中引入「路由」的相关功能,即在当前应用主域名下,根据不同的url来渲染出具有不同内容的页面,就像我们日常使用的绝大多数网站那样。

而今天的教程,我们就将一起学习在Dash中编写多url应用并进行路由控制的常用方法。

图1

2 编写多页面Dash应用

2.1 Location()的基础使用

要想在Dash中实现url路由功能,首先我们需要捕获到浏览器中地址栏对应的url是什么,这在Dash中可以通过在app.layout中构建一个可以持续监听当前Dash应用url信息的部件来实现。

我们使用官方依赖库dash_core_components中的Location()部件来实现上述功能,它的核心参数或属性有hrefpathnamesearchhash,让我们通过下面的例子来直观的了解它们各自记录了地址栏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)

图2

因此在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)

图3

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)

图4

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)

图5

类似的功能还有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)

图6

相关文章
|
8天前
|
存储 数据库连接 API
Python环境变量在开发和运行Python应用程序时起着重要的作用
Python环境变量在开发和运行Python应用程序时起着重要的作用
48 15
|
15天前
|
JSON 安全 API
如何使用Python开发API接口?
在现代软件开发中,API(应用程序编程接口)用于不同软件组件之间的通信和数据交换,实现系统互操作性。Python因其简单易用和强大功能,成为开发API的热门选择。本文详细介绍了Python开发API的基础知识、优势、实现方式(如Flask和Django框架)、实战示例及注意事项,帮助读者掌握高效、安全的API开发技巧。
41 3
如何使用Python开发API接口?
|
9天前
|
缓存 监控 测试技术
Python中的装饰器:功能扩展与代码复用的利器###
本文深入探讨了Python中装饰器的概念、实现机制及其在实际开发中的应用价值。通过生动的实例和详尽的解释,文章展示了装饰器如何增强函数功能、提升代码可读性和维护性,并鼓励读者在项目中灵活运用这一强大的语言特性。 ###
|
12天前
|
缓存 开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第35天】装饰器在Python中是一种强大的工具,它允许开发者在不修改原有函数代码的情况下增加额外的功能。本文旨在通过简明的语言和实际的编码示例,带领读者理解装饰器的概念、用法及其在实际编程场景中的应用,从而提升代码的可读性和复用性。
|
8天前
|
JSON API 数据格式
如何使用Python开发1688商品详情API接口?
本文介绍了如何使用Python开发1688商品详情API接口,获取商品的标题、价格、销量和评价等详细信息。主要内容包括注册1688开放平台账号、安装必要Python模块、了解API接口、生成签名、编写Python代码、解析返回数据以及错误处理和日志记录。通过这些步骤,开发者可以轻松地集成1688商品数据到自己的应用中。
24 1
|
8天前
|
Python
探索Python中的装饰器:简化代码,提升效率
【10月更文挑战第39天】在编程的世界中,我们总是在寻找使代码更简洁、更高效的方法。Python的装饰器提供了一种强大的工具,能够让我们做到这一点。本文将深入探讨装饰器的基本概念,展示如何通过它们来增强函数的功能,同时保持代码的整洁性。我们将从基础开始,逐步深入到装饰器的高级用法,让你了解如何利用这一特性来优化你的Python代码。准备好让你的代码变得更加优雅和强大了吗?让我们开始吧!
16 1
|
13天前
|
设计模式 缓存 监控
Python中的装饰器:代码的魔法增强剂
在Python编程中,装饰器是一种强大而灵活的工具,它允许程序员在不修改函数或方法源代码的情况下增加额外的功能。本文将探讨装饰器的定义、工作原理以及如何通过自定义和标准库中的装饰器来优化代码结构和提高开发效率。通过实例演示,我们将深入了解装饰器的应用,包括日志记录、性能测量、事务处理等常见场景。此外,我们还将讨论装饰器的高级用法,如带参数的装饰器和类装饰器,为读者提供全面的装饰器使用指南。
|
9天前
|
存储 缓存 监控
掌握Python装饰器:提升代码复用性与可读性的利器
在本文中,我们将深入探讨Python装饰器的概念、工作原理以及如何有效地应用它们来增强代码的可读性和复用性。不同于传统的函数调用,装饰器提供了一种优雅的方式来修改或扩展函数的行为,而无需直接修改原始函数代码。通过实际示例和应用场景分析,本文旨在帮助读者理解装饰器的实用性,并鼓励在日常编程实践中灵活运用这一强大特性。
|
13天前
|
存储 算法 搜索推荐
Python高手必备!揭秘图(Graph)的N种风骚表示法,让你的代码瞬间高大上
在Python中,图作为重要的数据结构,广泛应用于社交网络分析、路径查找等领域。本文介绍四种图的表示方法:邻接矩阵、邻接表、边列表和邻接集。每种方法都有其特点和适用场景,掌握它们能提升代码效率和可读性,让你在项目中脱颖而出。
28 5
|
11天前
|
机器学习/深度学习 数据采集 人工智能
探索机器学习:从理论到Python代码实践
【10月更文挑战第36天】本文将深入浅出地介绍机器学习的基本概念、主要算法及其在Python中的实现。我们将通过实际案例,展示如何使用scikit-learn库进行数据预处理、模型选择和参数调优。无论你是初学者还是有一定基础的开发者,都能从中获得启发和实践指导。
27 2
下一篇
无影云桌面