キャッシュレス還元対象店一覧（PDF 3608ページ）をワンライナーでCSV形式にしてみた

# とりあえずワンライナー

``` shell
pdftotext -raw -f 3 -l 2795 kameiten_touroku_list.pdf - | grep -E '^[0-9]+(,[0-9]+)* |[25]%$' | perl -pe 's/(?<![25]%)\n/ /g' | perl -pe 's/^(\S+) (\S+) (\S+) (.+) (\S+) (\S+) (\S+)$/"$1","$2","$3","$4","$5","$6","$7"/g' > kameiten.csv
```

※変換しているのは「①固定店舗（EC・通信販売を除く）」のページです。
※変換結果のCSVは、14MBくらいです。

# 背景

[キャッシュレス・消費者還元事業(公式サイト)](https://cashless.go.jp/consumer/index.html)

[国のキャッシュレスポイント還元PRサイト、「使えるお店一覧」から18万店を網羅した3608ページのPDFに飛ばす(ねとらぼ)](https://nlab.itmedia.co.jp/nl/articles/1909/02/news110.html)

最近話題の「キャッシュレス・消費者還元事業」のPRサイトで公開されているPDFについて、「せめてCSVにしろよ…」という声が多数あったので、ワンライナーでCSVに変換してみました。

ちなみにすでにCSVに変換している人はいます。早い。
twitterの方は結果をGoogleドライブに公開されています！

[PDFファイル（3608ページ）から表形式のテキストデータをタブ区切りで抽出するシェルスクリプト](https://qiita.com/arapower/items/9c564924de0d48729b39)

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ひまだったので、pdfの全ページをテキストファイルにコピペして、あれこれやってcsvにしてみました。<br>Googleドライブに入れておきますので、どうぞ御自由にお使いください。<a href="https://t.co/WjVJALXixj">https://t.co/WjVJALXixj</a></p>&mdash; かみ かずしげ (@kami_kazushige) <a href="https://twitter.com/kami_kazushige/status/1168359806924546048?ref_src=twsrc%5Etfw">2019年9月2日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


Webサービス公開している人まで…。早すぎ。

[ポイント還元ドットコム](https://www.pointkangen.com/ )

zaimも公開していますね。

[zaim キャッシュレス還元マップ](https://content.zaim.net/cashless)

また、公式サイトには「※9月中下旬には、地図上に対象店舗を表示するウェブ機能やアプリを公表予定」との記載があります。
ただ、[この記事の余談](#余談というか注意点)で書いているようにデータ不備があったりしますが、大丈夫なのでしょうか。。


# ワンライナーの解説

一応解説します。

## pdftotextでテキストに変換

``` shell
pdftotext -raw -f 3 -l 2795 kameiten_touroku_list.pdf -
```

`pdftotext` という、その名の通りPDFをテキスト変換してくれるコマンドがあります。
オプションには以下を指定しています。

```
-f : 開始ページを指定
-l : 終了ページを指定
-raw : テキストをコンテンツストリームの順序通りに出力する（非推奨）
```

`-layout` でもよいのですが、余計なスペースが出力されてしまうので、`-raw` を指定しています。
ただ、`-raw` は非推奨（Use of raw mode is no longer recommended.）らしいです。まあお遊びなので良いでしょう。

これを実行すると、以下のようなテキストに変換されます。（とりあえず最初のページのみ対象）

``` shell:実行
pdftotext -raw -f 3 -l 3 kameiten_touroku_list.pdf -
```

``` :結果
キャッシュレス・消費者還元事業 事務局審査を通過した加盟店一覧
①固定店舗（EC・通信販売を除く） 令和元年8月21日 現在
※9月中に地図上に掲載予定
No. 都道府県 市区町村 事業所名（屋号） 業種 還元率
1 北海道 愛別町 セブン－イレブン愛別町店 小売業 食料品 2%
2 北海道 愛別町 伊藤新聞販売所 小売業 その他小売 5%
…（中略）…
49 北海道 旭川市 旭川お城の鯉寿し サービス 飲食業 5%
50 北海道 旭川市 セブン－イレブン旭川買物公園店 小売業 食料品 2%
1

```


## 必要なレコード行のみ抽出

``` shell
grep -E '^[0-9]+(,[0-9]+)* |[25]%$'
```

ページごとに、説明文と表のヘッダーとページ数が挿入されているので、`grep` で必要なレコード行のみ抽出します。
判定として、「先頭が数字で直後に半角スペースがある行」または「末尾が'2%'または'5%'の行」としています。
なぜ2つの条件を OR としているかは、次のステップで説明します。
まあ、判定にはもっとスマートな方法がありそうですが。。

``` shell:実行
pdftotext -raw -f 3 -l 3 kameiten_touroku_list.pdf - | grep -E '^[0-9]+(,[0-9]+)* |[25]%$'
```

``` :結果
1 北海道 愛別町 セブン－イレブン愛別町店 小売業 食料品 2%
2 北海道 愛別町 伊藤新聞販売所 小売業 その他小売 5%
…（中略）…
49 北海道 旭川市 旭川お城の鯉寿し サービス 飲食業 5%
50 北海道 旭川市 セブン－イレブン旭川買物公園店 小売業 食料品 2%
```

## 2行に渡っているレコードを1行にする

``` shell
perl -pe 's/(?<![25]%)\n/ /g'
```

先ほどの結果でほぼ表形式のテキストが取得できていますが、一つ注意点があります。
例として、「さいたま市岩槻区」の箇所を見てみます。

``` shell:実行
pdftotext -raw -f 468 -l 468 kameiten_touroku_list.pdf - | grep -E '^[0-9]+(,[0-9]+)* |[25]%$'
```

``` :結果
23,251 埼玉県 さいたま市 マクドナルド埼大通り店 サービス 飲食業 2%
…（中略）…
23,288 埼玉県 さいたま市 ワンステップ大宮店 サービス 理容・美容業 5%
23,289 埼玉県 さいたま市岩槻区
アトリエビザビ 小売業 衣料品 5%
23,290 埼玉県 さいたま市岩槻区
伊太利家 Ｐｉｚｚｅｒｉａ サービス 飲食業 5%
…（中略）…
23,300 埼玉県 さいたま市岩槻区
セブン－イレブン岩槻本丸店 小売業 食料品 2%
```

なんと、レコードが2行になってしまっています。
これは、「さいたま市岩槻区」という市区町村名が、PDFの表の幅からはみ出しているために起こっているようです。
前のステップで「先頭が数字で直後に半角スペースがある行」 **または** 「末尾が'2%'または'5%'の行」としていたのは、これが理由です。

では、改行を置換して1行にしてみます。sed でも出来そうですが、perl 使った方が簡単かなと思います。
置換は「末尾が'2%'でも'5%'でもない行」について、「改行を半角スペースに変換」としています。

``` shell:実行
pdftotext -raw -f 468 -l 468 kameiten_touroku_list.pdf - | grep -E '^[0-9]+(,[0-9]+)* |[25]%$' | perl -pe 's/(?<![25]%)\n/ /g'
```

``` :結果
23,251 埼玉県 さいたま市 マクドナルド埼大通り店 サービス 飲食業 2%
…（中略）…
23,288 埼玉県 さいたま市 ワンステップ大宮店 サービス 理容・美容業 5%
23,289 埼玉県 さいたま市岩槻区 アトリエビザビ 小売業 衣料品 5%
23,290 埼玉県 さいたま市岩槻区 伊太利家 Ｐｉｚｚｅｒｉａ サービス 飲食業 5%
23,291 埼玉県 さいたま市岩槻区 出光上野ＳＳ 小売業 ガソリンスタンド 2%
23,292 埼玉県 さいたま市岩槻区 出光平林寺ＳＳ 小売業 ガソリンスタンド 2%
23,293 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻江川店 小売業 食料品 2%
23,294 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻駅東口店 小売業 食料品 2%
23,295 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻鹿室店 小売業 食料品 2%
23,296 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻慈恩寺店 小売業 食料品 2%
23,297 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻西原台１丁目店 小売業 食料品 2%
23,298 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻府内１丁目店 小売業 食料品 2%
23,299 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻本町店 小売業 食料品 2%
23,300 埼玉県 さいたま市岩槻区 セブン－イレブン岩槻本丸店 小売業 食料品 2%
```

1行になっていますね。


# CSV形式に変換

``` shell
perl -pe 's/^(\S+) (\S+) (\S+) (.+) (\S+) (\S+) (\S+)$/"$1","$2","$3","$4","$5","$6","$7"/g'
```

では最後、半角スペース区切りのデータを、ダブルクォーテーションで囲んでカンマで区切るだけです。

…だけ…なのですが、意外とここが一番苦労しました。
なぜなら、事業所名にも半角スペースが入っているからです。
たとえば、先頭から4つ目のレコードを見ると、

```
4 北海道 赤平市 赤平 ＳＳ 小売業 ガソリンスタンド 2%
```

となっています。
事業所名は「赤平 ＳＳ」なので、結果としては以下のようにしたいです。

```
"4","北海道","赤平市","赤平 ＳＳ","小売業","ガソリンスタンド","2%"
```


つまり、先頭から1,2,3番目のカラムはそのまま半角スペース区切り。
末尾から1,2,3番目のカラムも半角スペース区切り。
で、その間の n 個の要素については、ひとまとめの項目にしたい、ということになります。

awk でループ回して…というのも考えましたが、ループ使うのもアレなので、正規表現でどうにか置換しました。

見てわかるように、前から3つ、後ろから3つのスペース以外の文字のまとまりと、その間の要素…という感じでゴリゴリ書いて、置換しています。

この辺のもっと良い書き方は、誰か教えていただけると嬉しいです。。正規表現難しい。。

``` shell:実行
pdftotext -raw -f 3 -l 3 kameiten_touroku_list.pdf - | grep -E '^[0-9]+(,[0-9]+)* |[25]%$' | perl -pe 's/(?<![25]%)\n/ /g' | perl -pe 's/^(\S+) (\S+) (\S+) (.+) (\S+) (\S+) (\S+)$/"$1","$2","$3","$4","$5","$6","$7"/g'
```

``` :結果
"1","北海道","愛別町","セブン－イレブン愛別町店","小売業","食料品","2%"
"2","北海道","愛別町","伊藤新聞販売所","小売業","その他小売","5%"
"3","北海道","愛別町","三愛自動車工業株式会社","その他業種","ー","5%"
"4","北海道","赤平市","赤平 ＳＳ","小売業","ガソリンスタンド","2%"
…（中略）…
"49","北海道","旭川市","旭川お城の鯉寿し","サービス","飲食業","5%"
"50","北海道","旭川市","セブン－イレブン旭川買物公園店","小売業","食料品","2%"
```

ちょっと無理やりですが、何とか変換できました！


# 余談というか注意点

データ眺めていて気づいたのですが、かなり不備がありますね、これ…。
たとえば、No.37と38のところ。
半角スペースが入っているだけで、全く同じデータが入っています。

```
"37","北海道","旭川市","旭川末広ＳＳ","小売業","ガソリンスタンド","2%"
"38","北海道","旭川市","旭川末広 ＳＳ","小売業","ガソリンスタンド","2%"
```

明らかに入力ミスっぽいやつとか。

```
"3,896","北海道","札幌市中央区","函館開陽亭すすきの プラス 大三坂","サービス","飲食業","5%"
"3,897","北海道","札幌市中央区","函館開陽亭すすきの プラス 大三","サービス","飲食業","5%"
"3,898","北海道","札幌市中央区","函館開陽亭すすきの プラス 大","サービス","飲食業","5%"
```

事業所名が謎の「本社」だったり。事業所名ってそういうものなんでしょうか？
「本社ダミー」とか、明らかにおかしい気がするけど…。

```
"852","北海道","厚岸町","本社","小売業","その他小売","2%"
"1,040","北海道","岩見沢市","本社","小売業","ガソリンスタンド","2%"
"1,252","北海道","遠軽町","本社ダミー","小売業","ガソリンスタンド","2%"
"1,288","北海道","長万部町","本社","小売業","その他小売","2%"
"1,672","北海道","帯広市","本社","小売業","その他小売","2%"
"2,178","北海道","釧路市","本社ＰＯＳ","小売業","その他小売","2%"
```

利用する際には、注意しましょう。そのうち直るのかな…。