オンライン上のデータを取得するために、APIを利用したりスクレイピングをしたりして、CSVに書き出すことをする必要がある機会がちょこちょこあります。
その際以前に投稿した記事を参考にしたりして書くこともあるのですが、複数の記事に散らばっていたのでひとつにまとめておこうと思います。
個人的に、こういう場合PythonかRubyを使うことが多いのでこの言語について個人的なアプローチ方法を書きます。
過去記事
python お天気apiから近日の天気を取得する
LDAによるトピックモデル with gensim ~ Qiitaのタグからユーザーの嗜好を考える ~
Rails スクレイピング手法 Mechanizeの使い方
Ruby CSVを扱うためのメモ
概要
この記事は基本的にはコードベースで説明します。
説明することは
- Pythonで
requests
とBeautifulSoup
を用いてデータの取得を行い、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のスクレイピングはrequests
とBeautifulSoup
で行います。
BeautifulSoup
はselect
メソッドを使って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()
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_text
やget_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は以下のもの。
name
Hiroshi Abe
Abe Tsuyoshi
Osamu Adachi
Jin Akanishi
...
以下がコードです。Mechanize
のpost
の使い方がポイントです。
また、取得したい「ベーコン数」が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
終わりに
手法としては色々あるかと思いますが、今回紹介した道具で多くのことは対応出来ると思います。
是非、試してみてください!