Photo by Markus Spiske on Unsplash
在數據爆炸的時代,想要透過資料分析來取得其中有價值的資訊,就需要先獲取大量的資料,並且有效的儲存起來,如此才能夠進行多樣化的應用。而網頁則是最常見的資料蒐集管道,通常會利用API、Open Data(開放資料)或Python網頁爬蟲等技術來進行取得,那又該如何把這些資料儲存起來呢?
本文將以Yahoo奇摩股市為例,分享如何利用Python網頁爬蟲取得關注的股票資料後,存入MySQL資料庫中,讓後續能夠進行資料分析使用。其中的重點包含:
Yahoo奇摩股市網頁分析
建置Python網頁爬蟲
建立MySQL資料庫
建立MySQL資料表
存入爬取的網頁資料
一、Yahoo奇摩股市網頁分析
在進行Python網頁爬蟲開發前,首先來分析一下所要爬取的Yahoo股市網頁結構。前往Yahoo奇摩官方網站,在左側的地方可以看到「股市」的選項,如下圖:
進入Yahoo奇摩股市後,在上方即可看到股票查詢的功能,可以點擊右方的「台股代號查詢」,這時候會跳出一個小視窗,來選擇關注的股票,如下圖:
假設,選擇「2451創建」,則會顯示出當日的行情資料,如下圖:
在這個網頁中,有幾個部份是等一下開發Python網頁爬蟲時,所要取得的資料,分別是右上角的「資料日期」及「當日行情資料」,如下圖:
瞭解想要爬取的目標後,還有一個需求是,在一般情況下,用戶通常會關注一支以上的股票,如果要透過Python網頁爬蟲來自動化取得資料的話,該如何切換到另一支股票呢?
這時候可以看到上方的網址,最後都會帶有股票代號的參數,如下圖:
所以,Python網頁爬蟲只要替換這個股票代號,即可爬取不同股票的當日行情資料。
二、建置Python網頁爬蟲
Yahoo奇摩股市網頁分析好後,接下來就可以進行Python網頁爬蟲的開發,其中本文使用物件導向的設計,詳細的觀念可以參考[Python物件導向]淺談Python類別(Class)文章。
首先,利用以下的指令來安裝Python網頁爬蟲開發時所需的套件:
$ pip install beautifulsoup4
$ pip install requests
$ pip install lxml
其中lxml為BeautifulSoup所支援的HTML解析器,執行速度較快且擁有較佳的容錯能力。
開啟開發工具,本文以Visual Studio Code為例,建立scraper.py檔案,引用beautifulsoup及requests套件,如下範例:
from bs4 import BeautifulSoup
import requests
接著,建立一個股票類別(Stock),其中包含建構式(Constructor)及爬取(Scrape)方法(Method),如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self):
pass
#爬取
def scrape(self):
pass
由於在初始化股票(Stock)物件時,想要讓用戶能夠傳入多個股票代碼,以便可以利用Python網頁爬蟲取得不同股票的當日行情資料,所以,在建構式(Constructor)的地方使用*args參數,將傳入的多個股票代碼打包成一個元組(Tuple),如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
print(self.stock_numbers)
#爬取
def scrape(self):
pass
stock = Stock("2451","2454")
執行結果
('2451', '2454')
範例中,在股票(Stock)物件初始化時,傳入兩個股票代碼,使用*args參數後,就會打包成元組(Tuple),這樣在等一下開發Python網頁爬蟲時,就能夠透過迴圈的方式,讀取元組(Tuple)中的股票代碼,取得對應的當日行情資料。
接下來,在scrape()方法(Method)中,利用requests套件發送GET請求到Yahoo奇摩股市的「2451創建」當日行情網頁,在取得回傳結果(原始碼)後,使用lxml解析器來建立BeautifulSoup物件,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text, "lxml")
在剛剛第一節Yahoo奇摩股市網頁分析時,可以看到要爬取的「當日行情資料」表格中,股票名稱下方有「加到投資組合」的文字,並不需要,為了避免等一下爬取時,還要進行額外的處理,所以在取得網頁的回傳結果(原始碼)後,可以利用replace()方法(Method),將這個文字去掉,如下範例第14行:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
接著,就可以來爬取「資料日期」,點擊右鍵選擇檢查,即可看到HTML原始碼,如下圖:
從上圖可以看到,利用標籤及它的樣式類別(class)即可進行元素的定位,再透過BeautifulSoup套件的getText()方法就可以取得其中的資料日期。
由於取得的資料日期含有空白及「資料日期」文字,這時候就可以使用Python的strip()方法(Method)去除前後的空白後,再利用字串裁切的語法,取得其中的日期資料,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
而表格中的「當日行情資料」比照同樣的方式,可以看到如下圖的HTML原始碼:
首先,「當日行情資料」表格位於網頁表格中的第三個,所以,利用BeautifulSoup套件的find_all()方法取得網頁上所有的表格(table)後,取第三個,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
tables = soup.find_all("table")[2] #取得網頁中第三個表格(索引從0開始計算)
定位到「當日行情資料」表格後,就可以再利用find_all()方法(Method)取得底下的所有格子(td),並且只截取前10格,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
tables = soup.find_all("table")[2] #取得網頁中第三個表格(索引從0開始計算)
tds = tables.find_all("td")[0:11] #取得表格中1到10格
接著,就可以利用Python的Comprehension語法,讀取每一格的資料,經過取得文字(getText)及去除空白(strip)後,存放在元組(Tuple)中,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
tables = soup.find_all("table")[2] #取得網頁中第三個表格(索引從0開始計算)
tds = tables.find_all("td")[0:11] #取得表格中1到10格
tuple(td.getText().strip() for td in tds))
在這個元組(Tuple)中包含了「當日行情資料」,但是並沒有包含「資料日期」,所以就需要把剛剛所爬取的「資料日期」轉型為元組(Tuple)後,加入到「當日行情資料」元組(Tuple)中,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=2451")
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
tables = soup.find_all("table")[2] #取得網頁中第三個表格(索引從0開始計算)
tds = tables.find_all("td")[0:11] #取得表格中1到10格
(stock_date,) + tuple(td.getText().strip() for td in tds))
到目前為止,都只是爬取一支股票的資料,當有多支股票時,就需要透過迴圈來重覆執行,並且使用串列(List),將每一支股票的爬取結果打包起來,如下範例:
from bs4 import BeautifulSoup
import requests
class Stock:
#建構式
def __init__(self, *stock_numbers):
self.stock_numbers = stock_numbers
#爬取
def scrape(self):
result = list() #最終結果
for stock_number in self.stock_numbers:
response = requests.get(
"https://tw.stock.yahoo.com/q/q?s=" + stock_number)
soup = BeautifulSoup(response.text.replace("加到投資組合", ""), "lxml")
stock_date = soup.find(
"font", {"class": "tt"}).getText().strip()[-9:] #資料日期
tables = soup.find_all("table")[2] #取得網頁中第三個表格(索引從0開始計算)
tds = tables.find_all("td")[0:11] #取得表格中1到10格
result.append((stock_date,) +
tuple(td.getText().strip() for td in tds))
return result
stock = Stock('2451', '2454')
print(stock.scrape())
執行結果
[ ('109/07/31', '2451創見', '13:30', '66.0', '65.9', '66.0', '△0.2', '551', '65.8', '66.0', '66.0', '65.7'),
('109/07/31', '2454聯發科', '14:30', '701', '701', '702', '△18', '14,483', '683', '678', '707', '670')]
特別注意範例中的第18行,網址的最後參數需搭配迴圈替換初始化的股票代碼。
三、建立MySQL資料庫
Python網頁爬蟲開發完成後,接下來,就要準備MySQL資料庫,來儲存爬取的股票「當日行情資料」。
MySQL資料庫的安裝方式可以參考[Python實戰應用]掌握Python連結MySQL資料庫的重要操作文章的第二節。
安裝完成後,開啟MySQL Workbench資料庫管理工具,可以在Windows作業系統上,利用「開始」的搜尋功能,輸入關鍵字來進行開啟,如下圖:
點擊左下角的本地端伺服器,輸入安裝時所設定的密碼,即可登入。接下來,就可以建立Python網頁爬蟲所需的資料庫,如下圖:
填入資料庫名稱及設定utf8字元集,點擊右下角的「Apply」即可,如下圖:
四、建立MySQL資料表
在建立的資料庫(stock)下,點擊「Table(資料表)」右鍵,選擇「Create Table」,如下圖:
填入資料表名稱(Table Name)、設定utf8字元集與建立欄位,如下圖:
其中,market_date(資料日期)及stock_name(股票名稱)需設定為主鍵(Primary Key)。完成後同樣點擊右下角的「Apply」按鈕即可。
五、存入爬取的網頁資料
MySQL資料庫準備好後,現在就可以儲存Python網頁爬蟲所取得的資料,而要讓Python應用程式能夠與MySQL資料庫連接,需要安裝pymysql套件,可以利用以下的指令來安裝:
$ pip install pymysql
開啟scraper.py檔案,引用pymysql套件,並且新增一個save()方法(Method),含有股票資料的參數(stocks)。
接著,在save()方法(Method)裡面設定MySQL資料庫的連線資訊,如下範例:
def save(self, stocks):
db_settings = {
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "******",
"db": "stock",
"charset": "utf8"
}
有了連線的資訊,就可以透過pymysql套件的connect()方法(Method)來進行連接,如下範例:
def save(self, stocks):
db_settings = {
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "mysql123",
"db": "stock",
"charset": "utf8"
}
try:
conn = pymysql.connect(**db_settings)
except Exception as ex:
print("Exception:", ex)
而要進行資料庫的操作,就需要有cursor物件,才能夠執行MySQL的新增資料指令,如下範例:
def save(self, stocks):
db_settings = {
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "mysql123",
"db": "stock",
"charset": "utf8"
}
try:
conn = pymysql.connect(**db_settings)
with conn.cursor() as cursor:
sql = """INSERT INTO market(
market_date,
stock_name,
market_time,
final_price,
buy_price,
sell_price,
ups_and_downs,
lot,
yesterday_price,
opening_price,
highest_price,
lowest_price)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)"""
for stock in stocks:
cursor.execute(sql, stock)
conn.commit()
except Exception as ex:
print("Exception:", ex)
範例中,第15行使用with陳述式,在資料庫操作完成後,會自動釋放連線的資源。另外,第31行透過Python迴圈,讀取傳入的股票資料串列(stocks),將每一支股票資料傳入MySQL的新增資料指令(sql)中,最後,利用commit()方法(Method)進行儲存。
完成資料庫的存入方法(save)後,現在,建立一個股票(Stock)物件,傳入兩個公司的股票代碼來進行初始化,接著,呼叫scrape()方法(Method)爬取Yahoo奇摩股市的「當日行情資料」,將回傳的串列(List)結果傳入save()方法(Method),來存入MySQL資料庫中,如下範例:
stock = Stock('2451', '2454')
stock.save(stock.scrape())
執行結果
六、小結
透過本文的實際範例教學,瞭解如何將Python網頁爬蟲所取得的資料有效存入MySQL資料庫中,後續即可利用這些資料來進行分析、圖形化或提供預測的服務等,詳細的程式碼可以參考以下的GitHub網址。另外,您有關注的股票嗎?利用本文的教學來開發一個Python網頁爬蟲,自動化取得股票資料吧。