FTP/邮件/网络协议
Mail编程
电子邮件历史
https://blog.csdn.net/zhuix7788/article/details/86518117#_218 6.5节
相关的概念及简写
- 邮件服务器
要在Internet上提供电子邮件功能,必须有专门的电子邮件服务器。例如有很多相关的厂商:sina、sohu、163等都有自己的邮件服务器。
邮件服务器类似于现实生活中的邮局,负责接收用户投递过来的邮件,并把邮件投递到邮件接收者的电子邮箱中。
邮件服务器基本都有MTA,MDA,MRA 组成
常用的MUA有:outlook、thunderbird、Mac Mail、mutt;
常用的MTA服务有:sendmail、postfix;商业MTA:Microsoft Exchange、Lotus Notes Domino Mail Server
常用的MDA有:procmail、dropmail;
常用的MRA有:dovecot。 - MIME:通用因特网邮件扩充
意图是继续使用目前的RFC 822格式,但增加了邮件主体的结构,并定义了传送非ASCII码的编码规则。
主要包含三部分内容:- 5个新的邮件首部字段:MIME-Version Content-Description Content-Id Content-Transfer-Encoding Content-Type
dir(email.mime) #Content-Type的7种类型,一般使用文本用Text
[‘Audio’, ‘Base’, ‘Image’, ‘Message’, ‘Multipart’, ‘NonMultipart’, ‘Text’] - 定义了许多邮件内部的格式
- 定义了传送编码
- 5个新的邮件首部字段:MIME-Version Content-Description Content-Id Content-Transfer-Encoding Content-Type
- 电子邮箱
即Email地址,通过向邮件服务器申请可获得一个账户,邮件服务器会为这个账号分配一定的空间,用户使用该账号以及空间来发送电子邮件和保存别人发送过来的电子邮件。 - SMTP协议
SMTP协议是用户向邮件服务器发送一封电子邮件时遵循的通讯规则。我们把处理用户SMTP请求的服务器称为SMTP服务器(邮件发送服务器) - POP3协议
POP3协议是用户从邮件服务器管理的电子邮箱中接收一封电子邮件时遵循的通讯规则。我们把处理用户POP3请求的服务器称为POP3服务器(邮件接收服务器). - MUA(MailUserAgent)邮件用户代理:接收邮件所使用的邮件客户端,使用IMAP或POP3协议与服务器通信
- MTA(MailTransferAgent)邮件传输代理:通过SMTP协议发送、转发邮件;
- MDA(MailDeliveryAgent)邮件投递代理:将MTA接收到的邮件保存到磁盘或指定地方,通常会进行垃圾邮件及病毒扫描;
- MRA(Mail Receive Agent)邮件接收代理:负责实现IMAP与POP3协议,与MUA进行交互;
- IMAP(Internet Message Access Protocol):接收邮件使用的标准协议之一
电子邮件系统架构:
一封电子邮件的旅程如下:
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
-
- 如何找到消息应该去的下一台MTA?
答:通过域名服务器(DNS)查找目的域名的MX(mail eXchange)来完成.
- 如何找到消息应该去的下一台MTA?
-
- 如何来与另一台MTA通信?
电子邮件发送和接收流程
(参考:https://blog.csdn.net/u014520047/article/details/51966628)
下图为一封邮件的发送和接收过程:
③将接收到的邮件存储到to邮件分配的存储空间中
④通过POP3协议连接到POP3服务器收取邮件
⑤从to@sina.com账号的存储空间取出邮件
⑥POP3服务器将取出来的邮件回送给to@sina.com账户
Python发送/接收邮件
(https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432005226355aadb8d4b2f3f42f6b1d6f2c5bd8d5263000)
前面讲邮件发送的流程如下:
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
要编写程序来发送和接收邮件,本质上就是:
- 编写MUA把邮件发到MTA
- 编写MUA从MDA上收邮件
注意:
- 邮件客户端软件在发邮件时,会让你先配置SMTP服务器,也就是你要发到哪个MTA上。假设你正在使用163的邮箱,就得填163提供的SMTP服务器地址:smtp.163.com
- 从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以,Outlook之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。
Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
支持的模块有:smtplib(负责发送邮件)和email(负责构造邮件)
纯文本文件构造:
from email.mime.text import MIMEText
msg = MIMEText('HELLO, send by Python', 'plain', 'utf-8') #plain表示纯文本
SMTP发送邮件:
from_addr = "7x@qq.com"
passwd = "xxx"
to_addr = "xxx@163.com"
smtp_server = "smtp.qq.com"
import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
server.set_debuglevel(1) #打印出和SMTP服务器交互的所有信息
server.login(from_addr, passwd) #登录SMTP服务器
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
以上发送邮件成功,但没有主题和收件人的名称。完整的邮件如下:
#-*-coding:utf-8-*-
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib
def _format_addr(s): #用来格式化邮件地址。不能简单传入地址,因为如果包含中文,需要通过Header对象进行编码
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
from_addr = "xxx@qq.com"
passwd = "xxx"
to_addr = "xxxxx@163.com"
smtp_server = "smtp.qq.com"
msg = MIMEText('hello, send by Python..', 'plain', 'utf-8')
msg['From'] = _format_addr("send: %s" % from_addr)
msg['To'] = _format_addr('管理员:%s' % to_addr)
msg['Subject'] = Header('来自SMTP的问候...', 'utf-8').encode()
server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, passwd)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
发送HTML邮件
构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html
#-*-coding:utf-8-*-
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
from_addr = "xxxx@qq.com"
passwd = "xxxxxx"
to_addr = "xxx@163.com"
smtp_server = "smtp.qq.com"
msg = MIMEText('<html><body><h1>Hello</h1>' +
'<p>send by <a href="http://www.python.org">Python</a>...</p>' +
'</body></html>', 'html', 'utf-8')
msg['From'] = _format_addr("send: %s" % from_addr)
msg['To'] = _format_addr('to: %s' % to_addr)
msg['Subject'] = Header('hello...', 'utf-8').encode()
server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, passwd)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
发送附件
可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:
#-*-coding:utf-8-*-
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
from_addr = "from@qq.com"
passwd = "xxxxxxxx"
to_addr = "xxx@163.com"
smtp_server = "smtp.qq.com"
msg = MIMEMultipart()
msg['From'] = _format_addr("send: %s" % from_addr)
msg['To'] = _format_addr('to: %s' % to_addr)
msg['Subject'] = Header('hello...', 'utf-8').encode()
msg.attach(MIMEText('SEND with file...', 'plain', 'utf-8'))
with open("test.png", 'rb') as f:
mime = MIMEBase('image', 'png', filename='test.png')
mime.add_header('Content-Disposition', 'attachment', filename='test.png')
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
mime.set_payload(f.read())
encoders.encode_base64(mime)
msg.attach(mime)
server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, passwd)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
FTP协议
最流行的传输文件的协议有:文件传输协议FTP,Unix-to-Unix复制协议UUCP,以及网页的超文本传输协议HTTP,远程文件复制指令rcp(更安全的scp和rsync)
- FTP(FileTransferProtocal)文件传送协议
- 定制一些特殊的上传下载文件的服务
用户分类:登陆FTP服务器必须有一个账号- Real账户:注册账户
- Guest账户:可能临时对某一类人的行为进行授权
- Anonymous账户(匿名账户):允许任何人
FTP工作流程
- 客户端连接远程主机上的FTP服务器
- 客户端输入用户名和密码
- 客户端和服务器进行各种文件传输和信息查询操作
- 客户端从远程FTP服务器退出,结束传输
- FTP文件表示
- 分三段表示FTP服务器上的文件
- HOST:主机地址,类似于ftp.mozilia.org(火狐公共访问的ftp), 以ftp开头
- DIR:目录,表示文件所在本地的路径,例如/pub/android/focus/a.txt
- File: 文件名称
- 如果想完整精确表示ftp上某一个文件,需要上述三部分组合在一起
主动模式和被动模式
- 主动模式工作过程:
-
客户端以随机非特权端口N,就是大于1024的端口,对server端21端口发起连接
-
客户端开始监听 N+1端口;
-
服务端会主动以20端口连接到客户端的N+1端口。
主动模式的优点:服务端配置简单,利于服务器安全管理,服务器只需要开放21端口
主动模式的缺点:如果客户端开启了防火墙,或客户端处于内网(NAT网关之后), 那么服务器对客户端端口发起的连接可能会失败
- 被动模式工作过程:
-
客户端以随机非特权端口连接服务端的21端口
-
服务端开启一个非特权端口为被动端口,并返回给客户端
-
客户端以非特权端口+1的端口主动连接服务端的被动端口
被动模式缺点:
服务器配置管理稍显复杂,不利于安全,服务器需要开放随机高位端口以便客户端可以连接,因此大多数FTP服务软件都可以手动配置被动端口的范围被动模式的优点:
对客户端网络环境没有要求
Python FTP编程
FTP服务端:pyftpdlib相关函数
[pyftpdlib.ftpservers.FTPServer]接受客户端连接并将连接分发给handler
[pyftpdlib.handlers.FTPHandler]服务器协议解释器类,每来一个连接,FTPServer会创建一个FTPHandler处理其会话
[pyftpdlib.handlers.ActiveDTP]
[pyftpdlib.handlers.PassiveDTP]主动/被动DTP的基类
[pyftpdlib.handlers.DTPHandler]处理服务器DTP的数据
[pyftpdlib.authorizers.DummyAuthorizer]authorizer类处理FTP服务的认证和许可;在FTPHandler类中验证用户名和密码、获取根目录、获取读写许可。DummyAuthorizer提供了平台独立的接口用来管理虚拟用户。
[pyftpdlib.filesystems.AbstractedFS]文件系统类提供不同平台的交叉编译
使用案例:
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
authorizer = DummyAuthorizer() #实例化虚拟用户
#添加用户权限和路径
authorizer.add_user("user", "12345", "/home/giampaolo", perm="elradfmwMT")
#添加匿名用户
authorizer.add_anonymous("/home/nobody")
#ftp句柄
handler = FTPHandler
handler.authorizer = authorizer
#添加被动端口范围
handler.passive_ports = range(2000, 2333)
server = FTPServer(("127.0.0.1", 21), handler)
server.serve_forever()
FTP客户端:ftplib
- 流程:
1.客户端连接远程的FTP服务器
2.客户端输入user和pass
3.客户端做各种文件传输和信息查询操作
4.客户端登出远程FTP服务器,结束通信
#!/usr/bin/env python
# coding=utf-8
import ftplib
import os
import socket
import pdb
HOST = 'ftp.debian.org'
DIRN = 'debian/tools'
FILE = 'loadlin.exe'
def main():
#1.create FTP object
try:
f = ftplib.FTP(HOST)
except (socket.error, socket.gaierror) as e:
print 'ERROR: cannot reach "%s"' % HOST
return
print '*** Connected to host "%s"' % HOST
# 2.connect to ftp server
try:
f.login()
except ftplib.error_perm:
print 'ERROR: cannot login anonymously'
f.quit()
return
print '*** Logged in as "anonymous"'
# 3.cd to "pub" dir
try:
f.cwd(DIRN)
except ftplib.error_perm:
print 'ERROR: cannot CD to "%s"' % DIRN
f.quit()
return
print '*** changed to "%s" folder' % DIRN
#4.download
try:
f.retrbinary('RETR %s' % FILE,
open(FILE,'wb').write)
except ftplib.error_perm:
print 'ERROR: cannot read file "%s"' % FILE
os.unlink(FILE)
else:
print '*** Download "%s" to CWD' %FILE
f.quit()
if __name__ == '__main__':
main()
SOCKET网络编程
客户/服务器架构:服务器是一个软件或硬件,用于提供客户需要的"服务"。目的是等待客户的请求,给这些客户服务,然后等待其他的请求。
-
硬件的客户/服务器架构:打印机服务、文件服务器等。客户机器可以把服务器的磁盘映射到自己本地,就像本地磁盘一样使用它们。例如NFS网络文件系统。
-
软件客户/服务器架构:主要是程序的运行,数据的发送与接收,合并,升级或其他的程序或数据的操作。例如web服务器、数据库服务器、窗口服务器
-
套接字家族:
1.基于文件型:AF_UNIX,底层结构由文件系统来支持的
2.基于网络型:AF_INET/AF_INET6
python只支持AF_UNIX, AF_NETLINK(python2.5),AF_INET家族 -
套接字类型:
面向连接的套接字:即通讯之前一定要建立一条连接,主要协议是TCP协议,类型SOCK_STREAM
面向无连接的套接字:无需建立连接就可以进行通讯。主要协议是UDP,类型SOCK_DGRAM
TCP/IP简介
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。路由器负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
Socket对象的相关函数:
-
服务器端套接字
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来 -
客户端套接字
s.connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 -
公共用途的套接字函数
s.recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value) 设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen]) 返回套接字选项的值。
s.settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时。
s.gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件
UDP示例
import socket
def serverFunc():
# 1.建立socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #UDP
# 2.绑定ip/Port
addr = ("127.0.0.1", 7852)
sock.bind(addr)
# 3.接收对方消息
#recvfrom接收的范围值是一个tuple,前一项是数据,后一项是地址
# 参数的含义:缓冲区大小
#rst = sock.recvfrom(500)
data,addr = sock.recvfrom(500)
print (data)
print (type(data))
#发送过来的数据是bytes格式,必须通过解码才能得到str格式内存
text = data.decode()
print (type(text))
print (text)
#给对方发送
rsp = "Ich hab Hunge"
#发送的数据需要编码bytes格式,默认utf8
data = rsp.encode()
sock.sendto(data,addr)
import socket
#coding:utf8
def clientFunc():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
text = 'I love wangxioajing'
# 发送数据必须是bytes格式
data = text.encode()
# 发送
sock.sendto(data, ("127.0.0.1", 7852))
data,addr = sock.recvfrom(200)
data = data.decode()
print (data)
注意: Python2默认编码是ascii;python3默认编码是utf-8
sys.getdefaultencoding()
‘utf-8’ ##Python3
sys.getdefaultencoding()
‘ascii’ ##Python2
TCP示例
服务端
from socket import *
from time import ctime
HOST = ''
PORT = 21001
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)
while True:
print 'waiting for connection...'
tcpCliSock, addr = tcpSerSock.accept()
print '...connected from :',addr
while True:
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print type(data)
tcpCliSock.send('[{0}] {1}'.format(ctime(),data))
tcpCliSock.close()
tcpSerSock.close()
客户端
from socket import *
HOST = 'localhost'
PORT = 21001
BUFSIZ = 1024
ADDR = (HOST,PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM, 0)
tcpCliSock.connect(ADDR)
while True:
data = raw_input('>')
if not data:
break
tcpCliSock.send(data)
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print data
tcpCliSock.close()
UDP通信
服务端
from socket import *
from time import ctime
HOST = ''
PORT = 21000
BUFSIZ = 1024
ADDR = (HOST, PORT)
udpServSock = socket(AF_INET, SOCK_DGRAM)
udpServSock.bind(ADDR)
while True:
print 'waiting for message...'
data, addr = udpServSock.recvfrom(BUFSIZ)
udpServSock.sendto('[{0}] {1}'.format(ctime(), data), addr)
print '...recvied from and returned to:', addr
udpServSock.close()
客户端
from socket import *
HOST = 'localhost'
PORT = 21000
BUFSIZ = 1024
ADDR = (HOST,PORT)
udpCliSock = socket(AF_INET, SOCK_DGRAM)
while True:
data = raw_input('>')
if not data:
break
udpCliSock.sendto(data, ADDR)
data, ADDR = udpCliSock.recvfrom(BUFSIZ)
if not data:
break
print data
udpCliSock.close()
*SocketServer模块
- 用于简化网络客户与服务器的实现
- 类中包含了接受客户消息的事件处理器。
注释:从SocketServer的StreamRequestHandler类中派生出一个子类,并重写handler()函数
在由客户端进来的时候,handler()函数就会被调用,StreamRequestHandler类支持像操作文件对象那样操作输入输出套接字。
服务端
from SocketServer import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime
HOST = ''
PORT = 21001
ADDR = (HOST, PORT)
class MyRequestHandler(SRH):
def handler(self):
print '...connected from :', self.client_address
self.wfile.write('[{0}] {1}'.format(ctime(), self.rfile.readline()))
tcpServ = TCP(ADDR, MyRequestHandler)
print 'waiting for connection...'
tcpServ.serve_forever()
客户端
from socket import *
HOST = 'localhost'
PORT = 21001
BUFSIZ = 1024
ADDR = (HOST, PORT)
while True:
tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
data = raw_input('>')
if not data:
break
tcpCliSock.send('%s\r\n' % data)
data = tcpCliSock.recv(BUFSIZ)
print data
if not data:
break
print data.strip()
tcpCliSock.close()
测试时连接服务端没反应??
Twisted框架介绍
Twisted是一个完全事件驱动的网络框架。允许开发完全异步的网络应用程序和协议。(需要安装)
系统可以有:
网络协议、线程、安全和认证、聊天/即时通讯、数据库管理、
关系数据库继承、网页和互联网、电子邮件、命令行参数、图形界面集成
伪代码:
创建一个Twisted Reactor TCP服务器
通过transport对象与客户进行通讯。
factory = protocol.Factory() #创建一个工厂,每次都会"生产"一个protocol对象
factory.protocol = TSServProtocol
print ‘waiting for connection…’
reactor.listenTCP(PORT, factory) #安装监听器等待服务请求。当有请求进来时
#创建一个TSServProtocol实例来服务器客户
reactor.run()
服务端
from twisted.internet import protocol, reactor
from time import ctime
PORT = 21005
class TSServProtocol(protocol.Protocol):
def connectionMade(self):
clnt = self.clnt = self.transport.getPeer().host
print "...connected from:", clnt
def dataReceived(self, data):
self.transport.write('[{0}] {1}'.format(ctime(),data))
factory = protocol.Factory()
factory.protocol = TSServProtocol
print 'waiting for connection...'
reactor.listenTCP(PORT, factory)
reactor.run()
客户端
from twisted.internet import protocol, reactor
HOST = 'localhost'
PORT = 21005
class TSClntProtocol(protocol.Protocol):
def sendData(self):
data = raw_input('>')
if data:
print '...sending %s ...' % data
self.transport.write(data)
else:
self.transport.loseConnection()
def connectionMade(self):
self.sendData()
def dataReceived(self,data):
print data
self.sendData()
class TSClntFacory(protocol.ClientFactory):
protocol = TSClntProtocol
clientConnectionLost = clientConnectionFailed = lambda self, connector, reason: reactor.stop()
reactor.connectTCP(HOST, PORT, TSClntFacory())
reactor.run()
网络新闻传输协议NNTP
Usenet新闻系统是一个全球存档的"电子公告板"。由大量计算机组成的一个庞大的全球网络。
供用户在新闻组中下载或发表帖子的方法叫网络新闻传输协议NNTP
python的NNTP接口与FTP类似:
from nntplib import NNTP
n = NNTP('your.nntp.server')
r,c,f,l,g = n.group('comp.lang.python')
...
n.quit()