
在量化投研场景中,实时获取财经新闻及公告的情绪面数据至关重要。本次项目需求为:抓取某财经资讯平台(东方财富/巨潮资讯等)的实时公司公告PDF/HTML,提取文本并分析其“利好/利空”倾向。
技术挑战远非普通爬虫可比:
_signature、token),且请求头需携带特定的时间戳哈希。AnalyzeSentiment接口有UTF-8长度限制)会直接报错。本文将逐一给出生产级解决方案。
aiohttp并发拉取。痛点:目标站点的X-Request-Sign由 timestamp + nonce + secret 经过 HMAC-SHA256 加密,且加密逻辑在压缩JS中。直接Python无法生成。
解决方案:将核心加密逻辑剥离,部署在CVM上构建轻量级Sign Service(FastAPI),SCF每次请求前先获取签名。
// server.js 使用 express
const crypto = require('crypto');
app.get('/getSign', (req, res) => {
const timestamp = Date.now().toString();
const nonce = Math.random().toString(36).substr(2, 15);
const secret = process.env.SECRET_KEY;
const sign = crypto.createHmac('sha256', secret).update(timestamp + nonce).digest('hex');
res.json({ timestamp, nonce, sign });
});在SCF中,使用aiohttp配合Semaphore控制并发。此处记录一个深坑:SCF默认DNS解析缓存时间过长,当代理IP失效时会超时阻塞。解决方案:自定义DNS解析器,并设置超时重试。
import aiohttp
import asyncio
from aiohttp.resolver import AsyncResolver
resolver = AsyncResolver(nameservers=["114.114.114.114"])
connector = aiohttp.TCPConnector(resolver=resolver, ttl_dns_cache=60)
async def fetch_with_retry(session, url, max_retries=3):
for attempt in range(max_retries):
try:
# 先调CVM获取动态签名
async with session.get('http://内部CVM-IP/getSign') as sign_resp:
sign_data = await sign_resp.json()
headers = {
'X-Timestamp': sign_data['timestamp'],
'X-Nonce': sign_data['nonce'],
'X-Sign': sign_data['sign']
}
async with session.get(url, headers=headers, timeout=aiohttp.ClientTimeout(total=10)) as resp:
if resp.status == 200:
return await resp.text()
elif resp.status == 403: # 签名失效
await asyncio.sleep(2**attempt)
else:
break
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
# 踩坑记录:必须清空连接池中的坏连接
connector._conns.clear()
continue
return NonePDF/HTML解析后,文本长度常超 2048 字符,直接调用腾讯云NLP(AnalyzeSentiment)会抛出 InvalidParameterValue.TextTooLong。
方案:采用滑动窗口+情感加权策略。
Positive 与 Negative 概率。from tencentcloud.nlp.v20190408 import models
import re
def split_sentences(text, max_len=300):
raw_sents = re.split(r'[。!?]', text)
sents = []
cur = ""
for s in raw_sents:
if len(cur + s) < max_len:
cur += s + "。"
else:
if cur: sents.append(cur)
cur = s + "。"
if cur: sents.append(cur)
return sents
def nlp_analyze_batch(client, sents):
total_pos, total_neg, total_weight = 0, 0, 0
for sent in sents:
if len(sent) < 5: continue
req = models.AnalyzeSentimentRequest()
req.Text = sent
resp = client.AnalyzeSentiment(req)
# 腾讯云NLP返回 Positive, Negative, Neutral
pos = resp.Positive
neg = resp.Negative
weight = len(sent) # 长度加权
total_pos += pos * weight
total_neg += neg * weight
total_weight += weight
return total_pos/total_weight, total_neg/total_weight发现问题:纯调用腾讯云NLP,发现对“业绩预亏符合预期”、“减持完毕”等投行术语常常误判为消极或中性,准确率仅 84%。
解决方案:利用Scikit-learn训练一个极轻量的 Logistic Regression 辅助模型,使用 TF-IDF + 自定义财务情感词典(如“扭亏为盈”+1,“ST风险”-1)。最终将腾讯云NLP的概率输出与自定义模型输出作为特征,训练一个元分类器(Meta-learner)。
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
# 伪代码流程
# 1. 已有标注数据集 (X_train, y_train)
# 2. 调用腾讯云NLP获取概率特征 prob_cloud
# 3. 本地TF-IDF + LR 获取本地概率 prob_local
# 4. 特征拼接: X_meta = np.column_stack((prob_cloud, prob_local))
# 5. 元分类器(LogisticRegression)再次拟合
# 结果:集成后准确率从 84% -> 92.3%,且有效避免长文本截断误差。我在标准型S5(2核4G) CVM 和 SCF(内存配置 1024MB) 上分别跑了 1000 条任务:
指标 | 纯CVM方案 | SCF+预留并发(10实例)方案 |
|---|---|---|
总耗时(含解析) | 18分20秒 | 3分15秒 |
费用估算(月/日活1万) | 固定带宽费 ~300元 | 按量计费 ~120元 |
冷启动导致超时率(首次) | 0% | 8% (第二次调用后降为0) |
应对SCF冷启动优化:通过在SCF初始化阶段(global域)建立连接池和加载NLP Client,避免执行时加载依赖。
# 全局初始化(仅冷启动执行一次)
client = None
def get_nlp_client():
global client
if not client:
cred = credential.Credential(os.getenv("SECRET_ID"), os.getenv("SECRET_KEY"))
client = nlp_client.NlpClient(cred, "ap-guangzhou")
return clientasyncio 事件循环冲突:在 main_handler 中直接 asyncio.run() 会报错 RuntimeError: Event loop is closed。修复:使用 loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop); loop.run_until_complete(main())。stream.seek(0) 重置指针,否则文件损坏。本文不再拘泥于常规的静态页面爬虫,而是聚焦于财经领域动态加密接口的破解、云函数异步编程的坑点以及NLP长文本的企业级解决方案。通过将腾讯云NLP与本地轻量模型融合,在保证准确率的前提下极大降低了运维成本。该架构已在笔者团队内部稳定运行两个月,日处理数据 5W+ 条。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。