视频封面

视频元信息

  • 时长: 约3小时(包含休息时间)

内容摘要 (TL;DR)

本课是CS50本学期最后一讲,聚焦Web编程。课程从静态HTTP服务器转向Flask框架,讲解如何用Python动态生成HTML,处理用户输入(GET/POST),使用模板继承(Jinja2),引入数据库(SQLite),实现会话与Cookie,构建购物车和搜索API,最终整合HTML/CSS/JavaScript/Python/SQL构建完整Web应用。


1. 课程引入与回顾 [01:16]

  • 本课是CS50今年(2025)在演播室的最后一周。
  • 主题:Web编程,将Python与Flask框架结合,整合HTML、CSS、JavaScript以及前几周学过的所有概念。
  • 理解:本课目标是将所有积木组合起来,让学生能构建自己的项目,无论是本周的作业还是最终项目。

上一周回顾

  • 上次课介绍了HTTP(超文本传输协议),浏览器和web服务器通过HTTP通信,底层还有IP和TCP。
  • 上周用http-server命令启动静态文件服务器,它只能简单地从代码空间发送文件(.html、图片、JS、CSS)到浏览器。
  • 关键点:上周所有内容都是静态的——写文件、保存、刷新浏览器查看;只有JavaScript带来了一点客户端动态性(如自动补全)。

本周新方向

  • 转向服务端编程(server-side programming):用Python写代码,用函数、循环、条件语句来动态生成HTML。
  • 理解:对于有几十、几百、几千个网页的网站,不可能手动生成每个页面,必须通过编程动态生成。

2. URL结构与路径概念 [04:16]

  • 回顾URL结构:https://www.example.com/,斜杠/通常指默认网页(如index.html,或index.htm、default.aspx等)。
  • 可以指定具体文件:/file.html、/folder/、/folder/file.html,文件夹可嵌套。
  • 新术语:从今天起,/path泛指浏览器请求的所有文件和文件夹。
  • 重要概念:在Web编程中,这些路径对应的文件并不一定真实存在!路径可以代表一种“资源”(resource)。
  • 路由(route):指Web服务器上你选择的路径。
  • 理解:例如Google的/search?q=cat,我们上周只做了前端,今天可以实现后端(backend)——即程序员实现的部分;前端(frontend)是用户交互的部分。

3. HTTP请求与Flask框架介绍 [07:35]

  • 当浏览器通过表单提交请求时,HTTP“虚拟信封”里有这样的消息:
    • GET /search?q=cats HTTP/2
    • Host: ...
    • 其他头部信息
  • 手动写代码解析这些信封很麻烦,所以使用框架/库来简化。
  • Flask:Python中非常流行的微框架(micro framework),是一个相对小的库,解决了常见问题——读取信封、解析路径、提取键值对。

从http-server到flask run

  • 不再使用http-server(只能提供静态内容)。
  • 运行命令:flask run。
  • Flask既是库(可导入函数),又是命令(启动Web服务器)。
  • 理解:Flask更智能,它执行你的Python代码来动态生成网页,而不是直接输出文件内容。

4. 第一个Flask应用:Hello World [09:33]

  • 约定需要两个文件:
    1. app.py — Flask会查找并执行这个文件
    2. requirements.txt — 列出依赖库

代码解释

from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
    return "hello, world"
  • 第一行:从flask库导入Flask类(大写F表示类/对象)。
  • 第二行:__name__是当前文件名,告诉Flask将这个文件变成Web应用,变量名为app。
  • 装饰器(decorator):@app.route("/")是Python的装饰器语法,将函数包装为路由。
  • index函数返回字符串“hello, world”,当用户访问/时,Flask调用此函数并将返回值发送给浏览器。
  • 思考:这不是唯一实现Web应用的方式,但Flask标准化了路由和返回HTML的约定。学的是Web框架的代表,而非只学Flask本身。

requirements.txt

  • 内容:Flask(大写F)。
  • 安装命令:pip install -r requirements.txt。
  • 在CS50环境中已预装,但真实世界中需要此文件。

错误示范:用http-server运行Flask应用

  • 运行http-server后,浏览器点击app.py只会下载源码,而不是执行代码。
  • 易错点:不要用http-server运行Flask应用,否则会暴露源代码!

正确的运行方式

  • 按Ctrl+C停止http-server,运行flask run。
  • 默认端口:5000(不是http-server的8080,也不是80/443)。
  • 浏览器访问后显示“hello, world”。
  • 查看页面源码,只有纯文本,没有HTML标签。

5. 动态生成HTML与模板 [20:13]

  • 继续用Flask返回更复杂的HTML,但直接在Python字符串中写HTML很丑陋且易出错(引号冲突)。
  • 改进:将HTML放入独立的.html文件——模板(template)。

创建templates文件夹

  • 在项目文件夹中创建templates目录,放入index.html。
  • 在app.py中导入render_template函数。
from flask import Flask, render_template

@app.route("/")
def index():
    return render_template("index.html")
  • 注意:Flask默认在templates文件夹中查找模板。

6. 传递变量给模板:使用Jinja2 [29:00]

  • 目标:根据用户输入动态显示“hello, david”而不是固定文本。

读取URL参数

  • 在app.py中导入request对象。
  • 用request.args存储GET参数(类似Python字典)。
from flask import Flask, render_template, request

@app.route("/")
def index():
    name = request.args.get("name", "world")
    return render_template("index.html", name=name)
  • request.args.get("name", "world"):如果URL中有?name=david则取david,否则默认world。
  • 易错点:如果不设默认值,用户直接访问/会出现400 Bad Request错误。

模板中的占位符

  • 在index.html中使用双花括号语法:
hello, {{ name }}
  • Flask通过Jinja2模板引擎将name变量替换为实际值。
  • 理解:name=name看似重复,左边是模板中的占位符名称,右边是Python变量值。

7. 表单与POST方法 [35:41]

  • 用户不应手动编辑URL来输入数据,应使用Web表单。

创建表单

<form action="/greet" method="get">
    <input name="name" type="text" placeholder="Name" autocomplete="off" autofocus>
    <button type="submit">Greet</button>
</form>
  • autocomplete="off"关闭自动填充,autofocus让光标自动聚焦。
  • action="/greet"指定提交到/greet路由,method="get"使参数显示在URL中。

添加新路由

@app.route("/greet")
def greet():
    name = request.args.get("name", "world")
    return render_template("greet.html", name=name)
  • 创建greet.html模板。
  • 遇到404/500错误时,查看终端中Flask的日志定位问题。
  • 易错点:500 Internal Server Error = 你的代码有错误,终端日志会提示。 例如模板不存在、语法错误等。

GET vs POST

  • GET:参数在URL中可见,适合搜索等非敏感操作。
  • POST:数据放在虚拟信封更深处,不出现在URL,适合密码、信用卡等隐私数据。
  • 理解:POST避免数据出现在历史记录、自动补全中,更安全。

切换为POST

  • 表单method改为post。
  • 路由需指定支持POST:@app.route("/greet", methods=["POST"])。
  • 读取POST参数:request.form.get("name"),不是request.args!
  • 易错点:GET参数在request.args,POST参数在request.form。

8. 单一路由处理GET和POST [54:00]

  • 将代码合并到一条路由:
@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        name = request.form.get("name")
        return render_template("greet.html", name=name)
    else:
        return render_template("index.html")
  • 用request.method判断请求类型。
  • 表单不指定action时默认提交到当前URL(/)。
  • 思考:这样减少函数数量,使代码更紧凑,但并非唯一方式。

9. Jinja2模板继承(Layout) [01:00:00]

  • 问题:多个模板有大量重复HTML(<!DOCTYPE html>, <head>, <title>, <body>等)。
  • 解决方案:创建layout.html作为基模板。

layout.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>hello</title>
        <meta name="viewport" content="initial-scale=1, width=device-width">
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>
  • {% block body %}定义了一个可被子模板填充的区块。
  • meta viewport标签:让网页在移动设备上响应式显示。

子模板

{% extends "layout.html" %}
{% block body %}
    <!-- 每页不同的内容 -->
{% endblock %}
  • 要点:extends和block语法是Jinja2的核心,避免重复编写公共HTML。

10. 客户端验证 vs 服务端验证 [01:08:00]

  • 在表单中添加required属性可提供浏览器端验证。
  • 重要提醒:客户端验证可以被轻易绕过(用开发者工具删除required属性、修改选项值)。
  • 理解:恶意用户可以修改HTML选项,提交任何数据。所以必须进行服务端验证。

服务端验证示例(项目:Frosh IMs)

  • 用全局列表定义有效运动:
SPORTS = ["basketball", "soccer", "ultimate frisbee"]
  • 在模板中用Jinja2 for循环动态生成选项:
<select name="sport">
    <option disabled selected>Sport</option>
    {% for sport in sports %}
        <option value="{{ sport }}">{{ sport }}</option>
    {% endfor %}
</select>
  • 在路由中检查if not request.form.get("name") or request.form.get("sport") not in SPORTS:,若失败返回错误页面。
  • 易错点/考点:永远不要信任用户输入,永远要有服务端验证。

11. 数据库集成:SQLite [1:15:00]

  • 用全局字典存注册数据的问题:服务器重启后数据丢失。
  • 改用SQLite数据库(froshims.db)。
  • 用CS50库的SQL功能:
from cs50 import SQL
db = SQL("sqlite:///froshims.db")
  • 插入数据:
db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport)
  • 查询数据:
registrants = db.execute("SELECT name, sport FROM registrants")
  • 模板中迭代:
{% for registrant in registrants %}
    <tr><td>{{ registrant.name }}</td><td>{{ registrant.sport }}</td></tr>
{% endfor %}
  • 理解:SQLite中每行作为一个字典返回,可以通过点号或方括号访问列。

12. MVC架构模式 [1:43:00]

  • 本课应用代表了Web编程中的模型-视图-控制器(MVC)架构:
    • Controller(控制器):app.py,控制应用逻辑(路由、业务逻辑)。
    • View(视图):templates/中的模板,用户界面。
    • Model(模型):数据库或存储层,负责读写数据。
  • 理解:MVC是分离关注点的设计模式,帮助组织代码,不是必须遵守的规则。

  • HTTP是无状态的,每次请求都是独立的。
  • 需要记住用户是否已登录,类似游乐园在手上盖章(hand stamp)。
  • Cookie:服务器在HTTP响应头中发送Set-Cookie: session=<random_value>,浏览器存储并随后在每次请求中发送Cookie: session=<random_value>。
  • Session(会话):服务器通过Cookie维护的与特定浏览器的状态。
  • 理解:Cookie通常是大随机值,不是用户名/密码,避免信息泄露。广告商可用Cookie跟踪用户行为,私人浏览模式可清空Cookie。

Flask中的Session实现

from flask import Flask, render_template, request, session, redirect
from flask_session import Session

app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)
  • 登录逻辑:
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        session["name"] = request.form.get("name")
        return redirect("/")
    return render_template("login.html")
  • 显示当前用户:name = session.get("name")
  • 登出:session.clear(),然后重定向到/。
  • 要点:session是一个像全局字典一样的“每个用户的变量”,底层Flask处理所有Cookie细节。

14. 购物车示例 [2:02:00]

  • 使用Session存储购物车(列表)。
  • 数据库:store.db,有books表(id, title)。
  • 模板中为每本书创建表单,提交隐藏的id字段,而不是标题(避免重名问题,使用主键)。
{% for book in books %}
    <h2>{{ book.title }}</h2>
    <form action="/cart" method="post">
        <input name="id" type="hidden" value="{{ book.id }}">
        <button type="submit">Add to Cart</button>
    </form>
{% endfor %}
  • cart路由:
@app.route("/cart", methods=["GET", "POST"])
def cart():
    if request.method == "POST":
        book_id = request.form.get("id")
        if "cart" not in session:
            session["cart"] = []
        session["cart"].append(book_id)
        return redirect("/cart")
    else:
        books = db.execute("SELECT * FROM books WHERE id IN (?)", session.get("cart", []))
        return render_template("cart.html", books=books)
  • 注意:session["cart"]是列表,首次使用时需要初始化。
  • 文件系统session会生成flask_session文件夹中的文件,保存会话数据。

15. API与JSON [2:20:00]

  • 应用场景:搜索电视节目数据库(IMDB)。
  • 传统方法:表单提交,服务器返回完整HTML页面。
  • 改进:用JavaScript fetch API异步请求数据,无需刷新页面。

服务器端返回JSON

  • 导入jsonify:
from flask import jsonify
  • 修改搜索路由,返回JSON而不是HTML模板:
@app.route("/search")
def search():
    query = request.args.get("q")
    if query:
        shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", f"%{query}%")
    else:
        shows = []
    return jsonify(shows)
  • 理解:JSON是JavaScript对象表示法,与Python字典格式相似,是API交换数据的标准格式。

客户端JavaScript

let input = document.querySelector('input');
input.addEventListener('input', async function() {
    let response = await fetch(`/search?q=${input.value}`);
    let shows = await response.json();
    let html = '';
    for (let show of shows) {
        html += `<li>${show.title}</li>`;
    }
    document.querySelector('ul').innerHTML = html;
});
  • 使用async/await处理异步HTTP请求。
  • 易错点:从fetch返回的数据应调用.json()解析,否则无法作为对象使用。
  • 思考:这是API的本质——返回原始数据(JSON),而不是HTML片段。客户端负责渲染。

16. 总结与展望 [2:40:00]

  • 已综合运用:Python、Flask、HTML、CSS、JavaScript、SQL、Jinja模板、会话、API。
  • 能够构建完整的前后端Web应用,可用于最终项目。
  • CS50课程基本结束,还有最后一次课堂总结。

关键标签/关键词

Flask, Web编程, Python, HTML, CSS, JavaScript, Jinja2, 模板继承, HTTP, GET, POST, 路由, 服务端验证, 客户端验证, SQLite, SQL, MVC, 会话, Cookie, Session, 购物车, API, JSON, jsonify, 异步请求, fetch, CS50

核心考点/易错点总结:

  1. GET参数用request.args,POST参数用request.form。
  2. 用@app.route(..., methods=["GET", "POST"])明确支持方法。
  3. 永远进行服务端验证,不要只依赖客户端required。
  4. 模板继承用{% extends %}和{% block %}。
  5. Session可实现“每用户”状态,存储数据在服务器端,Cookie只在浏览器端存ID。
  6. 数据库操作使用参数化查询(?占位符)防止SQL注入。
  7. API应返回JSON(用jsonify),而非HTML片段。

生成时间:2026-09-11 15:44:16 生成平台:VideoNote www.xygy.top