PHPでInstagramの特定ユーザの写真をスクレイピングする2018/3/20版(認証不要)


##はじめに
Instagramのusernameもしくはuser_idを指定して写真をスクレイピングできないか考えてサイトにある情報を参考に作成したもの。

今日１日で作ったので検証も甘いです。関数名も見直すかもしれません。コーディングも未熟ですみません。

また筆者は個人的にこのコーディングを育てていくつもりだが、あくまで参考情報としてみて頂きたい。


##説明

###query_hashとは
query_hashはリクエストのアカウントを特定するものと思われる。
instagramの画面をスクロールすると古い投稿の画像が表示されるが、javascript等でクライアントサイドで都度、画像の情報一覧を取得している動きをしている。
その際に、リクエストを行っているアカウントが分かるようにquery_hashをWEBアクセス時にGET値として付与させる必要があるようだ。2018/03/20時点

query_hashを調べる方法については割愛（ブラウザのデバック機能などで調べられる）

少し前まではquery_idだったみたい。昔作成したアカウントであれば、query_idをもっており、query_idの付与でも取得できるようだ。筆者のアカウントは今年作成した為かquery_hashしかわからない。

###user_idとusername

qiitaとかgithubなどに投稿されているプログラムのソースをみると、たまにuser_idとusernameの呼び名（変数名）が区別がされていないことがあったが、instagramの仕組上ではちゃんと区別しておく必要がある。

user_idは数字だけで表現されており、おそらく変更できない。
usernameは英数字アンダースコアで表現されており、変更可能。（URLに利用されている名前）

usernameを変更されると、プログラムでusernameで埋め込んでいる場合同じアカウントの情報を取得できなくなる。



##クラスと実行例

###実行例

```php

$query_hash = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

$ins = new Insta();
$ins->setQuery_hash($query_hash);

$username = $ins->get_username_by_user_id('XXXXXXXXXXXXX');
var_dump($username);

$user_id = $ins->get_user_id_by_username("XXXXX_XXXXXX_official");
var_dump($user_id);

while($ins->is_cursorend){
	foreach ($ins->media_by_user as $media){

		$typename = $media["node"]["__typename"];
		if($typename =="GraphImage"){
			var_dump($media["node"]["id"]);
			var_dump($media["node"]["shortcode"]);
		} else if($typename =="GraphSidecar"){
			$ins->get_media_detail_by_shortcode($media["node"]["shortcode"]);
			foreach($ins->media_childrens as $media_children){
				var_dump($media_children["node"]["id"]);
				var_dump($media_children["node"]["shortcode"]);
			}
		}
	}
	$ins->get_media_id_user_feed($user_id,$ins->end_cursor,1);
}

$ins->is_cursorend = true;

```

###クラス

```php

class Insta{
	
	const api_user_detail = "https://i.instagram.com/api/v1/users/%s/info/";
	const url_user_detail = "https://www.instagram.com/%s/?__a=1";
	const url_media_detail = "https://www.instagram.com/p/%s/?__a=1";
	
	public $media_by_user = array();
	public $media_childrens = array();
	public $end_cursor = null;
	public $is_cursorend = true;
	private $query_hash = false;
	
	
	function setQuery_hash($query_hash){
		$this->query_hash = $query_hash;
	}
	
	
	function get_media_id_user_feed($user_id,$after,$first =12){
		
		if($this->query_hash == null){
			return null;
		}
		
		$variables = Array(
	        "id" => $user_id,
	        "first" => $first,
	    );

		if($after != null){
			$variables["after"] = $after;
		}
	
	    $url = "https://www.instagram.com/graphql/query/?".
	                             "query_hash=" .$this->query_hash."&".
	                             http_build_query(array("variables"  => json_encode($variables)));
	    
	    $r =self::rest("GET",$url);
	    $all_data = json_decode($r,true);

		if(array_key_exists("data",$all_data)){
		    if(count($all_data["data"]["user"]["edge_owner_to_timeline_media"]["edges"]) != 0){
		    	$this->media_by_user = $all_data["data"]["user"]["edge_owner_to_timeline_media"]["edges"];
		    	$this->end_cursor =$all_data["data"]["user"]["edge_owner_to_timeline_media"]["page_info"]["end_cursor"];
		    } else {
		    	$this->media_by_user = array();
		    	$this->end_cursor = false;
		    	$this->is_cursorend = false;
		    }
		    
	    }
    }
    
    
	function get_username_by_user_id($user_id){
		
		$url_info = sprintf(self::api_user_detail, $user_id);
		
		$r =self::rest("GET",$url_info);
		
		$all_data = json_decode($r,true);
		$username = $all_data["user"]["username"];
		
		return $username;
	}
	
	
	function get_user_id_by_username($username){
		$url = sprintf(self::url_user_detail, $username);
		$r =self::rest("GET",$url);

		$all_data = json_decode($r,true);
		
		$user_id = $all_data["graphql"]["user"]["id"];
		
		return $user_id;
	}
	
	
    function get_media_detail_by_shortcode($shortcode){
    	$media_url = sprintf(self::url_media_detail, $shortcode);
    	$r =self::rest("GET",$media_url);
    	 
    	$all_data = json_decode($r,true);
    	$username = $all_data['graphql']['shortcode_media']['owner']['username'];

    	$this->media_childrens = $all_data["graphql"]["shortcode_media"]["edge_sidecar_to_children"]["edges"];
    }
	
	
	    function rest($method,$uri){

	        if(!isset($handle)) $handle = curl_init();

	        $option_defaults = array(
	            CURLOPT_HEADER => false,
	            CURLOPT_RETURNTRANSFER => true,
	            CURLOPT_TIMEOUT => 2
	        ); 

	        $options = array(
	            CURLOPT_URL => $uri,
	            CURLOPT_CUSTOMREQUEST => $method,
	        ); 

	        curl_setopt_array($handle,($options + $option_defaults));


	        $responce =  curl_exec($handle);
	        //var_dump(curl_errno($handle));
	        //var_dump(curl_error($handle));
	        return $responce;
	    }

}
```


###関数名

**string get_username_by_user_id (string $user_id)**
user_idからusernameを取得

**string get_user_id_by_username (string $username)**
usernameからuser_idを取得

**array get_media_id_user_feed (string \$user_id, string \$after [,int \$first])**
\$user_idには数字だけのidを使う。（ピンとこない方は説明を参照）
\$afterには一度写真情報を取得した際のjsonにend_cursorの文字列を次の取得で渡すことで、前回取得した内容を除く次の古い写真情報を取得できるようになる。なお、nullにして取得することで最新の写真を取得できる。
\$firestは一度に取得できる投稿の数。最大いくつまでとれるかは未検証。

**array media_by_user**
get_media_id_user_feedで取得した写真情報をarray形式で参照できる

**get_media_detail_by_shortcode(string \$shortcode)**
$shortcodeには写真毎に割り当てされているコードを指定する

**array media_childrens**
get_media_detail_by_shortcodeで取得した写真情報（子供）をarray形式で参照できる



##参考
- https://qiita.com/kon_now/items/aa6fdd58c86664d55113
- https://qiita.com/daikiojm/items/197919acce4fa10f15dd
- https://github.com/instagrambot/instabot
- https://github.com/postaddictme/instagram-php-scraper