MATLAB爬虫爬取股票数据
近年来,大数据盛行,有关爬虫的教程层次不穷。那么,爬虫到底是什么呢?
什么是爬虫?
百度百科是这样定义的:
网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。更多解释
就我个人理解,所谓的爬虫,就是代替人工复制粘贴去获取网络资源。平常我们需要批量下载图片、下载表格数据时,在没有爬虫的帮助下,只能借助CTRL+C 、CTRL+V 了,非常的繁琐,还容易出错。但是,你会发现,这些资源呈现出来,都是经过整理的。图片的链接是有规律的字符串,数据的网页源码是有规律的标签包住的(比如用的是同一个Class,同一种标签。)。这些都是可以程序化的东西。我们通过编程,将这些有规律的东西,用正则表达式来表达出来,然后交给代码去提取内容,这样就是爬虫爬取数据的具体表现了。
MATLAB爬取股票数据
相信大家听的比较多的应该是用 Python 来爬取网页数据了,但其实,Matlab 也是可以的,这里我们来具体实现一下。场景是这样的:
新浪财经提供了历年各个季度的各股票数据,今天我们的任务就是,将上证综合指数(000001)1991年到1992年的数据爬取到,然后整理出来,保存到两个excel中,每个excel包括当年四个季度的数据,数据如上图所示,包括日期,开盘价、最高价、收盘价、最低价、交易量、交易金额。
爬取流程
本次爬取股票数据的流程是这样的:
观察网址规律
首先,观察当选择不同的年份与季度时,网页链接是有规律的:
http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/000001/type/S.phtml?year=2017&jidu=4
- stockid/000001 指明了所选股票代码
- year=2017 指明了所选的年份
- jidu=4 指明了所选季度
那么,通过观察,我们就可以知道,当修改对应的数字,就可以获取到不同年份和季度下的数据网页了。在代码里设置两层循环就可以搞定了。
提取网页内容
确定网址后,我们可以利用函数获取到当前网址的源码,什么是源码?在网页里右键,查看源码你就知道了,长这样:
在Matlab里,提供了urlread函数来获取源码,语法参考如下:
str = urlread(URL)
str = urlread(URL,Name,Value)
[str,status] = urlread(___)
- str = urlread(URL) 将 HTML 网页内容从指定的 URL 下载到字符向量 str 中。urlread 不检索超链接目标和图像。
- str = urlread(URL,Name,Value) 使用一个或多个 Name,Value 对组参数指定的其他选项。
- [str,status] = urlread(___) 禁止显示错误消息,并使用先前语法中的任何输入参数。当操作成功时,status 为 1。否则,status 为 0
也就是说,我们利用urlread函数会得到源码的文本。就像上图所示的那样,全是字符串。
观察提取内容的规律
我们提取的是股票的日期,开盘价、最高价、收盘价、最低价、交易量、交易金额。并且这些内容全部在源码里面了。源码是一堆乱七八糟的html标签还有Js等等。如何提取出我们想要的东西呢?这需要我们去观察源码。
匹配日期
首先定位到表格,通过F12,查看源码后,点击左下角的箭头,将箭头放到表格附近,就可以定位到元素的源码位置了。
其中日期的附近的源码是这样的:
<div align="center">
<a target="_blank" href="http://vip.stock.finance.sina.com.cn/quotes_service/view/vMS_tradehistory.php?symbol=sh000001&date=2017-12-28">
2017-12-28 </a>
</div>
仔细观察,在 2017-12-28 的前后都存在大量的空格,通过正则表达式,我们可以将其表述出来:
\s+(\d\d\d\d-\d\d-\d\d)\s*
怎么理解?\s+ 表示可以出现空格、换行、制表符等一次或者多次,(\d\d\d\d-\d\d-\d\d) 表示所有满足形如 2017-12-28这样的数字组合,\d代表0~9的阿拉伯数字,括号则表示所有满足这一组表达式匹配到的字符集合。最后\s*则表示末尾可以出现空格、换行、制表符等零次或者多次。通过正则表达式,可以提取到当前源码里所有满足这个规律的日期,从而返回相应的数据,这里使用matlab自带的regexp函数,具体语法如下:
startIndex = regexp(str,expression)
[startIndex,endIndex] = regexp(str,expression)
out = regexp(str,expression,outkey)
[out1,...,outN] = regexp(str,expression,outkey1,...,outkeyN)
___ = regexp(___,option1,...,optionM)
___ = regexp(___,'forceCellOutput')
- startIndex = regexp(str,expression) 返回 str 中与该正则表达式指定的字符模式匹配的每个子字符串的起始索引。如果没有匹配项,则 startIndex 为空数组。
- [startIndex,endIndex] = regexp(str,expression) 返回所有匹配项的开始和结束索引。
- out = regexp(str,expression,outkey) 返回 outkey 指定的输出。例如,如果 outkey 为 'match',则 regexp 返回与该表达式匹配的子字符串而非其开始索引。
- [out1,...,outN] = regexp(str,expression,outkey1,...,outkeyN) 按指定的顺序返回多个输出关键字指定的输出。例如,如果您指定 'match'、'tokens',则 regexp 返回与整个表达式匹配的子字符串以及与部分表达式匹配的标文。
- ___ = regexp(___,option1,...,optionM) 使用指定的选项标志修改搜索。例如,指定 'ignorecase' 以执行不区分大小写的匹配。您可以包括任何输入并请求之前语法中的任何输出。
- ___ = regexp(___,'forceCellOutput') 以标量元胞的形式返回每个输出参数。元胞包含被描述为上述语法输出的数值数组或子字符串。您可以包括任何输入并请求之前语法中的任何输出。
匹配数据
同理,我们也可以观察剩下的数据源码:
<td><div align="center">3295.246</div></td>
观察可以发现,数据都被<div align="center">xxx</div>
所包住,所以正则表达式为:
<div align="center">(\d*\.?\d*)</div>
即被标签包住,且数据满足整数或者小数。
数据整理与导出
通过上面的正则表达提取字符串后,进行一些数据的整理,例如,字符串转数字,行列重排等等,然后将其写入到excel中。这里的步骤就不细说了。
完整源码
最后贴出源码(源码是在CSDN找到的,原链接:https://blog.csdn.net/miscclp/article/details/26839095)
% 本程序用于获取网站中的表格
% written by longwen36
% all rights reserved
clc,clear;
warning off;
for year = 1991:1992 %年份
for jidu = 1:4
fprintf('%d年%d季度的数据...', year, jidu)
[sourcefile, status] = urlread(sprintf('http://vip.stock.finance.sina.com.cn/corp/go.php/vMS_MarketHistory/stockid/000001/type/S.phtml?year=%d&jidu=%d', year,jidu));
if ~status
error('读取出错!\n')
end
expr1 = '\s+(\d\d\d\d-\d\d-\d\d)\s*'; %获取日期
[datefile, date_tokens]= regexp(sourcefile, expr1, 'match', 'tokens');
date = cell(size(date_tokens));
for idx = 1:length(date_tokens)
date{idx} = date_tokens{idx}{1};
end
expr2 = '<div align="center">(\d*\.?\d*)</div>'; %获取数据
[datafile, data_tokens] = regexp(sourcefile, expr2, 'match', 'tokens');
data = zeros(size(data_tokens));
for idx = 1:length(data_tokens)
data(idx) = str2double(data_tokens{idx}{1});
end
data = reshape(data, 6, length(data)/6 )'; %重排
filename = sprintf('%d年',year);
pathname = [pwd '\data'];
if ~exist(pathname,'dir')
mkdir(pathname);
end
fullfilepath = [pwd '\data\' filename];
% 保存数据到Excel
sheet = sprintf('第%d季度', jidu);
xlswrite(fullfilepath, date' , sheet);
range = sprintf('B1:%s%d',char(double('B')+size(data,2)-1), size(data,1));
xlswrite(fullfilepath, data, sheet, range);
fprintf('OK!\n')
end
end
fprintf('全部完成!\n')
运行结果展示
点击运行后,命令行窗口会提示当前状态:
每写入一个季度的数据,就会提示一次OK,直到全部完成。
同时,在当前运行的文件下,会多出一个data文件夹,里面包括了1991和1992两个excel,打开后表格里有四个季度的数据: