Python基础教程书籍案例:新闻采集(新闻聚合)【下】

这一篇教程,我们一起采用一种更复杂,但是更具有扩展性、更易维护的方式来实现新闻采集的功能。

在上一篇教程中,已经提到我们会分别对NNTP服务器的新闻内容以及网页中的新闻内容进行获取,并且以不同的格式输出。

新闻的来源有两种:

大家可以通过上一篇教程的代码,以及访问上方36Kr的网页地址了解来源的不同形式。

最终输出的目标格式也有两种:

  • Print显示输出
  • HTML文件

目标格式下方图片所示。

Print显示输出:

《Python基础教程书籍案例:新闻采集(新闻聚合)【下】》
《Python基础教程书籍案例:新闻采集(新闻聚合)【下】》

HTML文件:

《Python基础教程书籍案例:新闻采集(新闻聚合)【下】》
《Python基础教程书籍案例:新闻采集(新闻聚合)【下】》

通过上方两张图片,大家能够看出,我们是从两个新闻源获取到新闻内容并混合到一起,采用不同的格式输出这些新闻内容。

为了清晰的实现这个功能,我们先进行一下结构的划分:

  • 新闻来源类型(NNTP来源类和Web来源类)
  • 新闻内容(新闻类)
  • 报告类型(普通显示输出类和HTML文件输出类)
  • 处理过程(代理类)
  • 主程序

根据不同类的功能,我们能够看到有明确的分层。

  • 不同的新闻来源构成后端
  • 不同的输出类型构成前端
  • 处理过程代理类是中间层

最终,通过主程序将这些具有不同功能的类结合到一起,共同实现目标功能。

1、添加需要使用的模块

在实现这个功能的过程中,我们需要用到很多模块。

示例代码:

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条新闻的标题和主体内容。

具体实现过程,大家通过代码中的注释进行理解。

示例代码:

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()  # 执行主程序

以上就是这个练习项目的完整实现过程。

    原文作者:NicePython
    原文地址: https://zhuanlan.zhihu.com/p/60570481
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞