1 數據源信息介紹
1.1 數據來源網站股票市場交易信息在網絡上隨處可查,但有的網站將數據流放在js中,相對來說難以統一處理;有的網站域名結構不夠清晰,無效字符比較多。經過對比,本案例選用股城網來進行股票交易數據的爬取。1.2 選取目標數據在股城網上,有著各種板塊的行情信息,診斷信息,各個股票的行情走勢,基本資料,高管介紹,資金流向等各類信息。我們需要在這些信息中,篩選出我們所需要的。選取某一隻個股,我們可以查看其個股首頁所展示的信息。
紅色方框內包含了這隻股票今日的一些行情信息。在本案例中,我們將這些信息列為獲取的對象之一。
注意到在行情信息的下方,還存在關於股票的更多方面的信息連結。在本案例中,我們進入到財務分析頁面,並將其中所展示的近五日的資金流向組成表格(如下)也列為我們要獲取的信息對象。我們想要通過爬蟲實現的目標,便是將股城網上每隻上海深圳交易所股票的上述兩種信息都獲取下來,並以表格的形式將這些信息儲存在DataFrame中。1.3 定位目標數據由於我們想要爬取股城網上所有股票的信息,我們需要一份股城網上所有股票名稱或代碼的清單。我們打開股城網的股票代碼一覽頁面 (https://hq.gucheng.com/gpdmylb.html) ,頁面中展示了上海深圳所有股票的名稱和代碼,每個股票名稱對應一個連結,連結即為該股票的個股首頁。但這些並不是我們所能直接直接用到的數據。每個網頁的構建包含html,css,js等模塊,我們如果想要獲取目前展現在我們眼前的數據,需要訪問構成網頁前端界面的這些基本組件。我們按F12,打開網頁的開發者頁面。
在右邊的彈出框中,默認選擇Elements並展示html文件,一般在這裡我們就可以看到網站的構成組件和信息。<>劃分了網站前端界面的各個模塊。滑鼠在某連結上點擊右鍵,點擊最後一個選項'檢查',開發者頁面會自動展開並高亮所對應的模塊。可以看到所有的個股首頁連結、股票名稱、股票代碼都存放在名為a的模塊中。接下來我們任意進入一個個股首頁連結,再看看個股首頁中的情況。
我們同樣按F12後進行類似操作,檢查今日開盤價所對應的模塊,可以看到開盤價的標籤儲存在dt中,值則儲存在dd中。對資金流向部分也同樣有類似的方法,來定位我們想要看到的數據所在的地方。如此一來,我們便明確了所需數據的位置。
1.4 最終目標數據我們希望最終獲取的數據欄位一共存放在兩個表格中,分別包含每隻股票今日的行情信息和近五日的資金流向信息。表1的數據欄位的說明如下:列名含義說明數據類型名稱股票名稱。object最高今日最高價。object最低今日最低價。object今開今日開盤價。object昨收昨日收盤價。object漲停漲停交易價。object跌停跌停交易價。object換手率一定時間內市場中股票轉手買賣的頻率。object振幅股票價格波動的幅度。object成交量股票買賣雙方達成交易的數量。object成交額股票買賣雙方達成交易的額度。object內盤股票的賣家以買家的買入價而賣出成交的數量。object外盤股票的買家以賣家的賣出價而買入成交的數量。object量比指股市開市後平均每分鐘的成交量與過去5個交易日平均每分鐘成交量之比。object漲跌幅上漲或下跌的幅度。object市盈率股票價格除以每股收益的比率。object市淨率每股股價與每股淨資產的比率。object流通市值某特定時間內當時可交易的流通股股數乘以當時股價得出的流通股票總價值。object總市值某特定時間內總股本數乘以當時股價得出的股票總價值。object列名含義說明數據類型名稱股票名稱。object日期資金流向信息的當天日期。object總流入股價上升期間發生的總交易量。object總流出股價下降期間發生的總交易量。object主力流入淨額股價上升期間主力資金髮生的總交易量。object主力流入佔比股價上升期間主力資金髮生的總交易量佔總流入比。object主力流出淨額股價下降期間主力資金髮生的總交易量。object主力流出淨額股價下降期間主力資金髮生的總交易量佔總流出比。object散戶流入淨額股價上升期間散戶資金髮生的總交易量。object散戶流入佔比股價上升期間散戶資金髮生的總交易量佔總流入比。object散戶流出淨額股價下降期間主力資金髮生的總交易量。object散戶流出佔比股價上升期間散戶資金髮生的總交易量佔總流入比。object2 爬蟲程序在完成數據定位工作之後,我們正式開始通過爬蟲來獲取這些數據。我們首先只取少量的數據進行測試,逐步地分析爬蟲程序的基本流程,直到在數據表中完成導入。完成分析後我們,定義相關的函數批量完成爬蟲任務。2.1 爬蟲程序基本流程我們首先導入整個過程中需要用到的包。主要包括requests, BeatifulSoup4, re ,pandas 。其中requests主要用於網絡訪問,BeatifulSoup4提供了許多解析網頁,定位數據的功能,re引入正則表達式,pandas用於表格操作。
import requests
from bs4 import BeautifulSoup
import re
import pandas as pd
pd.set_option('display.max_rows',30)
在開始前,我們應明確測試的基本思路:首先從已知的URL網址中獲取股票代碼一覽表這個網頁的Html文件,利用bs4解析網頁,取出我們要爬取的股票代碼信息,將其儲存在一個列表中。根據列表中的信息,我們可以直接獲得這些股票個股首頁的URL,再重複之前對URL的操作,獲取個股首頁裡面的信息,在整理後統一儲存在dataframe中。2.1.1 從URL獲取所對應的HTML首先我們獲取股票代碼一覽表的Html。我們可以通過requests包中的get方法完成這個操作,將返回值定義為r。kv = {'user-agent': 'Mozilla/5.0'}#模擬瀏覽器發送請求
r = requests.get("https://hq.gucheng.com/gpdmylb.html",headers=kv,timeout = 30)
在get方法中我們定義headers參數為以上的kv,目的是模擬瀏覽器來向網站發送請求,以免被反爬蟲策略識別。timeout參數指超時時間,設置為30時,如果超過30秒將不再等待。r.raise_for_status()
r.encoding = 'UTF-8'
這兩步是對返回值做檢查和處理。raise_for_status方法的作用是檢查返回值,如果Response為200則表示返回值無誤,調整encoding可以保證編碼格式不出錯。r.text
用.text方法取出r中的文字信息,便得到了我們所請求的網頁的html信息。可以看到這個信息的格式是非常混亂的。
2.1.2 利用BeautifulSoup解析網頁,定位數據為了解決得到的網頁html信息格式混亂的問題,我們所導入的beautifulsoup4終於可以派上用場了。soup = BeautifulSoup(r.text,'html.parser')#解析頁面
直接向BeautifulSoup中傳入r的文字信息和對用於對html解析的html.parser,我們就得到了一個解析後的html返回值soup。我們來看看它的效果。soup.head()
解析後的html格式清晰了許多,有助於我們從html頁面大量的信息中篩選出我們所需要的部分。以上這部分獲取html並進行解析的代碼可能在之後需要多次用到,我們定義一個輸入值為URL網址,返回值為解析後的soup的函數getHTML,以包含以上的所有步驟。
def getHTML(url):
kv = {'user-agent': 'Mozilla/5.0'}#模擬瀏覽器發送請求
r = requests.get(url,headers=kv,timeout = 30)
r.raise_for_status()
r.encoding = 'UTF-8'
soup = BeautifulSoup(r.text,'html.parser')
return soup
我們更進一步,利用beautifulsoup中的find_all方法只保留標籤為a內的數據,進一步縮小範圍。a = soup.find_all('a')#找到所有的a標籤
#a
我們先定義一個空的列表slist以便稍後將股票代碼數據儲存。slist = []
這些標籤為a的數據在格式上已經並沒有不同了,我們想要篩選出統一格式的股票名稱和代碼數據,還需要引入正則表達式,對所有標籤為a的數據來進行判斷。for i in a: #遍歷所有標籤為a數據
try:
href = i.attrs['href'] #對每個標籤的href特徵進行判斷
slist.append(re.findall(r"[S][ZH]\d{6}",href)[0]) #正則表達式匹配,符合要求的存入列表
except:
continue #不符合要求則繼續循環
使用try和except來只取出符合我們要求的數據,正則表達式匹配的是開頭為S,第二位為Z或者H,後面為六位數字形式的字符串,包含了所有上海和深圳的股票代碼。可以通過len查看slist列表的長度。len(slist)
可以看到slist中包含了3690條股票代碼數據。接下來我們取slist中的第一隻股票為例,繼續取出我們所需要的數據。股城網的個股首頁URL可以通過字符串拼接的方式,將域名和股票代碼拼接直接生成。對每個個股首頁的URL利用getHTML函數得到解析後的頁面,繼續分析。
url = 'https://hq.gucheng.com/' + slist[0] #根據個股代碼寫出該個股首頁URL
soup = getHTML(url) #調用函數獲取HTML並解析網頁
#soup #展示頁面
可以看到第一隻股票其實就是上證綜指。這個頁面相對而言內容更加複雜。我們需要取出之前所定位的行情信息模塊,考慮到我們還需要將股票的中文名稱也取出,我們可以先定義一個StockInfo變量,用beautifulsoup4中的find取出上一級div標籤中class為stock_top dapan_top clearfix的內容,這部分內容同時包括了行情和中文名稱信息,也避免了若直接對整個頁面操作,餘下部分可能名稱衝突的問題。StockInfo = soup.find('div',attrs={'class':'stock_top dapan_top clearfix'})
StockInfo
在這部分內容中,股票名稱上證指數在一個class為stock_title的header標籤下,行情的不同指標都在標籤dt下,相應的值在標籤dd下。我們將StockInfo中對應這些標籤的值分別儲存在name,keyList,valueList中。
name = StockInfo.find_all(attrs={'class':'stock_title'})[0] #選取第一項過濾可能出現的後面的無效信息
keyList = StockInfo.find_all('dt')
valueList = StockInfo.find_all('dd')
至此,我們就將網頁上我們所需要的一個個股的數據儲存到了三個變量中。接下來我們只需對這幾個變量進行處理,提取和儲存即可。2.1.3 數據處理與存儲我們現在有name,keyList,valueList三個變量,我們首先查看name中的信息。name
name中的信息包含了很多標籤分隔符,我們用text提取其中的文字信息。
name.text
可以看到只剩下了文字和換行符,我們用split方法分割字符串,並取第一個值,就得到了我們所需的name。
name = name.text.split()[0]
name
接下來考慮valueList和keyList。我們首先分別查看這兩個列表的信息。
print(valueList)
print(keyList)
我們希望得到的是兩個僅包含有效信息的列表,並將其分別作為一個新的DataFrame的標籤列和值列。valueList和keyList裡包含了較多的無效信息,我們可以循環提取列表中每個元素的text,並將其分別放入新的列表valueListStr和keyListStr中。我們首先定義這兩個新的列表,並將name的信息先放入其中,作為初始值。
keyListStr = ['名稱']
valueListStr = [name]
我們循環提取原列表中的信息,通過append逐個添加到新列表中。for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
keyListStr.append(key)
valueListStr.append(val)
完成後,我們查看新生成的列表,看看是否符合我們的要求。print(keyListStr)
print(valueListStr)
我們將其放入一個新的DataFrame中,將keyListStr作為列索引,valueListStr作為取值。
df = pd.DataFrame(columns = keyListStr)
df.loc[0] = valueListStr
df
至此,我們就成功將一隻股票的行情信息爬取下來,並儲存在了DataFrame格式的df中。
2.2 完整數據爬取流程我們按照上面的流程,對所有的股票數據重新進行爬取並獲取完整數據。注意:整個流程一共需要三小時左右,最終得到的數據已經儲存在data1.csv和data2.csv中。2.2.1 爬取股票行情數據首先我們獲取所有要爬取的股票列表slist。我們在前文中已經獲取了這個列表,我們整合前文的代碼即可。slist = []
soup = getHTML("https://hq.gucheng.com/gpdmylb.html")
a = soup.find_all("a")
for i in a: #遍歷所有標籤為a數據
try:
href = i.attrs['href'] #對每個標籤的href特徵進行判斷
slist.append(re.findall(r"[S][ZH]\d{6}",href)[0]) #正則表達式匹配,符合要求的存入列表
except:
continue #不符合要求則繼續循環
#slist
我們現在知道了第一隻股票代表的其實是上證綜指,並不是我們股票列表中的第一個:平安銀行。觀察頁面,我們知道可能是上方也存在著關於三大指數的標籤預覽,因此我們應先對slist進行切片操作,去除前三個非目標信息。slist = slist[3:]
對於slist中的每個股票,我們重複第二節中的全部處理。我們先取第一隻股票,單獨運行第一次,先導入keyListStr和valueListStr,之後循環中只導入後者。url = 'https://hq.gucheng.com/' + slist[0] #根據個股代碼寫出該個股首頁URL
soup = getHTML(url) #調用函數獲取HTML並解析網頁
StockInfo = soup.find('div',attrs={'class':'stock_top clearfix'})
name = StockInfo.find_all(attrs={'class':'stock_title'})[0] #選取第一項過濾可能出現的後面的無效信息
keyList = StockInfo.find_all('dt')
valueList = StockInfo.find_all('dd')
name = name.text.split()[0]
keyListStr = ['名稱']
valueListStr = [name]
for i in range(len(keyList)):
key = keyList[i].text
val = valueList[i].text
keyListStr.append(key)
valueListStr.append(val)
df = pd.DataFrame(columns = keyListStr)
df.loc[0] = valueListStr
df
列標籤固定後,剩下的只要將對每行將valueListStr循環添加即可。由於可能出現部分網頁/網址已經失效等情況,我們在循環前加入try和except,跳過無效的網頁。注意整個過程較長,約需要兩個小時左右。
#for i in range (len(slist)):
for i in range(5):
try:
url = 'https://hq.gucheng.com/' + slist[i] #根據個股代碼寫出該個股首頁URL
soup = getHTML(url) #調用函數獲取HTML並解析網頁
StockInfo = soup.find('div',attrs={'class':'stock_top clearfix'})
name = StockInfo.find_all(attrs={'class':'stock_title'})[0]
valueList = StockInfo.find_all('dd')
name = name.text.split()[0]
valueListStr = [name]
for j in range(len(valueList)):
val = valueList[j].text
valueListStr.append(val)
df.loc[i] = valueListStr
print('\r當前完成進度:{:.2f}%'.format(i*100/len(slist)),end=" ")
except:
continue
上僅爬取5個網站作為示例,有興趣的同學可以取消注釋全程爬取,約需要1-2小時。我們把完整的數據已經存在數據集文件data1.csv中,可以讀取查看。df = pd.read_csv('./dataset/data1.csv')
df
2.2.2 爬取資金流向數據
接下來我們進入每一隻股票個股首頁的下一個子頁面:資金流向頁面,獲取每一隻股票近五日的資金流向信息。在上一小節中得到的slist與本小節要用到的相同,可以保留。我們首先解析第一隻股票的資金流向頁面,觀察我們所需要的數據在什麼位置。url = 'https://hq.gucheng.com/' + slist[0] + '/zijinliuxiang'
soup = getHTML(url)
#soup
我們發現信息定位在某名為zjlxyl的section class中。效仿前文利用find定位數據。stockInfo = soup.find('section',attrs={'class':'zjlxyl'})
stockInfo
仔細分析頁面,發現名稱對應的標籤為h3,列標籤對應th,數據則對應td。但結合前文對這一塊表格的展示,我們知道列標籤長度和數據長度不是一一對應的,因為列標籤為了展示單位和說明單獨定義了兩行,並且有著部分標籤對應重合的關係。我們先仿照前文從stockInfo中根據標籤獲取name、keyList和valueList,並列印出他們。
name = stockInfo.find_all('h3')[0]
keyList = stockInfo.find_all('th')
valueList = stockInfo.find_all('td')
print(name)
print(keyList)
print(valueList)
上述數據中,name只需提取出中文即可。keyList中的標籤應去除中間多餘的四個主力流入等欄位,將這四個欄位的信息轉移到這些欄位所對應的子欄位上。valueList中的數據一共包含了五天的信息,應循環提取其中的信息,將其轉化為我們需要的格式。
首先處理name,利用split提取第一個括號(前的文字。name = name.text.split('(')[0]
name
對於keyList和valueList,首先仿照前文去除多餘字符信息,並保存在keyListStr和valueListStr中
keyListStr = ['名稱']
valueListStr = [name]
for i in range(len(valueList)):
val = valueList[i].text
valueListStr.append(val)
for j in range (len(keyList)):
key = keyList[j].text
keyListStr.append(key)
我們利用切片僅保留keyList中所需要的部分,並將缺失信息寫在ToFillList中,利用字符串拼接補全keyListStr。keyListStr = keyListStr[0:4] + keyListStr [8:16]
ToFillList = ['主力流入','主力流入','主力流出','主力流出','散戶流入','散戶流入','散戶流出','散戶流出']
for i in range(len(ToFillList)):
keyListStr[i+4] = ToFillList[i] + keyListStr[i+4]
keyListStr
對valueListStr還應將其分組,我們引入新的變量SepvalueStr以列表嵌套的形式存儲這些數據。取keylist的長度減去一為循環間隔,依次加入股票名和valueListStr中對應的數據,天數為5,我們循環5次。完成後,查看SepvalueStr驗證效果。
SepvalueStr = []
n = len(keyListStr)-1
for i in range(5):
SepvalueStr.append([name])
for j in range(n):
SepvalueStr[i].append(valueListStr[(n*i)+j+1])
SepvalueStr
至此數據已經整理至所需格式,將其讀取至數據表df2中。
df2 = pd.DataFrame(columns = keyListStr)
for i in range(len(SepvalueStr)):
df2.loc[i] = SepvalueStr[i]
df2
接下來對效仿第二節和上一小節中的操作,對slist中的所有數據進行循環讀取:
#for i in range (len(slist)):
for i in range(5):
try:
url = 'https://hq.gucheng.com/' + slist[i] + '/zijinliuxiang'
soup = getHTML(url) #調用函數獲取HTML並解析網頁
stockInfo = soup.find('section',attrs={'class':'zjlxyl'})
name = stockInfo.find_all('h3')[0]
valueList = stockInfo.find_all('td')
name = name.text.split('(')[0]
valueListStr = [name]
for j in range(len(valueList)):
val = valueList[j].text
valueListStr.append(val)
SepvalueStr = []
n = len(keyListStr)-1
for k in range(5):
SepvalueStr.append([name])
for m in range(n):
SepvalueStr[k].append(valueListStr[(n*k)+m+1])
df2.loc[5*i+k] = SepvalueStr[k]
print('\r當前完成進度:{:.2f}%'.format(i*100/len(slist)),end=" ")
except:
continue
以上同樣僅爬取5個網站作為示例,有興趣的同學可以取消注釋全程爬取,約需要1-2小時。我們把完整的數據已經存在數據集文件data2.csv中,可以讀取查看。df2 = pd.read_csv('./dataset/data2.csv')
df2
2.2.3 數據格式清洗與展示
在上文中,我們已經得到了所有我們所需要的數據,儲存在DataFrame表格df和df2中。但df中有些列的中文還帶有'萬','億'等中文,我們將其去除並為相應列的列標籤添加相應說明。df2
ToCleanList = ['成交量','成交額','內盤','外盤','流通市值','總市值']
for i in range(len(ToCleanList)):
df2[ToCleanList[i]] = df2[ToCleanList[i]].str.extract('([1-9]\d*\.?\d*)', expand=False)
if i < 4:
df2.rename(columns={ToCleanList[i]:ToCleanList[i]+'(萬)'},inplace=True)
else:
df2.rename(columns={ToCleanList[i]:ToCleanList[i]+'(億)'},inplace=True)
下面我們展示最終得到的結果df,df2,分別表示上海深圳所有股票的股票行情和每隻股票近五日的資金流向。df
df2
3 總結
我們基於request,bs4等庫完成了一次網絡爬蟲的實戰案例,獲取了所有股票的公開行情信息和近五日資金流向信息。網絡上有著大量的可以公開訪問數據,利用爬蟲獲取數據是一種能夠幫助節約大量人力物力的方式,是大數據分析的一種基礎手段。但隨著數據安全治理的重要性不斷上升,爬蟲相關的法律法規和各種網絡的反爬蟲手段也在不斷地完善之中。我們應該關注這些信息,在合理的範圍內使用技術。