近年來,大數據盛行,有關爬蟲的教程層次不窮。那么,爬蟲到底是什么呢?
什么是爬蟲?
百度百科是這樣定義的:
網絡爬蟲(又被稱為網頁蜘蛛,網絡機器人,在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,打開后表格里有四個季度的數據: