【Ruby】【Rails】【Mechanize】kindle.amazon.co.jpで自分がフォローしている人たちのハイライトを取得
自分のハイライトを取得する方法はちらほら見当たるんですけど、自分がフォローしている人たちのハイライトを取得するのはないなーと。
AmazonはそのためのAPIを提供していないようなので? 愚直にMechanizeでやろうかと。
コード
require "mechanize" mech = Mechanize.new mech.user_agent_alias = 'Mac Mozilla' # サインイン用のページ url = 'https://www.amazon.co.jp/ap/signin?openid.return_to=https%3A%2F%2Fkindle.amazon.co.jp%3A443%2Fauthenticate%2Flogin_callback%3Fwctx%3D%252F&pageId=amzn_kindle&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.assoc_handle=amzn_kindle_jp&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.pape.max_auth_age=0' page = mech.get(url) login_form = page.forms_with(:name => 'signIn').first login_form.fields_with(:name => 'email').first.value = "YOUR EMAIL" login_form.fields_with(:name => 'password').first.value = "YOUR PASSWORD" page2 = login_form.click_button # スクレイピングするページ数を入力 puts "How many pages?" pages = gets.chomp.to_i # 一応遷移先(ログイン後)のページタイトルとurl表示 puts page2.title puts page2.uri.to_s # あ、ここからー pages.times do |count| puts "Page: #{count+1}" puts "----------" kindle_page = mech.get(page2.uri.to_s) content = kindle_page.search('div.content') content.each do |c| puts "Authors:" authors = c.text.slice(/(by\s).+?\n/).gsub(/by\s|&/, "").chomp authors.split(",").each do |a| puts a.strip end c.css('a').each do |link| if link.attribute('href').value.start_with?("/work/") puts "Title:"+link.text.gsub(/\(J\w*|E\w*\)/, "").strip puts "ASIN:"+link.attribute('href').value.split("/").last end end # (1/14)下記のコードではうまくいかない場合があるので修正しました。 # puts "Quote:"+c.css('span.sampleCloseQuote').text.strip puts "Quote:"+c.css('div.sampleHighlight > div:first-child').text.strip puts "----------" end see_more = page2.link_with(:class => 'seeMore expand') page2 = see_more.click end
ちょっと解説を
サインイン後のページをget
サインインページで
page2 = login_form.click_button
送信ボタンをクリックして遷移先(自分のホーム)の情報をpage2
に突っ込むのはいいんですけど、
Mechanizeでget
するときにページのuriをさらに文字列にして渡さないといけないです。
kindle_page = mech.get(page2.uri.to_s)
必要なコンテンツはdiv.content
内にありますのでsearch
で取得し、それをeach
で回してあげて処理、という流れになります。
content = kindle_page.search('div.content') content.each do |c| # 以下処理
著者名取得
authors = c.text.slice(/(by\s).+?\n/).gsub(/by\s|&/, "").chomp
著者名はby
から始まるので、.slice(/(by\s).+?\n/)
で取り出す・・・のですが、それだとby
と改行が前後にくっつくので、さらに
.gsub(/by\s|&/, "")
で置換させます。たまに著者名が複数あって最後が&
で終わってるものもあるので・・・これとか
静かなる革命へのブループリント この国の未来をつくる7つの対話
なので``&```も置換対象にします。んーここら辺もうちょっとスマートにできないかなと。正規表現難しいなあ・・・。
で、著者名が複数の場合もあるので
authors.split(",").each do |a| puts a.strip end
カンマ区切りでsplit
してeach
で回して表示。
書籍のタイトル取得
まず<a>
タグのhref
の値はいっぱい取れてしまいますので、その中から/work/
で始まるものだけを取得。start_with?()
を使います。
引数に"/work/"
を渡してあげれば良いと。含まれていればtrue
が返ってきます。
c.css('a').each do |link| if link.attribute('href').value.start_with?("/work/") puts "Title:"+link.text.gsub(/\(J\w*|E\w*\)/, "").strip puts "ASIN:"+link.attribute('href').value.split("/").last end end
取得できるタイトルには必ずと言っていいほど(Japanese Edition)
と余計なものが付いてますのでgsub
で置換・・・なのですが、
たまに(Japan
で途切れているものもあります。これとか(またか)
静かなる革命へのブループリント この国の未来をつくる7つの対話
なので
link.text.gsub(/\(J\w*|E\w*\)/, "").strip
(J
で始まる単語またはE
で始まり)
で終わる単語を置換対象にしています。(Japanese Edition)
なら両方対象ですので間の空白を残して置換できます。
Jでなくて(なんたら Edition)
だったらどうしようかと思いますが、とりあえずこれで用は足せてます。
ASIN取得
今回はASINを取得したいので、単純に/
で分割して、最後の要素(これがASIN)を取り出しています。
link.attribute('href').value.split("/").last
引用の取得
引用はクラスがsampleCloseQuote
の<span>
タグのテキストにあるので
・・・とは限らない場合がありましたので修正しました。
- c.css('span.sampleCloseQuote').text.strip + c.css('div.sampleHighlight > div:first-child').text.strip
これで取得。
See Moreリンクのクリック
デフォルトでは引用は3つしか表示されていないので、その左下のSee Moreリンクをクリックする必要があります(さらに10個表示される) なので最後に
see_more = page2.link_with(:class => 'seeMore expand') page2 = see_more.click
クラスがseeMore expand
のリンクをクリック。それをpage2
(自分のkindle.amazon.co.jpのホーム)に格納し直し。そしてまたループと。
もうなんかぐちゃぐちゃですが、一応取得できてます。
実行結果
Page: 1 ---------- Authors: 司馬遼太郎 Title:街道をゆく 10 羽州街道、佐渡のみち ASIN:B00M3V4974 Quote:江戸幕府では、勘定畑(長官は勘定奉行)が、もっとも優秀な人材をあつめるしきたりになっている。それが明治政府の大蔵省にひきつがれ、こんにちなお、各省を超越して人材をあつめる優先権をこの省が持っているというのはおもしろい。 ---------- Authors: 吉行 淳之介 Title:贋食物誌 ASIN:B00GQQTXU0 Quote:山本五十六だったか、「いまどきの若い者などと申すまじくそうろう」とか言った ---------- Authors: 吉行 淳之介 Title:贋食物誌 ASIN:B00GQQTXU0 Quote:戦前には「キツネうどん」はあったが、「タヌキうどん」というものは存在しなかった。 ---------- Page: 2 ---------- Authors: 吉行 淳之介 Title:贋食物誌 ASIN:B00GQQTXU0 Quote:意外なことにイクラというのはロシア語 ---------- Authors: 岸見 一郎 古賀 史健 Title:嫌われる勇気 ASIN:B00H7RACY8 Quote:いかなる経験も、それ自体では成功の原因でも失敗の原因でもない。われわれは自分の経験によるショック——いわゆるトラウマ——に苦しむのではなく、経験の中から目的にかなうものを見つけ出す。自分の経験によって決定されるのではなく、経験に与える意味によって自らを決定するのである ---------- Authors: 阿川 弘之 Title:私記キスカ撤退 (文春文庫) ASIN:B009HO4IYO Quote:支百勇一静可以制百動 ---------- Authors: 阿川 弘之 Title:私記キスカ撤退 (文春文庫) ASIN:B009HO4IYO Quote:「人ノマサニ死ナントスルヤ其ノ言ヤヨシ」 ---------- Authors: ピーター・ティール ブレイク・マスターズ Title:ゼロ・トゥ・ワン 君はゼロから何を生み出せるか ASIN:B00NQ3QONK Quote:ベンチャーキャピタルにとっての何よりも大きな隠れた真実は、ファンド中最も成功した投資案件のリターンが、その他すべての案件の合計リターンに匹敵するか、それを超えることだ。 ---------- Authors: ジャレド ダイアモンド Title:銃・病原菌・鉄 下巻 ASIN:B00DNMG8QC Quote:つまり、「平等」という形容詞は、小規模血縁集団では、個人の人柄、強靭さ、知性、戦いの技量などにもとづき、リーダーが非公式に決まるということを意味している。 ---------- Authors: ジャレド ダイアモンド Title:銃・病原菌・鉄 下巻 ASIN:B00DNMG8QC Quote:小規模血縁集団は、「平等」という形容詞で表現されるが、それは、すべてのメンバーがわけへだてなく平等な権限を持つという意味ではなく、階級が未分化でメンバーが社会的に区別されていない、という意味である。 ---------- Authors: ジャレド ダイアモンド Title:銃・病原菌・鉄 下巻 ASIN:B00DNMG8QC Quote:人間社会の多様性は、音楽様式や生活様式などと同様、時間の経過とともに連続的に変化するものであり、段階的に線引きする分類はどうしても不完全なものにならざるをえない。 ---------- Authors: ジャレド ダイアモンド Title:銃・病原菌・鉄 下巻 ASIN:B00DNMG8QC Quote:人類の科学技術史は、こうした大陸ごとの面積や、人口や、伝播の容易さや、食料生産の開始タイミングのちがいが、技術自体の自己触媒作用によって時間の経過とともに増幅された結果である。 ---------- Authors: 宇野常寛 門脇耕三 落合陽一 尾原和啓 猪子寿之 駒崎弘樹 根津孝 Title:静かなる革命へのブループリント この国の未来をつくる7つの対話 ASIN:B00L8EMLGS Quote:東京に新しいネットワーク構造をつくるのは、道路や鉄道のような物理空間におけるインフラだけではなく、もしかしたら情報空間におけるインフラなのかもしれません。 ----------
参考記事:
amazonの購入履歴を取得する。 - mirandora.commirandora.com
ruby - mechanize how to get current url - Stack Overflow
修正点
たいてい<span class="sampleCloseQuote">
内に引用が格納されているのですが、そうでない場合もありました。
<div class="sampleHighlight"> <div> 文学なんて必要ないという人もいますが、とんでもない話です。統計データだけで理解できるほど、世の中は単純ではありません。 <span class="sampleCloseQuote">───ストーリーがないと、普通の人がどうなのかなんてわからないと。</span> </div> </div>
このように、<span class="sampleCloseQuote">
の外に出ちゃってる場合もあるので、<div class="sampleHighlight">
の最初の子要素のテキストを取得せねばなりません。
Note:が付いている場合
<div class="sampleHighlight"> <div> <span class="sampleCloseQuote">相変わらず作戦目的はあいまいで、米軍の本土上陸を引き延ばすための戦略持久か航空決戦かの間を揺れ動いた。とくに注目されるのは、大本営と沖縄の現地軍にみられた認識のズレや意思の不統一であった。</span> </div> <div class="sampleNote"> <div> <span class="note">Note:</span> <span class="noteText">*****</span> </div> </div> </div>
読者がメモを付けている場合は、Note:
として表示されるのですが、それは取得しない。
なので前述した通り<div class="sampleHighlight">
の最初の子要素を取ってくる。今の所、NoteはQuoteの下にくる構造になっているのでそれでなんとかいけます。
# puts "Quote:"+c.css('span.sampleCloseQuote').text.strip puts "Quote:"+c.css('div.sampleHighlight > div:first-child').text.strip
さらに修正(1/26)
すいませんほんとにすいません
なんとQuoteがない!?場合もある。いやマジで。
引用をせずにNote、つまりメモだけ、とか。テストとして投稿するパティーンが多いようで。
例えばこんなのとか。
<div class="eventHighlight"> <div class="sampleHighlight noQuote"> <div class="sampleNote"> <div> <span class="note">Note:</span> <span class="noteText">テスト</span> </div> </div> </div> </div>
引用がない場合、sampleHighlight
にさらにnoQuote
というクラスが付加されるようなので、
# noQuoteが存在しない(つまり引用があるとみなす)限りは unless c.css('div.noQuote').text.present? (以下処理) else (引用がないよう、と) end
というような条件分岐でなんとか回避すると。present?
を使っているのでRailsでの話になりますね。
Rubyだとどうか分からんです。nil?
やらempty?
やらでどうにかするのかなと。