这一篇教程,我们一起采用一种更复杂,但是更具有扩展性、更易维护的方式来实现新闻采集的功能。
在上一篇教程中,已经提到我们会分别对NNTP服务器的新闻内容以及网页中的新闻内容进行获取,并且以不同的格式输出。
新闻的来源有两种:
- NNTP服务器(web.aioe.org)的新闻组(comp.lang.python)
- 36Kr网站上的新闻快讯(http://36kr.com/newsflashes)
大家可以通过上一篇教程的代码,以及访问上方36Kr的网页地址了解来源的不同形式。
最终输出的目标格式也有两种:
- Print显示输出
- HTML文件
目标格式下方图片所示。
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
Print显示输出:
HTML文件:
通过上方两张图片,大家能够看出,我们是从两个新闻源获取到新闻内容并混合到一起,采用不同的格式输出这些新闻内容。
为了清晰的实现这个功能,我们先进行一下结构的划分:
- 新闻来源类型(NNTP来源类和Web来源类)
- 新闻内容(新闻类)
- 报告类型(普通显示输出类和HTML文件输出类)
- 处理过程(代理类)
- 主程序
根据不同类的功能,我们能够看到有明确的分层。
- 不同的新闻来源构成后端
- 不同的输出类型构成前端
- 处理过程代理类是中间层
最终,通过主程序将这些具有不同功能的类结合到一起,共同实现目标功能。
1、添加需要使用的模块
在实现这个功能的过程中,我们需要用到很多模块。
示例代码:
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
from datetime import date, timedelta
from nntplib import NNTP
from email import message_from_string
from urllib.request import urlopen
import re, textwrap
上面这些模块,在之后的使用中,大家能够看到它们的用途,这里不再一一介绍。
2、定义类和主程序
根据上面结构的划分,我们编写的代码中,需要完成以下类和主程序的编写。
示例代码:
class NewsItem: # 新闻内容类
pass
class NNTPSource: # NNTP新闻源类
pass
class SimpleWebSource: # 网页新闻源类
pass
class PlainDestination: # 普通输出目标类
pass
class HTMLDestination: # HTML文件目标类
pass
class NewsAgent: # 新闻代理类
pass
def runDefaultSetup(): # 主程序
pass
3、定义处理每行新闻内容宽度的方法
我们在普通输出目标时,新闻内容的每一行都应该有固定的宽度。
所以,我们可以先定义一个处理内容宽度的方法。
示例代码:
def wrap(string, width=50): # 定义处理字符串宽度的方法
return '\n'.join(textwrap.wrap(string, width)) + '\n' # 返回处理之后的结果
4、各个类的实现
(1)新闻内容类
首先,需要定义一条新闻包含的内容:标题和新闻主体。
另外,在普通输出目标时,标题和主体之间要使用横线“—–”进行分隔,横线的数量取决于字符的数量。
但是,中文和英文字符宽度不一样,在显示时,一个英文字符占一个字节宽度,而一个中文字符占两个字节宽度。
所以,在这个类中,我们还需要定义一个字符的字节数量,默认一个字符的字节数量为1。
示例代码:
class NewsItem: # 新闻内容类
def __init__(self, title, body, byteNumber=1):
self.title = title # 新闻标题
self.body = body # 新闻主体
self.byteNumber = byteNumber # 每个字符的字节数量
(2)NNTP新闻源类
这个类,我们需要实现从NNTP服务器的新闻组获取新闻源并将每一条新闻生成的方法。
因为获取到的新闻数量比较多,在这里,我限制了获取数量为前10条新闻。
具体实现过程,大家通过代码中的注释进行理解。
示例代码:
class NNTPSource: # NNTP新闻源类
def __init__(self, server_name, group, window):
self.server_name = server_name # 服务器地址
self.group = group # 新闻组名称
self.window = window # 时间窗口
def getItems(self): # 新闻生成器
yesterday = date.today() - timedelta(days=self.window) # 计算新闻获取的起始时间
server = NNTP(self.server_name) # 创建服务器连接对象
ids = server.newnews(self.group, yesterday)[1] # 获取新闻id列表
count = 0 # 创建计数变量
for id in ids: # 循环获取新闻id
count += 1 # 计数递增
if count <= 10: # 如果计数小于10
article = server.article(id)[1][2] # 获取指定id的新闻文章
lines = [] # 创建每行新闻内容的列表
for line in article: # 从新闻文章中读取每一行内容
lines.append(line.decode()) # 将每行新闻内容解码,添加到新闻内容列表。
message = message_from_string('\n'.join(lines)) # 合并新闻列表内容为字符串并转为消息对象
title = message['subject'].replace('\n', '') # 从消息对象中获取标题
body = message.get_payload() # 从消息对象中获取到新闻主体内容
if message.is_multipart(): # 如果消息对象包含多个部分
body = body[0] # 获取到的内容中第1个部分获取新闻主体内容
yield NewsItem(title, body) # 生成1个新闻内容对象
else: # 如果超出10条内容
break # 跳出循环
server.quit() # 关闭连接
(3)网页新闻源类
这个类,我们需要实现从指定网页获取新闻源并将每一条新闻生成的方法。
具体实现过程,大家通过代码中的注释进行理解。
示例代码:
class SimpleWebSource: # 网页新闻源类
def __init__(self, url, titlePattern, bodyPattern):
self.url = url # 网页地址
self.titlePattern = re.compile(titlePattern) # 提取新闻标题的正则表达式
self.bodyPattern = re.compile(bodyPattern) # 提取新闻主体内容的正则表达式
def getItems(self): # 新闻生成器
text = urlopen(self.url).read().decode() # 读取目标网页内容并解码
titles = self.titlePattern.findall(text) # 通过正则表达式获取所有新闻标题
bodies = self.bodyPattern.findall(text) # 通过正则表达式获取所有新闻主体内容
for title, body in zip(titles, bodies): # 将新闻标题和内容混合到一起并获取每1条新闻的标题和主体内容
yield NewsItem(title, wrap(body), 2) # 生成1个每行新闻具有指定宽度并且每个字符宽度为2个字节的新闻内容对象
(4)普通输出目标类
这个类,我们显示输出新闻对象列表中每1条新闻的标题、分隔线和主体内容。
示例代码:
class PlainDestination: # 普通输出目标类
def receiveItems(self, items): # 定义接收到新闻对象时的处理方法
for item in items: # 遍历新闻对象列表
print(item.title) # 显示输出新闻标题
print('-' * item.byteNumber * len(item.title)) # 显示输出分隔线
print(item.body) # 显示输出新闻主体内容
(4)HTML文件目标类
这个类,我们将新闻对象列表中的所有新闻标题生成HTML文件中的新闻标题列表,并且在HTML文件中写入每1条新闻的标题和主体内容。
具体实现过程,大家通过代码中的注释进行理解。
示例代码:
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
class HTMLDestination: # HTML文件目标类
def __init__(self, filename):
self.filename = filename # 创建的HTML文件名称
def receiveItems(self, items): # 定义接收到新闻对象时的处理方法
with open(self.filename, 'w', encoding='UTF-8') as file: # 打开或创建HTML文件
file.write('<html>\n' # 写入HTML文件头部内容、网页主体中的标题以及新闻列表的开始标签
'<head>\n'
'<meta charset="UTF-8">\n' # 注意:需要声明网页文件的编码类型
'<title>24小时快讯</title>\n'
'</head>\n'
'<body>\n'
'<h1>24小时快讯</h1>\n'
'<ul>\n')
id = 0 # 初始化HTML文件中的新闻id
for item in items: # 遍历新闻对象集合
id += 1 # 递增新闻id
file.write('<li><a href="#%i">%s</a></li>\n' % (id, item.title)) # 写入1条新闻的列表项并指定id
file.write('</ul>') # 写入新闻列表的结束标签
id = 0 # 初始化HTML文件中的新闻id
for item in items: # 遍历新闻对象集合
id += 1 # 递增新闻id
file.write('<h2><a name="%i">%s</a></h2>\n' % (id, item.title)) # 写入1条新闻的标题并指定id
file.write('<p>' + item.body + '</p>\n') # 写入1条新闻的主体内容
file.write('</body>\n' # 写入HMTL文件主体的结束标签和HTML文件结束标签
'</html>')
在上方代码中,新闻id的作用是点击新闻标题列表中的某个标题时,能够让页面滚动到该新闻所在的位置。
这样的交互需要在标题的<a>标签中指定“href”属性为“#id”,“#”表示当前页面,“id”表示锚链接要连接到的目标,也就是<a>标签中“name”属性与“id”相同的页面内容。
(5)代理类
这个类,负责处理新闻源和输出目标,进行最终的分发。
具体实现过程,大家通过代码中的注释进行理解。
示例代码:
class NewsAgent: # 新闻代理类
def __init__(self):
self.sources = [] # 新闻源列表
self.destinations = [] # 输出目标列表
def add_source(self, source): # 添加新闻源的方法
self.sources.append(source)
def add_destinations(self, dest): # 添加输出目标的方法
self.destinations.append(dest)
def distribute(self): # 进行分发的方法
items = [] # 新闻对象列表
for source in self.sources: # 遍历每1个新闻源
items.extend(source.getItems()) # 从新闻源的生成器将所有新闻对象添加到新闻对象列表
for dest in self.destinations: # 遍历每1个输出目标
dest.receiveItems(items) # 调用处理新闻对象的方法处理新闻对象集合
(6)主程序
主程序负责指定新闻来源和输出目标,将来源和目标添加到代理类对象中进行处理,完成最终的分发。
具体实现过程,大家通过代码中的注释进行理解。
示例代码:
def runDefaultSetup(): # 主程序
agent = NewsAgent() # 创建代理类对象
server_name = 'web.aioe.org' # 指定NNTP服务器
group = 'comp.lang.python' # 指定访问的新闻组
window = 1 # 指定时间窗口
clpa = NNTPSource(server_name, group, window) # 创建新闻源对象
url = r'http://36kr.com/newsflashes' # 指定目标网页地址
titlePattern = r'"pin":"0","title":"(.{10,60})","catch_title"' # 组织提取新闻标题的正则表达式
bodyPattern = r'"description":"(.{100,400})","cover"' # 组织提取新闻主体内容的正则表达式
web = SimpleWebSource(url, titlePattern, bodyPattern) # 创建新闻源对象
agent.add_source(clpa) # 添加NNTP新闻源对象
agent.add_source(web) # 添加网页新闻源对象
agent.add_destinations(PlainDestination()) # 添加普通输出目标对象
agent.add_destinations(HTMLDestination('news.html', )) # 添加输出HTML文件目标对象
agent.distribute() # 调用分发方法
if __name__ == '__main__':
runDefaultSetup() # 执行主程序
以上就是这个练习项目的完整实现过程。
完整代码
from datetime import date, timedelta
from nntplib import NNTP
from email import message_from_string
from urllib.request import urlopen
import re, textwrap
'''
想要学习Python?Python学习交流群:984632579满足你的需求,资料都已经上传群文件,可以自行下载!
'''
def wrap(string, width=50): # 定义处理字符串宽度的方法
return '\n'.join(textwrap.wrap(string, width)) + '\n' # 返回处理之后的结果
class NewsItem: # 新闻内容类
def __init__(self, title, body, byteNumber=1):
self.title = title # 新闻标题
self.body = body # 新闻主体
self.byteNumber = byteNumber # 每个字符的字节数量
class NNTPSource: # NNTP新闻源类
def __init__(self, server_name, group, window):
self.server_name = server_name # 服务器地址
self.group = group # 新闻组名称
self.window = window # 时间窗口
def getItems(self): # 新闻生成器
yesterday = date.today() - timedelta(days=self.window) # 计算新闻获取的起始时间
server = NNTP(self.server_name) # 创建服务器连接对象
ids = server.newnews(self.group, yesterday)[1] # 获取新闻id列表
count = 0 # 创建计数变量
for id in ids: # 循环获取新闻id
count += 1 # 计数递增
if count <= 10: # 如果计数小于10
article = server.article(id)[1][2] # 获取指定id的新闻文章
lines = [] # 创建每行新闻内容的列表
for line in article: # 从新闻文章中读取每一行内容
lines.append(line.decode()) # 将每行新闻内容解码,添加到新闻内容列表。
message = message_from_string('\n'.join(lines)) # 合并新闻列表内容为字符串并转为消息对象
title = message['subject'].replace('\n', '') # 从消息对象中获取标题
body = message.get_payload() # 从消息对象中获取到新闻主体内容
if message.is_multipart(): # 如果消息对象包含多个部分
body = body[0] # 获取到的内容中第1个部分获取新闻主体内容
yield NewsItem(title, body) # 生成1个新闻内容对象
else: # 如果超出10条内容
break # 跳出循环
server.quit() # 关闭连接
class SimpleWebSource: # 网页新闻源类
def __init__(self, url, titlePattern, bodyPattern):
self.url = url # 网页地址
self.titlePattern = re.compile(titlePattern) # 提取新闻标题的正则表达式
self.bodyPattern = re.compile(bodyPattern) # 提取新闻主体内容的正则表达式
def getItems(self): # 新闻生成器
text = urlopen(self.url).read().decode() # 读取目标网页内容并解码
titles = self.titlePattern.findall(text) # 通过正则表达式获取所有新闻标题
bodies = self.bodyPattern.findall(text) # 通过正则表达式获取所有新闻主体内容
for title, body in zip(titles, bodies): # 将新闻标题和内容混合到一起并获取每1条新闻的标题和主体内容
yield NewsItem(title, wrap(body), 2) # 生成1个每行新闻具有指定宽度并且每个字符宽度为2个字节的新闻内容对象
class PlainDestination: # 普通输出目标类
def receiveItems(self, items): # 定义接收到新闻对象时的处理方法
for item in items: # 遍历新闻对象集合
print(item.title) # 显示输出新闻标题
print('-' * item.byteNumber * len(item.title)) # 显示输出分隔线
print(item.body) # 显示输出新闻主体内容
class HTMLDestination: # HTML文件目标类
def __init__(self, filename):
self.filename = filename # 创建的HTML文件名称
def receiveItems(self, items): # 定义接收到新闻对象时的处理方法
with open(self.filename, 'w', encoding='UTF-8') as file: # 打开或创建HTML文件
file.write('<html>\n' # 写入HTML文件头部内容、网页主体中的标题以及新闻列表的开始标签
'<head>\n'
'<meta charset="UTF-8">\n' # 注意:需要声明网页文件的编码类型
'<title>24小时快讯</title>\n'
'</head>\n'
'<body>\n'
'<h1>24小时快讯</h1>\n'
'<ul>\n')
id = 0 # 初始化HTML文件中的新闻id
for item in items: # 遍历新闻对象集合
id += 1 # 递增新闻id
file.write('<li><a href="#%i">%s</a></li>\n' % (id, item.title)) # 写入1条新闻的列表项并指定id
file.write('</ul>') # 写入新闻列表的结束标签
id = 0 # 初始化HTML文件中的新闻id
for item in items: # 遍历新闻对象集合
id += 1 # 递增新闻id
file.write('<h2><a name="%i">%s</a></h2>\n' % (id, item.title)) # 写入1条新闻的标题并指定id
file.write('<p>' + item.body + '</p>\n') # 写入1条新闻的主体内容
file.write('</body>\n' # 写入HMTL文件主体的结束标签和HTML文件结束标签
'</html>')
class NewsAgent: # 新闻代理类
def __init__(self):
self.sources = [] # 新闻源列表
self.destinations = [] # 输出目标列表
def add_source(self, source): # 添加新闻源的方法
self.sources.append(source)
def add_destinations(self, dest): # 添加输出目标的方法
self.destinations.append(dest)
def distribute(self): # 进行分发的方法
items = [] # 新闻对象列表
for source in self.sources: # 遍历每1个新闻源
items.extend(source.getItems()) # 从新闻源的生成器将所有新闻对象添加到新闻对象列表
for dest in self.destinations: # 遍历每1个输出目标
dest.receiveItems(items) # 调用处理新闻对象的方法处理新闻对象集合
def runDefaultSetup(): # 主程序
agent = NewsAgent() # 创建代理类对象
server_name = 'web.aioe.org' # 指定NNTP服务器
group = 'comp.lang.python' # 指定访问的新闻组
window = 1 # 指定时间窗口
clpa = NNTPSource(server_name, group, window) # 创建新闻源对象
url = r'http://36kr.com/newsflashes' # 指定目标网页地址
titlePattern = r'"pin":"0","title":"(.{10,60})","catch_title"' # 组织提取新闻标题的正则表达式
bodyPattern = r'"description":"(.{100,400})","cover"' # 组织提取新闻主体内容的正则表达式
web = SimpleWebSource(url, titlePattern, bodyPattern) # 创建新闻源对象
agent.add_source(clpa) # 添加NNTP新闻源对象
agent.add_source(web) # 添加网页新闻源对象
agent.add_destinations(PlainDestination()) # 添加普通输出目标对象
agent.add_destinations(HTMLDestination('news.html', )) # 添加输出HTML文件目标对象
agent.distribute() # 调用分发方法
if __name__ == '__main__':
runDefaultSetup() # 执行主程序