利用BeautifulSoup、Pandas、StyleFrame、Openpyxl 將檔案存成 .xlsx格式
終於要寫人生中的第一篇IT筆記了,寫故事是很難的,真的很佩服網路上那些前輩的技術文章,寫得清晰又明瞭。 另外希望透過這樣紀錄的方式,能讓自己更了解學到了甚麼。
那麼話不多說,直接進入正題吧。
這篇要來分享使用Python做爬蟲之外,如何同一時間將爬到的圖片及文字一起以.xlsx格式存進Excel file裡面。這裡面用了一些文字爬蟲及繪圖的套件,如Beautifulsoup、Pandas、StyleFrame 及 Openpyxl 。當然,對於Python新手的我,今天要分享的都是自己摸索出來的方式,如果還有更快更方便的解決辦法,希望各位前輩們可以不吝指教。另外這篇有提到一些基礎HTML及CSS語法,這裡就不詳細介紹。
今天用來練習的是日本美食推薦網站 "食べログ",相信很多人並不陌生。那麼進入首頁之後,我們直接選擇大阪(好想再去一次阿)區域然後點擊"大阪的餐廳排行榜"。接著就會看到餐廳排名的清單,在頁面空白處點擊右鍵然後選擇"檢查(N)"叫出開發人員工具(或者按F12就可以直接叫出(Chrome瀏覽器))。點擊一下開發人員工具介面左上角的游標(可以試著點擊餐廳名稱及分數),然後點擊左側任何一個推薦的餐廳物件。

我們直接進入程式碼。
Part1:
- 使用urllib這個套件來送出url request,接著使用BeautifulSoup來解析從tablelog網站回傳回來的html response .
- 取得所有需要的資料及印出
# 使用內建的 urllib.request 裡的 urlopen 這個功能來送出網址
from urllib.request import urlopen, urlretrieve
from urllib.error import HTTPError
from bs4 import BeautifulSoup
#如果是 MAC 電腦, 請務必加入下面兩行, 因為 MAC 有視 https 的 ssl 證書無效的 bug
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
page = 59
while True:
url = "https://tabelog.com/tw/osaka/rstLst/" + str(page) + "/?SrtT=rt"
print("處理頁面:", url)
#如果頁面超過(找不到),直接印出completed然後break 跳出迴圈
try:
response = urlopen(url)
except HTTPError:
print("Completed")
break
#使用BeautufulSoup解析tablelog 網站回傳的html response
html = BeautifulSoup(response)
for list in html.find_all("li", class_="list-rst"): # => find_all 必轉出 list
jap = list.find("small", class_="list-rst__name-ja") #取得日文名
eng = list.find("a", class_="list-rst__name-main") #取得英文名
scores = list.find_all("b", class_="c-rating__val") #取得評分,注意這邊有三個評分,所以使用find_all
img = list.find("img", class_="c-img") #要取得圖片網址 => img["src"]
fname = "tablelog/" + img["src"].split("/")[-1] #設定圖片儲存路徑及檔案名稱
urlretrieve(img["src"], fname) #使用urlretrieve下載圖片以及存入
#試著把所有取得的資料印出來看看吧
print(list), print("======\n"), print(jap), print("======\n"),
print(eng), print("======\n"), print(scores), print("======\n"),
print(img), print("======\n"), print(img["src"])
page = page + 1
Part 2:
- import pandas 套件,把所有取得的資料轉成DataFrame (一行一行的資料append到 DataFrame)
- 這時候已經可以利用pandas 將資料存成.xlsx file。
- 但因為待會我們也要把圖片存入,需調整欄位size,所以會把DataFrame再轉成StyleFrame方便做設定。(可以試試如果不做欄位設定就把圖存入會發生甚麼事情)
import pandas as pd
df = pd.DataFrame(columns=["餐廳美圖", "綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名", "介紹網址"])
import warnings
#忽略掉對ignore的warning
warnings.filterwarnings('ignore')
...
...
for list in html.find_all("li", class_="list-rst"):
...
...
# 準備Series 以及 append進DataFrame。值會放到相對應的column
s = pd.Series([scores[0].text, scores[1].text, scores[2].text, jap.text, eng.text, eng["href"]],
index=["綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名", "介紹網址"])
# 因為 Series 沒有橫列的標籤, 所以加進去的時候一定要 ignore_index=True
df = df.append(s, ignore_index=True)
page = page + 1
# 儲存成 .xlsx, 不過列編號的數字不用存, 所以index=False
df.to_excel("tablelog.xlsx", encoding="utf-8", index=False)
Part 3:
- 轉成StyleFrame格式,做style設定
from StyleFrame import StyleFrame
...
...
sf = StyleFrame(df) #轉成StyleFrame
#設定欄寬
sf.set_column_width_dict(col_width_dict={
("餐廳美圖"): 25.5,
("綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名") : 20,
("介紹網址"): 65.5
})
#設定列高
all_rows = sf.row_indexes
sf.set_row_height_dict(row_height_dict={
all_rows[1:]: 120
})
#存成excel檔
sf.to_excel('tablelog.xlsx',
sheet_name='Sheet1', #Create sheet
right_to_left=False, #False 所以sheet放置是從左到右 left-to-right
columns_and_rows_to_freeze='A1', #資料從A1整個貼上
row_to_add_filters=0).save() #不要忘記要save
Part 4:
- 使用openpyxl寫入圖案至excel file
from StyleFrame import StyleFrame
import glob
import openpyxl
from openpyxl import load_workbook
from openpyxl.drawing import image
import os
...
...
col = 0
wb = load_workbook('tablelog.xlsx') #把檔案先讀出來
ws = wb.worksheets[0] #要把圖檔加進第一個sheet
#使用glob套件做讀檔得動作,從一個資料夾裡把每一個檔案讀出來。
#使用os套件,在讀檔的時候,從時間最早的檔案先讀,避免順序不對
searchedfiles = sorted(glob.glob("tablelog/*.jpg"), key=os.path.getmtime)
for fn in searchedfiles :
img = openpyxl.drawing.image.Image(fn) # create image instances
c = str(col + 2) #記得轉成String
ws.add_image(img, 'A' + c) #從A2開始寫入
col = col + 1
wb.save('tablelog.xlsx') #不要忘記save
那我們來看看結果吧!

最後附上完整source code:
# 使用內建的 urllib.request 裡的 urlopen 這個功能來送出網址
from urllib.request import urlopen, urlretrieve
from urllib.error import HTTPError
from bs4 import BeautifulSoup
#如果是 MAC 電腦, 請務必加入下面兩行, 因為 MAC 有視 https 的 ssl 證書無效的 bug
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import pandas as pd
df = pd.DataFrame(columns=["餐廳美圖", "綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名", "介紹網址"])
import warnings
#忽略掉對ignore的warning
warnings.filterwarnings('ignore')
from StyleFrame import StyleFrame
import glob
import openpyxl
from openpyxl import load_workbook
from openpyxl.drawing import image
import os
page = 1
while True:
url = "https://tabelog.com/tw/osaka/rstLst/" + str(page) + "/?SrtT=rt"
print("處理頁面:", url)
try:
response = urlopen(url)
except HTTPError:
print("Completed")
break
html = BeautifulSoup(response)
for list in html.find_all("li", class_="list-rst"): # => find_all 必轉出 list
jap = list.find("small", class_="list-rst__name-ja")
eng = list.find("a", class_="list-rst__name-main")
scores = list.find_all("b", class_="c-rating__val")
img = list.find("img", class_="c-img")
fname = "tablelog/" + img["src"].split("/")[-1]
urlretrieve(img["src"], fname)
# 準備Series 以及 append進DataFrame。值會放到相對印的column
s = pd.Series([scores[0].text, scores[1].text, scores[2].text, jap.text, eng.text, eng["href"]],
index=["綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名", "介紹網址"])
# 因為 Series 沒有橫列的標籤, 所以加進去的時候一定要 ignore_index=True
df = df.append(s, ignore_index=True)
page = page + 1
sf = StyleFrame(df)
sf.set_column_width_dict(col_width_dict={
("餐廳美圖"): 25.5,
("綜合評分", "晚間評分", "年間評分", "日文店名", "英文店名") : 20,
("介紹網址"): 65.5
})
all_rows = sf.row_indexes
sf.set_row_height_dict(row_height_dict={
all_rows[1:]: 120
})
sf.to_excel('tablelog.xlsx',
sheet_name='Sheet1', #Create sheet
right_to_left=False,
columns_and_rows_to_freeze='A1',
row_to_add_filters=0).save()
col = 0
wb = load_workbook('tablelog.xlsx')
ws = wb.worksheets[0]
searchedfiles = sorted(glob.glob("tablelog/*.jpg"), key=os.path.getmtime)
for fn in searchedfiles :
img = openpyxl.drawing.image.Image(fn) # create image instances
c = str(col + 2)
ws.add_image(img, 'A' + c)
col = col + 1
wb.save('tablelog.xlsx')