【Python×Excel】窓付き封筒の宛名印刷を自動化するコード全文を公開―経理×python実装例
返金業務で利用していた窓付き封筒の宛名印刷をPythonで自動化した際のコード全文を公開します。pandasによるExcel読込、住所分割、要確認フラグ、エラーログ出力まで含めた実装例を紹介します。
はじめに
前回の記事では、
実際にどのような考え方で住所分割ロジックを作ったのか
について紹介しました。
今回は、その続きとして、
実際に作成したPythonコード全文
を紹介したいと思います。
今回のプログラムは、
- Excelファイル読込
- 住所の正規化
- 住所1・住所2への分割
- 要確認フラグ
- エラーログ出力
- Excelファイル書き出し
までを一つのプログラムで実行できるようにしています。
実行環境
今回の検証環境は以下の通りです。
- Windows 11
- Python 3.10
- pandas
- openpyxl
ライブラリのインストールは以下のコマンドで行いました。
1
pip install pandas openpyxl
フォルダ構成
今回のサンプルでは、以下のような構成を採用しています。
1
2
3
4
5
6
7
8
9
refund_project
├─ main.py
├─ input
│ └─ 返金対応リスト.xlsx
├─ output
│ └─ 宛名印刷データ.xlsx
└─ log
└─ error_log.txt
返金リストを input フォルダに配置し、
1
python main.py
を実行すると、
outputフォルダに宛名印刷用Excelが生成されます。
コード全文
以下が実際に作成したコードです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import pandas as pd
import re
import unicodedata
# ==========================
# ファイル設定
# ==========================
input_file = r"input\返金対応リスト2025-6-17.xlsx"
output_file = r"output\宛名印刷データ.xlsx"
log_file = r"log\error_log.txt"
error_logs = []
# ==========================
# 建物キーワード
# ==========================
BUILDING_WORDS = [
"マンション",
"ハイツ",
"ビル",
"タワー",
"寮",
"コーポ",
"レジデンス"
]
# ==========================
# 住所正規化
# ==========================
def normalize_address(address):
address = str(address)
# 全角スペース→半角スペース
address = address.replace(" ", " ")
# 余分なスペース削除
address = re.sub(r"\s+", " ", address)
return address.strip()
# ==========================
# 文字数確認
# ==========================
def get_display_width(text):
width = 0
for char in str(text):
# 全角文字
if unicodedata.east_asian_width(char) in ['F', 'W', 'A']:
width += 2
# 半角文字
else:
width += 1
return width
# ==========================
# 要確認判定
# ==========================
def need_check(address2):
if address2 == "":
return False
if len(address2) <= 2:
return True
if address2.startswith("丁目"):
return True
if address2.startswith("条"):
return True
return False
# ==========================
# 住所分割
# ==========================
def split_address(address):
address = normalize_address(address)
# -----------------------------------
# パターン0:番地の後に建物名が続く
# -----------------------------------
m = re.search(
r'(\d+(?:-\d+)+号?)(.+)$',
address
)
if m:
after_text = m.group(2).strip()
# 後ろが日本語や英字で始まる場合のみ建物とみなす
if re.match(r'[ぁ-んァ-ヶ一-龠ヲ-゚A-Za-z]', after_text):
address1 = address[:m.end(1)]
address2 = after_text
check = ""
if need_check(address2):
check = "要確認"
# 住所1の表示幅チェック
if get_display_width(address1) > 32:
check = "要確認"
return address1, address2, check
# -----------------------------------
# パターン1:スペース区切り
# -----------------------------------
if " " in address:
parts = address.split(" ", 1)
if len(parts[1]) >= 4:
address1 = parts[0]
address2 = parts[1]
check = ""
if need_check(address2):
check = "要確認"
# 住所1の表示幅チェック
if get_display_width(address1) > 32:
check = "要確認"
return address1, address2, check
# -----------------------------------
# パターン2:建物キーワード検出
# -----------------------------------
for word in BUILDING_WORDS:
pos = address.find(word)
if pos != -1:
prefix = address[:pos]
m = re.search(
r'[0-90-9\--番地号丁目条西東南北]+$',
prefix
)
if m:
cut_pos = m.end()
address1 = address[:cut_pos]
address2 = address[cut_pos:]
check = ""
if need_check(address2):
check = "要確認"
if get_display_width(address1) > 32:
check = "要確認"
return address1.strip(), address2.strip(), check
# -----------------------------------
# パターン3:建物なし
# -----------------------------------
address1 = address
address2 = ""
check = ""
if get_display_width(address1) > 32:
check = "要確認"
return address1, address2, check
# ==========================
# Excel読込
# ==========================
df = pd.read_excel(input_file)
output_rows = []
# ==========================
# データ処理
# ==========================
for _, row in df.iterrows():
try:
address1, address2, check = split_address(row["住所"])
output_rows.append({
"顧客氏名": row["顧客氏名"],
"郵便番号": str(row["郵便番号"]).zfill(7),
"住所1": address1,
"住所2": address2,
"要確認": check
})
except Exception as e:
error_logs.append(
f"{row['返金ID']} : {str(e)}"
)
# ==========================
# 出力Excel作成
# ==========================
output_df = pd.DataFrame(output_rows)
output_df.to_excel(
output_file,
index=False
)
# ==========================
# エラーログ出力
# ==========================
with open(log_file, "w", encoding="utf-8") as f:
if len(error_logs) == 0:
f.write("エラーなし")
else:
for log in error_logs:
f.write(log + "\n")
print("処理完了")
このプログラムで行っている処理
① Excelファイルの読込
pandasを利用して返金リストを読み込みます。
1
df = pd.read_excel(input_file)
② 住所の正規化
全角スペースや不要な空白を整理します。
1
address = address.replace(" ", " ")
③ 建物名の判定
以下のようなキーワードを利用して建物名を検出します。
1
2
3
4
5
6
7
BUILDING_WORDS = [
"マンション",
"ハイツ",
"ビル",
"タワー",
"寮"
]
④ 番地+建物名を優先的に分割
例えば、
1
東京都千代田区千代田5-2-3スカイビル402号室
を、
1
2
東京都千代田区千代田5-2-3
スカイビル402号室
のように分割します。
⑤ 要確認フラグ
以下のようなケースでは、
1
要確認
を付与します。
- 建物名が短すぎる
- 住所1が長すぎる
- 判定が難しい住所
⑥ エラーログ出力
処理できなかったデータは、
1
error_log.txt
へ出力します。
完璧ではない
今回のプログラムは、
決して100%正しく動作するわけではありません。
例えば、
1
佐賀県佐賀市川副町大字犬井道9476-187
のような特殊な住所は、
機械だけで正しく判定することが難しい場合があります。
そのため、
完全自動化を目指すのではなく、要確認フラグを付けて人間が確認する
という運用を前提にしています。
実際に作って感じたこと
最初は、
「住所を2行に分けるだけ」
と思っていました。
しかし実際には、
- 表記ゆれ
- 建物名の種類
- 特殊な住所
- 長い住所
など、想像以上に例外が多く存在しました。
そして最終的に感じたのは、
業務改善で重要なのは、100%自動化することではない
ということでした。
むしろ、
「どこを人間に残すか」
を設計することの方が重要だったように思います。
おわりに
今回紹介したコードは、
あくまで一つの実装例です。
住所データの形式や業務内容によって、
最適なロジックは変わります。
しかし、
- pandas
- openpyxl
- 要確認フラグ
- エラーログ
という考え方は、住所処理以外の業務改善にも応用できると思います。
今回の記事が、
Pythonによる業務改善を検討している方の参考になれば幸いです。