EDUCBA-Python-Web-开发和部署笔记-全-

EDUCBA Python Web 开发和部署笔记(全)

001:使用 wxPython 创建图形界面

概述

在本节课中,我们将学习如何使用 Python 创建图形用户界面程序。我们将从命令行程序转向窗口化程序,学习如何创建带有按钮、文本框等元素的窗口。我们将使用 wxPython 这个 GUI 工具包来完成这个任务,并最终创建一个简易的文本编辑器。

GUI 工具包简介

上一节我们介绍了本课程的目标。本节中,我们来看看什么是 GUI 工具包。

Python 有多种图形用户界面工具包,但没有一个被官方认定为标准工具包。这既有优点也有缺点。

以下是几种常见的 Python GUI 工具包:

  • wxPython:基于 wxWidgets,跨平台,功能丰富。
  • Tkinter:使用 Tk 平台,是 Python 的标准库之一,但功能相对基础。
  • PyQt/PySide:基于 Qt 框架,功能强大,在 Linux 上较流行。
  • PythonWin:仅适用于 Windows 平台。

在本教程中,我们将使用 wxPython。它基于 Windows 风格,是一个标准化的平台,并且非常流行。需要注意的是,wxPython 目前不支持 Python 3.0 以上的版本,因此我们将使用 Python 2.7.9 进行教学。了解 Python 2.7 仍然很重要,因为许多地方仍在使用它。

下载与安装 wxPython

上一节我们选择了 wxPython 作为工具包。本节中,我们来学习如何下载和安装它。

要编写 GUI 程序,首先需要决定使用哪个 GUI 平台。简单来说,平台就是通过特定 Python 模块(如 GUI 工具包)可以访问的一组图形组件。

由于 wxPython 不支持 Python 3.0 以上版本,我们将使用 Python 2.7.9。请根据你的操作系统和 Python 版本下载对应的 wxPython 安装包。

以下是下载步骤:

  1. 在浏览器中搜索 “wxPython”。
  2. 访问 wxPython 官方网站。
  3. 根据你的操作系统(Windows、Mac、Linux)选择下载链接。
  4. 对于 Windows 用户,请确保下载与你的 Python 版本(例如 Python 2.7.9)和系统位数(32位或64位)匹配的安装包。
  5. 下载完成后,双击安装文件,按照提示完成安装。

对于 Linux 用户,如果使用带有包管理器(如 apt-getyum)的发行版,通常可以直接通过命令行安装,系统会自动选择适合的版本。

安装完成后,你可以在 Python 交互式环境中输入 import wx 来测试是否安装成功。

项目规划:简易文本编辑器

在开始编码之前,规划程序界面和功能很有帮助。我们将创建一个简易的文本编辑器。

为了便于学习,本章将使用一个贯穿始终的示例。我们的任务是创建一个能编辑文本文件的基本程序。编写一个功能完整的文本编辑器超出了本章范围,但我们会涵盖核心机制。

这个简易文本编辑器需要满足以下基本要求:

  • 能够打开系统中已存在的文件。
  • 允许用户编辑文件内容。
  • 能够将编辑后的内容保存到文件。
  • 提供程序退出的方式。

在编写 GUI 程序时,先画一个界面草图很有用。我们的编辑器布局将类似一个简单的命令行工具,但带有图形界面。

界面元素规划如下:

  • 一个用于输入文件名的文本框。
  • “打开” 和 “保存” 两个按钮。
  • 一个大的多行文本框,用于显示和编辑文件内容。
  • 用户可以在左侧文本框输入文件名,点击“打开”按钮来打开文件,文件内容会显示在下方的文本框中供编辑。编辑后可以点击“保存”。我们暂时不专门做“退出”按钮,关闭窗口即可退出程序。

第一个 wxPython 程序

现在,让我们开始编写程序。首先从导入模块和创建应用对象开始。

要开始创建我们的程序,首先需要导入 wx 模块。我将创建一个新文件并保存为 simple_text_editor.py

一个 wxPython 程序无法绕过的一步是创建应用程序对象。基础的应用程序类叫做 wx.App,它在后台处理各种初始化工作。

最简单的 wxPython 程序如下所示:

import wx
app = wx.App()
app.MainLoop()

保存并运行这段代码,你会发现程序立即结束,因为没有任何窗口可供用户交互。

从这个例子可以看出,wx 包中的方法名是大写的,这与 Python 常见的命名规范不同。原因是这些方法名映射了底层 C++ 包 wxWidgets 中的方法名。

创建窗口和组件

窗口(也称为框架)是 wx.Frame 类的实例。现在让我们来创建一个窗口。

在 wx 框架中,组件(Widgets)在创建时,其构造函数的第一个参数通常是它的父组件。对于独立的顶级窗口,则没有父组件,可以使用 None

以下是创建并显示一个基本窗口的代码:

import wx
app = wx.App()
win = wx.Frame(None) # 创建一个没有父窗口的框架
win.Show() # 显示窗口
app.MainLoop() # 启动主事件循环

注意:必须在调用 app.MainLoop() 之前调用窗口的 Show() 方法,否则窗口会保持隐藏状态。运行这段代码,你将看到一个空白的窗口。

添加按钮并设置属性

现在,让我们为这个窗口添加一个按钮,并设置一些基本属性如标签、位置和大小。

为了向框架添加按钮,我们创建 wx.Button 的实例。仅仅创建按钮还不够,我们需要设置标签、位置和大小,否则多个组件会重叠在一起。

我们可以通过构造函数的 label 参数设置按钮文本,通过 pos 参数设置位置,通过 size 参数设置大小。同样,可以使用 title 参数设置窗口标题。使用关键字参数可以让代码更清晰。

以下是为窗口添加两个按钮并设置属性的示例代码:

import wx
app = wx.App()
# 创建窗口,设置标题和初始大小
win = wx.Frame(None, title="简易文本编辑器", size=(410, 335))
# 创建“打开文件”按钮,并设置位置和大小
loadButton = wx.Button(win, label=‘打开文件’, pos=(225, 5), size=(80, 25))
# 创建“保存文件”按钮,并设置位置和大小
saveButton = wx.Button(win, label=‘保存文件’, pos=(315, 5), size=(80, 25))
win.Show()
app.MainLoop()

运行这段代码,你将看到一个带有标题和两个按钮的窗口。

添加文本框

最后,我们来添加用于输入文件名和编辑文件内容的文本框。

除了按钮,我们还需要文本输入框。wx.TextCtrl 类用于创建文本框。对于编辑文件内容的大文本框,我们还需要设置多行和滚动条的样式。

以下是添加文件名输入框和内容编辑框的代码:

import wx
app = wx.App()
win = wx.Frame(None, title="简易文本编辑器", size=(410, 335))
loadButton = wx.Button(win, label=‘打开文件’, pos=(225, 5), size=(80, 25))
saveButton = wx.Button(win, label=‘保存文件’, pos=(315, 5), size=(80, 25))
# 创建文件名输入框
filename = wx.TextCtrl(win, pos=(5, 5), size=(210, 25))
# 创建内容编辑框,设置为多行并带垂直滚动条
contents = wx.TextCtrl(win, pos=(5, 35), size=(390, 260), style=wx.TE_MULTILINE | wx.HSCROLL)
win.Show()
app.MainLoop()

代码中 wx.TE_MULTILINE 样式允许文本框输入多行文本,wx.HSCROLL 样式为其添加水平滚动条。运行程序,你将看到简易文本编辑器的完整界面。

总结

本节课中,我们一起学习了使用 wxPython 进行 GUI 编程的基础知识。我们从介绍各种 GUI 工具包开始,选择了 wxPython 并完成了其安装。接着,我们规划了一个简易文本编辑器项目,并逐步实现了它:首先创建了应用程序对象和主窗口,然后添加了按钮并设置了它们的属性,最后添加了用于交互的文本框。现在,我们已经拥有了一个具有基本图形界面的程序框架。在下一节课中,我们将为这些按钮添加实际的功能,让我们的编辑器能够真正地打开和保存文件。

002:增强文本编辑器

在本节课中,我们将学习如何改进之前创建的简单文本编辑器,使其界面更美观、布局更灵活,并为其添加事件处理功能,使其成为一个真正可用的图形用户界面应用程序。

概述

上一节我们创建了一个基础的文本编辑器,它具备了打开和保存文件的核心功能。本节中,我们将重点优化其用户界面,使用更智能的布局管理器替代硬编码的坐标,并深入讲解如何为按钮绑定事件处理函数,让程序响应用户的操作。

优化界面布局

在之前的程序中,我们通过指定每个组件的绝对坐标(position)和尺寸(size)来排列它们。这种方法虽然直观,但存在明显缺点:当窗口大小改变时,内部组件的位置和大小不会随之调整,导致界面看起来不协调。

引入布局管理器:WxPython BoxSizer

为了解决上述问题,WxPython 提供了布局管理器,其中最常用的是 wx.BoxSizer。布局管理器负责自动计算和安排组件的位置与大小。

以下是使用 wx.BoxSizer 重构布局的核心步骤:

  1. 创建一个容器(如 wx.Panel)作为所有组件的父级背景。
  2. 创建 wx.BoxSizer 对象,可以指定为水平(wx.HORIZONTAL)或垂直(wx.VERTICAL)排列。
  3. 使用 Add() 方法将组件添加到 Sizer 中,并可以设置比例、对齐方式和边框等参数。
  4. 最后,通过容器的 SetSizer() 方法应用这个 Sizer。

以下是一个使用 BoxSizer 的代码示例:

import wx

app = wx.App()
win = wx.Frame(None, title="文本编辑器", size=(400, 300))

# 1. 创建面板作为容器
bkg = wx.Panel(win)

# 2. 创建组件,但不指定绝对坐标
loadButton = wx.Button(bkg, label='打开文件')
saveButton = wx.Button(bkg, label='保存文件')
filename = wx.TextCtrl(bkg)
contents = wx.TextCtrl(bkg, style=wx.TE_MULTILINE | wx.HSCROLL)

# 3. 创建水平 BoxSizer 来放置文件名输入框和按钮
hbox = wx.BoxSizer()
hbox.Add(filename, proportion=1, flag=wx.EXPAND)
hbox.Add(loadButton, proportion=0, flag=wx.LEFT, border=5)
hbox.Add(saveButton, proportion=0, flag=wx.LEFT, border=5)

# 4. 创建垂直 BoxSizer 作为主布局
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(hbox, proportion=0, flag=wx.EXPAND | wx.ALL, border=5)
vbox.Add(contents, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, border=5)

# 5. 将主 Sizer 设置给面板
bkg.SetSizer(vbox)

win.Show()
app.MainLoop()

代码关键参数解释:

  • proportion:定义组件在剩余空间中的分配比例。proportion=1 的组件会比 proportion=0 的获得更多扩展空间。
  • flag:用于控制对齐和边框等行为,常用值如 wx.EXPAND(组件填充分配的空间)、wx.LEFT(边框应用于左侧)。
  • border:设置组件周围的边框宽度(像素)。

使用 Sizer 后,当用户调整窗口大小时,filename 输入框会水平拉伸,而 contents 文本区域会同时向水平和垂直方向拉伸,按钮则保持原大小,界面变得灵活而美观。

添加事件处理

一个只有界面而不能交互的程序是没用的。在 GUI 编程中,用户的操作(如点击按钮)被称为事件。我们需要让程序能够“感知”并“响应”这些事件。

事件绑定机制

在 WxPython 中,我们通过 Bind() 方法将某个事件(如按钮点击)与一个事件处理函数关联起来。当事件发生时,对应的函数就会被自动调用。

以下是事件绑定的语法:

widget.Bind(event, handler_function)
  • widget:发生事件的组件,例如一个按钮。
  • event:要监听的事件类型,例如 wx.EVT_BUTTON 表示按钮点击事件。
  • handler_function:当事件发生时要执行的函数。

实现文件打开与保存功能

现在,我们为“打开文件”和“保存文件”按钮创建具体的事件处理函数。

打开文件函数 (load) 的逻辑:

  1. 获取文件名输入框中的文本。
  2. 以读取模式打开该文件。
  3. 将文件内容读入,并设置到主文本区域中。
  4. 关闭文件。

保存文件函数 (save) 的逻辑:

  1. 获取文件名输入框中的文本。
  2. 以写入模式打开该文件。
  3. 将主文本区域中的内容写入文件。
  4. 关闭文件。

以下是这两个函数的实现代码:

def load(event):
    """打开文件事件处理函数"""
    file = open(filename.GetValue(), 'r')  # 获取文件名并打开
    contents.SetValue(file.read())         # 读取内容并显示
    file.close()                           # 关闭文件

def save(event):
    """保存文件事件处理函数"""
    file = open(filename.GetValue(), 'w')  # 获取文件名并打开(写入模式)
    file.write(contents.GetValue())        # 将文本区域内容写入文件
    file.close()                           # 关闭文件

将函数绑定到按钮

创建好处理函数后,需要在程序初始化部分将它们绑定到对应的按钮上:

# 将 load 函数绑定到“打开文件”按钮的点击事件
loadButton.Bind(wx.EVT_BUTTON, load)
# 将 save 函数绑定到“保存文件”按钮的点击事件
saveButton.Bind(wx.EVT_BUTTON, save)

完成以上步骤后,一个功能完整、布局灵活的文本编辑器就创建好了。用户可以输入文件名,编辑文本内容,并通过按钮进行文件的打开和保存操作。

打包为独立应用

默认情况下,双击 .py 文件运行程序会同时打开一个命令行窗口。如果你希望程序像普通桌面应用一样,只显示图形界面,可以将文件后缀从 .py 改为 .pyw。在 Windows 系统中,.pyw 文件会使用 pythonw.exe 来运行,从而不显示控制台窗口。

总结

本节课中我们一起学习了如何增强一个基础的文本编辑器。我们首先引入了 wx.BoxSizer 布局管理器来替代硬编码坐标,使得界面能够自适应窗口大小的变化。接着,我们深入讲解了 GUI 编程的核心概念——事件处理,学习了如何使用 Bind() 方法将用户操作(事件)与具体的处理函数关联起来,并实现了文件的打开和保存功能。最后,我们还了解了如何将 Python 脚本打包成不显示命令行窗口的独立应用(.pyw)。通过这些步骤,我们成功将一个简单的演示程序转变为一个实用、美观的桌面应用程序。

003:联网模块

概述

在本章节中,我们将学习Python如何帮助编写使用网络(如互联网)作为重要组件的程序。Python是网络编程的强大工具,也称为套接字编程。Python标准库提供了许多用于常见网络协议和不同抽象层次的预置库,使我们能够专注于程序逻辑,而非处理底层的比特流传输。由于Python擅长处理字节流中的各种模式,并且拥有丰富的网络工具,我们只能简要介绍其网络功能。本章将概述Python标准库中的一些网络模块,讨论SocketServer类及其相关功能,并简要探讨同时处理多个连接的各种方法。

网络编程基础

上一节我们介绍了Python网络编程的概览,本节中我们来看看网络编程的基本组件——套接字。

套接字是网络编程的基本组件。套接字是一个两端都有程序的信息通道。程序可能位于不同的计算机上,并通过套接字相互发送信息。Python中的大多数网络编程都隐藏了socket模块的基本工作原理,不直接与套接字交互。

套接字有两种类型:服务器套接字客户端套接字。创建服务器套接字后,需要告诉它等待连接。它将在某个网络地址(IP地址和端口号的组合)监听,直到客户端套接字连接,然后两者可以进行通信。处理客户端套接字通常比处理服务器端简单得多,因为服务器必须随时准备处理客户端的连接,并且必须处理多个连接,而客户端只需连接、执行操作然后断开连接。

在本章中,我们将通过SocketServer类族来处理服务器端编程。套接字是socket模块中socket类的一个实例。它使用最多三个参数实例化:地址族、流或数据报套接字类型以及协议。对于普通的旧式套接字,不需要提供任何参数。

以下是创建和使用套接字的基本步骤:

  1. 服务器套接字:使用bind方法绑定地址,然后调用listen方法开始监听。
  2. 客户端套接字:使用connect方法连接到服务器,地址与服务器bind的地址相同。
  3. 地址格式:地址是一个元组,形式为(host, port),其中host是主机名,port是端口号(整数)。
  4. 监听队列listen方法接受一个参数,即其积压连接数(允许排队等待接受的连接数量)。
  5. 接受连接:服务器套接字监听后,可以使用accept方法开始接受客户端连接。该方法会阻塞直到有客户端连接,然后返回一个形式为(client, address)的元组,其中client是客户端套接字,address是地址。
  6. 处理连接:服务器处理完一个客户端后,可以通过再次调用accept来等待新的连接。这通常在一个无限循环中完成。这种形式的服务器编程称为阻塞同步网络编程。

数据传输

上一节我们介绍了套接字的基本连接过程,本节中我们来看看如何在已建立的连接上发送和接收数据。

套接字有两个用于数据传输的方法:sendrecv。你可以使用字符串参数调用send来发送数据,并使用期望的字节数调用recv来接收数据。如果不确定使用多少字节,1024通常是一个不错的选择。

如果你在同一台机器上运行客户端和服务器对,服务器应该打印一条关于获得连接的消息,然后客户端应该打印出从服务器接收到的消息。你可以在服务器仍在运行时运行多个客户端,只需将客户端中调用gethostname()的部分替换为客户端运行机器的实际主机名即可。然而,你也可以让两个程序通过网络从一台机器连接到另一台机器。

以下是关于端口号的注意事项:

  • 在Linux/Unix系统上,端口号通常受到限制。你需要管理员权限才能使用1024以下的端口。
  • 这些低编号端口用于标准服务,例如端口80用于Web服务器。
  • 如果你用Ctrl+C停止服务器,可能需要等待一段时间才能再次使用相同的端口号。

简单服务器与客户端示例

前面我们了解了套接字通信的理论,现在让我们通过代码示例来看看一个简单的服务器和客户端是如何实现的。

以下是一个极简的服务器示例。网络编程常用于创建数据库(如谷歌的信息收集数据库),或主要用于道德黑客等领域。病毒实际上并不常用,更多的是用于创建特洛伊木马主机。这是一个更好的例子,当你进入他人的计算机时,你可以将其计算机作为主机,并直接访问其中的所有信息。

import socket

s = socket.socket()
host = socket.gethostname()
port = 12345
s.bind((host, port))
s.listen(5)
while True:
    c, addr = s.accept()
    print('Got connection from', addr)
    c.send('Thank you for connecting'.encode())
    c.close()

运行此服务器时,防火墙可能会警告并阻止连接。你需要配置防火墙以允许Python进行网络工作,或者直接断开网络。请注意,任何连接到网络的软件都存在潜在的安全风险,即使是你自己编写的软件。

以下是对应的极简客户端示例:

import socket

s = socket.socket()
host = socket.gethostname()
port = 12345
s.connect((host, port))
print(s.recv(1024).decode())

使用urllib访问网络资源

在众多可用的网络库中,urlliburllib2可能为我们提供了最高的性价比。它们使我们能够像访问本地计算机上的文件一样,通过简单的函数调用访问网络上的文件。几乎所有你可以用统一资源定位符(URL)引用的内容都可以作为我们程序的输入。想象一下,如果结合正则表达式模块,你可以下载网页、提取信息并自动生成报告。

这两个模块的功能大致相同。然而,urllib2稍微更高级一些。对于简单的下载,我们可以只使用urllib。如果我们需要HTTP身份验证、Cookie,或者想要编写扩展来处理我们自己的协议,那么urllib2可能更适合。

以下是打开远程文件的基本命令:

from urllib.request import urlopen
webpage = urlopen('http://www.python.org')

如果你在线,变量webpage现在将显示一个类似文件的对象,链接到Python网页。如果你想实验但不在线,可以使用file:// URL访问本地文件。

假设我们想提取刚刚打开的Python网页上教程链接的URL。我们也可以使用正则表达式来实现。

import re
text = webpage.read()
m = re.search(b'<a href="([^"]+)" .*?>about</a>', text, re.IGNORECASE)
print(m.group(1))

检索和保存远程文件

urlopen函数为我们提供了一个类似文件的对象,我们可以从中读取。如果我们更希望urllib为我们下载文件,并在本地文件中存储一个副本,我们可以使用urlretrieve。它不返回类似文件的对象,而是返回一个元组(filename, headers),其中filename是本地文件的名称(由urllib自动创建),headers包含有关远程文件的一些信息。

如果你想为下载的副本指定文件名,也可以提供第二个参数:

from urllib.request import urlretrieve
urlretrieve('http://www.python.org', 'C:\\python_webpage.html')

这将检索Python主页并将其存储在C盘,命名为python_webpage.html。如果不指定文件名,文件将被放在某个临时位置。完成后,你可能希望将其删除,以免占用硬盘空间。要清理此类临时文件,可以调用urlcleanup()函数,它会为我们处理其他事情。

使用SocketServer框架

到目前为止,你可能觉得编写套接字服务器代码并不难。但如果我们想超越基础,获得一些帮助会很好。socketserver模块是标准库中几个服务器类的基础,包括BaseHTTPServerSimpleHTTPServerCGIHTTPServer等。socketserver包含四个基本的服务器类:TCPServer用于TCP流套接字,UDPServer用于UDP数据报套接字,以及更晦涩的UnixStreamServerUnixDatagramServer。你可能不需要后两个,但很可能需要TCPServer,甚至可能不需要UDPServer

使用socketserver风格编写服务器时,你将大部分代码放在一个请求处理程序中。每次服务器收到请求时,都会实例化请求处理程序,并根据特定服务器和处理程序类的不同,调用其上的各种处理方法。你还可以子类化它们以创建具有自定义处理程序集的服务器。一个基本的处理程序类将所有操作放在一个名为handle的方法中,该方法由服务器调用。此方法可以通过属性self.request访问客户端套接字。

如果你使用流(如TCP),可以使用StreamRequestHandler,它设置了另外两个属性self.rfileself.wfile,用于读写。你可以使用这些类似文件的对象与客户端通信。

以下是如何创建一个基于socketserver的最小服务器:

from socketserver import TCPServer, StreamRequestHandler

class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        self.wfile.write('Thank you for connecting'.encode())

server = TCPServer(('', 12345), Handler)
server.serve_forever()

你可以在标准库文档中找到关于socketserver框架的更多信息。使用这些框架可以更轻松地连接客户端并创建自己的网络模块。

处理多个连接

到目前为止讨论的服务器解决方案都是同步的,一次只能有一个客户端连接并处理其请求。如果一个请求需要一些时间(例如,一个完整的聊天会话),那么能够处理多个连接就很重要,因为我们不希望只与一个人聊天。

实现这一目标主要有三种方式:

  1. 分叉 (Forking)
  2. 线程 (Threading)
  3. 异步I/O (Asynchronous I/O)

分叉和线程可以通过使用socketserver模块中的混合类与任何套接字服务器类结合来简单实现。如果你想自己实现它们,这些方法也相当容易使用,但它们也有缺点。

分叉会占用大量资源,如果客户端很多,可能无法很好地扩展。然而,对于合理数量的客户端,或者在现代Unix/Linux系统上,分叉效率相当高,如果你有多CPU系统,效率会更高。但线程通常更好,尽管线程可能导致同步问题。

异步I/O在底层实现起来更困难。基本机制是select模块的select函数,处理起来相当棘手。幸运的是,我们有一些框架在更高层次上处理异步I/O,为我们提供了一个简单抽象的接口,以实现非常强大和可扩展的机制。标准库中包含的一个基本框架是asyncoreasynchat模块。另一个非常强大的异步网络编程框架是Twisted模块,我将在本章末尾讨论它。

如果你不了解分叉或线程,这里简单说明一下:

  • 分叉:当你分叉一个进程(一个正在运行的程序)时,它基本上被复制,两个结果进程都从当前执行点继续,各自拥有自己的内存、变量等副本。在服务器中,为每个客户端连接创建一个子进程(分叉),父进程继续监听新连接。当客户端满意时,子进程退出。客户端不必相互等待。
  • 线程:线程是轻量级进程或子进程,它们存在于同一进程内并共享相同的内存。这种资源消耗的减少带来了缺点:因为它们共享内存,你必须确保它们不会相互干扰变量或试图同时修改相同的东西。这些问题属于同步的范畴。

在现代操作系统(除了不支持分叉的Windows)和现代硬件上,分叉实际上相当快,可以轻松处理此类资源消耗。如果你不想处理同步问题,并且处理器有足够的性能,分叉可能是一个很好的选择。

然而,最好的方法是避免线程和分叉的思考,转而使用Stackless Python。这是Python的一个版本,旨在能够快速、无痛地在不同上下文之间切换,并支持一种类似线程的并行形式,称为微线程,其扩展性比真实线程好得多。例如,Stackless Python微线程已在《EVE Online》中用于服务数千用户,比普通线程或分叉高效得多。

总结

在本章中,我们一起学习了Python网络编程的基础知识。我们从套接字的概念讲起,了解了服务器套接字和客户端套接字的区别,以及如何建立连接和传输数据。我们通过简单的代码示例演示了服务器和客户端的实现。接着,我们探讨了使用urlliburllib2库方便地访问网络资源。然后,我们介绍了更高级的socketserver框架,它简化了服务器端的开发。最后,我们讨论了处理多个客户端连接的三种主要策略:分叉、线程和异步I/O,并简要提及了Stackless Python作为高性能的替代方案。掌握这些内容,你将能够使用Python开发各种网络应用程序。

004:异步输入输出 第一部分

在本节课中,我们将要学习如何使用Python的socketserver框架创建支持多进程(forking)和多线程(threading)的服务器,以及如何使用selectpoll模块实现异步I/O来处理多个客户端连接。我们还将初步了解Twisted这个强大的异步网络框架。

使用Forking和Threading的Socket服务器

上一节我们介绍了基础的Socket编程。本节中我们来看看如何使用socketserver框架轻松地创建支持并发处理的服务器。

使用socketserver框架创建支持forkingthreading的服务器非常简单。

以下是创建这两种服务器的步骤:

  1. 导入必要的模块:需要从socketserver模块导入TCPServerForkingMixIn/ThreadingMixIn以及StreamRequestHandler
  2. 定义请求处理类:创建一个继承自StreamRequestHandler的类,并重写其handle方法。这个方法定义了服务器如何处理每个客户端的连接。
  3. 创建并启动服务器:使用TCPServer和相应的Mixin类来实例化服务器对象,并调用serve_forever方法启动。

需要注意的是,forking行为仅在handle方法需要较长时间完成时才有用。另外,forking在Windows系统上无法工作,它主要用于Unix/Linux系统。

Forking服务器示例

以下是创建一个Forking服务器的代码示例:

from socketserver import TCPServer, ForkingMixIn, StreamRequestHandler

class Server(ForkingMixIn, TCPServer):
    pass

class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        # 这里可以编写处理客户端请求的具体逻辑

server = Server(('', 1234), Handler)
server.serve_forever()

Threading服务器示例

创建一个Threading服务器的代码与Forking服务器几乎相同,唯一的区别是使用的Mixin类:

from socketserver import TCPServer, ThreadingMixIn, StreamRequestHandler

class Server(ThreadingMixIn, TCPServer):
    pass

class Handler(StreamRequestHandler):
    def handle(self):
        addr = self.request.getpeername()
        print('Got connection from', addr)
        # 这里可以编写处理客户端请求的具体逻辑

server = Server(('', 1234), Handler)
server.serve_forever()

这两种服务器都能同时处理多个客户端连接。它们的核心区别在于,Forking服务器为每个连接创建一个新的进程,而Threading服务器为每个连接创建一个新的线程。

使用Select和Poll的异步I/O

上一节我们介绍了使用多进程和多线程处理并发。本节中我们来看看另一种更高效的异步I/O方法。

当服务器与客户端通信时,从客户端接收的数据可能是断断续续的。使用forkingthreading时,这不是问题,因为一个进程/线程等待数据时,其他进程/线程可以继续处理自己的客户端。

然而,另一种方法是只处理在给定时刻真正有数据要说的客户端。我们不需要完全听完一个客户端,而是听一点,然后把它放回队列,再去处理其他客户端。这就是selectpoll框架(如asyncoreTwisted模块的基础)所采用的策略。

这种功能的基础是select模块中的select函数,或者在可用的情况下使用poll函数。poll的可扩展性更强,但仅在Unix系统上可用。

Select函数的工作原理

select函数接受三个序列作为其强制参数,第四个可选参数是超时时间(秒)。这三个序列分别代表需要监视的输入、输出和异常条件的文件描述符(整数)。

函数会阻塞,直到至少一个文件描述符准备好进行读/写操作,或者超时发生。select的返回值是一个三元组,每个元素都是一个列表,包含对应参数中处于活动状态的子集。

例如,返回的第一个列表将包含有数据可读的输入文件描述符。

一个使用Select的简单日志服务器

以下是一个简单的日志服务器示例,它打印出从所有客户端接收到的所有数据。我们可以使用Telnet建立多个连接来测试它是否能同时服务多个客户端。

import socket
from select import select

s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)
inputs = [s]

while True:
    rs, ws, es = select(inputs, [], [])
    for r in rs:
        if r is s:
            c, addr = s.accept()
            print('Got connection from', addr)
            inputs.append(c)
        else:
            try:
                data = r.recv(1024)
                if data:
                    print(data)
                else:
                    print('Client disconnected')
                    inputs.remove(r)
                    r.close()
            except:
                inputs.remove(r)

Poll方法

poll方法比select更容易使用。当我们调用poll()时,会得到一个poll对象。然后可以使用register方法向这个poll对象注册文件描述符或具有fileno()方法的对象。之后还可以使用unregister方法移除这些对象。

注册了一些对象(例如套接字)后,我们可以调用poll对象的poll方法(可带超时参数)。它会返回一个可能为空的列表,列表中的元素是(fd, event)形式的元组。其中fd是文件描述符,event是一个位掩码,表示实际发生的事件。

select模块中定义了各种poll事件常量,例如:

  • POLLIN:有数据可读。
  • POLLPRI:有紧急数据可读。
  • POLLOUT:文件描述符已准备好写入数据且不会阻塞。
  • POLLERR:与文件描述符关联的某些错误条件。
  • POLLHUP:连接已挂断。
  • POLLNVAL:请求无效(例如,连接未打开)。

使用Poll重写服务器

以下示例是使用poll方法重写的服务器,它比使用select的版本更高效。

import socket
import select

s = socket.socket()
host = socket.gethostname()
port = 1234
s.bind((host, port))
s.listen(5)

p = select.poll()
p.register(s, select.POLLIN)

# 使用字典映射文件描述符到套接字对象
fd_to_socket = {s.fileno(): s}

while True:
    events = p.poll()
    for fd, event in events:
        sock = fd_to_socket[fd]
        if sock is s:
            # 有新连接
            c, addr = s.accept()
            print('Got connection from', addr)
            p.register(c, select.POLLIN)
            fd_to_socket[c.fileno()] = c
        elif event & select.POLLIN:
            # 有数据可读
            data = sock.recv(1024)
            if data:
                print(data)
            else:
                # 连接关闭
                print('Client disconnected')
                p.unregister(sock)
                sock.close()
                del fd_to_socket[fd]

关于selectpoll的更多信息,可以参考Python标准库文档,或者阅读asyncoreasynchat等标准库模块的源代码,这有助于深入理解它们之间的区别。

Twisted网络框架简介

上一节我们探讨了底层的selectpoll机制。本节中我们来看看一个更高级、功能更丰富的异步网络框架——Twisted

Twisted源自Twisted Matrix Laboratories,是一个用Python编写的事件驱动网络框架。它最初是为网络游戏开发的,但现在被用于各种网络软件。

Twisted中,我们可以实现事件处理器,类似于在图形用户界面工具包中的用法。事实上,Twisted可以与多个GUI工具包很好地协同工作。

Twisted是一个非常丰富的框架,支持许多协议,如Web服务器/客户端、HTTP、SMTP、POP3、IMAP、Jabber、IRC、MSN、NTP、DNS等等,兼容性很强。

基本概念

编写一个基本的Twisted服务器,我们需要实现处理以下情况的事件处理器:新客户端连接、新数据到达、客户端断开连接。专门的类可以从这些基本事件中构建出更精细的事件。

所有事件处理程序都在一个Protocol类中定义。我们还需要一个Factory(工厂),当新连接到达时,它能构造这样的协议对象。如果我们想创建自定义协议类的实例,可以使用Twisted内置的工厂类Factory

要编写自己的协议,需要使用twisted.internet.protocol.Protocol作为父类。当建立连接时,会调用事件处理器connectionMade;当连接丢失时,调用connectionLost;当从客户端接收到数据时,调用dataReceived。我们可以使用self.transport对象向客户端发送数据,它有一个write方法。self.transport还有一个client属性,包含客户端地址。

Twisted还有一个核心概念是Deferred(延迟执行),用于处理异步操作的回调,更多细节可以参考其文档。

一个简单的Twisted服务器示例

以下是一个使用Twisted编写的简单日志服务器示例。你会发现Twisted版本更加简洁和易读。

from twisted.internet import reactor, protocol
from twisted.internet.protocol import Protocol, Factory

class SimpleLogger(Protocol):
    def connectionMade(self):
        print('Connection from', self.transport.client)

    def connectionLost(self, reason):
        print('Disconnected from', self.transport.client)

    def dataReceived(self, data):
        print(data)

factory = Factory()
factory.protocol = SimpleLogger

reactor.listenTCP(1234, factory)
reactor.run()

要测试这个服务器,可以使用Telnet连接。你可能会看到每行输出都有一个额外的换行符,这取决于缓冲等因素。当然,你可以使用sys.stdout.write代替print来避免这个问题。

在许多情况下,你可能希望每次获取一行数据,而不是任意数据块。为此编写一个自定义协议很容易,但事实上,twisted.protocols.basic模块中已经存在这样一个有用的类:LineReceiver。它实现了dataReceived,并在收到完整一行时调用事件处理器lineReceived。如果你在接收数据时除了使用lineReceived外还需要其他操作,可以使用LineReceiver定义的另一个新事件处理器rawDataReceived

切换到这个协议只需要很少的工作。使用这个服务器时,你会看到换行符被剥离了,但使用print不会给你双换行符。


本节课中我们一起学习了多种在Python中实现并发网络服务器的方法。我们从使用socketserver框架的forkingthreading服务器开始,然后深入探讨了更底层的异步I/O机制selectpoll,最后介绍了功能强大的高级异步网络框架Twisted的基本用法。每种方法都有其适用场景,Twisted为复杂的网络应用提供了最全面的解决方案。

005:改进型日志服务器与Web数据抓取入门 🚀

在本章中,我们将学习两个核心主题。首先,我们将改进上一节创建的简单服务器,使用Twisted框架的LineReceiver协议构建一个更高效的日志服务器。随后,我们将进入Web编程领域,初步探索如何使用Python从网页中提取信息,即“屏幕抓取”。


改进型日志服务器 📡

上一节我们介绍了如何使用Twisted的Protocol类构建一个基础服务器。本节中,我们将利用LineReceiver协议来创建一个改进版本,它能按行接收和处理数据,使日志记录更加清晰。

以下是构建此服务器的步骤:

  1. 导入必要模块:我们需要从twisted.internet导入reactorfactory,并从twisted.protocols.basic导入LineReceiver

    from twisted.internet import reactor
    from twisted.internet.protocol import Factory
    from twisted.protocols.basic import LineReceiver
    
  2. 定义协议处理类:创建一个继承自LineReceiver的类,并重写连接建立、连接断开以及行数据接收的方法。

    class SimpleLogger(LineReceiver):
        def connectionMade(self):
            print(f'Connection from {self.transport.getPeer()}')
    
        def connectionLost(self, reason):
            print(f'Disconnected from {self.transport.getPeer()}')
    
        def lineReceived(self, line):
            print(f'Received line: {line}')
    
  3. 创建工厂并启动服务器:创建一个工厂类,将其协议设置为我们的SimpleLogger,然后使用reactor监听TCP端口。

    factory = Factory()
    factory.protocol = SimpleLogger
    
    reactor.listenTCP(8000, factory)
    reactor.run()
    

核心改进:与使用dataReceived方法处理原始数据流不同,LineReceiverlineReceived方法确保每次接收到以换行符结尾的完整一行数据时才触发处理,这简化了基于行的协议(如日志记录)的开发。


Web数据抓取入门 🌐

从网络服务器获取数据后,我们常常需要从网页中提取特定信息。这个过程称为“屏幕抓取”。本节我们将了解其基本概念和一种简单实现方法。

屏幕抓取的核心是下载网页并从中提取信息。一个直接的方法是使用urllib获取HTML源码,然后用正则表达式匹配所需内容。

例如,假设我们要从某个招聘页面提取公司名称和网站:

import urllib.request
import re

![](https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/educba-pyweb-dev-dpl/img/4236c1c6d1a9ae9a858e24b43a2f8f14_5.png)

![](https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/educba-pyweb-dev-dpl/img/4236c1c6d1a9ae9a858e24b43a2f8f14_6.png)

# 打开网页并读取内容
url = 'http://example.com/jobs.html'
html = urllib.request.urlopen(url).read().decode('utf-8')

# 使用正则表达式查找模式(示例模式,实际需根据网页结构调整)
pattern = re.compile(r'<h4>(.*?)</h4>.*?<a href="(.*?)">')
for name, link in pattern.findall(html):
    print(f'Company: {name}, URL: {link}')

然而,这种方法存在三个主要弱点

  1. 复杂的正则表达式难以编写和维护。
  2. 无法妥善处理HTML的异常情况,如CDATA节或字符实体(如&amp;)。
  3. 代码高度依赖网页的具体结构,页面布局微调就可能导致抓取失败。

为了解决这些问题,通常有两种更健壮的方案:

  1. 结合使用Tidy等工具清理HTML,然后使用标准库的HTMLParser等模块进行解析。
  2. 使用专为抓取设计的第三方库,如Beautiful Soup

在接下来的课程中,我们将深入探讨这些更强大的工具。


本章总结 📝

本节课我们一起学习了两个重要部分。

首先,我们改进了网络服务器,利用Twisted框架的LineReceiver协议构建了一个按行接收数据的日志服务器,这比处理原始数据流更为简便和清晰。

接着,我们初步进入了Web数据抓取领域。我们了解了屏幕抓取的基本概念,即下载并解析网页以提取信息。我们看到了使用urllib和正则表达式的简单方法,同时也认识到这种方法在可读性、健壮性和可维护性上的局限性,并引出了使用HTMLParserBeautiful Soup等更优解决方案的必要性。

在下一节中,我们将学习如何使用Tidy工具和HTMLParser来更有效地解析网页内容。

006:Beautiful Soup与CGI编程

概述

在本节课中,我们将学习两个重要的Python Web开发工具:Beautiful Soup库和CGI(通用网关接口)编程。首先,我们将了解如何使用Beautiful Soup从杂乱的HTML/XML文档中提取所需数据。接着,我们将探索如何使用CGI创建动态网页,处理用户输入并生成响应。


Beautiful Soup:06-01:数据提取利器

上一节我们介绍了使用正则表达式解析网页,本节中我们来看看一个更强大的工具:Beautiful Soup。

Beautiful Soup是一个Python库,用于从HTML或XML文件中提取数据。它能够解析杂乱的标记文档,并从中提取出我们关心的数据,而不关心文档本身的格式或外观是否美观。

安装Beautiful Soup

你可以从其官方网站下载Beautiful Soup。以下是安装方式:

  • 访问网站:https://www.crummy.com/software/BeautifulSoup/
  • 下载压缩包,并将其放入你的Python包目录中。
  • 对于Linux或Unix系统,可以使用安装脚本进行安装。

使用Beautiful Soup清理文档

在之前的例子中,我们使用xml.etree.ElementTree解析文档,代码较为复杂。现在,我们使用Beautiful Soup来简化这一过程。

以下是使用Beautiful Soup的基本步骤:

  1. 导入必要的模块。
  2. 打开并读取文档。
  3. 创建Beautiful Soup对象。
  4. 使用其方法提取数据。
from bs4 import BeautifulSoup
import urllib.request

url = "你的目标网址"
response = urllib.request.urlopen(url)
html = response.read()
response.close()

soup = BeautifulSoup(html, 'html.parser')

提取特定元素

创建Soup对象后,我们可以轻松地提取文档树中的特定部分。

例如,要获取所有<h4>标题元素,可以这样做:

headers = soup.find_all('h4')
for header in headers:
    print(header.get_text())

要获取某个元素的所有子元素,可以访问其属性。使用CSS选择器类属性可以让操作变得更简单。

# 假设我们想从具有特定类的元素中提取链接
link_elements = soup.select('.reference-class a')
for link in link_elements:
    print(link.get('href'))

优化输出结果

为了使程序更有用,我们可以在提取数据后进行处理,例如使用set去除重复项,并使用sorted对结果进行排序。这虽然与Beautiful Soup本身无关,但能提升输出质量。

unique_links = sorted(set(link.get('href') for link in link_elements))
for link in unique_links:
    print(link)

总结:本节我们一起学习了Beautiful Soup库。它通过几行代码就能帮助我们解析和提取HTML/XML文档中的数据,比手动解析要简单高效得多。接下来,我们将进入动态网页的世界。


CGI编程:06-02:创建动态网页

上一节我们介绍了静态数据提取,本节中我们来看看如何让网页“动”起来,即创建动态网页。

CGI(通用网关接口)是一种标准机制,Web服务器通过它可以将用户通过网页表单提交的查询,传递给专门的程序(如Python脚本)进行处理,并将处理结果返回显示为网页。这是一种无需编写专用应用服务器即可创建Web应用的简单方法。

CGI编程核心:cgi模块

Python中进行CGI编程的关键工具是cgi模块。你可以在Python库参考中找到其详细描述。另一个在开发CGI脚本时非常有用的模块是cgitb,我们稍后会介绍。

部署CGI脚本的三个步骤

要让CGI脚本能被Web访问,你需要完成以下三个准备步骤:

步骤一:将脚本放入Web服务器可访问的目录
你的CGI程序必须放在一个可以通过Web访问的目录中。通常,这类似于将网页和图片放在一个特定目录(例如public_html)中。如果你不知道如何操作,请联系你的网络服务提供商或系统管理员。

此外,必须让Web服务器能识别出这是CGI脚本,而不是普通的源代码文件。有两种常见方法:

  • 将脚本放在名为cgi-bin的子目录中。
  • 给脚本文件加上.cgi扩展名。

具体配置方式因服务器而异,例如使用Apache服务器可能需要启用ExecCGI选项。

步骤二:添加“Shebang”行
在脚本的正确位置放置好后,必须在脚本开头添加一行特殊的注释,称为“Shebang”行。这行代码告诉系统使用哪个解释器来执行脚本。

对于Python脚本,Shebang行通常如下所示:

#!/usr/bin/env python

如果上述方式不工作,你可能需要指定Python解释器的完整路径:

#!/usr/local/bin/python3

步骤三:设置正确的文件权限
你需要设置适当的文件权限,确保Web服务器能够读取和执行你的脚本文件,同时只有你(脚本所有者)可以写入(编辑)它。

在Unix/Linux系统中,可以使用chmod命令来添加执行权限:

chmod +x your_script.cgi

完成所有准备工作后,你应该能够通过完整的URL(而不是本地文件路径)在浏览器中访问脚本,并看到它被执行。

CGI脚本的安全风险

使用CGI程序涉及许多安全问题。如果允许你的CGI脚本在服务器上写入文件,除非编码非常谨慎,否则这种能力可能被用来破坏数据。同样,如果将用户提供的数据作为Python代码或Shell命令来执行,将面临执行任意命令的巨大风险。关于Web安全的更多信息,可以参考万维网联盟(W3C)的安全常见问题解答。

总结:本节我们一起学习了部署CGI脚本的三个关键步骤:放置脚本、添加Shebang行和设置权限,并了解了相关的安全注意事项。接下来,我们将动手编写一个简单的CGI脚本。


第一个CGI脚本:06-03:Hello, CGI World!

上一节我们了解了CGI的部署基础,本节中我们来看看如何编写一个最简单的CGI脚本。

一个最基本的CGI脚本看起来像这样:

#!/usr/bin/env python
print("Content-Type: text/plain")
print()
print("Hello, World!")

代码解析

  1. #!/usr/bin/env python: Shebang行,指定使用Python解释器。
  2. print("Content-Type: text/plain"): 输出HTTP头。Content-Type告诉浏览器返回的内容类型是纯文本。如果要返回HTML,则应使用text/html
  3. print(): 输出一个空行,这是HTTP协议中头部结束和正文开始的标志。
  4. print("Hello, World!"): 输出网页的实际内容。

将这个文件保存为simple.cgi,并通过Web服务器访问其URL,你将在浏览器中看到一个只包含“Hello, World!”纯文本的网页。

使用cgitb进行调试

在开发过程中,程序可能会因为异常而终止,并产生难以理解的服务器错误。为了获得更详细的错误信息,可以导入cgitb模块并调用其enable()函数。

#!/usr/bin/env python
import cgitb
cgitb.enable() # 启用详细错误报告

print("Content-Type: text/plain")
print()

# 故意制造一个错误
result = 1 / 0
print("Hello, World!")

访问这个脚本,浏览器将显示一个详细的错误追踪页面,指出是ZeroDivisionError,并显示导致错误的函数调用序列。请注意,在产品环境中应关闭cgitb功能,因为追踪页面不适合普通用户查看。

总结:本节我们一起编写并分析了第一个CGI脚本,了解了其基本结构,并学会了使用cgitb模块来辅助调试。接下来,我们将学习如何处理用户输入。


处理用户输入:06-04:与表单交互

上一节我们创建了只输出的脚本,本节中我们来看看如何让CGI脚本接收和处理用户的输入。

用户通过HTML表单提交的数据,会以“键-值”对的形式传递给CGI脚本。我们可以在脚本中使用cgi模块的FieldStorage类来获取这些数据。

获取表单数据

创建FieldStorage实例后,它会从请求中获取输入变量,并通过一个类似字典的接口呈现给你的程序。

以下是两种获取特定字段值的方法:

方法一:直接通过键查找

#!/usr/bin/env python
import cgi

![](https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/educba-pyweb-dev-dpl/img/661fd3ee755e0ef19e4dcbef7219b7fb_1.png)

form = cgi.FieldStorage()
# 获取名为‘name’的字段值
user_name = form.getvalue('name', 'Unknown') # ‘Unknown’是默认值

方法二:使用getvalue方法
getvalue方法与字典的get方法类似,但它直接返回字段的value属性。第二个参数可以指定默认值。

user_name = form.getvalue('name', 'Guest')

创建交互式页面

我们可以将表单和处理逻辑放在同一个脚本中。以下是一个完整的例子,它显示一个问候语和一个可以修改名字的表单:

#!/usr/bin/env python
import cgi

form = cgi.FieldStorage()
name = form.getvalue('name', 'World')

print("Content-Type: text/html")
print()
print("""
<html>
<head><title>Greeting Page</title></head>
<body>
<h1>Hello, {}!</h1>
<form action="this_script.cgi" method="post">
Change name: <input type="text" name="name" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
""".format(name))

代码解析

  1. form = cgi.FieldStorage(): 获取用户提交的表单数据。
  2. name = form.getvalue('name', 'World'): 尝试获取名为name的字段值,如果用户没有输入,则使用默认值"World"
  3. 输出HTML页面,其中:
    • 标题<h1>中动态插入了name变量的值。
    • 包含一个HTML表单,其action属性指向脚本自身(this_script.cgi需要替换为实际脚本名)。这意味着提交表单后会重新加载此脚本。
    • 表单中有一个文本输入框(name="name")和一个提交按钮。

当用户在文本框中输入新名字并提交后,name参数就有了新值,页面刷新后标题就会随之改变。

学习HTML表单

要创建更复杂的表单,你需要了解HTML表单的各种元素(如文本框、单选按钮、复选框、下拉列表等)。一个很好的学习方法是查看其他网站的页面源代码。在浏览器中右键点击页面,选择“查看页面源代码”或类似选项,可以研究其HTML结构。

总结:本节课我们一起学习了Beautiful Soup和CGI编程。我们掌握了使用Beautiful Soup从网页中提取数据的简洁方法,并学会了如何通过CGI创建能处理用户输入、生成动态内容的Web应用程序。从解析静态数据到创建交互式网页,你已经迈出了Python Web开发的重要一步。

007:CGI处理器与Apache配置 🚀

在本教程中,我们将学习如何配置Apache服务器以使用mod_python模块,并探索其提供的三种主要处理器:CGI处理器、PSP处理器和发布处理器。我们将了解它们的基本概念、配置方法以及各自的适用场景。


概述

上一节我们介绍了CGI的基本概念。本节中,我们将进一步探讨mod_python模块。如果你喜欢CGI,那么你很可能也会喜欢mod_python。它是Apache Web服务器的一个扩展,可以从mod_python网站获取。它使Python解释器直接成为Apache的一部分,从而开启了许多不同的可能性。其核心是让你能够用Python(而非通常的C语言)编写Apache处理器。mod_python处理器框架为你提供了访问丰富API的能力,涵盖了Apache内部机制等更多内容。

除了基本功能外,它还附带几个处理器,可以使Web开发任务更加便捷。这些处理器包括:

  • CGI处理器:让你可以使用mod_python解释器运行CGI脚本,显著提升执行速度。
  • PSP处理器:让你可以将HTML和Python代码混合在一起,创建可执行的网页。
  • 发布处理器:让你可以通过URL调用Python函数。

现在你可能会想,仅通过URL就能调用Python函数,这听起来太棒了。在本节中,我将重点介绍这三种标准处理器。如果你想编写自己的自定义处理器,可以查阅mod_python文档。


安装与配置 mod_python

首先,我们来看看如何安装mod_python并使其正常工作,这是最重要的部分。这比我们之前发现的任何其他软件包安装都要困难一些。你至少需要让它与Apache协同工作。如果你能自己安装mod_python,你应该使用某种包管理器系统来自动安装和运行它,或者确保你对运行和维护Apache Web服务器有所了解。如果你不能,那么你需要先去学习,然后再回到本教程。当然,如果你幸运的话,你可能已经可以访问一台安装了mod_python的机器。如果你不确定,可以尝试安装并运行它。如果无法安装或运行,请保存本教程视频,去学习Apache Web服务器或获取相关知识,然后再回来。如果你不想自己安装,可以从mod_python文档中获取所需信息,这些文档显然可以在线获取或从其特定网站下载。我将其称为mod_python,但有时会简称为mod_python

安装过程根据你使用的是Unix服务器还是Windows而略有不同。

在Linux/Unix系统上安装

如果你在类Unix系统上安装,并且已经编译了Apache Web服务器并拥有Apache源代码,以下是编译和安装mod_python的要点:

  1. 下载mod_python源代码。
  2. 解压归档文件并进入目录。
  3. 运行mod_python的配置脚本。你可以这样添加脚本:./configure --with-apxs=/usr/local/apache/bin/apxs。记下你遇到的任何有用信息,例如关于加载模块的消息。
  4. 配置完成后,在终端中输入make来编译所有内容。
  5. 编译完成后,输入make install。这三步将在你的Linux或Unix发行版下安装所有内容。

在Windows系统上安装

在Windows上安装有点棘手。适用于Windows的mod_python二进制版本要求你使用Python 2.3版本,不能使用2.4、3.4等版本。因此,请从Apache.org下载mod_python安装程序并双击它。安装过程是直接的,它会引导你完成步骤。但在此之前,你需要安装Python 2.3,否则在过程结束时可能会出错。如果你没有为Python安装Tcl/Tk,安装程序会告诉你如何手动完成安装。为此,只需将mod_python.pyd从Python的Lib/site-packages文件夹复制到Apache根目录下的modules目录。


配置Apache

假设到目前为止我告诉你的一切都进展顺利,并且你知道如何配置Apache以使用mod_python。找到用于特定模块的Apache配置文件,它通常被称为httpd.confapache.conf。根据你的操作系统添加相应的行。

如果你使用的是Linux,你需要在配置文件中添加这样一行:

LoadModule python_module /usr/lib/apache2/modules/mod_python.so

请确保在此行中没有拼写错误,否则它将无法运行。

如果你使用的是Windows,那么你的行可能如下所示:

LoadModule python_module modules/mod_python.so

你可能看不到太多区别,但这造成了很大的不同。编写此文件的方式可能会有细微变化,例如mod_python.so的确切路径。如果无法运行,请检查系统中mod_python.so的正确路径。Unix的正确版本实际上应该作为运行configure的结果报告出来。如果你正确运行了此操作并且没有出现任何错误,你可能不需要运行此操作或将其复制到你的配置文件中。

现在,Apache会尝试查找mod_python,但它没有理由使用它。这意味着我们需要给它一个理由去查找和使用这一特定行。为此,我们必须在你的Apache配置中添加行,可以是在某个主配置文件中,也可以是在你放置网页的目录中一个名为.htaccess的文件中。我假设你使用的是.htaccess,因为大多数时候它们都在那里。否则,你必须在你的Windows或Unix用户目录中指定位置。


使用CGI处理器

这是本教程中我将教授的第一个处理器。在下一个教程中,我将教授PSP和发布处理器。

CGI处理器模拟了我们的程序运行的环境,就像我们实际使用CGI一样。因此,我们实际上是使用mod_python来运行程序,但我们仍然可以像使用CGI脚本一样编写它,例如使用cgicgitb模块。因此,与普通CGI相比,使用CGI处理器的主要原因是性能。根据mod_python文档中的一个简单测试,你可以将性能提高大约一个数量级,甚至更多。然而,发布处理器比这个模块快得多,而编写你自己的处理器可能比CGI处理器快三倍。如果你追求速度,CGI处理器不是你的选择。但如果你正在编写新代码,并且想要功能性和灵活性的结合,并使用其他解决方案之一,那么这可能是一个好主意。使用CGI处理器是因为它并没有真正挖掘出mod_python的巨大潜力,但它最适合与遗留代码一起使用。

无论如何,为了让CGI处理器开始工作,我们在存放CGI脚本的目录中放入以下.htaccess文件。对于调试信息,你可以添加以下行:

AddHandler mod_python .py
PythonHandler mod_python.cgihandler
PythonDebug On

你应该在开发完成后删除此目录。在程序实际公开或开源之前,没有理由将程序的内部暴露给公众。一旦你正确设置了这六行,你应该能够像以前一样运行你的CGI脚本。为了使此工作正常,你可能需要为你的脚本添加.py扩展名,即使你使用以.cgi结尾的URL访问它。mod_python在查找文件以满足请求时会将.cgi转换为.py。这就是原因。

本教程到此结束。在下一个教程中,我将教你如何使用PSP和发布模块来编写CGI脚本。如果你无法正确理解这一点,或者现在对你来说有点困难,谷歌是你获取所需更多信息的最佳选择。最后我想告诉你一件事,无论你看多少教程,你仍然需要自己动手,创建自己的示例,这样对你来说处理示例会更容易。并非每个教程都相同,你需要从自身获得一些帮助,以便能够从所有教程中获取信息,使用谷歌获取更多模块或更简单的方法。当你阅读这些教程时,可能有些内容已经再次更新,我现在教给你的一些东西可能无法工作,因为它们可能已经更新。因此,你需要保持自己的知识是最新的,以便知道如何使用每个教程和每个模块。本教程到此结束,下一个教程将是关于PSP和发布处理器的。


使用PSP处理器

欢迎来到下一个Python编程教程。在本教程中,我将教你如何使用CGI处理器以及一些例外情况。在本教程中,我将教你PSP,当我说PSP时,请记住这不是你的PlayStation PSP,而是我将要教你的不同的东西。我将教你PSP以及发布处理器,这是剩下的两个模块。

如果你使用过PHP(超文本预处理器,最初称为个人主页)、Microsoft ASP(活动服务器页面)、JSP(Java服务器页面)或类似的东西,那么PSP(Python服务器页面)背后的概念对你来说应该很熟悉。PSP文档是HTML和Python代码的混合体,Python代码包含在特殊用途的标签中。任何HTML都将转换为对输出函数的调用。

设置Apache以提供你的PSP页面非常简单,只需在你的.htaccess文件中添加一行,例如:

AddHandler mod_python .psp
PythonHandler mod_python.psp

但请记住,在开发PSP页面时使用指令PythonDebug On可能对你有用。不过,当系统实际使用时不应保持开启,因为PSP页面中的任何错误都会导致异常回溯,包括正在使用的源代码被用户看到。因此,潜在地说,让恶意用户看到我们程序的源代码是我们不应掉以轻心的事情,因为你不想让程序的源代码被他人获取,他们以后可能会将其用于任何非法目的。如果你故意发布代码,其他人可能会发现你的安全漏洞,这绝对是开源软件开发的一个强烈动机。然而,让用户通过错误消息窥见你的代码可能没有用处,并且存在潜在风险。

PSP主要有两组标签:一组用于语句,另一组用于表达式。表达式标签中的表达式值直接放入输出文档中。让我展示一个简单的PSP代码示例:

<html>
<head><title>My PSP Page</title></head>
<body>
<p>
<%
# 这是一个Python代码块
import time
current_time = time.ctime()
%>
The current time is: <%= current_time %>
</p>
</body>
</html>

这是一个简单的PSP示例,它执行一些代码,然后使用表达式标签输出一些数据作为网页的一部分。你可以以任何你喜欢的方式混合输出、语句和表达式。你可以这样写注释:

<%-- 这是一个注释 --%>

这是你编写PSP页面时写注释的方式。除了这些基础之外,编程内容真的很少。不过,你需要注意一个问题:如果语句中的代码开始了一个缩进块,该块将持续到HTML被放入块内。确保这一点最简单的方法是,你需要关闭块才能插入注释,否则它将无法工作。通常,如果你使用过PHP或JSP,你可能没有注意到PSP对换行和缩进更加挑剔。当然,这是Python本身的一个特性,即Python更关心换行和缩进。

有许多其他系统在某种程度上类似于mod_python的PSP,甚至有些几乎完全相同。例如,Web开发系统Zope有自己的模板语言,与Webware的PSP系统有些相似。因此,你可以访问Python的Web类别,或者只需进行Web搜索“Python模板系统”,这应该会指向其他几个有趣的系统。但这不是我在这里的目的。我在这里是为了教你一些你不知道的东西,所有那些额外的东西你需要自己处理。我们可以将其视为类似课后作业,在你观看教程后,去搜索这些东西。


使用发布处理器

现在我来教你什么是发布处理器。发布处理器是mod_python真正发挥其作用的地方。它提供了一个比CGI脚本更有趣的环境。要使用发布处理器,你需要在你的.htaccess文件中添加以下行:

AddHandler mod_python .py
PythonHandler mod_python.publisher

你需要输入这两行来创建这个处理器。Python将是一个小程序。这将使用发布处理器运行任何以.py结尾的文件作为Python脚本。

关于发布处理器,首先要知道的是它将函数作为文档暴露给Web。例如,假设你有一个名为xyz.py的脚本,位于http://example.com/script,即在这个网站的example.com中,你有一个名为xyz.py的脚本,其中包含一个函数,比如read,它打开URL abc.com。那么,发布处理器将首先运行这个函数,然后将返回的任何内容作为文档显示给用户。换句话说,如果有一个函数,它将首先运行该函数,然后将文档中的任何内容显示给用户。这类似于普通的Web文档,但默认文档在这里被称为index,而函数内的特定名称,例如example.com/xyz.py(这是我们的脚本),将作为该名称的函数被调用。换句话说,类似以下内容就足以让用户发布你的处理器:

def index(req):
    return "Hello, World!"

这里的req(请求)对象让你可以访问有关接收到的请求的多个信息片段,以及设置自定义HTTP头部等。你可以查阅mod_python文档。

你也可以这样写:

def index():
    return "Hello, World!"

但这里的发布处理器实际上会检查给函数传递了多少参数,它们被称为什么,以及它可以接受什么参数。当你输入req时,它让你访问有关接收到的请求的多个信息片段,这是两者之间的区别。如果你愿意,也可以进行某种检查。它不一定在所有Python实现中都可移植,但如果你坚持使用CPython,你也可以使用inspect模块来探查函数的某些角落,看看它们接受多少参数以及参数叫什么。

你可以给函数更多参数,然后请求对象。你可以这样调用它,例如:

def read(name="World"):
    return "Hello, %s!" % name

而不是写那个你只是“Hello, World!”的地方。注意,这里的调度器使用参数的名称。因此,当没有名为req的参数时,你将不会收到请求对象。

你可以使用URL访问此函数并提供参数,例如http://example.com/xyz.py/read?name=Gumby。那么你的页面现在将包含文本“Hello, Gumby!”,而不是“Hello, World!”。原因是我之前在这里提到了它有name参数,但由于我给了它一个名字Gumby,函数将输出它。让我展示给你看,如果我尝试访问类似这样的URL:http://example.com/script/read?name=Gumby,那么它将给你类似“Hello, Gumby!”的内容。注意,默认文档在用户不提供任何参数时非常有用。这意味着如果我不提供任何参数,比如这里的Gumby,它将简单地显示“Hello, World!”。但由于我提供了name=Gumby,它将给我“Hello, Gumby!”而不是“Hello, World!”。


总结

在本节课中,我们一起学习了如何配置Apache服务器以使用mod_python模块。我们详细探讨了其三种核心处理器:

  1. CGI处理器:用于提升传统CGI脚本的性能,配置简单,适合遗留代码。
  2. PSP处理器:允许混合HTML和Python代码创建动态页面,类似于PHP或JSP。
  3. 发布处理器:功能强大,允许通过URL直接调用Python函数,提供了更灵活和高效的Web开发环境。

我们还介绍了在Linux和Windows系统上安装mod_python的步骤,以及如何通过修改Apache配置文件(httpd.conf.htaccess)来启用这些处理器。理解这些工具将帮助你根据项目需求选择合适的技术,并构建更高效的Python Web应用程序。

008:带标识的RSS订阅源

概述

在本教程中,我们将学习Web服务,特别是RSS(简易信息聚合)和XML-RPC(远程过程调用)协议。我们将了解它们是什么,如何工作,并通过Python代码示例来演示如何解析RSS订阅源以及如何与XML-RPC服务器进行交互。


Web服务简介

上一节我们介绍了网络编程的基础概念。本节中,我们来看看Web服务。Web服务类似于计算机友好的网页。

它们基于标准和协议,使得程序能够通过网络交换信息。通常,一个程序(称为客户端)向另一个程序(称为服务提供者)请求信息或服务,而服务提供者则提供这些信息或服务。

Web服务的工作原理与之前章节讨论过的网络编程非常相似。由于您正在学习本教程,我们假设您已经了解网络编程。Web服务通常在较高层次上抽象,它们使用HTTP作为底层协议,并在此基础上使用更面向内容的协议(例如XML格式)来编码请求和响应。

这意味着Web服务器可以作为Web服务的平台。正如本节标题所示,我们可以将Web服务视为动态网页,但其设计目标是计算机而非人类。存在多种Web服务标准,涵盖了各种复杂性,但您也可以从简单应用开始。


重要Web服务协议

以下是两种重要的Web服务协议,我们将从最简单的一种开始。

RSS(简易信息聚合)

RSS,全称为Rich Site Summary(丰富站点摘要)或Really Simple Syndication(简易信息聚合),是一种用于聚合新闻条目的XML格式。

RSS文档与普通HTML文档的区别在于,它们被期望定期或不定期更新。它们甚至可以是动态生成的,例如代表博客的最新文章。市面上有许多RSS阅读器。由于RSS格式简单,很容易为其找到新的应用。例如,某些浏览器允许您将RSS订阅源添加为书签,并提供一个包含各个新闻条目的动态子菜单。

RSS的一个稍显混乱之处在于其版本。版本0.9和2.0(现在主要称为简易信息聚合)彼此兼容,但它们与版本1.0完全不兼容。此外,还有其他格式用于新闻订阅和站点聚合,例如Atom。

问题是,如果您想编写一个处理多个站点订阅源的客户端程序,您必须准备解析几种不同的格式。您甚至可能需要在消息本身中解析XML片段。在本节中,我们将使用RSS 2.0的一个小子集,并向您展示一个RSS文件的实际样子。

一个基本的RSS 2.0文档结构如下:

<?xml version="1.0"?>
<rss version="2.0">
    <channel>
        <title>Example Top Stories</title>
        <link>http://example.com</link>
        <description>Meaningless news items</description>
        <item>
            <title>Interesting Stuff</title>
            <link>http://example.com/item1</link>
            <description>Today's interesting news.</description>
        </item>
        <item>
            <title>More Interesting Stuff</title>
            <link>http://example.com/item2</link>
            <description>Another piece of news.</description>
        </item>
    </channel>
</rss>

RSS 2.0标准规定了一些必需元素和许多可选元素。您可以依赖<channel>元素包含<title><link><description>。您可以拥有一个或多个<item>元素,每个至少包含<title><description>

如果您编写程序来处理特定的订阅源,一个好方法是先查明它提供了哪些元素。另一个使解析具有挑战性的事实是,尽管RSS应该是有效的XML,但您很可能会遇到格式不规范的RSS订阅源。新闻消息本身可能包含未转义的“&”符号等非法字符。

幸运的是,我们有Beautiful Soup库,它可以处理XML和HTML,并且不会对RSS订阅源中的一些小问题抱怨。以下是一个从新闻网站获取头条新闻的示例程序,它使用了BeautifulStoneSoup类(专门用于XML)而非BeautifulSoup(专门用于HTML)。

from BeautifulSoup import BeautifulStoneSoup
from urllib import urlopen
from textwrap import wrap

url = 'http://www.example.com/feed/rss2'
soup = BeautifulStoneSoup(urlopen(url).read())

![](https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/educba-pyweb-dev-dpl/img/84d40c7e8fa113af7295934774137bff_1.png)

![](https://github.com/OpenDocCN/cs-notes-pt1-zh/raw/master/docs/educba-pyweb-dev-dpl/img/84d40c7e8fa113af7295934774137bff_2.png)

for item in soup('item'):
    title = item.title.string
    print '\n'.join(wrap(title))
    print '-' * len(title)
    description = item.description.string
    print '\n'.join(wrap(description))
    print item.link.string

XML-RPC(远程过程调用)

上一节我们介绍了RSS。本节中,我们来看看XML-RPC。XML-RPC代表远程过程调用,XML是您已经多次遇到的语言。

Python标准库中的xmlrpclib模块用于连接RPC服务器。一个简单的XML-RPC服务器是一个类。在后续教程中,我将教您如何编写此类服务器。

为了展示XML-RPC库的易用性,我将创建一个连接到RPC服务器并查询信息的示例。假设我们有一个网站,它拥有所有歌曲的封面数据库。

from xmlrpclib import ServerProxy

server = ServerProxy("http://coversproject.com/xmlrpc.php")
covers = server.coverart.getCover

artist = covers("Monty Python", "Bicycle")
if artist:
    print artist

服务器代理对象看起来像一个普通对象,您可以调用其各种方法。实际上,调用covers.coverart.getCover会向服务器发送请求,服务器响应请求并返回答案。从某种意义上说,您是在服务器本身上调用方法。

网络编程可以比这更简单。您阅读的相关资料越多,就越容易理解更复杂的示例。


总结

在本教程中,我们一起学习了Web服务,特别是RSS订阅源和XML-RPC协议。我们了解了它们的基本概念,查看了RSS的XML结构,并使用Beautiful Soup库编写了解析RSS订阅源的Python代码。我们还通过一个示例演示了如何使用xmlrpclib模块与XML-RPC服务器进行交互,调用远程方法并获取结果。这些知识为进一步学习更复杂的网络服务和应用程序测试奠定了基础。

posted @ 2026-03-29 09:13  绝不原创的飞龙  阅读(4)  评论(0)    收藏  举报