feat(route/eastmoney): 增加支持东方财富类型为个股研报的研究报告 (#16526)

* feat(route/eastmoney): 增加支持东方财富类型为个股研报的研究报告

* feat(route/eastmoney): 个股研报标题增加公司名

* feat(route/eastmoney): 东方财富个股研报获取收益/市盈率 方法合并
This commit is contained in:
ueiu
2024-08-24 22:08:05 +08:00
committed by GitHub
parent 534da1549b
commit 0888a2c259
3 changed files with 134 additions and 15 deletions
+63 -15
View File
@@ -1,8 +1,14 @@
import { Route } from '@/types';
import { getCurrentPath } from '@/utils/helpers';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { art } from '@/utils/render';
import path from 'node:path';
import { getRatingChangeStr, getEpsOrPeStr } from '../utils';
const __dirname = getCurrentPath(import.meta.url);
export const route: Route = {
path: '/report/:category',
@@ -25,9 +31,9 @@ export const route: Route = {
name: '研究报告',
maintainers: ['syzq'],
handler,
description: `| 策略报告 | 宏观研究 | 券商晨报 | 行业研究 |
| -------------- | ----------- | ------------ | -------- |
| strategyreport | macresearch | brokerreport | industry |`,
description: `| 策略报告 | 宏观研究 | 券商晨报 | 行业研究 | 个股研报 |
| -------------- | ----------- | ------------ | -------- | -------- |
| strategyreport | macresearch | brokerreport | industry | stock |`,
};
async function handler(ctx) {
@@ -39,12 +45,14 @@ async function handler(ctx) {
industry: '行业研报',
macresearch: '宏观研究',
strategyreport: '策略报告',
stock: '个股研报',
};
const linkType = {
brokerreport: 'zw_brokerreport',
industry: 'zw_industry',
macresearch: 'zw_macresearch',
strategyreport: 'zw_strategy',
stock: 'info',
};
const res = await got(`${baseUrl}/report/${category}`);
@@ -56,27 +64,67 @@ async function handler(ctx) {
.match(/var initdata(.=?)(.*?);/)[2]
);
const list = initData.data.map((item) => ({
title: `[${item.orgSName}]${item.title}`,
link: `${baseUrl}/report/${linkType[category]}.jshtml?encodeUrl=${item.encodeUrl}`,
pubDate: parseDate(item.publishDate),
author: item.researcher,
}));
const list = initData.data.map((item) => {
const stockName = category === 'stock' ? `[${item.stockName}]` : '';
return {
title: `[${item.orgSName}]${stockName}${item.title}`,
link: `${baseUrl}/report/${linkType[category]}` + (category === 'stock' ? `/${item.infoCode}.html` : `.jshtml?encodeUrl=${item.encodeUrl}`),
pubDate: parseDate(item.publishDate),
author: item.researcher,
originItem: item, // temp use
};
});
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
list.map((item) => {
const tempOriginItem = item.originItem;
delete item.originItem; // temp use
return cache.tryGet(item.link, async () => {
try {
const { data: response } = await got(item.link);
const $ = load(response);
item.link = $('.pdf-link').attr('href');
item.description = $('.ctx-content').text();
if (category === 'stock') {
const { title, stockName, stockCode, emRatingName, ratingChange, orgSName, indvInduName } = tempOriginItem;
const ratingChangeStr = getRatingChangeStr(ratingChange);
const currentYear = new Date().getFullYear();
const nextYear = currentYear + 1;
const description = $('.newsContent').html();
const enclosureUrl = $('#ContentBody .rightlab').attr('href');
const predictThisYearEps = getEpsOrPeStr(tempOriginItem.predictThisYearEps, 3);
const predictThisYearPe = getEpsOrPeStr(tempOriginItem.predictThisYearPe, 2);
const predictNextYearEps = getEpsOrPeStr(tempOriginItem.predictNextYearEps, 3);
const predictNextYearPe = getEpsOrPeStr(tempOriginItem.predictNextYearPe, 2);
item.enclosure_url = enclosureUrl;
item.description = art(path.join(__dirname, '../templates/stock_description.art'), {
title,
stockName,
stockCode,
emRatingName,
ratingChangeStr,
description,
orgSName,
predictThisYearEps,
predictThisYearPe,
predictNextYearEps,
predictNextYearPe,
indvInduName,
currentYear,
nextYear,
enclosureUrl,
});
} else {
item.link = $('.pdf-link').attr('href');
item.description = $('.ctx-content').text();
}
return item;
} catch {
return item;
}
})
)
});
})
);
return {
@@ -0,0 +1,36 @@
<table>
<tbody>
<tr>
<th rowspan="2">股票代码</th>
<th rowspan="2">股票简称</th>
<th rowspan="2">报告名称</th>
<th rowspan="2">东财评级</th>
<th rowspan="2">评级变动</th>
<th colspan="2">{{ currentYear }}盈利预测</th>
<th colspan="2">{{ nextYear }}盈利预测</th>
<th rowspan="2">行业</th>
</tr>
<tr>
<th>收益</th>
<th>市盈率</th>
<th>收益</th>
<th>市盈率</th>
</tr>
<tr>
<td>{{ stockCode }}</td>
<td>{{ stockName }}</td>
<td><a href="{{ enclosureUrl }}">{{ title }}</a></td>
<td>{{ emRatingName }}</td>
<td>{{ ratingChangeStr }}</td>
<td>{{ predictThisYearEps }}</td>
<td>{{ predictThisYearPe }}</td>
<td>{{ predictNextYearEps }}</td>
<td>{{ predictNextYearPe }}</td>
<td>{{ indvInduName }}</td>
</tr>
</tbody>
</table>
<div>
{{@ description }}
</div>
+35
View File
@@ -0,0 +1,35 @@
function getRatingChangeStr(ratingChange) {
let ratingChangeName = '';
switch (String(ratingChange)) {
case '0':
ratingChangeName = '调高';
break;
case '1':
ratingChangeName = '调低';
break;
case '2':
ratingChangeName = '首次';
break;
case '3':
ratingChangeName = '维持';
break;
case '4':
ratingChangeName = '无';
break;
default:
ratingChangeName = '-';
break;
}
return ratingChangeName;
}
// 按照东方财富的 js 规则:收益是保留三位小数,市盈率是保留两位小数
function getEpsOrPeStr(epsOrPe, keepDecimal) {
let EpsOrPeStr = '';
if (epsOrPe !== undefined) {
EpsOrPeStr = epsOrPe === '' ? '' : Number(epsOrPe).toFixed(keepDecimal);
}
return EpsOrPeStr;
}
export { getRatingChangeStr, getEpsOrPeStr };