【Python・Ruby】コードで理解するオンライン上からデータを取得してCSVに書き出す方法

オンライン上のデータを取得するために、APIを利用したりスクレイピングをしたりして、CSVに書き出すことをする必要がある機会がちょこちょこあります。
その際以前に投稿した記事を参考にしたりして書くこともあるのですが、複数の記事に散らばっていたのでひとつにまとめておこうと思います。
個人的に、こういう場合PythonかRubyを使うことが多いのでこの言語について個人的なアプローチ方法を書きます。

# 過去記事
[python お天気apiから近日の天気を取得する](http://qiita.com/shizuma/items/ad04e08ab31ba436d34e)
[LDAによるトピックモデル with gensim ~ Qiitaのタグからユーザーの嗜好を考える ~](http://qiita.com/shizuma/items/44c016812552ba8a8b88)
[Rails スクレイピング手法 Mechanizeの使い方](http://qiita.com/shizuma/items/d04facaa732f606f00ff)
[Ruby CSVを扱うためのメモ](http://qiita.com/shizuma/items/7719172eb5e8c29a7d6e)

# 概要

この記事は基本的にはコードベースで説明します。
説明することは

- Pythonで`requests`と`BeautifulSoup`を用いてデータの取得を行い、CSVにする。
- Rubyで`Mechanize`を用いてデータの取得を行い、CSVにする。

です。

# Python

## APIの利用

### urllib2

以前書いた以下の記事では下のコードのように`urllib2`を使ってデータを取得しました。
[python お天気apiから近日の天気を取得する](http://qiita.com/shizuma/items/ad04e08ab31ba436d34e)

当時Python2を使っていたので、Python2のコードです。Python3では`urllib2`ライブラリに変更があったようです


> urllib2 モジュールは、Python 3 で urllib.request, urllib.error に分割されました。 2to3 ツールが自動的にソースコードのimportを修正します。 (http://docs.python.jp/2/library/urllib2.html)

```python
import urllib2, sys
import json

try: citycode = sys.argv[1]
except: citycode = '460010' #デフォルト地域
resp = urllib2.urlopen('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode).read()

# 読み込んだJSONデータをディクショナリ型に変換
resp = json.loads(resp)
print '**************************'
print resp['title']
print '**************************'
print resp['description']['text']

for forecast in resp['forecasts']:
    print '**************************'
    print forecast['dateLabel']+'('+forecast['date']+')'
    print forecast['telop']
print '**************************'
```

### requests

現在はPython3で`requests`を使うようになりました。書き直してみると以下のような感じです。

```python
import requests, sys

try: citycode = sys.argv[1]
except: citycode = '460010' #デフォルト地域
resp = requests.get('http://weather.livedoor.com/forecast/webservice/json/v1?city=%s'%citycode)

resp = resp.json()
print('**************************')
print(resp['title'])
print('**************************')
print(resp['description']['text'])

for forecast in resp['forecasts']:
    print('**************************')
    print(forecast['dateLabel']+'('+forecast['date']+')')
    print(forecast['telop'])
print('**************************')
```

詳しい内容はDocumentで確認出来ます。RequestsのこのDocumentはかなり丁寧に書いてあるので嬉しいです。
[Requests: 人間のためのHTTP](http://requests-docs-ja.readthedocs.io/en/latest/)

ざっと使い方を確認したい方は下記記事を見てみるとよいと思います。
[Requests の使い方 (Python Library)](http://qiita.com/sqrtxx/items/49beaa3795925e7de666)
が参考になると思います。

## スクレイピング

ここでも`requests`を使ってデータを取り込みたいと思います。
下記のコードはwikipediaの日本人俳優・女優の名前をスクレイピングするコードです。
取得したHTMLのパーサーとしては`BeautifulSoup`を使います。XMLでも使えるので便利です。

つまり、Pythonのスクレイピングは`requests`と`BeautifulSoup`で行います。

`BeautifulSoup`は`select`メソッドを使ってCSSセレクタで選択する方法がやりやすいと思います。

```python
import requests
from bs4 import BeautifulSoup
import csv
import time

base_url = 'https://en.wikipedia.org/wiki/'

url_list = ['List_of_Japanese_actors', 'List_of_Japanese_actresses']

for i in range(len(url_list)):
    target_url = base_url + url_list[i]
    target_html = requests.get(target_url).text
    soup = BeautifulSoup(target_html, 'html.parser')
    names = soup.select('#mw-content-text > h2 + ul > li > a')


    for k, name in enumerate(names):
        print(name.get_text())

    time.sleep(1) 
    print('scraping page: ' + str(i + 1))
```

詳しい情報は
[Beautiful Soup Documentation](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
ざっくりみたい方は
[PythonとBeautiful Soupでスクレイピング](http://qiita.com/itkr/items/513318a9b5b92bd56185)

## CSV出力
さて、上記の日本人俳優・女優名をCSVに書き出してみます。

`csv`ライブラリを使えば簡単に出来ます。

```python
import requests
from bs4 import BeautifulSoup
import csv
import time

base_url = 'https://en.wikipedia.org/wiki/'

url_list = ['List_of_Japanese_actors', 'List_of_Japanese_actresses']

all_names = []

for i in range(len(url_list)):
    target_url = base_url + url_list[i]
    target_html = requests.get(target_url).text
    soup = BeautifulSoup(target_html, 'html.parser')
    names = soup.select('#mw-content-text > h2 + ul > li > a')


    for k, name in enumerate(names):
        all_names.append(name.get_text())

    time.sleep(1) 
    print('scraping page: ' + str(i + 1))

f = open('all_names.csv', 'w') 
writer = csv.writer(f, lineterminator='\n')
writer.writerow(['name'])
for name in all_names:
    writer.writerow([name])

f.close()
```

```all_names.csv
name
Hiroshi Abe
Abe Tsuyoshi
Osamu Adachi
Jin Akanishi
...
```

`csv`ライブラリの使い方は下記の記事がすっきりとまとめてあります。
[PythonでCSVの読み書き](http://qiita.com/okadate/items/c36f4eb9506b358fb608)

こちらの記事でもおすすめされていますが、CSVの読み込みに関しては`open`を使うのも悪くないですが、その後の解析も考えて`pandas`を使うことが結構多いのでおすすめです。

```python
import csv

with open('all_name.csv', 'r') as f:
  reader = csv.reader(f)
  header = next(reader)

  for row in reader:
    print row
```

```python
import pandas as pd
df = pd.read_csv('all_name.csv')
```

# Ruby

## APIの利用

Rubyでは`Mechanize`を利用します。
'Mechanize`で受け取ったJSONをパースして使います。
上記のPythonのお天気APIの利用と同じことを以下でやっています。

```rb
require 'mechanize'
require 'json'

citycode = '460010'
agent = Mechanize.new
page = agent.get("http://weather.livedoor.com/forecast/webservice/json/v1?city=#{citycode}")
data = JSON.parse(page.body)

puts '**************************'
puts data['title']
puts '**************************'
puts data['description']['text']

data['forecasts'].each do |forecast|
  puts '**************************'
  puts "#{forecast['dataLabel']}(#{forecast['date']})"
  puts forecast['telop']
end
puts '**************************'
```

おまけですが、httparty等も使えると思います。
[jnunemaker/httparty](https://github.com/jnunemaker/httparty)
ただし、`Mechanize`で十分でしょう。

## スクレイピング及びCSV

基本的に下記記事で事足りると思います。
[Rails スクレイピング手法 Mechanizeの使い方](http://qiita.com/shizuma/items/d04facaa732f606f00ff)

以下のように`get`を使ってデータを取得し`search`メソッドで該当する箇所を抜き出し、`inner_text`や`get_attribute`でテキストや属性を取り出します。

```rb
require 'mechanize'

agent = Mechanize.new
page = agent.get("http://qiita.com")
elements = page.search('li a')

elements.each do |ele|
  puts ele.inner_text
  puts ele.get_attribute(:href)
end
```

今回は上記記事内ではやっていないpostメソッドを用いたデータ取得を具体的な利用例で紹介します。

[The Oracle of Bacon](http://oracleofbacon.org/)というサイトは俳優の名前を入力すると「ベーコン数」というものを返してくれるサイトです。
本記事の内容とはそれますが、「ベーコン数」とはその俳優の共演者を辿っていき、何回たどるとケヴィン・ベーコンという俳優にたどり着くかを表すものです。[六次の隔たり](https://ja.wikipedia.org/wiki/%E5%85%AD%E6%AC%A1%E3%81%AE%E9%9A%94%E3%81%9F%E3%82%8A)を考えることが出来面白いです。2011年時点で`世界中のFacebookユーザーのうち任意の2人を隔てる人の数は平均4.74人である`と言われており世界は意外と小さいということがわかります。

ここでは、上記のpythonのコードで日本人の俳優・女優名を取得してCSVにしたのでそのそれぞれに対してベーコン数を取得しCSVにしたいと思います。

俳優・女優のCSVは以下のもの。

```all_names.csv
name
Hiroshi Abe
Abe Tsuyoshi
Osamu Adachi
Jin Akanishi
...
```

以下がコードです。`Mechanize`の`post`の使い方がポイントです。
また、取得したい「ベーコン数」がHTMLから単純には取れなかったので（タグがついていないテキストだった）、正規表現を使いました。
参考: [Ruby 正規表現の使い方](http://qiita.com/shizuma/items/4279104026964f1efca6)

CSVの扱いは[Ruby CSVを扱うためのメモ](http://qiita.com/shizuma/items/7719172eb5e8c29a7d6e)に書いています。`File.open`と同じように`CSV.open`が使えるのでここではこちらを使いました。


```rb
require 'mechanize'
require 'csv'
require 'kconv'

def get_bacon_num_to(person)

  agent = Mechanize.new
  page = agent.post('http://oracleofbacon.org/movielinks.php',  { a: 'Kevin Bacon', b: person })
  main_text = page.at('#main').inner_text.toutf8
  match_result = main_text.match(/has a Bacon number of ([0-9]+)/)

  bacon_number = 0

  if match_result.nil?
    puts "#{person}: Not found."
  else
    bacon_number = main_text.match(/has a Bacon number of ([0-9]+)/)[1]
    puts "#{person}: #{bacon_number}"
  end

  return bacon_number

end

people = CSV.read('all_names.csv', headers: true)

CSV.open("result.csv", 'w') do |file|
  people.each do |person|
    num = get_bacon_num_to(person['name'])
    file << [person['name'], num]
    sleep(1)
  end

end
```

# 終わりに

手法としては色々あるかと思いますが、今回紹介した道具で多くのことは対応出来ると思います。
是非、試してみてください！