当前位置: 首页 → 技术圈 → 

HTML

技术选股

 

为排除业绩地雷,有人提到一个选股指标:最近三年净利润同比增长10%+,且净资产收益率(ROE)15%+。于是很好奇究竟是哪些股票符合“绩优”的范畴?

虽然在部分APP、网站有提供此类选股功能,但同时一直也想着以爬虫的名义做些技术性的尝试,于是便有了这个DEMO。

1. 抓取全市场股票数据

数据来源无非东财、同花顺等,这里选择了东财。

访问http://quote.eastmoney.com/stock_list.html页面,可以看到全部的股票列表。通过查看HTML可以看到其页面结构,是两个UL列表,因此,便可通过requestcheerio模块获取数据并加以处理了。如下:

request({
    method: 'get',
    uri: 'http://quote.eastmoney.com/stock_list.html',
    encoding: null
}, (err, response, body) => {
    if (err) {
        return cb(err);
    }
    const $ = cheerio.load(iconv.decode(body, 'gb2312'), {decodeEntities: false});
    const list = [];
    const $stocks = $('#quotesearch li > a');
    $stocks.each((i, ele) => {
        const $ele = $(ele);
        const href = $ele.attr('href');
        if (href) {
            const code = href.match(/(s[hz])(\d{6})/i);
            const name = $ele.html().match(/([^()]+)/i)[1];
            if (code) {
                list.push({
                    name,
                    code: code[2],
                    label: code[0],
                    market: code[1]
                });
            }
        }
    });
    cb(null, list);
});

抓取的结果会涉及中文编码的问题,因此,需要额外进行处理:

const $ = cheerio.load(iconv.decode(body, 'gb2312'), {decodeEntities: false});

2. 存储股票数据

获取数据后,根据其数据内容建立相应的库表,以进行持久化存储。这里采用async.times和事务结合的方式进行处理。如下:

models.sequelize.transaction((t) =>
    new Promise((resolve, reject) => {
        async.times(data.length, (i, nextFn) => {
            Stock.create(data[i], {
                transaction: t
            }).then((stock) => nextFn(null, stock));
        }, (err, result) => {
            if (err) {
                reject(util.catchError({
                    status: 500,
                    code: STATUS_CODES.STOCK_SAVE_ERROR,
                    message: 'Stock Save Error.',
                    messageDetail: `${data.length} stocks save failed.`,
                    data
                }));
            } else {
                resolve(result);
            }
        });
    })
).then(successCb, errorCb);

3. 抓取股票财务数据

进入东财的股票详情页面,再点击进入“F10财务分析”页面,通过网络请求可以拿到财务指标的数据接口。对其URL进行分析,可以找到其规律:http://f10.eastmoney.com/NewFinanceAnalysis/MainTargetAjax?type=1&code=xxx,其中的code就是股票代码参数(含前缀)。因此,抓取代码如下:

request({
    method: 'get',
    uri: `http://f10.eastmoney.com/NewFinanceAnalysis/MainTargetAjax?type=1&code=${stock.label}`,
    json: true,
    encoding: null
}, (err, res, data) => {
    if (err) {
        return cb(err);
    }

    cb(null, {finance: data, stock});
});

4. 存储财务数据

同存储股票数据,将抓取到的财务数据的所有字段进行持久化存储。代码如下:

async.times(data.stocks.length, (i, nextFn) => {
    stockService.importStockFinance(data.stocks[i], (err, finance) => {
        console.log(`Process progress: ${(i + 1)} / ${data.stocks.length}`);
        nextFn(null, finance);
    });
}, (err, result) => {
    if (err) {
        return next(err);
    }
    console.log(`${data.stocks.length} stocks is imported. Be Happy! -_^`);
    logger.info(formatOpLog({
        fn: 'importStockFinance',
        msg: `finance data is imported.`,
        req
    }));

    res.type('application/json');
    res.send({
        status: 200,
        code: STATUS_CODES.SUCCESS,
        message: null,
        data: result.length
    });
});

5. 数据分析

拿到数据之后便可以进行数据分析了。我们的目标是筛选出最近三年每年归属净利润同比增长10%(或15%)以上,且加权净资产收益率在15%以上的股票。

最初想法是直接在SQL中分组过滤三年的数据,但过于复杂,且性能并没有太多优势,于是便转而在应用层实现。代码如下:

const stocks = {};
result.stocks.forEach((stock) => {
    stocks[stock.stockCode] = stocks[stock.stockCode] || [];
    if (stocks[stock.stockCode].length < 3) {
        stocks[stock.stockCode].push(stock);
    }
});
const stockList = [];
Object.keys(stocks).forEach((code) => {
    let match = true;
    for (let finance of stocks[code]) {
        const gsjlrtbzz = parseFloat(finance.gsjlrtbzz) || 0;
        const jqjzcsyl = parseFloat(finance.jqjzcsyl) || 0;
        if (gsjlrtbzz < 10 || jqjzcsyl < 15 || finance.date < '2017-01-01') {
            match = false;
            break;
        }
    }
    if (!match) {
        delete stocks[code];
    } else {
        stockList.push(stocks[code]);
    }
});

res.render(`${appConfig.pathViews}/stock-filter`, {
    stockList
});

6. 结果展示

最后一步便是对查询结果进行友好的界面化展示,这里无疑是选择表格(或列表)。代码如下:

<div style="font-weight: bold;margin: 10px 0;">
    总数:<span style="color: #f00;"><?- stockList.length ?></span> 只股票
</div>
<table>
    <tr>
        <th>代码</th>
        <th>名称</th>
        <th>报告期</th>
        <th>营业总收入同比增长(%)</th>
        <th>归属净利润同比增长(%)</th>
        <th>扣非净利润同比增长(%)</th>
        <th>加权净资产收益率(%)</th>
        <th>摊薄净资产收益率(%)</th>
        <th>摊薄总资产收益率(%)</th>
        <th>资产负债率(%)</th>
    </tr>
    <? stockList.forEach(function(finance){ ?>
    <? finance.forEach(function(item, itemIdx){ ?>
    <tr>
        <? if(itemIdx < 1){ ?>
        <td rowspan="<?- finance.length ?>"><?- finance[0].Stock.code ?></td>
        <td rowspan="<?- finance.length ?>"><?- finance[0].Stock.name ?></td>
        <? } ?>
        <td><?- item.date ?></td>
        <td><?- item.yyzsrtbzz ?></td>
        <td><?- item.gsjlrtbzz ?></td>
        <td><?- item.kfjlrtbzz ?></td>
        <td><?- item.jqjzcsyl ?></td>
        <td><?- item.tbjzcsyl ?></td>
        <td><?- item.tbzzcsyl ?></td>
        <td><?- item.zcfzl ?></td>
    </tr>
    <? }) ?>
    <? }) ?>
</table>

效果如下:

绩优股

至此,完美拿到想要的结果。

通过这个结果可知,大A的“核心资产”或绩优股并不多,区区不到200只,占比不过4.52%

 

附:符合上述条件的绩优股名单

序号 代码 名称 序号 代码 名称 序号 代码 名称 序号 代码 名称
1 300003 乐普医疗 51 600436 片仔癀 101 603506 南都物业 151 002179 中航光电
2 300014 亿纬锂能 52 600452 涪陵电力 102 603517 绝味食品 152 002262 恩华药业
3 300015 爱尔眼科 53 600466 蓝光发展 103 603568 伟明环保 153 002271 东方雨虹
4 300122 智飞生物 54 600486 扬农化工 104 603579 荣泰健康 154 002311 海大集团
……

注:红色字体部分表示净利润同比15%+。

获取完整绩优股股票列表(包含以上两种组合条件的筛选结果),请搜索微信公众号:黑匣子思维,或扫描下方二维码添加关注后,在公众号聊天界面回复“绩优股”,即可收到。


公众号:黑匣子思维
 

*

*

* 验证码

*