Openpyxlでのマージされたセルの取り扱い方


# はじめに
Openpyxlを使って、エクセルを読み込み、マージされたセルをどのように扱うのかについてメモを残しておきます。

今回はこのようなエクセルデータを使用しました。
![スクリーンショット 2019-05-27 17.48.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227613/131ec8e3-afc3-6b47-ec7d-d7eb0f09e437.png)


# Excelデータを読み込む

```.py
import openpyxl

# エクセエルファイルを指定し、読み込みたいシート名を指定
excel_file = 'sample.xlsx'
wb = openpyxl.load_workbook(filename = excel_file)
sheet = wb['Sheet4']
```

# セル範囲を指定してセルを取得する

```.py
# 先ほど読み込んだシートの中で読み込みたい範囲を指定する
for 行変数 in シート変数[開始セル:終了セル]:
 for セル変数 in 行変数:
  print(セル変数.value ) 
```
例えば、A1~D7に書かれたデータを読み込みたい場合は以下のように範囲を指定します。


```.py
# １回目のfor文では、行単位で、2回目のfor文で行のなかにあるcell単位で出力することができる
for row in sheet['A1:D7']:
 for cell in row:
  print(cell, cell.value) 
```
出力結果

```.py
<Cell 'Sheet4'.A1> None
<Cell 'Sheet4'.B1> サンプル
<MergedCell 'Sheet4'.C1> None
<MergedCell 'Sheet4'.D1> None
<MergedCell 'Sheet4'.A2> None
<Cell 'Sheet4'.B2> レコードヘッダー1
<Cell 'Sheet4'.C2> レコードヘッダー2
<Cell 'Sheet4'.D2> レコードヘッダー3
<MergedCell 'Sheet4'.A3> None
<MergedCell 'Sheet4'.B3> None
<MergedCell 'Sheet4'.C3> None
<MergedCell 'Sheet4'.D3> None
<MergedCell 'Sheet4'.A4> None
<MergedCell 'Sheet4'.B4> None
<MergedCell 'Sheet4'.C4> None
<MergedCell 'Sheet4'.D4> None
<Cell 'Sheet4'.A5> カラムヘッダー1
<Cell 'Sheet4'.B5> サンプルセル1
<Cell 'Sheet4'.C5> サンプルセル2
<Cell 'Sheet4'.D5> サンプルセル3
<Cell 'Sheet4'.A6> カラムヘッダー2
<Cell 'Sheet4'.B6> サンプルセル4
<Cell 'Sheet4'.C6> サンプルセル5
<Cell 'Sheet4'.D6> サンプルセル6
<Cell 'Sheet4'.A7> カラムヘッダー3
<Cell 'Sheet4'.B7> サンプルセル7
<Cell 'Sheet4'.C7> サンプルセル8
<Cell 'Sheet4'.D7> サンプルセル9
```

cellオブジェクトから得られる情報は、
&emsp;・マージされたセルか(Cell or MergedCell) 
&emsp;・セルの属するシート名 (シート名)
&emsp;・セルの座標 (アルファベット+数字)

cell.valueから得られる情報は、
&emsp;・セルの中に書かれたテキスト
&emsp;(* mergeされたcellの場合はNoneになる)


# mergeされたセルの値
セルが縦方向または横方向にマージされている場合、そのセルの一番左上が代表のセルとなり、 
(縦方向でのマージでは一番上にあるセル、横方向のマージでは一番左にあるセル)
それ以外のセルはMergedCellとなります。


具体的に見てみると、
今回のデータでは、サンプルと書かれたセルが、B1~D1にかけてマージされています。
![スクリーンショット 2019-05-27 17.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/227613/672e995a-1d6c-846c-2274-aa3fda3d7c3b.png)

この時、先ほどの実行結果を見てみると、

```.py
<Cell 'Sheet4'.B1> サンプル
<MergedCell 'Sheet4'.C1> None
<MergedCell 'Sheet4'.D1> None
<MergedCell 'Sheet4'.A2> None
<Cell 'Sheet4'.B2> レコードヘッダー1
<Cell 'Sheet4'.C2> レコードヘッダー2
<Cell 'Sheet4'.D2> レコードヘッダー3
```
左上のB1のセルは'Cell'として、"サンプル"というテキストをもち、
それ以外のC1, D1は'MergedCell'となり、valueがNoneとなっています。

同様に、レコードヘッダー1, レコードヘッダー2, レコードヘッダー3と書かれた
縦にマージされたセルについて見てみると、一番上のB2, C2, D2のみテキストが入り、
それ以外のセルは'MergedCell'とされています。

```.py
<Cell 'Sheet4'.B2> レコードヘッダー1
<Cell 'Sheet4'.C2> レコードヘッダー2
<Cell 'Sheet4'.D2> レコードヘッダー3
<MergedCell 'Sheet4'.A3> None
<MergedCell 'Sheet4'.B3> None
<MergedCell 'Sheet4'.C3> None
<MergedCell 'Sheet4'.D3> None
<MergedCell 'Sheet4'.A4> None
<MergedCell 'Sheet4'.B4> None
<MergedCell 'Sheet4'.C4> None
<MergedCell 'Sheet4'.D4> None
```

# mergeされたセルの範囲を取得
sheetの中のマージされたセルの範囲を取得する関数が用意されていたのでメモ。

```.py
sheet.merged_cells.ranges
```
* [openpyxl公式ページ](https://openpyxl.readthedocs.io/en/stable/api/openpyxl.worksheet.worksheet.html#openpyxl.worksheet.worksheet.Worksheet.merged_cell_ranges)より、cell.merged_cell_rangesが廃止され、  
cell.merged_cells.rangesを使用することが推奨されています。

出力結果

```.py
[<CellRange B1:D1>,
 <CellRange B2:B4>,
 <CellRange C2:C4>,
 <CellRange A1:A4>,
 <CellRange D2:D4>]
```

これを使えば、どのセルがどこからどこまでマージされているのかリストで取得できるため、
先ほどの実行結果と組み合わせることで、色々使えそうです！