12 动手实战:挑选后生成简报
你好,我是叶伟民。
上一节课我们完成了项目环境搭建,完成了代码主干,设置了任务计划以每天运行脚本。
这一节课我们将读取元数据,抓取新闻内容,对新闻进行摘要,翻译标题,翻译全文内容,最后整合成更方便用户阅读查看的简报,然后自动打开简报。
我们先回到 feed.py
文件,把最后两行代码删除掉。
所需要的类
另外我们需要新建一个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
文件,在头部添加以下代码导入以上这些类。
现在我们可以读取新闻的元数据了。
读取元数据
我们打开 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
命令行,安装相关依赖。
文本摘要和机器翻译的基础代码
接下来我们可以进行文本摘要和机器翻译了。在此之前,我们需要新开一个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
文件,在头部添加以下代码导入以上这些函数。
现在万事俱备,只欠东风。所有需要的函数我们都有了,现在我们把它们整合成工作流。
整合工作流
我们回到 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)
打开每日简报(每日简报文件)
我们来看看完整的工作流程,一共是十个步骤,
- 从第7行开始遍历新闻列表。
- 在第8行获取元数据。
- 第9行根据元数据过滤新闻。
- 第14行设置元数据。
- 第16行抓取新闻内容。
- 第18行开始摘要。
- 第19行对标题翻译。
- 第20行对内容翻译。
- 第28行生成每日简报。
- 第29行打开每日简报。
运行程序
现在所有代码都编写完毕,让我们来运行一下。
打开命令行,运行 feed.bat
文件。注意后面的文件名里应该是 你替换后的项目路径,可能和我这里展示的不一样。
运行之后,程序将会打开浏览器,展示如下界面。
好了,现在我们已经生成了 每日新闻简报。但是如果我们不仅仅想看当天的简报,我们还想看前天甚至最近一周的简报,那该怎么办呢?下一节课我们再讨论这个问题。
小结
好了,今天这一讲到这里就结束了,最后我们来回顾一下。这一讲我们学会了四件事情。
第一件事是根据元数据过滤出今天的新闻。我们首先读取元数据,然后根据元数据里面的创建时间来过滤出今天的新闻。
第二件事是抓取新闻内容,并进行摘要和翻译。我们将对标题和内容进行翻译。
第三件事是生成和打开简报。
第四件事是将以上所有函数整合成一个工作流,然后使用 feed.bat
文件运行起来。
思考题
学习学习,既要学又要习,才能真正掌握一项技能,现在动起手来吧,基于今天课程的代码去生成你感兴趣的每日简报。
如果你觉得有收获的话,欢迎你把这节课分享出去,让更多的人了解将数据获取进RAG应用的方法。