日々之迷歩

世の中わからんことだらけ

ITが複雑で難しくなっていく様に翻弄される日々です。微力ながら共著させていただいた「シェル・ワンライナー160本ノック」をよろしくお願い申し上げます。

久留米市人口データのスクレイピング

Webページから情報収集をしてくる処理をスクレイピングって言うらしい。シェル芸勉強会でもスクレイピングみたいなことをやった。

福岡県久留米市では、オープンデータカタログとして人口情報をCSVにて公開している。

久留米市:オープンデータカタログ

ただ、過去の情報はExcel形式での公開。xlsx形式ならばエクシェル芸で捌けそうであるが、残念ながら旧形式のようだ。

平成25年より新しいデータについては、HTMLでWebページとして公開されている。ならばスクレイピングでCSVにした方が扱いやすいのでは?

ということで、人口情報が記載されたWebページからCSV作成して保存するスクレイピングなシェルスクリプトを作ってみた。パソコンはMac、OSはOSX Yosemiteだが、Linux、FreeBSDなどでも動くはず。

必要なコマンド

GNU系のツールについては、OSXやFreeBSDの場合は追加インストールしよう。Linuxの場合はgsedsedと読み替えること。

  • curl
  • nkf
  • GNU coreutils
  • GNU sed
  • GNU awk

では作成したスクリプトを順次紹介。実行するときは適当な一時ディレクトリを作成し、その中にスクリプトをコピペして保存して利用のこと。カレントディレクトリにCSVファイルがたくさん出来るので要注意。

WebページをCSVへ保存

久留米市の人口情報は下記のようなURLで、HTMLのTableを使って表現されている。

久留米市:年齢別人口 平成27年7月1日現在

まずは中核となる、上記のようなWebページからCSVに保存するスクリプトを作成。スクリプトのファイル名はcsv_save.sh

#!/bin/sh
curl $1 |
nkf -wLux |
sed -n '/<div id="content-otherpage">/,/<\/div>/p' |
sed -n '/<table/,/<\/table>/p' |
tr -d '\n' |
gsed 's;</table>;&\n;' |
head -n 1 |
gsed 's;<caption>;\n&;' |
gsed 's;<tr>;\n&;g' |
tail -n +2 |
sed 's/<[^<>]*>/ /g' |
tr -d ',' |
sed 's/^ *//' |
sed 's/ *$//' |
tr -s ' ' |
sed '1s/ /_/' |
sed '1s/ /_/' |
awk 'NR==1{filename=$0}NR!=1{gsub(/ /,",",$0);print > filename".csv"}'

使い方はこちら。人口情報WebページのURLを指定して実行すると、TableのCaptionをファイル名としたCSVファイルに保存する。

$ ./csv_save.sh http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0703-1719-442.html

$ ls *.csv
年齢別人口_平成27年7月1日現在.csv

$ head -n 5 年齢別人口_平成27年7月1日現在.csv
年齢,男,女,計
0,1512,1382,2894
1,1451,1486,2937
2,1516,1414,2930
3,1552,1409,2961
4,1592,1410,3002
5,1469,1437,2906
6,1446,1387,2833
7,1514,1397,2911
8,1460,1384,2844

WebページのURLリスト作成

人口情報へのURLリストは、下記のようなWebページになっている。

久留米市:住民基本台帳 年齢別人口

ここからリンクURLを抽出して一覧にするスクリプトを作成。スクリプトのファイル名はlist_url.sh

#!/bin/sh
curl $1 |
nkf -wLux |
sed -n '/<div id="content-otherpage">/,/<\/div>/p' |
sed -n '/<ul>/,/<\/ul>/p' |
grep '^<li>' |
grep -v '過去データ' |
sed 's/<li><a href="\([^"]*\)".*$/\1/' |
sed "s;^;$1;"

使い方はこちら。下記のWebページへのURLを指定して実行すると、人口情報WebページへのリンクURLを抽出する。一番下の「過去データ」はxls形式のデータだけなので外す。

$ ./list_url.sh http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0703-1719-442.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0603-1706-442.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0508-1537-442.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0406-1701-442.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/2015-0305-0957-442.html
....

これと先ほどのcsv_save.shを組み合わせることでCSVファイルを一括作成する。 実際に使うときは、連続でたくさんのURLにアクセスするとよくない気がするので、1秒毎に休みながら連続実行。

$ ./list_url.sh http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/ | xargs -I {} sh -c './csv_save.sh {}; sleep 1'

$ ls *.csv
年齢別人口_平成26年10月1日現在.csv    年齢別人口_平成26年8月1日現在.csv
年齢別人口_平成26年11月1日現在.csv    年齢別人口_平成26年9月1日現在.csv
年齢別人口_平成26年12月1日現在.csv    年齢別人口_平成27年1月1日現在.csv
年齢別人口_平成26年1月1日現在.csv     年齢別人口_平成27年2月1日現在.csv
年齢別人口_平成26年2月1日現在.csv     年齢別人口_平成27年3月1日現在.csv
年齢別人口_平成26年3月1日現在.csv     年齢別人口_平成27年4月1日現在.csv
年齢別人口_平成26年4月1日現在.csv     年齢別人口_平成27年5月1日現在.csv
年齢別人口_平成26年5月1日現在.csv     年齢別人口_平成27年6月1日現在.csv
年齢別人口_平成26年6月1日現在.csv     年齢別人口_平成27年7月1日現在.csv
年齢別人口_平成26年7月1日現在.csv

3種類の人口情報をまとめてCSV保存

久留米市の人口情報は、上記の年齢別だけでなく、校区別、町別でも公開されている。

久留米市:住民基本台帳 年齢別人口

久留米市:住民基本台帳 校区別人口及び世帯数

久留米市:住民基本台帳 町別人口及び世帯数

上記3種類の人口情報を一括でCSV保存するスクリプトを作成。スクリプトのファイル名はkurume_getopendata_tocsv.sh

#!/bin/sh

# 住民基本台帳 年齢別人口
# http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/index.html
# 住民基本台帳 校区別人口及び世帯数
# http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4010koukujinkou/index.html
# 住民基本台帳 町別人口及び世帯数
# http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4020machijinkou/index.html

cat <<EOF |
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4030nenreijinkou/index.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4010koukujinkou/index.html
http://www.city.kurume.fukuoka.jp/1080shisei/2010shoukai/3040toukei/4020machijinkou/index.html
EOF
sed 's/index.html$//' |
xargs -n 1 ./list_url.sh |
xargs -I {} sh -c './csv_save.sh {}; sleep 1'

このスクリプトを実行すると、下記のようにたくさんのCSVファイルが出来る。

$ ./kurume_getopendata_tocsv.sh 

$ ls *.csv
年齢別人口_平成26年10月1日現在.csv
年齢別人口_平成26年11月1日現在.csv
年齢別人口_平成26年12月1日現在.csv
....
町別人口及び世帯数 平成26年10月1日現在.csv
町別人口及び世帯数 平成26年11月1日現在.csv
町別人口及び世帯数 平成26年12月1日現在.csv
....
校区別人口及び世帯数_平成26年10月1日現在.csv
校区別人口及び世帯数_平成26年11月1日現在.csv
校区別人口及び世帯数_平成26年12月1日現在.csv

作成してみて感想

最初のスクレイピング本体スクリプトcsv_save.shよりも、一覧作成してどうやって連続実行させるか?の方が意外と悩んでしまった。

上記のように、スクレイピングをする処理、URL一覧を作る処理をそれぞれコマンドにしてしまい、xargsで連続実行するというのがスッキリしていいのではないかと思う。

単機能なフィルタコマンドを作る。結局UNIX哲学に則るのが一番シンプルになるというのと実感したのであった。