1. 程式人生 > >爬蟲練習五:多進程爬取股市通股票數據

爬蟲練習五:多進程爬取股市通股票數據

pen ... 3.6 job form append head odi date

在上網查閱一些python爬蟲文章時,看見有人分享了爬取股票的交易數據,不過實現得比較簡單。這裏就做個小練習,從百度股票批量爬取各股票的交易信息。

文章出處為:Python 爬蟲實戰(2):股票數據定向爬蟲。

爬取數據:每個股票的日度交易數據

爬取來源:百度股市通

python版本:3.6.6

時間:20190115

1. 找到日度數據url

以中化國際的日K數據為例,通過分析網頁,我們可以發現,日度交易數據是通過接口的形式獲取的。

獲取的url為:https://gupiao.baidu.com/api/stocks/stockdaybar?from=pc&os_ver=1&cuid=xxx&vv=100&format=json&stock_code=sh600500&step=3&start=&count=160&fq_type=no&timestamp=1547534373604

技術分享圖片

這個頁面是通過get進行請求的,url中包含的參數如下:

技術分享圖片

其中stock_code是請求的股票代碼,因為中化國際是在上交所交易的,所以股票代碼前加上了sh。如果請求時沒有加上對應的sh/sz就無法查詢到信息

count是當前返回的數據量,因為是日度數據,160就是返回最近160天的日度數據。

測試後發現,fq_typetimestamp參數可以不填。

所以我們就得到了獲取日度交易數據的URL; https://gupiao.baidu.com/api/stocks/stockdaybar?from=pc&os_ver=1&cuid=xxx&vv=100&format=json&stock_code=sh600500&step=3&start=&count=160

stock_code進行替換就可以獲得不同股票對應的日度數據。

2. 找到每個股票對應的股票代碼

既然我們找到了根據股票代碼獲取其對應日度數據的地址,接下來就要去找一下如何獲取每個股票的股票代碼。

沒有在股市通中找到哪個頁面有陳列所有股票的=對應的股票代碼,這裏借鑒上面提到的文章,從東方財富網抓取每個股票對應的代碼:http://quote.eastmoney.com/stocklist.html

技術分享圖片

這個頁面中每個股票對應的代碼是直接寫在HTML代碼裏,所以我們只需要使用BeautifulSoup或其他庫解析網頁即可。

技術分享圖片

3. 實現 import requestsfrom bs4 import BeautifulSoup

import pymysql
from multiprocessing import Pool
import time # 從東方財富網獲取各股票代碼 def get_stock_info(list_url): stock_info = [] # 各股票信息存入stock_info try: r = requests.get(list_url) r.encoding = ‘gb2312‘ # 網頁編碼為gb2312 soup = BeautifulSoup(r.text, ‘html.parser‘) stock_city = ‘sh‘ # 默認為sh,先爬取上交所交易的股票,再爬取深交所的股票 for each_city in soup.find(‘div‘, class_=‘quotebody‘)(‘ul‘): for each_stock in each_city.findAll(‘li‘): stock_info.append( {‘stock_name‘: each_stock.get_text().split(‘(‘)[0], # 股票名稱 ‘stock_code‘: each_stock.get_text().split(‘(‘)[1].replace(‘)‘, ‘‘), # 股票代碼 ‘stock_url‘: each_stock.find(‘a‘)[‘href‘], # 股票對應的url ‘stock_city‘: stock_city # sh/sz } ) stock_city = ‘sz‘ # 上交所的讀取完後切換為深交所 print(‘獲取股票代碼成功,共‘, len(stock_info), ‘只股票‘) except: print(‘獲取股票代碼失敗‘) return stock_info # 根據股票代碼進行訪問,每次獲取一只股票的過去250天交易數據,並一次性插入數據庫 def get_stock_data(each_stock): header = { ‘Host‘: ‘gupiao.baidu.com‘, ‘User-Agent‘: ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36‘ } db = pymysql.connect(host=‘localhost‘, user=‘root‘, password=‘mysqlkey‘, db=‘test_db‘, port=3306) cursor = db.cursor() # 創建遊標對象cursor stock_url = ‘https://gupiao.baidu.com/api/stocks/stockdaybar?from=pc&os_ver=1&cuid=xxx&vv=100&format=json&stock_code={}&start=&count=250‘.format( each_stock[‘stock_city‘]+str(each_stock[‘stock_code‘])) try: stock_r = requests.get(stock_url, headers=header).json()
     time.sleep(0.2)
if stock_r[‘errorMsg‘]==‘SUCCESS‘: stock_json = stock_r[‘mashData‘] insert_data = [] # 保存單個股票的250天的交易數據 for each_day in stock_json: # 每一天的數據 data = {‘stock_name‘: str(each_stock[‘stock_name‘]), ‘stock_code‘: str(each_stock[‘stock_code‘]), ‘date‘: each_day[‘date‘], ‘open‘: each_day[‘kline‘][‘open‘], ‘high‘: each_day[‘kline‘][‘high‘], ‘low‘: each_day[‘kline‘][‘low‘], ‘close‘: each_day[‘kline‘][‘close‘], ‘volume‘: each_day[‘kline‘][‘volume‘], ‘amount‘: each_day[‘kline‘][‘amount‘], ‘pre_close‘: each_day[‘kline‘][‘preClose‘], ‘net_change_ratio‘: each_day[‘kline‘][‘netChangeRatio‘], ‘ma5_volume‘: each_day[‘ma5‘][‘volume‘], ‘ma5_avg_prive‘: each_day[‘ma5‘][‘avgPrice‘], ‘ma10_volume‘: each_day[‘ma10‘][‘volume‘], ‘ma10_avg_price‘: each_day[‘ma10‘][‘avgPrice‘], ‘ma20_volume‘: each_day[‘ma20‘][‘volume‘], ‘ma20_avg_price‘: each_day[‘ma20‘][‘avgPrice‘], ‘macd_diff‘: each_day[‘macd‘][‘diff‘], ‘macd_dea‘: each_day[‘macd‘][‘dea‘], ‘macd‘: each_day[‘macd‘][‘macd‘], ‘kdj_k‘: each_day[‘kdj‘][‘k‘], ‘kdj_d‘: each_day[‘kdj‘][‘d‘], ‘kdj_j‘: each_day[‘kdj‘][‘j‘], ‘rsi_1‘: each_day[‘rsi‘][‘rsi1‘], ‘rsi_2‘: each_day[‘rsi‘][‘rsi2‘], ‘rsi_3‘: each_day[‘rsi‘][‘rsi3‘] } # 本只股票的250日數據,為包含tuple的list,用於MySQL的executemany批量插入 insert_data.append(tuple((str(values) for key,values in data.items()))) # 插入MySQL,每只股票只執行一次插入操作 insert_into_mysql(db=db, cursor=cursor, data=insert_data, stock_code=str(each_stock[‘stock_code‘])) print(each_stock[‘stock_code‘], ‘insert done‘) except: print(each_stock[‘stock_code‘], ‘not done‘) finally: cursor.close() db.close() # 創建MySQL表 def create_table(): db = pymysql.connect(host=‘localhost‘, user=‘root‘, password=‘mysqlkey‘, db=‘test_db‘, port=3306) cursor = db.cursor() # 創建遊標對象cursor cursor.execute(‘DROP TABLE IF EXISTS stock‘) # 執行SQL,如果stock表存在就刪除 # 創建表 # 因為對MySQL各種數據類型還沒有做詳細了解,這裏暫時都設置為CHAR類型 create_table_sql = """ CREATE TABLE stock( stock_name CHAR(50), stock_code CHAR(50), date CHAR(50) COMMENT ‘日期‘, open CHAR(50) COMMENT ‘開盤價‘, high CHAR(50) COMMENT ‘最高價‘, low CHAR(50) COMMENT ‘最低價‘, close CHAR(50) COMMENT ‘收盤價‘, volume CHAR(50) COMMENT ‘成交量‘, amount CHAR(50), pre_close CHAR(50) COMMENT ‘前一天收盤價‘, net_change_ratio CHAR(50) COMMENT ‘凈換手率‘, ma5_volume CHAR(50) COMMENT ‘5日均線交易量‘, ma5_avg_prive CHAR(50) COMMENT ‘5日均價‘, ma10_volume CHAR(50) COMMENT ‘5日均價‘, ma10_avg_price CHAR(50) COMMENT ‘5日均價‘, ma20_volume CHAR(50) COMMENT ‘5日均價‘, ma20_avg_price CHAR(50) COMMENT ‘5日均價‘, macd_diff CHAR(50) COMMENT ‘5日均價‘, macd_dea CHAR(50) COMMENT ‘5日均價‘, macd CHAR(50) COMMENT ‘5日均價‘, kdj_k CHAR(50) COMMENT ‘5日均價‘, kdj_d CHAR(50) COMMENT ‘5日均價‘, kdj_j CHAR(50) COMMENT ‘5日均價‘, rsi_1 CHAR(50) COMMENT ‘5日均價‘, rsi_2 CHAR(50) COMMENT ‘5日均價‘, rsi_3 CHAR(50) COMMENT ‘5日均價‘ )""" try: cursor.execute(create_table_sql) db.commit() print(‘create table stock done‘) except: db.rollback() print(‘create table stock not done‘) finally: cursor.close() db.close() # 插入MySQL def insert_into_mysql(db, cursor, data, stock_code): insert_sql = """ INSERT INTO stock( stock_name, stock_code, date, open, high, low, close, volume, amount, pre_close, net_change_ratio, ma5_volume, ma5_avg_prive, ma10_volume, ma10_avg_price, ma20_volume, ma20_avg_price, macd_diff, macd_dea, macd, kdj_k, kdj_d, kdj_j, rsi_1, rsi_2, rsi_3) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s, %s) """ try: cursor.executemany(insert_sql, data) # executemany批量插入數據 db.commit() except: db.rollback() stock_info = get_stock_info(‘http://quote.eastmoney.com/stocklist.html‘) # 獲取東方財富網的所有股票代碼 create_table() # 創建stock表 pool = Pool(4) # 4個進程的進程池 pool.map(get_stock_data, stock_info) pool.close() # 進程池停止寫入 pool.join() # 進程池阻塞 print(‘all done‘)

效果如下:

技術分享圖片

4. 解釋

4.1 對每個股票都重新同MySQL建立連接

開始想的是在創建stock表的時候就同MySQL建立連接,之後每個進程的每次插入都使用這個連接。

參考Python多線程運行帶多個參數的函數,使用partial函數為get_stock_info()傳遞connect和cursor。但是顯示cannot serialize _io.BufferedReader object錯誤。

搜尋信息後發現報錯原因是講一個不可序列化的對象傳遞給對象導致的問題,這裏的理解是,MySQL的connect和cursoe不可序列化,無法被實例傳遞給子進程因而出現了錯誤。

從這裏:數據庫並行讀取和寫入(Python實現) 找到一個解決方案:應該為每個進程分配不同的connect和cursor,否則數據庫會報錯。

技術分享圖片

4.2 為什麽不直接從東方財富網抓取每只股票的信息

既然在1中我們已經能夠拿到每只股票對應的詳情頁面,為什麽不直接從這個詳情頁面進行抓取呢?

原因是:東方財富網確實有每只股票的詳情頁面,包含這只股票的各種數據。但是在之前分析頁面的時候,居然發現股票的數據都是以圖片形式傳輸的,不是格式化的數據...

現在忘了當時查看的是哪只股票了,就沒有辦法重現。現在重新查看,發現這裏的股票數據應該也是通過接口形式傳輸的,這個就等之後有興趣再嘗試了。

技術分享圖片

爬蟲練習五:多進程爬取股市通股票數據