​新闻汇总(1):初次实现​

初次实现管用,但很不灵活,因为使用它只能从Usenet讨论组获取新闻。在再次实现中,你将对代码稍作重构以修复这种问题。你将各部分代码放在类和方法中,以提高程序的结构化程度和抽象程度,这样就可用其他类替换有些部分,这比初次实现的部分代码要容易的多。

那么需要哪些类呢?我建议,快速浏览一些问题描述中的重要名词:信息、代理、新闻、汇总、网络、新闻源、目的地、前端、后端和主引擎。这个名词清单表明,需要下面这些主要的类:NewsAgent、NewsItem、Source、Destination。

各种新闻源构成了前端,目的地构成了后端,而新闻代理位于中间层。

在这些类中,最简单的是NewsItem,它只表示一段数据,其中包括标题和正文。因此可像下面这样实现它:

新闻汇总(2):再次实现_html

为准确地确定要从新闻源和新闻目的地获取什么,先来编写个代理本身是个不错的主意。代理必须维护两个列表:源列表和目的地列表。添加源和目的地的工作方法可通过方法add_source和add_destination来完成。

新闻汇总(2):再次实现_超类_02

现在唯一缺失的是将新闻从源分发到目的地的方法。在分发期间,新闻源必须有一个返回其所有新闻的方法,而目的地必须有一个接受所有要分发的新闻的方法。分别将这两个方法命名为get_items和receive_items。出于灵活性考虑,只要求get_items返回一个可用于获取NewsItem的迭代器。然而,为了让目的地更容易实现,假设调用receive_items时,可将一个序列作为一个参数。(这样可多次迭代这个参数。例如,先创建目录再列出各条新闻。)根据这些决策,NewsAgent的方法distribute将如下:

新闻汇总(2):再次实现_超类_03

这个方法遍历所有的新闻源,并创建一个新闻列表。然后,它遍历所有的目的地,并将完整的新闻列表提供给每个目的地。

现在余下的工作只有创建表示新闻源和目的地的类。为进行试验,可创建一个简单的目的地类,他像第一个原型那样将新闻打印出来。

新闻汇总(2):再次实现_html_04

打印代码与前面相同,不同的是你将这些代码封装起来了:这些代码现在位于目的地类中,而不是以硬编码方式放在主程序中。在最终的程序中,使用了一个复杂些的目的地类(生成HTML的HTMLDestination)。它在PlainDestination的基础上添加了以下几项功能。

  • 生成的文本为HTML。
  • 将文本写入文件而不是标准输出中。
  • 除新闻列表外,还创建了一个目录。

就这么简单。目录是使用链接到页面相应部分的超链接创建的。为此,我们还将使用形如<a href="#nn">...</a>的链接(其中nn为数字),这将链接到包含锚点标签<a name="nn">...</a>(其中nn是与目录中相同的数字)的标题。目录和主新闻列表是使用两个不同的for循环创建的。

在设计方面,我考虑过使用新闻源超类和新闻目的地超类,但不同的新闻源和新闻目的地在行为上没有共同之处,因此使用超类毫无意义。只要新闻源和新闻目的地类正确的实现了必要的方法(get_items和receive_items),NewsAgent就会感到满意。(与其使用超类,不如使用协议。)

创建NNTPSource类时,大部分代码都可从最初的原型中复制而来。相比于最初的原型,主要的不同之处如下。

  • 代码封装在方法get_items中。原来的变量servername和group现在是构造函数的参数。另外,变量howmany也变成了构造函数的参数。
  • 调用了decode_header,它负责处理报头字段(如subject)使用的特殊编码。
  • 不是直接打印每条新闻,而是生成NewsItem对象(让get_items变成了生成器)。

为证明这种设计的灵活性,我们再添加一个新闻源——可从网页提取新闻的新闻源。(这是使用正则表达式实现的。)SimpleWebSource的构造函数将一个URL和两个正则表达式(一个用于匹配标题,另一个用于匹配正文)作为参数。在get_items中,它使用了正则表达式方法findall找出所有匹配的标题和正文,并使用zip将它们组合起来。然后,它迭代(title, body)列表,并根据每个(title, body)生成一个NewsItem。如你所见,添加新的新闻源(或目的地)并不太难。为让代码能够正确的运行,我们实例化一个代理以及一些新闻源和新闻目的地。在函数run_default_setup中(这个函数将在其所属模块作为程序运行时被调用),实例化了几个这样的对象。

  • 表示路透社网站的SimpleWebSource,它使用两个简单的正则表达式提取所需的信息。

注意    路透社网站网页的HTML布局可能发生变化。在这种情况下,你需要修改正则表达式。当然,从其他网页提取信息时,也需要这样做。为此,可查看网页的HTML源代码,并找出适用的模式。


  • 表示gmane.comp.python.committers的NNTPSource。实例化这个对象时,将howmany设置成了10,因此其工作原理与最初的原型类似。
  • 一个PlainDestination对象,它打印收集的所有新闻。
  • 一个HTMLDestination对象,它生成新闻页面news.html。

创建所有这些对象并将其添加到NewsAgent中后,调用了方法distribute。

再次实现的完整源代码如图所示。

新闻汇总(2):再次实现_正则表达式_05

新闻汇总(2):再次实现_超类_06

新闻汇总(2):再次实现_正则表达式_07

新闻汇总(2):再次实现_超类_08

新闻汇总(2):再次实现_html_09

生成的页面news.html如图所示。

新闻汇总(2):再次实现_html_10

6.进一步探索

鉴于其扩展性,这个项目提供了很大的探索空间。下面是一些建议。

  • 使用屏幕抓取技术创建一个更厉害的WebSource类。
  • 创建一个RSSSource,它执行RSS解析。、
  • 改进HTMLDestination生成的HTML页面的布局。
  • 创建一个页面监视器,它在指定网页发生变化时生成新闻。(只需下载当前页面,并将其与以前的页面进行比较。请研究标准库中用于文件比较的模块filecmp。)
  • 创建这个新闻的CGI版本。
  • 创建一个EmailDestination类,它通过电子邮件将新闻发送给你。(请参阅标准库中用于发送电子邮件的模块smtplib。)
  • 添加指定要使用哪种新闻格式的开关。(参见标准库模块argparse。)
  • 向新闻目的地提供有关新闻来自何方的消息,以实现更漂亮的布局。
  • 尝试对新闻进行分类(为此可在新闻中搜索关键字)。
  • 创建一个XMLDestination类,它生成可供之前项目中网站生成器使用的XML文件。这样你就可以创建一个新闻网站了。

代码已经上传到了群文件,欢迎大家加群下载(群号:822163725,备注:小陈学Python,不备注了是会被拒绝的哦~!)。还有就是8月2日到8月7日因为去ChinaJoy而暂停更新公众号,敬请谅解!

最后欢迎大家扫码关注

新闻汇总(2):再次实现_正则表达式_11