Ruby
Python
スクレイピング
CSV
Mechanize

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

More than 1 year has passed since last update.

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

過去記事

python お天気apiから近日の天気を取得する
LDAによるトピックモデル with gensim ~ Qiitaのタグからユーザーの嗜好を考える ~
Rails スクレイピング手法 Mechanizeの使い方
Ruby CSVを扱うためのメモ

概要

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

  • PythonでrequestsBeautifulSoupを用いてデータの取得を行い、CSVにする。
  • RubyでMechanizeを用いてデータの取得を行い、CSVにする。

です。

Python

APIの利用

urllib2

以前書いた以下の記事では下のコードのようにurllib2を使ってデータを取得しました。
python お天気apiから近日の天気を取得する

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

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

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を使うようになりました。書き直してみると以下のような感じです。

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

ざっと使い方を確認したい方は下記記事を見てみるとよいと思います。
Requests の使い方 (Python Library)
が参考になると思います。

スクレイピング

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

つまり、PythonのスクレイピングはrequestsBeautifulSoupで行います。

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

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
ざっくりみたい方は
PythonとBeautiful Soupでスクレイピング

CSV出力

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

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

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の読み書き

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

import csv

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

  for row in reader:
    print row
import pandas as pd
df = pd.read_csv('all_name.csv')

Ruby

APIの利用

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

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
ただし、Mechanizeで十分でしょう。

スクレイピング及びCSV

基本的に下記記事で事足りると思います。
Rails スクレイピング手法 Mechanizeの使い方

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

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というサイトは俳優の名前を入力すると「ベーコン数」というものを返してくれるサイトです。
本記事の内容とはそれますが、「ベーコン数」とはその俳優の共演者を辿っていき、何回たどるとケヴィン・ベーコンという俳優にたどり着くかを表すものです。六次の隔たりを考えることが出来面白いです。2011年時点で世界中のFacebookユーザーのうち任意の2人を隔てる人の数は平均4.74人であると言われており世界は意外と小さいということがわかります。

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

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

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

以下がコードです。Mechanizepostの使い方がポイントです。
また、取得したい「ベーコン数」がHTMLから単純には取れなかったので(タグがついていないテキストだった)、正規表現を使いました。
参考: Ruby 正規表現の使い方

CSVの扱いはRuby CSVを扱うためのメモに書いています。File.openと同じようにCSV.openが使えるのでここではこちらを使いました。

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

終わりに

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