🔙 Back to Top

Mon Dec 11 2017

日記

金曜に溜めて帰ってしまってた仕事に追われてた. 20 時からサークルに行った.

小説

これは次の11日のための記事です。

Webスクレイピング Advent Calendar 2017 https://adventar.org/calendars/2105

intro

普通に生きていると生活上の要請から必要になることがあります. 私の場合、2017年で最も役に立ったのは、スイッチの再販の監視でした. ところであれは、時間少し前からスマホからのアクセスだけ妙に重たくなったのだが、意図的なものだったのだろうか. Firefox で試しにアクセスすると何にも問題がなく普通に買えた.

1

そうそうあることではありません。しかし人生で一度か二度かは、Instagramから画像を集めたくなる場面があるかと思います. まだ無いという人もいつかはあります. 備えましょう.

Instagram といえば mixi や twitter, pinterest と並ぶ立派な企業が運営する SNS です. ユーザーが便利できるように API が整備されているのが普通です. Twitter がここまで普及したのも、デザインが拙いながらも API を広く公開したからです.

https://www.instagram.com/developer/

これがドキュメントのトップページらしいです. 私はついつい先にAPIの仕様を見に行こうとシてしまうのですが、、、どこに書いてあるんだろう?

ところで頭に "The Instagram Graph API is Now Available to All Developers. " という、前見た時には無かったはずの文言が追加されていました. でもこれは有償のAPIのようなので、スルーします.

https://www.instagram.com/developer/sandbox/

API には Sandbox Mode なるものがあって、かなり制限されたモードのことらしいです. サンドボックスというくらいだしね?

で、読んでて段々分かってきたのですが、 2015年の12月頃から、APIの使用を制限しており、

  1. まずSandbox状態のAPIを使わせる
  2. それでアプリを自由に作る
  3. Instagram 様にアプリをご覧いただく
  4. Instagram 様がよしと言うとAPIがSandboxではないフルの状態に昇格する

という仕様になったようです.

こちらとしては、ただ一回きり、画像の収集に使いたいだけなので、 そんな目的のために Instagram 様のお気に召すようなアプリを偽装的に作りたくはない.

2

[instagram scraper] [検索]

https://github.com/sborod/ruby-instagram-scraper

こんなのありました. 使い方は大体 README で雰囲気は分かると思います.

今改めて検索すると https://github.com/rarcega/instagram-scraper こんなのもありました. 私はこのライブラリは知らないのですが、ここの記事に書いてあることのいくつかはもしかしたら、このライブラリによって解決するかもしれません.

例えば、あるユーザーが投稿した写真を取得するには、

require 'ruby-instagram-scraper'

username = 'your-user_name'
RubyInstagramScraper.get_user_media_nodes( username ).each do |node|
  node  # a post
  p node['display_src']  # photo url
end

こんな感じです.

ユーザーを特定せずにキーワードで検索して投稿を取得するパターンを考えましょう. 自由なキーワードではありませんが、 ハッシュタグをキーに検索するためのメソッドなら用意されていました.

# require 略
hashtag = 'selfie'
RubyInstagramScraper.get_tag_media_nodes( hashtag ).each do |node|
  p node['display_src']
end

良いですね. ただ英語のハッシュタグを使ってるのは英語圏の人たちです. 日本語のハッシュタグを使いましょう.

hashtag = 'いっぱい'
RubyInstagramScraper.get_tag_media_nodes( hashtag ).each do |node|
  p node['display_src']
end

実行するとこんな

/usr/lib/ruby/2.3.0/uri/rfc3986_parser.rb:21:in `split': URI must be ascii only "https://www.instagram.com/explore/tags/\u{3044}\u{3063}\u{3071}\u{3044}/?__a=1" (URI::InvalidURIError)
    from /usr/lib/ruby/2.3.0/uri/rfc3986_parser.rb:73:in `parse'
    from /usr/lib/ruby/2.3.0/uri/common.rb:227:in `parse'
    from /usr/lib/ruby/2.3.0/open-uri.rb:34:in `open'
    from /var/lib/gems/2.3.0/gems/ruby-instagram-scraper-0.1.7/lib/ruby-instagram-scraper.rb:36:in `get_tag_media_nodes'
    from test.rb:4:in `<main>'

中で url のパースが行われていて、その url の中に日本語が含まれているのでアレらしいです. URIEncode すればいいです.

require 'uri'
hashtag = URI.escape(hashtag)

できました.

ところでこのライブラリは問題があります. それは Issue #8 として既に書かれています.

このAPIは一回叩くだけでは 49 件程度だけが得られます. それはユーザー基点で投稿を取得する get_user_media_nodes でも同じなのですが、そちらでは、 その人の最近の投稿を取得するので、その中でもっとも古い投稿のIDをoffset として第二引数に渡すと、それより昔の投稿を取得します. get_tag_media_nodes も全く同様に行いたいのですが、何故かそれに対応してません.

たしか当時私はフォークして修正を試みたのですが、そもそものAPIが対応してないとかで諦めたのかもしれません. とにかく今は急ぎなので、別のアプローチに切り替えます.

3

実際に Instagram を web ブラウザで開いて、検索してみて、観察することにします. どこで検索するねん、って感じですが (ログインしてない)、 各自頑張って、

https://www.instagram.com/p/(ここに文字列)/

みたいな URL の投稿ページを探すとその上に検索窓がありました.

では普通に検索してみます. F12 を押してから.

そうすると、 https://www.instagram.com/explore/tags/%E3%81%84%E3%81%A3%E3%81%B1%E3%81%84%E3%81%84%E3%81%9F/?__a=1 に検索してることが分かります. このURIエンコード部分は「いっぱいいた」です. 前章のライブラリのエラー文を見たので分かるのですが、これは先ほどの get_tag_media_nodes です.

ページを下にスクロールすると

こんなのがありました. 正に今欲しいやつです. 一回これを押すと、これ以降、下にスクロールすると延々と新しい画像を表示してくれます.

観察の結果、結局、

https://www.instagram.com/graphql/query?query_id=${QUERY_ID}&first=7&tag_name=${TAG_NAME}&after=${AFTER}

という url がAPIであることが分かりました. 3つ変数があります. QUERY_ID は long な整数でしたが、リクエストで全く変わっていないので定数だと思っていいと思います. TAG_NAME はタグです. URI エンコードしておけばいいと思います. そして &after=${AFTER} が正にオフセットです. 初めてのリクエストではこの部分はありませんでした. 二回目以降、これが登場します.

一回目は無くて二回目から変わる部分が登場するパターン、稀に頻出します. 私はよく次のように書いています.

QUERY_ID=17882293912014529  # const
TAG_NAME="$1"
AFTER=  # empty

for i in $(seq 10); do
    URL="https://www.instagram.com/graphql/query?query_id=${QUERY_ID}&first=7&tag_name=${TAG_NAME}&${AFTER}"
    echo $URL
    AFTER="after=hogehoge"  # set
    sleep 1
done

API の URL を眺めるだけでいい人はこれでいいですが、 例えば wget というコマンドに渡すと json が返ってくるらしいので jq すればいいと思います.

owari

世界が平和でありますように

@cympfh