LLM应用实战-财经新闻自动聚合
1. 背景
这段时间项目比较忙,所以本qiang~有些耽误了学习,不过也算是百忙之中,抽取时间来支撑一个读者的需求,即爬取一些财经网站的新闻并自动聚合。
该读者看了之前的《AI资讯的自动聚合及报告生成》文章后,想要将这一套流程嵌套在财经领域,因此满打满算耗费了2-3天时间,来完成了该需求。
注意:爬虫不是本人的强项,只是一丢丢兴趣而已; 其次,本篇文章主要是用于个人学习,客官们请勿直接商业使用。
2. 面临的难点
1. 爬虫框架选取: 采用之前现学现用的crawl4ai作为基础框架,使用其高阶技能来逼近模拟人访问浏览器,因为网站都存在反爬机制,如鉴权、cookie等;
2. 外网新闻: 需要kexue上网;
3. 新闻内容解析: 此处耗费的工作量最多,并不是html的页面解析有多难,主要是动态页面加载如何集成crawl4ai来实现,且每个新闻网站五花八门。
3. 数据源
|
数据源 |
url |
备注 |
|
财lian社 |
https://www.cls.cn/depth?id=1000 |
1000: 头条, 1003: A股, 1007: 环球 |
|
凤huang网 |
||
|
新lang |
https://finance.sina.com.cn/roll/#pageid=384&lid=2519&k=&num=50&page=1 https://finance.sina.com.cn/roll/#pageid=384&lid=2672&k=&num=50&page=1 |
2519: 财经 2672: 美股 |
|
环qiu时报 |
https://finance.huanqiu.com |
|
|
zaobao |
国内及世界 |
|
|
fox |
美国及世界 |
|
|
cnn |
https://edition.cnn.com/business https://edition.cnn.com/business/china |
国内及世界 |
|
reuters |
https://www.reuters.com/business |
4. 部分源码
为了减少风险,本qiang~只列出财lian社网页的解析代码,读者如想进一步交流沟通,可私信联系。
代码片段解析:
1. schema是以json格式叠加css样式的策略,crawl4ai基于schema可以实现特定元素的结构化解析
2. js_commands是js代码,主要用于模拟浏览新闻时的下翻页
import asyncio
from crawl4ai import AsyncWebCrawler
from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
import json
from typing import Dict, Any, Union, List
import os
import datetime
import re
import hashlib def md5(text):
m = hashlib.md5()
m.update(text.encode('utf-8'))
return m.hexdigest() def get_datas(file_path, json_flag=True, all_flag=False, mode='r'):
"""读取文本文件"""
results = [] with open(file_path, mode, encoding='utf-8') as f:
for line in f.readlines():
if json_flag:
results.append(json.loads(line))
else:
results.append(line.strip())
if all_flag:
if json_flag:
return json.loads(''.join(results))
else:
return '\n'.join(results)
return results def save_datas(file_path, datas, json_flag=True, all_flag=False, with_indent=False, mode='w'):
"""保存文本文件"""
with open(file_path, mode, encoding='utf-8') as f:
if all_flag:
if json_flag:
f.write(json.dumps(datas, ensure_ascii=False, indent= 4 if with_indent else None))
else:
f.write(''.join(datas))
else:
for data in datas:
if json_flag:
f.write(json.dumps(data, ensure_ascii=False) + '\n')
else:
f.write(data + '\n') class AbstractAICrawler(): def __init__(self) -> None:
pass
def crawl():
raise NotImplementedError() class AINewsCrawler(AbstractAICrawler):
def __init__(self, domain) -> None:
super().__init__()
self.domain = domain
self.file_path = f'data/{self.domain}.json'
self.history = self.init() def init(self):
if not os.path.exists(self.file_path):
return {}
return {ele['id']: ele for ele in get_datas(self.file_path)} def save(self, datas: Union[List, Dict]):
if isinstance(datas, dict):
datas = [datas]
self.history.update({ele['id']: ele for ele in datas})
save_datas(self.file_path, datas=list(self.history.values())) async def crawl(self, url:str,
schema: Dict[str, Any]=None,
always_by_pass_cache=True,
bypass_cache=True,
headless=True,
verbose=False,
magic=True,
page_timeout=15000,
delay_before_return_html=2.0,
wait_for='',
js_code=None,
js_only=False,
screenshot=False,
headers={}): extraction_strategy = JsonCssExtractionStrategy(schema, verbose=verbose) if schema else None async with AsyncWebCrawler(verbose=verbose,
headless=headless,
always_by_pass_cache=always_by_pass_cache, headers=headers) as crawler:
result = await crawler.arun(
url=url,
extraction_strategy=extraction_strategy,
bypass_cache=bypass_cache,
page_timeout=page_timeout,
delay_before_return_html=delay_before_return_html,
wait_for=wait_for,
js_code=js_code,
magic=magic,
remove_overlay_elements=True,
process_iframes=True,
exclude_external_links=True,
js_only=js_only,
screenshot=screenshot
) assert result.success, "Failed to crawl the page"
if schema:
res = json.loads(result.extracted_content)
if screenshot:
return res, result.screenshot
return res
return result.html class FinanceNewsCrawler(AINewsCrawler): def __init__(self, domain='') -> None:
super().__init__(domain) def save(self, datas: Union[List, Dict]):
if isinstance(datas, dict):
datas = [datas]
self.history.update({ele['id']: ele for ele in datas})
save_datas(self.file_path, datas=datas, mode='a') async def get_last_day_data(self):
last_day = (datetime.date.today() - datetime.timedelta(days=1)).strftime('%Y-%m-%d')
datas = self.init()
return [v for v in datas.values() if last_day in v['date']] class CLSCrawler(FinanceNewsCrawler):
"""
财某社新闻抓取
"""
def __init__(self) -> None:
self.domain = 'cls'
super().__init__(self.domain)
self.url = 'https://www.cls.cn' async def crawl_url_list(self, url='https://www.cls.cn/depth?id=1000'):
schema = {
'name': 'caijingwang toutiao page crawler',
'baseSelector': 'div.f-l.content-left',
'fields': [
{
'name': 'top_titles',
'selector': 'div.depth-top-article-list',
'type': 'nested_list',
'fields': [
{'name': 'href', 'type': 'attribute', 'attribute':'href', 'selector': 'a[href]'}
]
},
{
'name': 'sec_titles',
'selector': 'div.depth-top-article-list li.f-l',
'type': 'nested_list',
'fields': [
{'name': 'href', 'type': 'attribute', 'attribute':'href', 'selector': 'a[href]'}
]
},
{
'name': 'bottom_titles',
'selector': 'div.b-t-1 div.clearfix',
'type': 'nested_list',
'fields': [
{'name': 'href', 'type': 'attribute', 'attribute':'href', 'selector': 'a[href]'}
]
}
]
} js_commands = [
"""
(async () => {{ await new Promise(resolve => setTimeout(resolve, 500)); const targetItemCount = 100; let currentItemCount = document.querySelectorAll('div.b-t-1 div.clearfix a.f-w-b').length;
let loadMoreButton = document.querySelector('.list-more-button.more-button'); while (currentItemCount < targetItemCount) {{
window.scrollTo(0, document.body.scrollHeight); await new Promise(resolve => setTimeout(resolve, 1000)); if (loadMoreButton) {
loadMoreButton.click();
} else {
console.log('没有找到加载更多按钮');
break;
} await new Promise(resolve => setTimeout(resolve, 1000)); currentItemCount = document.querySelectorAll('div.b-t-1 div.clearfix a.f-w-b').length; loadMoreButton = document.querySelector('.list-more-button.more-button');
}}
console.log(`已加载 ${currentItemCount} 个item`);
return currentItemCount;
}})();
"""
]
wait_for = '' results = {} menu_dict = {
'1000': '头条',
'1003': 'A股',
'1007': '环球'
}
for k, v in menu_dict.items():
url = f'https://www.cls.cn/depth?id={k}'
try:
links = await super().crawl(url, schema, always_by_pass_cache=True, bypass_cache=True, js_code=js_commands, wait_for=wait_for, js_only=False)
except Exception as e:
print(f'error {url}')
links = []
if links:
links = [ele['href'] for eles in links[0].values() for ele in eles if 'href' in ele]
links = sorted(list(set(links)), key=lambda x: x)
results.update({f'{self.url}{ele}': v for ele in links})
return results async def crawl_newsletter(self, url, category):
schema = {
'name': '财联社新闻详情页',
'baseSelector': 'div.f-l.content-left',
'fields': [
{
'name': 'title',
'selector': 'span.detail-title-content',
'type': 'text'
},
{
'name': 'time',
'selector': 'div.m-r-10',
'type': 'text'
},
{
'name': 'abstract',
'selector': 'pre.detail-brief',
'type': 'text',
'fields': [
{'name': 'href', 'type': 'attribute', 'attribute':'href', 'selector': 'a[href]'}
]
},
{
'name': 'contents',
'selector': 'div.detail-content p',
'type': 'list',
'fields': [
{'name': 'content', 'type': 'text'}
]
},
{
'name': 'read_number',
'selector': 'div.detail-option-readnumber',
'type': 'text'
}
]
} wait_for = 'div.detail-content'
try:
results = await super().crawl(url, schema, always_by_pass_cache=True, bypass_cache=True, wait_for=wait_for)
result = results[0]
except Exception as e:
print(f'crawler error: {url}')
return {} return {
'title': result['title'],
'abstract': result['abstract'],
'date': result['time'],
'link': url,
'content': '\n'.join([ele['content'] for ele in result['contents'] if 'content' in ele and ele['content']]),
'id': md5(url),
'type': category,
'read_number': await self.get_first_float_number(result['read_number'], r'[-+]?\d*\.\d+|\d+'),
'time': datetime.datetime.now().strftime('%Y-%m-%d')
} async def get_first_float_number(self, text, pattern):
match = re.search(pattern, text)
if match:
return round(float(match.group()), 4)
return 0 async def crawl(self):
link_2_category = await self.crawl_url_list()
for link, category in link_2_category.items():
_id = md5(link)
if _id in self.history:
continue
news = await self.crawl_newsletter(link, category)
if news:
self.save(news)
return await self.get_last_day_data() if __name__ == '__main__':
asyncio.run(CLSCrawler().crawl())
5. 总结
一句话足矣~
开发了一款新闻资讯的自动聚合的工具,基于crawl4ai框架实现。
有问题可以私信或留言沟通!
6. 参考
(1) Crawl4ai: https://github.com/unclecode/crawl4ai

LLM应用实战-财经新闻自动聚合的更多相关文章
- CSS实战 模拟 新闻列表
总结:所使用的知识点:1.padding应用以及box-sizing: border-box自动内减撑大的盒子 2.ul>li 的使用,去除黑圆圈 3.a标签的使用,去除默认样式<下划线& ...
- PHP读取sphinx 搜索返回结果完整实战实例
PHP读取sphinx 搜索返回结果完整实战实例 网上搜索N久都没有一个正在读取返回sphinx结果的实例,都是到了matches那里就直接var_dump或者print_r了,没有读取到字段的例子, ...
- 一个使用fasttext训练的新闻文本分类器/模型
fastext是什么? Facebook AI Research Lab 发布的一个用于快速进行文本分类和单词表示的库.优点是很快,可以进行分钟级训练,这意味着你可以在几分钟时间内就训练好一个分类模型 ...
- 基于TF-IDF的新闻标签提取
基于TF-IDF的新闻标签提取 1. 新闻标签 新闻标签是一条新闻的关键字,可以由编辑上传,或者通过机器提取.新闻标签的提取主要用于推荐系统中,所以,提取的准确性影响推荐系统的有效性.同时,对于将标签 ...
- 读书笔记:《HTML5开发手册》--HTML5新的结构元素
读书笔记:<HTML5开发手册> (HTML5 Developer's CookBook) 虽然从事前端开发已有很长一段时间,对HTML5标签也有使用,但在语义化上面理解还不够清晰.之前在 ...
- 读书笔记:《HTML5开发手册》
一.HTML5中新的结构元素 1. HTML5初始文件 1.1.doctype 在之前,doctype的声明是这样的: <!DOCTYPE HTML PUBLIC "-//W3C//D ...
- [JS,NodeJs]个人网站效果代码集合
上次发的个人网站效果代码集合: 代码集合: 1.彩色文字墙[鼠标涟漪痕迹] 2.彩色旋转圆环 [模仿http://www.moma.org/interactives/exhibitions/2012/ ...
- 阿里商业评论 | 互联网POI数据及其在营销中的应用
阿里商业评论 | 互联网POI数据及其在营销中的应用 时间 2014-11-05 10:40:50 阿里研究院 原文 http://www.aliresearch.com/index.php?m- ...
- 仿windows phone风格主界面
使用了ZAKER到最新版本,其主界面采用windows phone的风格,感觉还蛮好看的,挺喜欢的,就模仿写了一下,实现到界面截图如下: 第一版面: 第二版面: 在实现了它到九宫格菜单,还实现了背景图 ...
- SQL语法集锦一:显示每个类别最新更新的数据
本文转载http://www.cnblogs.com/lxblog/archive/2012/09/28/2707504.html (1)显示每个类别最新更新的数据 在项目中经常遇到求每个类别最新显示 ...
随机推荐
- CPU缓存伪共享
CPU缓存什么东西?当然这个问题很多人有可能觉得比较傻,CPU缓存什么,肯定是缓存数据(代码)啊,要不然还能缓存啥,这个确实没问题,但是CPU到底缓存什么样的数据呢?因为对CPU来说,无论是指令,还是 ...
- 墨天轮沙龙 | 腾讯云陈昊:TDSQL-C Serverless应用与技术实践
导读 数据库的发展由对性能的要求,逐步发展为对更为极致成本的要求,Serverless数据库是在高性能云数据库之上的极致成本优化方案.[墨天轮数据库沙龙-Serverless专场]邀请到腾讯云数据库产 ...
- Vue 组件如何进行传值的?
1 父子传值 在子组件标签设置属性,在子组件内使用 props 接收属性值 : 2. 子父传值 在子组件中使用 emit 自定义事件,在子组件标签注册自定义事件 ,接收参数 : 3. vuex 状态管 ...
- kotlin更多语言结构——>相等性
Kotlin 中有两种类型的相等性: - 结构相等(用 equals() 检测); - 引用相等(两个引用指向同一对象). 结构相等 结构相等由 ==(以及其否定形式 !=)操作判断.按照惯例,像 ...
- Solon 3.0 新特性:HttpUtils 了解一下
Solon 3.0 引入一个叫 HttpUtils 小插件,这是一个简单的同步 HTTP 客户端,基于 URLConnection 适配(也支持切换为 OkHttp 适配).使得编写 HTTP 客户端 ...
- ARM 版 OpenEuler 22.03 部署 KubeSphere v3.4.0 不完全指南续篇
作者:运维有术 前言 知识点 定级:入门级 KubeKey 安装部署 ARM 版 KubeSphere 和 Kubernetes ARM 版 KubeSphere 和 Kubernetes 常见问题 ...
- JavaScript网页设计案例
1.引言 在前端开发中,JavaScript 无疑是一个非常重要的语言.它不仅可以用于表单验证.动态内容更新,还能实现复杂的交互效果和动画.通过 JavaScript,网页变得更加生动和富有互动性.本 ...
- PHP将整形数字转为Excel下标
1.背景 这两天在接到一个需求,需要导出一个班级所有学员的所有成绩,在最后excel表处理的时候发现导出的列超过了26列,后面会出现AA之类的下标,所以写了一个函数把数字整型转为Excel对应的下标. ...
- 掀起云端革命!ToDesk云电脑与传统PC电脑的差异分析
在科技日新月异的今天,传统PC电脑的市场地位正悄然发生变化.随着云计算技术的不断成熟与普及,云电脑逐渐走进大众视野,不同于传统PC电脑的高昂的成本和易退化的硬件性能,云电脑正以其轻成本高性能的优势吸引 ...
- Solon 之 STOMP
一.STOMP 简介 如果直接使用 WebSocket 会非常累,就像用 Socket 编写 Web 应用.没有高层级的交互协议,就需要我们定义应用间所发消息的语义,还需要确保连接的两端都能遵循这些语 ...