Skip to content

12 动手实战:挑选后生成简报

你好,我是叶伟民。

上一节课我们完成了项目环境搭建,完成了代码主干,设置了任务计划以每天运行脚本。

这一节课我们将读取元数据,抓取新闻内容,对新闻进行摘要,翻译标题,翻译全文内容,最后整合成更方便用户阅读查看的简报,然后自动打开简报。

我们先回到 feed.py 文件,把最后两行代码删除掉。

if __name__ == "__main__":
    新闻列表 = 获取数据()

所需要的类

另外我们需要新建一个py文件来声明我们所需要的类,我们将这个py文件命名为 新闻.py,然后添加以下代码。

import json

class 新闻:
    def __init__(self):
        self.元数据 = 元数据()
        self.新闻内容 = None
        self.新闻内容_中文翻译 = None
        self.摘要 = None

    def set_元数据(self, 元数据):
        self.元数据 = 元数据

    def set_新闻内容(self, 新闻内容):
        self.新闻内容 = 新闻内容

    def set_新闻内容_中文翻译(self, 新闻内容_中文翻译):
        self.新闻内容_中文翻译 = 新闻内容_中文翻译

    def set_标题_中文翻译(self, 标题_中文翻译):
        self.元数据.标题_中文翻译 = 标题_中文翻译

    def set_摘要(self, 摘要):
        self.摘要 = 摘要

class 新闻Encoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, 新闻):
            return {"元数据": json.loads(json.dumps(obj.元数据, cls=元数据Encoder)), "新闻内容": obj.新闻内容,"摘要": obj.摘要}
        return super().default(obj)

class 元数据:
    def __init__(self):
        self.标题 = None
        self.标题_中文翻译 = None
        self.作者 = None
        self.创建日期 = None
        self.url = None

    def set_标题(self, 标题):
        self.标题 = 标题

    def set_标题_中文翻译(self, 标题_中文翻译):
        self.标题_中文翻译 = 标题_中文翻译

    def set_作者(self, 作者):
        self.作者 = 作者

    def set_创建日期(self, 创建日期):
        self.创建日期 = 创建日期

    def set_url(self, url):
        self.url = url

class 元数据Encoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, 元数据):
            return {"标题": obj.标题,"标题_中文翻译": obj.标题_中文翻译, "作者": obj.作者, "创建日期": obj.创建日期, "url": obj.url}
        return super().default(obj)

然后我们打开 feed.py 文件,在头部添加以下代码导入以上这些类。

from 新闻 import *

现在我们可以读取新闻的元数据了。

读取元数据

我们打开 feed.py 文件,在最底部加入以下代码。

def 获取元数据(current):
    元数据实例 = 元数据()
    元数据实例.set_标题(current.title)
    元数据实例.set_作者(current.author)
    元数据实例.set_创建日期(current.published_parsed)
    元数据实例.set_url(current.link)
    return 元数据实例

以上代码接受一个参数,生成了一个元数据类的实例,并把参数里面的值赋给这个实例。

根据元数据过滤新闻

然后我们根据元数据里面的创建时间过滤出今天的新闻。我们继续在 feed.py 文件最底部加入以下代码。

def 根据元数据过滤新闻(current元数据):
    今天 = date.today()
    今天struct_time格式 = time.struct_time(今天.timetuple())
    return current元数据.创建日期 > 今天struct_time格式

这里的第5行代码用来判断新闻创建日期是否大于今天,并返回判断结果。

抓取新闻内容

然后我们开始定义抓取新闻内容的函数。

def 抓取新闻内容(url):
    response = requests.get(url)
    response.encoding = 'utf-8' # 指定编码
    soup = BeautifulSoup(response.text, 'html.parser')

    正文 = soup.find('div', {'class': 'c-pageArticle_content'})
    if 正文 is not None and 正文.text is not None:
        return 正文.text
    else:
        return None

以上函数用于爬取和解析网页,其中第6行可能会根据网页布局的变化而变化,如果同学们跑不通,则需要更新一下这行代码。

除此之外,我们还需要回到 Anaconda Powershell Prompt 命令行,安装相关依赖。

pip install requests
pip install bs4

文本摘要和机器翻译的基础代码

接下来我们可以进行文本摘要和机器翻译了。在此之前,我们需要新开一个py文件,命名为 rag.py,然后把RAG相关内容放进去。

import json
import os

import requests

#region 跟具体大模型相关的,如果需要修改大模型,可能需要修改这部分函数
文本划分长度 = 1500

def get_access_token():
  ernie_client_id = os.getenv("baiduclientid")
  ernie_client_secret = os.getenv("baiduclientsecret")
  url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={ernie_client_id}&client_secret={ernie_client_secret}"

  playload = json.dumps("")
  headers = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
  }
  response = requests.request("POST", url, headers=headers, data=playload)
  return response.json().get("access_token")

def 对话模式(messages):
  url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-lite-8k?access_token=" + get_access_token()

  json_obj = {
      "messages": messages,
  }

  playload = json.dumps(json_obj)
  headers = {
      'Content-Type': 'application/json'
  }

  response = requests.request("POST", url, headers=headers, data=playload)
  json_result = json.loads(response.text)
  if "error_code" in json_result:
    return json_result["error_msg"] + ":" + playload
  else:
    return json_result["result"]
#endregion

#region 构造messages相关
def 构造文本摘要messages(输入字符串):
  messages=[
  {"role": "user", "content": f"""
  请对以下文本进行摘要:

  {输入字符串}
  """},
  ]
  return messages

def 构造英译中messages(输入字符串):
  messages=[
  {"role": "user", "content": f"""
  请将以下文本翻译成中文:

  {输入字符串}
  """},
  ]
  return messages

#endregion

这里有几个地方我们要注意一下。其中第7行是前面第10节课提到的文本划分长度。从第9到40行我们在实战案例1接触过。第42到第63行代码就是我们在第10节课提到的构造文本摘要和机器翻译的messages代码。

对新闻文本摘要

现在我们可以对新闻文本进行摘要。我们继续在 feed.py 文件最底部加入以下代码。

def 按长度划分文本(输入文本, 文本划分长度):
    return [输入文本[i:i+文本划分长度] for i in range(0, len(输入文本), 文本划分长度)]

def 文本摘要(输入文本):
    return 对话模式(构造文本摘要messages(输入文本))

def 对长文本进行摘要(输入文本):
  if len(输入文本) > 文本划分长度:
    文本list = 按长度划分文本(输入文本, 文本划分长度)
    文本摘要结果 = ''
    for 当前文本 in 文本list:
    #   print('当前文本='+当前文本)
      当前文本摘要结果=文本摘要(当前文本)
    #   print('当前文本摘要结果='+当前文本摘要结果)
      文本摘要结果 += 当前文本摘要结果 + '\n'
    #   print('文本摘要结果='+文本摘要结果)
    return 文本摘要结果
  else:
    return 文本摘要(输入文本)

其中第1到2行代码是按长度划分文本的代码,第3到19行代码我们在 第10节课 讲述过(如果需要回顾可以点开超链接看一下)。

机器翻译

现在我们继续编写机器翻译代码。我们继续在 feed.py 文件最底部加入以下代码。

def 翻译成中文(输入文本):
    return 对话模式(构造英译中messages(输入文本))

def 对长文本进行翻译(输入文本):
  if len(输入文本) > 文本划分长度:
    文本list = 按长度划分文本(输入文本, 文本划分长度)
    文本翻译结果 = ''
    for 当前文本 in 文本list:
      当前文本翻译结果=翻译成中文(当前文本)
      文本翻译结果 += 当前文本翻译结果 + '\n'
    return 文本翻译结果
  else:
    return 翻译成中文(输入文本)

以上代码我们在第10节课讲述过了。

生成简报

现在我们可以生成简报了。我们新开一个py文件,命名为 生成简报.py,然后加入以下代码。

from datetime import datetime
import subprocess

def 生成每日简报(今天的新闻list:list):
    print("生成每日简报")

    now = datetime.now()

    formatted_date = now.strftime("%Y-%m-%d")

    每日简报文件 = f"{formatted_date}.CNET每日简报.html"

    with open(每日简报文件, 'w', encoding='utf-8') as f:
        f.write('''<html>
    <head>
        <title>CNET每日简报</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
        <link
      href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.2/css/bootstrap.min.css"
      rel="stylesheet"
    />
    <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.11.0/font/bootstrap-icons.css" rel="stylesheet">
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.2/js/bootstrap.bundle.min.js"></script>
    </head>
<body>

                ''')
        f.write(f"<h1>CNET每日简报-{formatted_date}</h1>")
        f.write('<div class="container" id="main_div">')
        for current in 今天的新闻list:
            f.write('''
<div class="row">
            <div class="accordion" id="accordionExample">
                <div class="card">
                <div class="card-header" id="headingOne">
                    <h2 class="mb-0">
                    <button class="btn btn-link text-left" type="button" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne"><p><h3>
                    ''')
            f.write(current.元数据.标题_中文翻译)
            f.write("</h3></p><p>")
            f.write(current.摘要)
            f.write('''
 </p>
                    </button>
                    </h2>
                </div>
                <div id="collapseOne" class="collapse" aria-labelledby="headingOne" data-parent="#accordionExample">
                    <div class="card-body"><p>
                    ''')
            f.write(current.新闻内容_中文翻译)
            f.write('''
</p
<p><a href="
''')
            f.write(current.元数据.url)
            f.write('''
" target="_blank">阅读原文</a></p>
                    </div>
                </div>
            </div>
        </div>
''')
        f.write("</ul>")
        f.write("</div></body></html>")

    return 每日简报文件
def 打开每日简报(每日简报文件):
    subprocess.run(['start', 每日简报文件], shell=True, check=True)

以上代码只有两个函数,一个生成简报,一个打开简报。主要代码是HTML相关代码。

然后我们回到 feed.py 文件,在头部添加以下代码导入以上这些函数。

from 新闻 import *

现在万事俱备,只欠东风。所有需要的函数我们都有了,现在我们把它们整合成工作流。

整合工作流

我们回到 feed.py 文件,在底部添加以下代码。

if __name__ == "__main__":
    新闻列表 = 获取数据()
    print(f'共获取{len(新闻列表)}条新闻')
    今天的新闻list = []
    入库条数上限 = 3 # 出于教学和调试目的设置的上限,你可以设置为0,表示不限制入库条数
    当前入库条数 = 0
    for current in 新闻列表:
        current元数据 = 获取元数据(current)
        if 根据元数据过滤新闻(current元数据):
            if 入库条数上限 > 0 and 当前入库条数 >= 入库条数上限:
                break

            今天的新闻 = 新闻()
            今天的新闻.set_元数据(current元数据)
            print('属于今天的新闻,准备处理')
            今天的新闻.set_新闻内容(抓取新闻内容(今天的新闻.元数据.url))
            if 今天的新闻 is not None and 今天的新闻.新闻内容 is not None:
                今天的新闻.set_摘要(对长文本进行摘要(今天的新闻.新闻内容))
                今天的新闻.set_标题_中文翻译(翻译成中文(今天的新闻.元数据.标题))
                今天的新闻.set_新闻内容_中文翻译(对长文本进行翻译(今天的新闻.新闻内容))
                今天的新闻list.append(今天的新闻)
                当前入库条数 += 1
        else:
            print('不是今天的新闻,跳过')

    with open('result.json', 'w', encoding='utf-8') as f:
        json.dump(今天的新闻list, f, ensure_ascii=False, indent=4, cls=新闻Encoder)
    每日简报文件 = 生成每日简报(今天的新闻list)
    打开每日简报(每日简报文件)

我们来看看完整的工作流程,一共是十个步骤,

  1. 从第7行开始遍历新闻列表。
  2. 在第8行获取元数据。
  3. 第9行根据元数据过滤新闻。
  4. 第14行设置元数据。
  5. 第16行抓取新闻内容。
  6. 第18行开始摘要。
  7. 第19行对标题翻译。
  8. 第20行对内容翻译。
  9. 第28行生成每日简报。
  10. 第29行打开每日简报。

运行程序

现在所有代码都编写完毕,让我们来运行一下。

打开命令行,运行 feed.bat 文件。注意后面的文件名里应该是 你替换后的项目路径,可能和我这里展示的不一样。

运行之后,程序将会打开浏览器,展示如下界面。

好了,现在我们已经生成了 每日新闻简报。但是如果我们不仅仅想看当天的简报,我们还想看前天甚至最近一周的简报,那该怎么办呢?下一节课我们再讨论这个问题。

小结

好了,今天这一讲到这里就结束了,最后我们来回顾一下。这一讲我们学会了四件事情。

第一件事是根据元数据过滤出今天的新闻。我们首先读取元数据,然后根据元数据里面的创建时间来过滤出今天的新闻。

第二件事是抓取新闻内容,并进行摘要和翻译。我们将对标题和内容进行翻译。

第三件事是生成和打开简报。

第四件事是将以上所有函数整合成一个工作流,然后使用 feed.bat 文件运行起来。

思考题

学习学习,既要学又要习,才能真正掌握一项技能,现在动起手来吧,基于今天课程的代码去生成你感兴趣的每日简报。

如果你觉得有收获的话,欢迎你把这节课分享出去,让更多的人了解将数据获取进RAG应用的方法。