17.3 网络新闻
17.3.1 Usenet与新闻组
Usenet新闻系统是一个全球存档的“电子公告板”。各种主题的新闻组一应俱全,从诗歌到政治,从自然语言学到计算机语言,从软件到硬件,从种植到烹饪以及招工、应聘、音乐、魔术、分手、求爱等。新闻组可以是面向全球泛泛而谈,也可以是只面向某个地理区域。
整个系统是一个由大量计算机组成的一个庞大的全球网络,计算机之间共享Usenet上的帖子。如果某一个用户发了一个帖子到本地的Usenet计算机上,这个帖子会被传播到其他相连的计算机上,并再由这些计算机传到与它们相连的计算机上,直到这个帖子传播到了全世界,每个人都收到这个帖子为止。
每个系统都有一个它已经“订阅”的新闻组的列表,它只接收它感兴趣的新闻组里的帖子——而不是服务器上所有新闻组的帖子。Usenet新闻组服务内容取决于服务提供者,很多都是可供公众访问的,也有一些只允许特定的用户使用,例如付费用户、特定大学的学生等。如果Usenet系统管理员设置了的话,有可能会要求输入用户名和密码。管理员也可以设置是否只允许上传或只允许下载。
17.3.2 网络新闻传输协议(NNTP)
供用户在新闻组中下载或发表帖子的方法叫网络新闻传输协议(NNTP)。BrainKantor(加利福尼亚大学圣地亚哥分校)和Phil Lapsley(加利福尼亚大学伯克利分校)创建并记录在RFC 977中,于1986年2月公布。其后的更新记录在RFC 2980,于2000年10月公布。
作为客户端/服务器架构的另一个例子,NNTP与FTP的操作方式很像,而且简单得多。FTP需要不同的端口来做登录、数据传输和控制,而NNTP只使用一个标准端口119来做通讯。你给服务器一个请求,它做相应的反馈,见图17-2。
图 17-2 因特网上的NNTP客户端和服务器。客户端主要阅读新闻,有时也发帖子。文章会在服务器之间做同步
17.3.3 Python和NNTP
由于之前已经有了Python和FTP的经验,你也许可以猜到,一定有一个库nntplib和一个类nntplib.NNTP,你要实例化这个类。你猜对了。和FTP一样,我们所要做的就是导入那个Python模块,然后调用相应的方法。我们先大致看一下这个协议:
1.连接到服务器;
2.登录(如果需要的话);
3.发送请求;
4.退出。
是不是有点熟悉?是的,这几乎就是完全复制了FTP协议。唯一的不同就是根据NNTP服务器的配置不一样,登录这一步是可选的。
下面是一段Python的伪代码:
一般来说,在你登录完成后,你要调用group()方法来选择一个感兴趣的新闻组。方法返回服务器的返回信息、文章的数量、第一个和最后一个文章的ID和组的名字。在有了这些信息后,你会做一些其他的操作,如从头到尾看文章、下载整个帖子(文章的标题和内容)或发表一篇文章等。
在看真实的例子之前,我们要先介绍一下nntplib.NNTP类的一些常用的方法。
17.3.4 nntplib.NNTP类方法
跟前一节列出ftplib.FTP类的方法时一样,我们不会列出nntplib.NNTP的所有方法,只列出你创建NNTP客户端程序时可能用得着的方法。
跟上一节的FTP对象表一样,还有一些NNTP对象的方法没有提及。为了避免混乱,我们只列出了你可能用得到的。其余的,我们再次建议你参考Python手册。
17.3.5 交互式NNTP举例
接下来,是一个如何使用Python中NNTP库的交互式的例子。它看上去跟交互式的FTP的例子差不多(出于保密的原因,电子邮件地址都做了修改)。
在调用表17.2中所列的group()方法连接到一个组的时候,你会得到一个5元组。
17.3.6 客户端程序NNTP举例
在NNTP客户端例子中,我们来点更复杂的。在之前的FTP客户端例子中,我们是下载最新的文件,这一次,我们要下载Python语言新闻组com.lang.python里的最后一篇文章。下载完成后,我们会显示文章的前20行,而且是前20行有意义的内容。有意义的内容是指那些不是被引用的文本(引用以“>”或 “|”开头),也不是像这样的文本 “ In article <…>,soAndSo@some.domain wrote:”。
最后,我们要智能地处理空行。在文章中出现了一行空行,那我们就显示一行空行,但如果有多行连续的空行,那只显示一行空行。只有有数据的行才算在“前20行”之中。所以,最多可能显示39行输出,20行实际数据间隔了19行空行。
如果脚本的运行正常的话,我们可能会看到这样的输出:
这个脚本下载并显示Python新闻组comp.lang.python最后一篇文章的前20个“有意义的”行。
这个输出显示了新闻组帖子的原始内容,如下:
当然,由于新文章不断出现,输出经常会不一样。只要你的服务器里一有文章更新,输出就会不一样了。
逐行解释
1 ~ 9行
程序开始是一些导入语句和常量定义,跟FTP客户端差不多。
11 ~ 40行
在第一部分,我们尝试连接到NNTP服务器,如果失败就退出(13〜24行)。第15行故意注释掉了,如果需要输入用户名和密码进行认证的话,可以打开这一行,并修改第14行。后面是尝试读取指定的新闻组。同样,如果新闻组不存在,服务器没有保存这个新闻组,或是需要认证的话,退出(26〜40行)。
42 ~ 55行
下面这一部分,我们读一些头信息,并显示出来(42〜51行)。最有用处的头信息包括作者、主题和日期。这些数据会被读取并显示给用户。在每一次调用 xhdr()方法时,都要给定想要提取信息头的文章的范围。我们只想取一条信息,所以范围就是“X-X”,其中,X是最后一条信息的号码。
xhdr()方法返回一个2元组,包含了服务器的返回信息(rsp)和我们指定范围的信息头的列表。由于我们只指定了一个消息(最后一个),我们只取列表的第一个元素(hdr[0])。数据元素是一个2元组,包含文章号和数据字符串。由于我们已经知道了文章号(我们在请求中给出了),我们只关心第二个元素,数据字符串(hdr[0][1])。
最后一部分是下载文章的内容(53〜55行)。先调用body()方法,然后显示前20个有意义的行,最后登出服务器,完成执行。
57 ~ 80行
主要的处理任务由displayFirst200函数完成(57~80行)。它接受文章的所有行作为参数,并做一些预处理,如把计数器清0,创建一个生成器表达式对文章内容的所有行做一些处理,然后“假装”我们刚碰到并显示了一行空行(59〜61行,稍后细说)。由于前导空格可能是Python代码的一部分,所以在我们去掉字符串中的空格的时候,只删除字符串右边的空格(rstrip())。
我们要做的是,我们不要显示引用的文本和引用文本指示行。这就是65〜71行(也包含64行)的那个大if语句所要做的事。如果这一行不是空行的时候,才做这个检查(63行)。检查的时候,会把字符串转成小写,这样就能做到比较的时候大小写无关(64行)。
如果一行以“>”或“|”开头,说明这一般是一个引用。不过,我们认为“>>>”是一个例外,因为这有可能是交互命令行的提示,虽然这样可能有问题,因为它也可能是一段被引用了三次的消息(1段文本到第4个回复的帖子时被引用了3次)却被显示了。
现在来处理空行。我们想让程序聪明一些,它应该能显示文章中的空行,但对空行的处理要做到智能。如果有多个连续的空行,则只显示第一个,这样用户不用看那么多行信息,导致有用的信息却在屏幕之外。我们也不能把空行计算到20行有意义的行之中。所有这些要求都在72〜78行内实现。
72行的if语句表示只有在上一行不为空,或者上一行为空但当前行不为空的时候才显示。也就是说,如果显示了当前行的话,就说明要么当前行不为空,要么当前行为空但上一行不为空。这是另一个比较有技巧的地方:如果我们碰到了一个非空行,计数器加1,并设置lastBlank标志为False,以表示这一行非空(74〜76行)。否则,表示我们碰到了空行,把标志设为True。
现在回到第61行,我们设lastBlank标志为True,是因为,如果内容的第一行实际数据(不是前导数据或是引用数据)是一个空行,我们不会显示它。因为我们想要看第一行实际数据!
最后,如果我们已经显示了20行非空行,则退出,放弃其余的行(79〜80行)。否则,我们应该已经遍历了所有行,循环也正常结束了。
17.3.7 NNTP的其他方面
从NNTP协议定义/规范(RFC 977)中,你可以得到更多关于NNTP的信息:ftp://ftp.isi. edu/in-notes/rfc977.txt以及网页http://www.networksorcery.com/enp/protocol/nntp.htm。其他相关的RFC有1036、2980。想了解更多Python对NNTP的支持,可以从这里开始:http://python.org/docs/current/lib/module-nntplib.html