清除 Excel 特殊字符
主要是為了做一個筆記, 用 遍歷 DataFrame 用正則匹配特殊字符並替換.
是上個月初的項目了, 其中有個將 Excel 傳入數據庫的時候, 發現有特殊字符, 很奇怪的那種特殊字符, 什么小五角星, 小書書, 小太陽, 耳機 等這種字符, 真的好奇怪. 后來發現, 這種問題竟然是經常出現, 於是來做個筆記, 方便自己以后再遇到的時候, 能輕松復制粘貼.
需求
清除 Excel 的特殊字符, 諸如 小星星, 小花花, 小太陽, 小提琴, 小耳機 ... 等這些小可愛.
輸入是一個 Excel 表格, 輸出去 清除 (替換) 掉特殊字符的 Excel 表格 ( 跟輸入一樣) .
方案
-
特殊字符匹配用 正則表達式在查詢並替換 re.compile(); re.sub();
-
用 Pandas 讀取 Excel 數據為 DataFrame
-
遍歷 DataFrame 的每個格子, 進行正則匹配 並替換 df.loc 和 iloc
查找特殊字符並替換
找了好久的規律, 仔細琢磨發現的, 那些特殊字符是可用 unicode 進行匹配出來.
[\U0001000 - \U001ffff]
然后呢, 我們的操作就是, 查找, 並並進行替換, 封裝為一個函數哦.
def replace_spec_char(char, replace_char):
"""替換掉特殊字符, 用某種自定義標記"""
if not isinstance(char, str):
return "輸入的是非字符串哦"
pattern = re.compile(u'[\U0001000 - \U001ffff]')
# 替換
return pattern.sub( replace_char, char)
遍歷 DataFrame 並清理
用下標索引 iloc 的方式來弄即可. 其實關於 df.loc 和 df.iloc 我從來就沒有真正記住和區分過, 大約只是記得 loc 用於有名字的 所有, 如字段啥的. iloc 是按照下標索引.
而我想表達的學習方法是, 根本不用去刻意記, 有印象和臨時百度就好啦. 就好比, 我始終不能區分 json.loads() 和 json.dumps() ; 或者 eval() 和 __ repr __ () ; 或者 pandas / numpy 中 的 軸 axis = 1 或 0 ... 這類到底是啥的問題,
解決之道 是 留大概印象, 即用即查.
def parse_data(file_path, replace_char):
"""替換 Excel 特殊字符"""
try:
df = pd.read_excel(file_path):
except:
raise "數據讀取異常"
# 遍歷每個單元格, 先行后列我比較習慣而已
row, col = df.shape
for i in range(row):
for j in range(col):
# 當前格元素, 發現上面寫不對, 字符校驗應該在這里
cur_value = df.iloc[i, j]
pure_char = replace_char(cur_value, replace_char)
# 把特殊字符, 及其所在的行列坐標給打印出來
if cur_value != pure_chare:
print(f"特殊字符: {cur_value} 位於 {i+1}行, {j+1}列.")
# 並同時將當前值用 清理好的字符替換
df.iloc[i, j] = pure_char
print("清理完畢!")
遍歷DF 的 cell
還是稍微演示一把好一點.
df = pd.DataFrame({
'name':['youyou', 'youge', 'jieer'],
'age': [18, 22, nan],
'gender': ['F', nan, 'M']
})
df
name age gender
0 youyou 18.0 F
1 youge 22.0 NaN
2 jieer NaN M
# 我個人喜歡按 row 來進行遍歷哦
row, col = shape
for i in range(row):
for j in range(col):
print(df.iloc[i, j]ue,'\t\t', type(df.iloc[i, j]))
youyou <class 'str'>
18.0 <class 'numpy.float64'>
F <class 'str'>
youge <class 'str'>
22.0 <class 'numpy.float64'>
nan <class 'float'>
jieer <class 'str'>
nan <class 'numpy.float64'>
M <class 'str'>
for i in range(df.shape[0]):
for j in range(df.shape[1]):
cur_value = df.iloc[i, j]
if isinstance(cur_value, str):
df.iloc[i, j] = "是字符"
# df.iloc[i, j] = 'xxx' 是原地的哦
print(df)
name age gender
0 是字符 18.0 是字符
1 是字符 22.0 NaN
2 是字符 NaN 是字符
完整實現
上面的代碼呢, 后面的函數, 嵌套了前面的函數. 我之前覺得沒啥, 后來跟小伙伴, 他做 Java 的, 然后聊了一波 面向對象, 同時也是, 之前接了一波同事的 數據處理代碼, Python 的面向對象寫法. 我發現, Python 的面向對象寫法, 其實不太好. 一不小心就, 容易, 代碼給 混在一起, 互相調用, 容易搞成 高耦合., 別人很難去維護.
至少是腳本這塊哈, 我希望自己是:
可能更傾向於, 函數式編程的模式;
或者說, 一個函數一個功能, 盡可能解耦, 最后用 main 來調用.
從本例, 就盡量,不要出現, 第二個 一個函數中, 調用 另一個函數 的風格. 這種可以寫在 main 中, 就這樣吧. 重在理解過程和讓代碼可讀性更強一些.
def replace_spec_char(char, replace_char):
"""替換掉特殊字符, 用某種自定義標記"""
# 匹配並進行替換
pattern = re.compile(u'[\U0001000 - \U001ffff]')
return pattern.sub( replace_char, char)
def get_data(file_path):
"""將 Excel 數據讀入為 DataFrame"""
try:
df = pd.read_excel(file_path)
except:
raise "數據讀取異常!"
return df
def main():
data = get_data(file_path)
# 獲取data 的行, 列數, 並進行遍歷
row, col = data.shape
# 遍歷每個 cell, 並對字符串的 cell 進行清洗
for i in range(row):
for j in range(col):
# cell 值
cur_value = data.iloc[i, j]
# 只對字符如進行清洗
if isinstance(cur_value, str):
pure_char = replace_spec_char(char, replace_char)
# 跟清洗前做比對, 打印出特殊符行列號
if cur_value != pure_char:
print(f"特殊字符: {cur_value} 位於 {i+1}行, {j+1}列.")
# 同時將當前個用清理好的進行原地替換
data.iloc[i, j] = pure_char
print("清理完畢")
因為我經常會用到, 因此還打了個包, exe 的, 哎呀不寫了, 打包用 pyinstaller 又不難的, 不展開了.
小結
- 匹配特殊字符, 結合強大的正則表達式, 如 re.compile(), re.sub(), re.match(), re.findall ( ) 都很常用
- 遍歷 DataFrame 用索引方式 df.iloc 速度還行. 遍歷這塊, 同樣 df.iterrows() 也是我常用的呢
- 原地替換 data.iloc[i, j] = 666 簡單又暴力, 現腳本這塊更傾向函數式編程, 尤其是解耦和可讀性, 我覺得更重要