最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

练习项目05:解析XML(上)

Python教程 小楼一夜听春语 4482浏览 0评论

这个练习项目来自《Python基础教程(第2版)》,案例原名为“万能的XML”。

XML是一种可扩展标记语言,具备以下特点(来自百度百科):

  • 可扩展标记语言是一种很像超文本标记语言的标记语言。
  • 它的设计宗旨是传输数据,而不是显示数据。
  • 它的标签没有被预定义,需要自行定义标签。
  • 它被设计为具有自我描述性。
  • 它是W3C的推荐标准。

上面所说的超文本标记语言,大家已经见过。

一般超文本标记语言就是指HTML。

HTML文件由多个标记(即通常所说的标签)组成。

这些标记通常是成对出现,即一个开始标记对应一个 结束标记。

XML和HTML非常相像,它的标记也是成对出现。

不过,和HTML不同的是,HTML中的标记名称都是固定的,而XML中的标记名称是需要自定义的。

并且,XML和HTML最大不同之处是:XML的目的是传输信息,HTML的目的是显示信息。

例如,一个描述网站结构与页面内容的XML文件。

示例代码:

<website>
    <page name="index" title="首页">
        <h1>欢迎访问小楼的个人网站!</h1>
        <p>您正在访问的是小楼的个人网站,这个网站包含以下内容:</p>
        <ul>
            <li>
                <a href="catalog/articles.html">文章</a>
            </li>
            <li>
                <a href="catalog/downloads.html">下载</a>
            </li>
            <li>
                <a href="catalog/documents.html">文档</a>
            </li>
        </ul>
    </page>
    <directory name="catalog">
        <page name="articles" title="文章">
            <h1>您正在访问文章列表页!</h1>
            <p>....</p>
        </page>
        <page name="downloads" title="下载">
            <h1>您正在访问资源下载页!</h1>
            <p>...</p>
        </page>
        <page name="documents" title="文档">
            <h1>您正在访问文档列表页!</h1>
            <p>....</p>
        </page>
    </directory>
</website>

在上方XML代码中,通过自定义标记<website>、<directory>以及<page>定义了网站的结构。

并且,通过一些HTML标记,描述了每个网页的内容。

那么,我们就可以通过这个XML文件传输整个网站内容的数据,并通过对XML的解析,生成网站的结构和HTML页面文件。

所以,基于XML传输信息的特点,我们能够通过XML实现很多功能。

如果想很好的掌握XML,还需要深入的学习,请大家自行查阅相关技术文档。【点此查看推荐教程

对XML有了一些基本的了解之后,我们来看一下如何对XML文件内容进行解析。

首先,我们先来做个小试验。

通过Python内置的xml模块就能够对XML文件进行解析。

这里需要先导入xml模块的一些功能。

示例代码:

from xml.sax import parse
from xml.sax.handler import ContentHandler

parse()函数具有解析功能,ContentHandler类则具有内容处理的方法。

ContentHandler类所包含的方法有很多,比较常用的就是对开始元素、内容以及结束元素进行处理的方法。

这些方法,我们需要重写才能够实现我们想要的功能。

在重写方法之前,我们先来看一下通过这个类处理内容时,我们都能获取什么样的内容。

以处理开始标记的方法startElement()为例。

我们创建一个类继承自ContentHandler类,然后重写startElement()方法。

示例代码:

class XMLHandler(ContentHandler):
    def startElement(self, name, attrs):  # 重写startElement方法
        print(name, list(zip(attrs.keys(), attrs.values())))  # 显示输出参数内容

parse('website.xml', XMLHandler())  # 调用解析函数

运行上方代码,我们能够看到以下内容:

很显然,所有开始标签的名称和属性都能够被获取到。

接下来,我们再进一步,将XML中的每个网页的一级标题(<h1>标签中的内容)提取出来。

为了避免混乱,我们可以删除刚才的代码,重新定义一个类。

示例代码:

class HeadLineHandler(ContentHandler):
    def __init__(self, headLines):
        super().__init__()  # 不写对结果也没有影响
        self.headLines = headLines  # 初始化类的变量为传入的标题列表
        self.in_headLine = False  # 初始化开关变量
        self.data = []  # 初始化临时保存数据的变量

    def startElement(self, name, attrs):  # 重写开始元素的方法
        if name == 'h1':  # 如果是一级标题开始标记
            self.in_headLine = True  # 打开开关

    def characters(self, content):  # 重写元素内容的方法
        if self.in_headLine:  # 如果开关打开
            self.data.append(content)  # 添加内容到临时变量

    def endElement(self, name):  # 重写结束元素的方法
        if name == 'h1':  # 如果是一级标题结束标记
            content = ''.join(self.data)  # 提取内容为临时变量中保存的所有内容
            self.data = []  # 清空临时变量
            self.headLines.append(content)  # 标题列表中添加提取到的内容
            self.in_headLine = False  # 关闭开关


if __name__ == '__main__':
    headLines = []  # 创建空的一级标题列表
    parse('website.xml', HeadLineHandler(headLines))  # 调用解析方法
    for line in headLines:  # 遍历通过解析写入内容的一级标题列表
        print(line)  # 显示输出每一个标题内容

运行上方代码,显示结果为:

欢迎访问小楼的个人网站!
您正在访问文章列表页!
您正在访问资源下载页!
您正在访问文档列表页!

代码比较简单,大家参照注释进行理解基本就能够明白整个程序的运行过程。

这里面有两个关键点:

  • 变量in_headLine是一个开关,读取到一级标题的开始标记时,打开这个开关,并且将开关打开时读取到的内容提取;当读取到一级标题的结束标记时,关闭这个开关,以免其他内容被提取。
  • 变量data是一个临时保存提取内容的列表,在上方代码中并未发挥实际作用,它的作用是将某一对标记之间的多段数据内容顺序保存。只有当一对标记间存在其它标记时,才会出现多段内容,当前练习项目中不存在这种情况。

通过前面两段代码,我们已经对XML解析有了一些了解。

下面,我们就完成解析XML代码并生成HTML页面的功能。

为了避免混乱,我们可以新建一个Python文件。

实现的思路为:

  • 读取开始标记时,如果是页面标记(page),创建页面文件,写入页头HTML代码;并打开写入页面内容的开关;
  • 读取开始标记时,如果写入页面内容开关被打开,原样写入该开始标记的名称与属性到页面文件;
  • 读取标记内容时,如果写入页面内容开关被打开,直接写入内容到页面文件;
  • 读取结束标记时,如果是页面标记,关闭写入页面内容的开关,写入页脚HTML代码,关闭页面文件;
  • 读取结束标记时,如果写入页面内容开关被打开,直接写入结束标记。

示例代码:

from  xml.sax import parse
from xml.sax.handler import ContentHandler

class MakePages(ContentHandler):
    in_page = False  # 定义开关变量

    def startElement(self, name, attrs):  # 重写开始元素的方法
        if name == 'page':  # 如果是页面标记
            self.in_page = True  # 打开开关
            self.file = open(attrs['name'] + '.html', 'w')  # 创建HTML文件
            self.file.write('<html>\n<head>\n<meta charset="gbk">\n<title>{}</title>\n</head>\n<body>\n'
                            .format(attrs['title']))  # 写入页头HTML代码
        elif self.in_page:  # 如果开关被打开
            self.file.write('<' + name)  # 写入标记开始符号与名称
            for key, value in attrs.items():  # 遍历属性集合
                self.file.write(' {}="{}"'.format(key, value))  # 写入属性的键值
            self.file.write('>')  # 写入标记末尾符号

    def characters(self, content):  # 重写元素内容的方法
        if self.in_page:  # 如果开关被打开
            self.file.write(content)  # 写入内容

    def endElement(self, name):  # 重写结束元素的方法
        if name == 'page':  # 如果是页面标记
            self.in_page = False  # 关闭开关
            self.file.write('\n</body>\n</html>\n')  # 写入页脚HTML代码
            self.file.close()  # 关闭创建的页面文件
        elif self.in_page:  # 如果开关被打开
            self.file.write('</' + name + '>')  # 写入结束标记

if __name__ == '__main__':
    parse('website.xml', MakePages())  # 调用解析函数

运行以上代码后,虽然会在项目文件夹中出现4个html文件,但是明显和我们的预期结果不一样。

我们希望能够将除了“index.html”页面之外的页面放在名为“catalog”目录中。

那么,如何解决这个问题呢?

在下一篇教程中,我带大家一起再次实现这个功能,让它能够完美实现。

本节练习源代码:【点此下载

转载请注明:魔力Python » 练习项目05:解析XML(上)

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)