2013-06-09 :-)
_ [はてな][スクレイピング][ruby][mechanize]はてなブログをスクレイピングする
# coding: utf-8
# hatenablog をスクレイピング
#
# 使い方:
# hatenablog.rb <hatenablog URI> [カテゴリ]
#
# 例:
# ruby hatenablog.rb http://jkondo.hatenablog.com/ > jkondo.txt
#
# ruby hatenablog.rb http://dennou-kurage.hatenablog.com/ 仕事観 > kurage.txt
#
require 'mechanize'
require 'uri'
require 'pp'
def get_text(uri)
agent = Mechanize.new
agent.get(uri)
texts = ""
while true
agent.page.at("//div[@class='entry-content']").children.each {|node|
text = node.text
texts << text
}
link = agent.page.link_with(:text => '次のページ')
break if link == nil
agent.page.link_with(:text => '次のページ').click
end
return texts
end
def main(argv)
uri_base = argv[0]
cat = ""
if argv[1] != nil
cat = "category/" + URI::encode(argv[1])
end
uri = uri_base + cat
text = get_text(uri)
puts text
end
main(ARGV)
_ [自然言語処理][係り受け解析][構文解析][ruby][NLP]テキストを係り受け解析する
前提
テキスト解析サンプルコード:係り受け解析 - Yahoo!デベロッパーネットワーク
日本語係り受け解析API の制限が以下のようになっている。
日本語係り受け解析Web APIは、24時間以内で1つのアプリケーションIDにつき50000件のリクエストが上限となっています。また、1リクエストの最大サイズを4KBに制限しています。詳しくは「利用制限」をご参照ください。
UTF-8 エンコード 1 文字は最大 4 バイトでエンコードされるとする。
それを URI.encode すると最大 12 バイトになると見積もる
( たとえば 4 バイト 0xAA 0xBB 0xCC 0xDD であるとすると、これが URI.encode されるとテキストで表現されるので %AA %BB %CC %DD の 12 文字、つまり 12 バイトになる )
↓リクエストパラメータのうち &sentence= までで 152 バイトある。
http://jlp.yahooapis.jp/DAService/V1/parse?appid=<あなたのアプリケーションID>&sentence=
↓リクエストに指定できる sentence としては 3848 バイト。
(4 * 1000) - 152 = 3848
上述のように 1 文字 12 バイトとして 320 文字まで指定できる。
3848 / 12 = 320
320 文字はギリギリなので、これをプログラムで扱うときは。まあざっくり 300 文字までとしてみる。
与えられたテキストが長文だった場合を考慮して、最初に「。」でぶったぎっておいて、300 文字を超えないように文を再度連結していく。しかしそもそも「。」までが 300 文字を超えるような文もあるわけだが、それは無視する(どこで区切るのか割りと重要かもそれんけどよく分からないのでスルー)。
実装
とりあえず作ったのがこちら。
syntactic.rb としておく。
# coding: utf-8
# Yahoo デベロッパーネットワーク の 日本語係り受け解析API を使う
# http://developer.yahoo.co.jp/webapi/jlp/da/v1/parse.html
require 'uri'
require 'open-uri'
require 'rexml/document'
require 'pp'
module NLP
module Syntactic
SENTENCE_LENGTH_MAX = 300
def parse(xml)
syntactic ||= []
doc = REXML::Document.new(xml)
doc.elements.each('ResultSet/Result/ChunkList/Chunk') do |chunk|
chunks = ""
chunk.elements.each('MorphemList') do |ml|
ml.elements.each("Morphem/Surface") do |mo|
chunks << mo.text
end
end
syntactic << chunks
end
return syntactic
end
def build_sweep(text)
text.gsub!("\n", "")
text.rstrip!
text.lstrip!
text.gsub!(/\A +/, "")
text.gsub!(/ /, "")
text.chomp!
text.gsub!(/[\r\n]/, "")
return text
end
def build_sentence(text)
text = build_sweep(text)
request_sentence ||= []
if text.length < SENTENCE_LENGTH_MAX
request_sentence << text
else
lump = ""
text.split(/。/).each {|sentence|
s = sentence + "。"
if (lump + s).length < SENTENCE_LENGTH_MAX
lump << s
else
request_sentence << lump
lump = ""
lump << s
end
}
end
return request_sentence
end
# 文章が長いのは無視
def _analysis(text)
apiuri = "http://jlp.yahooapis.jp/DAService/V1/parse"
appid = "?appid=" + ここにあたなのAPI IDを入れる
sentence = "&sentence=" + URI.encode(text)
request_uri = apiuri + appid + sentence
syntactics = ""
begin
response = open(request_uri).read()
syntactics = parse(response)
rescue => e
end
return syntactics
end
def analysis(text)
syntactics ||= []
sentences = build_sentence(text)
sentences.each {|s|
syntactics << _analysis(s)
}
return syntactics
end
end
end
使うときはこう。
# coding: utf-8
#
# 与えられたファイルを係り受け解析する
#
require 'pp'
require './syntactic'
include NLP::Syntactic
def build(text)
return NLP::Syntactic::analysis(text)
end
def read(text)
File.open(text).read
end
def main(argv)
infile = argv[0]
text = read(infile)
syntactic = build(text)
puts syntactic.join("\n")
end
main(ARGV)
_ [ベイズ][自然言語処理][ナイーブベイズ][ruby][NLP]文章をナイーブベイズする
以前作業しておいたナイーブベイズ[ 20130505#p04 ] を利用する。
# -*- encoding: utf-8 -*-
#
# ナイーブベイズを用いたテキスト分類 - 人工知能に関する断創録
# http://aidiary.hatenablog.com/entry/20100613/1276389337
#
def maxint()
return 2 ** ((1.size) * 8 -1 ) -1
end
def sum(data)
return data.inject(0) {|s, i| s + i}
end
include Math
require 'pp'
require 'json'
require 'yaml'
module NLP
# Multinomial Naive Bayes
class NaiveBayes
def initialize()
# カテゴリの集合
@categories = []
# ボキャブラリの集合
@vocabularies = []
# wordcount[cat][word] カテゴリでの単語の出現回数
@wordcount = {}
# catcount[cat] カテゴリの出現回数
@catcount = {}
# denominator[cat] P(word|cat)の分母の値
@denominator = {}
end
# ナイーブベイズ分類器の訓練
def train(data)
data.each {|d|
cat = d[0]
@categories << cat
}
@categories.each {|cat|
@wordcount[cat] ||= {}
@wordcount[cat].default = 0
@catcount[cat] ||= 0
}
# 文書集合からカテゴリと単語をカウント
data.each {|d|
cat, doc = d[0], d[1, d.length-1]
@catcount[cat] += 1
doc.each {|word|
@vocabularies << word
@wordcount[cat][word] += 1
}
}
@vocabularies.uniq!
# 単語の条件付き確率の分母の値をあらかじめ一括計算しておく(高速化のため)
@categories.each {|cat|
s = sum(@wordcount[cat].values)
@denominator[cat] = s + @vocabularies.length
}
end
# 事後確率の対数 log(P(cat|doc)) がもっとも大きなカテゴリを返す
def classify(doc)
best = nil
max = -maxint()
@catcount.each_key {|cat|
_p = score(doc, cat)
if _p > max
max = _p
best = cat
end
}
return best
end
# 単語の条件付き確率 P(word|cat) を求める
def wordProb(word, cat)
return (@wordcount[cat][word] + 1).to_f / (@denominator[cat]).to_f
end
# 文書が与えられたときのカテゴリの事後確率の対数 log(P(cat|doc)) を求める
def score(doc, cat)
# 総文書数
total = sum(@catcount.values)
# log P(cat)
sc = Math.log((@catcount[cat]) / total.to_f)
doc.each {|word|
# log P(word|cat
sc += Math.log(wordProb(word, cat))
}
return sc
end
# 総文書数
# def to_s()
# total = sum(@catcount.values)
# return "documents: #{total}, vocabularies: #{@vocabularies.length}, categories: #{@categories.length}"
# end
end
end # end of module
if __FILE__ == $0
# Introduction to Information Retrieval 13.2の例題
data = [
["yes", "Chinese", "Beijing", "Chinese"],
["yes", "Chinese", "Chinese", "Shanghai"],
["yes", "Chinese", "Macao"],
["no", "Tokyo", "Japan", "Chinese"]
]
# ナイーブベイズ分類器を訓練
nb = NLP::NaiveBayes.new
nb.train(data)
p nb
puts "P(Chinese|yes) = #{nb.wordProb('Chinese', 'yes')}"
puts "P(Tokyo|yes) = #{nb.wordProb('Tokyo', 'yes')}"
puts "P(Japan|yes) = #{nb.wordProb('Japan', 'yes')}"
puts "P(Chinese|no) = #{nb.wordProb('Chinese', 'no')}"
puts "P(Tokyo|no) = #{nb.wordProb('Tokyo', 'no')}"
puts "P(Japan|no) = #{nb.wordProb('Japan', 'no')}"
# テストデータのカテゴリを予測
test = ['Chinese', 'Chinese', 'Chinese', 'Tokyo', 'Japan']
puts "log P(yes|test) = #{nb.score(test, 'yes')}"
puts "log P(no|test) = #{nb.score(test, 'no')}"
puts nb.classify(test)
end
_ [構文解析][係り受け解析][社畜][ベイズ][自然言語処理][ナイーブベイズ][ruby][NLP]文章が社畜的かどうかをナイーブベイズ推定する
準備
社畜的文章の学習データとして 脱社畜ブログ のカテゴリ 仕事観 を利用する。
非社畜的文章の学習データとして jkondoのはてなブログ を利用する。
先ほどのコード[ 20130609#p04 ]でスクレイピングする。
% ruby hatenablog.rb http://jkondo.hatenablog.com/ > jkondo.txt
% ruby hatenablog.rb http://dennou-kurage.hatenablog.com/ 仕事観 > kurage.txt
先ほどのコード[ 20130609#p05 ]で係り受け解析しておく
% ruby parse.rb kurage.txt > kurage.2.txt
% ruby parse.rb jkondo.txt > jkondo.2.txt
判定の実装
先ほどのナイーブベイズのコード [ 20130609#p06 ] を利用する。
企業戦士として名高い クラウド・ストライフさん のセリフを社畜判定してみる。( FFシリーズ セリフ人気投票 )
# coding: utf-8
require './naivebayes'
require './syntactic'
include NLP::Syntactic
def get_words(text)
words = NLP::Syntactic::analysis(text)
words.flatten!
words.map! {|w|
w.gsub(/[、。\n]/, "")
}
return words
end
def build_learning(filepath, cat)
lines = File.open(filepath).readlines()
lines.map! {|w|
w.gsub(/[、。\n]/, "")
}
data = [cat, *lines]
return data
end
def classify(nb, text)
words = get_words(text)
cat = nb.classify(words)
# puts nb.score(words, "社畜")
# puts nb.score(words, "人間")
return "#{text} => #{cat}"
end
def main(argv)
shatiku_file = argv[0]
not_shatiku_file = argv[1]
shatiku_data = build_learning(shatiku_file, "社畜")
not_shatiku_data = build_learning(not_shatiku_file, "人間")
nb = NLP::NaiveBayes.new
nb.train([shatiku_data])
nb.train([not_shatiku_data])
text = %w(
興味ないね
エアリスはもう喋らない・・・笑わない・・・泣かない、怒らない・・・!
大切じゃない物なんか無い!
俺は俺の現実を生きる
俺は、お前の生きた証だ
指先がチリチリする。口の中はカラカラだ。目の奥が熱いんだ!
オレが・・・お前の生きた証・・・
引きずりすぎて少しすり減ったかな・・・
お前の分まで生きよう。そう決めたんだけどな…
もう・・・揺るがないさ・・
帰るぞ
俺は幻想の世界の住人だった。でも、もう幻想はいらない……俺は俺の現実を生きる
ここに女装に必要な何かがある。おれにはわかるんだ。いくぜ!
星よ・・・降り注げ!!
罪って…許されるのかな?
まだ終わりじゃない…終わりじゃないんだ!
俺はクラウド、ソルジャークラス1st
)
text.each {|t|
puts classify(nb, t)
}
end
main(ARGV)
実行
% ruby shatiku.rb kurage.2.txt jkondo.2.txt 興味ないね => 社畜 エアリスはもう喋らない・・・笑わない・・・泣かない、怒らない・・・! => 社畜 大切じゃない物なんか無い! => 社畜 俺は俺の現実を生きる => 社畜 俺は、お前の生きた証だ => 社畜 指先がチリチリする。口の中はカラカラだ。目の奥が熱いんだ! => 社畜 オレが・・・お前の生きた証・・・ => 社畜 引きずりすぎて少しすり減ったかな・・・ => 人間 お前の分まで生きよう。そう決めたんだけどな… => 社畜 もう・・・揺るがないさ・・ => 社畜 帰るぞ => 社畜 俺は幻想の世界の住人だった。でも、もう幻想はいらない……俺は俺の現実を生きる => 社畜 ここに女装に必要な何かがある。おれにはわかるんだ。いくぜ! => 人間 星よ・・・降り注げ!! => 社畜 罪って…許されるのかな? => 社畜 まだ終わりじゃない…終わりじゃないんだ! => 人間 俺はクラウド、ソルジャークラス1st => 社畜
ふむ。










