PDFファイル（3608ページ）から表形式のテキストデータをタブ区切りで抽出するシェルスクリプト

## 経緯

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">国のキャッシュレスポイント還元PRサイト、「使えるお店一覧」から18万店を網羅した3608ページのPDFに飛ばす <a href="https://twitter.com/hashtag/SmartNews?src=hash&amp;ref_src=twsrc%5Etfw">#SmartNews</a> <a href="https://t.co/2dkv221ddi">https://t.co/2dkv221ddi</a></p>&mdash; ある２ー (@rdy3e24) <a href="https://twitter.com/rdy3e24/status/1168475077840523265?ref_src=twsrc%5Etfw">September 2, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
このようなTweetがTLに流れてきました。
元記事にも指摘があるとおり、PDFファイルが3000ページを超えていました。
さすがにユーザビリティに問題があると考えたのか、9月中に地図上に掲載予定とのことです。

ただ、PDFの状態にどうしても我慢できない人はおり、下記TweetのようにPythonスクリプトでテキストデータに加工した人もいました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">なんかムラムラしてきたので、一次データをtsvにするコードを書いてみた<br>なぜかしらんが緯度経度付与して、空間検索インデックスにぶち込みたい（#^ω^）<a href="https://t.co/YI05uNX9lJ">https://t.co/YI05uNX9lJ</a></p>&mdash; しーちょー🚲 (@c_tyo) <a href="https://twitter.com/c_tyo/status/1168555657437442048?ref_src=twsrc%5Etfw">September 2, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
私も同じようにテキストデータに加工したいと考えた口です。
そこで、PDFからテキストデータをシェルスクリプトだけで抽出した手順を示します。

- 検証環境 … Ubuntu 18.04 LTS

## PDFファイルからテキストデータを抽出する

テキストデータの抽出自体はUnix系OSであれば下記CLIコマンドで実施可能です。

```sh
# infile.pdf … 加工対象のPDFファイル
# outfile.txt … 抽出したテキストデータの保存先
pdftotext -layout infile.pdf target.txt
```

## 読みやすく使いやすい形に加工する

抽出されたテキストデータを確認すると、以下のような形式です。

```sh
No.     都道府県    市区町村             事業所名（屋号）                業種              還元率
      1 北海道    愛別町     セブン－イレブン愛別町店              小売業     食料品              2%
      2 北海道    愛別町     伊藤新聞販売所                   小売業     その他小売            5%
      3 北海道    愛別町     三愛自動車工業株式会社               その他業種   ー                5%
      4 北海道    赤平市     赤平   ＳＳ                   小売業     ガソリンスタンド         2%
      5 北海道    赤平市     赤平平岸ＳＳ                    小売業     ガソリンスタンド         2%
      6 北海道    赤平市     セブン－イレブン赤平文京町店            小売業     食料品              2%
      7 北海道    赤平市     セブン－イレブン赤平茂尻店             小売業     食料品              2%
      8 北海道    赤平市     出光茂尻ＳＳ                    小売業     ガソリンスタンド         2%
      9 北海道    赤平市     海鮮居食屋暖らん                  サービス    飲食業              5%
  10 北海道       赤平市     株式会社菱友 赤平支店               小売業     その他小売            5%
  11 北海道       赤平市     セルフ幌岡ＳＳ                   小売業     ガソリンスタンド         2%
  12 北海道       赤平市     デンキの桐原                    小売業     電化製品             5%
```

これを以下のシェルスクリプト（totsv.sh）を作成してタブ区切りのデータに整形します。

```sh
#!/bin/sh

cat "$1" |
awk 'BEGIN{ # 必要な範囲のテキストデータのみを取得
	target = 0 # 出力フラグ
}
{
	if($0 ~ /固定店舗（EC・通信販売を除く）.*令和元年8月21日   現在/){ # 固定店舗を対象とするため、フラグを有効化
		target = 1
	}
	if($0 ~ /EC・通信販売（楽天市場）/){# 通販のリストは除外するため、フラグを無効化
		target = 0
	}
	if(target == 1){ # 有効な区間の文字列を出力
		print $0
	}
}' > tmp_result

cat tmp_result |
sed 's/.*キャッシュレス・消費者還元事業.*事務局審査を通過した加盟店一覧.*//' | # 不要な文字列を削除
sed 's/.*固定店舗（EC・通信販売を除く）.*//' | # 不要な文字列を削除
sed 's/^ *[0-9]* *$//' | # ページ番号を削除
sed 's/.*※9月中に地図上に掲載予定.*//' | # 不要な文字列を削除
sed 's/.*都道府県.*//' | # 不要な文字列を削除
grep -v '^ *$' | # 不要な空行を削除
awk 'BEGIN{ # 項目ごとに整形
	OFS="\t"
}
{
	industry1 = $(NF - 2) # 業種1列目
	industry2 = $(NF - 1) # 業種2列目
	rate = $NF # 還元率
	name = "" # 店舗名
	for(i = 4; i <= NF - 3; i++){ # 店舗名を半角スペースで結合
		if(i == 4){name = $i; continue}
		name = name " " $i
	}
	print $1, $2, $3, name, industry1, industry2, rate # タブ区切りで出力
}'
```

## シェルスクリプトの実行

以下のコマンドで`result.tsv`というファイルに出力されます。

```sh
# target.txtを引数としてシェルスクリプトを実行し、`result.tsv`として保存
$ totsv.sh target.txt > result.tsv
```
## シェルスクリプトの解説

シェルスクリプトは大きく2つのブロックに分かれます。
1つはテキストデータから**必要なデータの範囲のみ抽出する**上半分のブロックと、**抽出したデータを整形する**下半分のブロックです。

### 必要なデータの抽出

まず必要なデータ範囲ですが、今回は固定店舗のみを対象とします。
元のPDFファイルを見ると表のあるページ先頭には必ず見出しがあります。
この見出しをキーとしてそのページが必要かどうかを判定します。
この判定は`awk`コマンドのみで実施しました。
出力結果は確認のためにあえて`tmp_result`ファイルに出力しています。

### 整形処理前半：不要な文字列の削除

`tmp_result`ファイルを見て確認した状態をもとに整形処理を作成しました。
整形処理の前半では`sed`と`grep`コマンドで不要な文字列を一掃します。

### 整形処理後半：タブ区切りに整える

Excel等の表計算ソフトや文字列処理でも扱いやすいことから、タブ区切りに整形しました。
これは`awk`コマンドのみで済ませました。

## 処理した結果

`result.tsv`ファイルの中身はそのままスプレッドシートやExcelにコピー＆ペーストやインポートが可能な形式になっています。
grepコマンドなどで住所を指定して検索すれば、特定の住所の事業者一覧を作ることができますし、これを元データとしてWeb APIや検索サービスとして提供することも難しくないでしょう。

```sh
# 検索の例
$ grep "東京都\s*目黒区" result.tsv
```

ちなみに上記検索条件では687件表示されました。
このPDFの住所は市区町村までしか記載がないため、これ以上詳しい住所では検索できず、地図へのプロットも大雑把すぎるため断念しました。
先ほど記したように公式では9月中に地図上に掲載予定とのことですが、今回私が作成したものより使いやすいとうれしいですね。