PythonとSeleniumでインスタグラムの投稿にいいねをしてくれた人にいいねを返すプログラム

この記事の内容をアップデートして2022年2月版の記事を書きました。
続・PythonとSeleniumでインスタグラムの投稿にいいねをしてくれた人にいいねを返すプログラム

Instagramでフォロワーが増えてくるといいね返すのが大変になってきますよね。

時間が空いている時にインスタを見るのですが、仕事が忙しいとなかなか全部見切れないことも多くて、せっかくいいねくれたのに申し訳ないという気持ちがあって、いいねを自動で返せる仕組み作ろうかなと考えたのが去年の年始でした。

そこから個人的に使い勝手を良くしていった結果、

  • いいねを返したい自分の投稿のURLを入力
  • いいねをしてくれた人のリストを取得
  • いいねをしてくれた人の投稿にいいねする

というプログラムになりました。

ちなみに、環境は以下の通り。

  • OS:macOS Catalina
  • Python:バージョン3.7.2
  • Chrome:バージョン80.0.3987.87

なお、Pythonのwebdriverやtime、sys、re、randoを使いますので、pipなどを使ってインストールしておきましょう。

また、selenium webdriverをダウンロードしておきます。自分の使っているChromeのバージョンに合わせましょう。今回の例では、80ですね。

selenium webdriverのchromeは以下からダウンロードできます。
ChromeDriver – WebDriver for Chrome

コードの全体

コードの全体象はこちら

※2020年2月10日現在は動作します。

# coding: UTF-8
from selenium import webdriver
import time
import sys
import re
import random

# 既存のChromeプロファイルを使う準備をします。
options = webdriver.ChromeOptions()
options.add_argument('--user-data-dir=/Users/fujitashunichi/Library/Application Support/Google/Chrome/Default')

# chromedriverのパスと、オプションを指定してドライバーを作成
# あらかじめ使っているChromeのバージョンに合わせたchromedriverをダウンロードして/usr/local/bin/にいれておきます
browser = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=options)

# 最初に表示するページ
startURL = 'https://www.instagram.com/p/ByklngHgf8U/'

# 入力受付用の変数
inputURL = ''

# いいねしてくれた人の表示するボタンのパス
likeListButtonPath = '//*[@id="react-root"]/section/main/div/div/article/div[2]/section[2]/div/div[2]/button'
# いいねしてくれた人のリストの親コンテナーのパス
likeListPath = '/html/body/div[4]/div/div[2]/div/div'
# いいねしてくれた人のリストのパス
likeListItemPath = '/html/body/div[4]/div/div[2]/div/div/div'

# ユーザーの投稿一覧のパス
postsPath = '//*[@id="react-root"]//article/div[1]/div/div[1]//a'

# いいねボタンアイコン(SVG)のパス
likeSvgPath = '//*[@id="react-root"]/section/main/div/div/article/div[2]/section[1]/span[1]/button/*[name()="svg"]'


# 投稿を格納する配列
posts = []

if __name__ == '__main__':

	#===========================
	# 起動
	#===========================
	browser.get(startURL)
	time.sleep(random.randint(3, 8))

	while True :

		# インスタグラムの投稿URLの入力を受付
		# exitが入力されたらプログラムを終了
		print('INPUT YOUR POST URL')
		print('(exit to command "exit")')
		print('>>', end='')
		stdin = input()

		if stdin == 'exit' :
			connect.close()
			browser.close()
			sys.exti()
		elif re.match(r'https:\/\/www\.instagram\.com\/p\/[ -~]{11}\/', stdin) :
			print('URL CHECK OK : ' + stdin)
			inputURL = stdin
			browser.get(inputURL)
			time.sleep(random.randint(3, 5))
		else :
			print('URL CHECK NG : ' + stdin)
			continue

		#==============================================
		# 投稿にいいねした人のリストを取得
		#==============================================
		liker = []
		likeListButton = browser.find_element_by_xpath(likeListButtonPath)
		likeListButton.click()
		time.sleep(random.randint(5, 15))
		likeList = browser.find_element_by_xpath(likeListPath)
		last_height = browser.execute_script("return arguments[0].scrollHeight", likeList)
		time.sleep(random.randint(1, 5))

		# 最後に到達するまでスクロールする
		while True:

			likeLists = browser.find_elements_by_xpath(likeListItemPath)
			for user in likeLists :
				username = user.find_elements_by_tag_name('a')
				liker.append(username[0].get_attribute('href'))

			# スクロールは、JavaScriptで実行
			browser.execute_script("arguments[0].scrollIntoView(false)", likeList)

			time.sleep(random.randint(2, 5))

			new_height = browser.execute_script("return arguments[0].scrollHeight", likeList)

			if new_height == last_height:
				break
			last_height = new_height

		# 重複を削除
		liker2 = list(dict.fromkeys(liker))

		print( 'いいねの数:' + str(len(liker2)) )

		# ユーザー毎のループ
		i = 0
		for i in range(0, len(liker2)):

			browser.get(liker2[i])

			time.sleep(random.randint(5, 12))

			print( "\nPERSON:" + str(i) )
			print( browser.current_url)

			posts = browser.find_elements_by_xpath(postsPath)

			post_urls = []

			for post in posts:
				post_urls.append(post.get_attribute('href'))

			# 投稿毎のループ
			i = 0
			plen = 0
			if len(post_urls) > 0:
				plen = random.randint(1, len(post_urls))
			else :
				plen = len(post_urls)
			for i in range(0, plen):

				print( " 投稿インデックス:" + str(i))
				print( " 投稿URL:" + post_urls[i] )

				# 投稿ページへ
				browser.get(post_urls[i])

				time.sleep(random.randint(8, 20))

				likeIcon = browser.find_elements_by_xpath(likeSvgPath)
				if len(likeIcon) > 0 :
					likeState = likeIcon[0].get_attribute('aria-label')
					if likeState == '「いいね!」を取り消す':
						print('  この投稿はすでに「いいね」済み')
						continue
					elif likeState == 'いいね!':
						print('  まだ「いいね」してないので「いいね」します')
						favbtn = likeIcon[0].find_element_by_xpath('./..')
						favbtn.click()
						continue
					else:
						print('  不明です')
				else:
					print('  いいねボタンが見つかりませんでした')
	browser.close()
	sys.exit()

コードの流れは以下。

  1. Selemiumでインスタグラムにアクセス。
  2. 標準入力で、投稿のURLを入力します。
  3. Selemiumが、入力された投稿のURLに飛び、いいねしてくれた人のリストを取得します。
  4. いいねしてくれた人を見にいきます
  5. その人の投稿を最新から順に(1〜3つ)見ていきます。
  6. いいねされていない投稿があれば、いいねします。
  7. 次の人の5へ。
  8. リストを回りきったら、標準入力で投稿URLの入力を待ちます。(プログラムを終了するときは、ここで”exit”を入力)
    ※標準入力に投稿のURLを入力するときは手動になります

コードの細かい解説

SVGの取得について

# いいねボタンアイコン(SVG)のパス
likeSvgPath = '//*[@id="react-root"]/section/main/div/div/article/div[2]/section[1]/span[1]/button/*[name()="svg"]'

プログラムで、いいねのアイコンを取得して使うのですが、このアイコンがSVGなのです。

SVGは、

find_element_by_xpath('//*[@id="react-root"]/section/main/div/div/article/div[2]/section[1]/span[1]/button/svg')

のような書き方で取得できません。

‘*[name()=”svg”]’というようにname()を使うことでsvg要素の取得ができます。

ログインについて

このプログラムは、ログインする処理は入っていません。

普段使いのChromeでインスタにログインしている場合、そのユーザープロファイルを利用することで、立ち上げ時に都度ログインする必要がなくなります。

そうすると何がいいかというと、ログインする度にくるメール通知がこなくなるんですね。

# 既存のChromeプロファイルを使う準備をします。
options = webdriver.ChromeOptions()
options.add_argument('--user-data-dir=/Users/USERNAME/Library/Application Support/Google/Chrome/Default')

# chromedriverのパスと、オプションを指定してドライバーを作成
# あらかじめ使っているChromeのバージョンに合わせたchromedriverをダウンロードして/usr/local/bin/にいれておきます
browser = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver', options=options)

「USERNAME」の部分には、自分のユーザー名を入れてください。

ただし、このプロファイルを使っているSelemiumのドライバーのタスクが残ったまま、このプログラムを実行しようとするとエラーになります。その場合は、全部のChromeを終了してからプログラムを実行します。

参考
Python + Selenium + Chrome で自動ログインいくつか

標準入力で投稿のURLを入力

# インスタグラムの投稿URLの入力を受付
# exitが入力されたらプログラムを終了
print('INPUT YOUR POST URL')
print('(exit to command "exit")')
print('>>', end='')
stdin = input()

if stdin == 'exit' :
    connect.close()
    browser.close()
    sys.exti()
elif re.match(r'https:\/\/www\.instagram\.com\/p\/[ -~]{11}\/', stdin) :
    print('URL CHECK OK : ' + stdin)
    inputURL = stdin
    browser.get(inputURL)
    time.sleep(random.randint(3, 5))
else :
    print('URL CHECK NG : ' + stdin)
    continue

この部分は、ターミナルからインスタグラムの投稿のURLを入力を受け付けて、そのURLがインスタグラムのURLとして正しいかチェック、問題なければ、次のフェーズにすすむ処理になっています。

標準入力は、input()で取得できます。

‘exit’が入力された場合は、プログラムを終了します。

このプログラムでは、たまに

time.sleep(random.randint(x,x))

のような記述がありますが、あまりに早く処理しすぎると、インスタグラムの制限がかかり、アカウントが制限されたりします。そのために、少し処理を遅くするために、プログラムを停止しています。制限になるのには、さまざまな条件があるようなので気になる方は、調べて見てください。

いいねリストの取得

# 最後に到達するまでスクロールする
while True:

    likeLists = browser.find_elements_by_xpath(likeListItemPath)
    for user in likeLists :
        username = user.find_elements_by_tag_name('a')
        liker.append(username[0].get_attribute('href'))

    # スクロールは、JavaScriptで実行
    browser.execute_script("arguments[0].scrollIntoView(false)", likeList)

    time.sleep(random.randint(2, 5))

    new_height = browser.execute_script("return arguments[0].scrollHeight", likeList)

    if new_height == last_height:
        break
    last_height = new_height

いいねしてくれた人リストは、一回で取得しようとしてもできません。

Chromeの開発ツールなどでDOMをみるとわかるのですが、ポップアップに表示されている人+上下に数人分だけがDOMになっていて、それ意外は、DOMすらないので、取得が不可能なんですね。

Reacの仕様でしょうか?

なので、見える範囲分だけ、スクロールして、そこでDOMになっている人を取得、そしてスクロールして、取得・・・といったことを繰り返していきます。

そこで、スクロールをしつつ、表示されている人のDOMを取得、スクロールして・・・という処理を繰り返していきます。

スクロールは、Pythonだけではできないので、execute_script()を使い、JavaScriptでスクロールするようにします。

scrollIntoView(false)で、見えている部分の下端までスクロールしてくれます。

スクロールしたら、scrollHeightで現在のいいねしてくれた人の部分のスクロール位置を取得します。

スクロールが最下端にいったら、スクロール位置は大きくならないので、そこで、ループを抜ける仕組みです。

詳しくは、以下を見てください。

参考1
あまり知られてなさそうなメソッド element.scrollIntoView()

参考2
How can I scroll a web page using selenium webdriver in python?

ここで、取得したリストには、表示部分より少し多めにユーザーが表示されている関係で、重複して表示されているユーザーがいるので、

liker2 = list(dict.fromkeys(liker))

で、重複を削除します。

参考
Pythonでリスト(配列)から重複した要素を削除・抽出

いいねくれた人達の投稿をみていき、いいねする

プログラムも終盤、実際にいいね!を返します。

likeIcon = browser.find_elements_by_xpath(likeSvgPath)
if len(likeIcon) > 0 :
    likeState = likeIcon[0].get_attribute('aria-label')
    if likeState == '「いいね!」を取り消す':
        print('  この投稿はすでに「いいね」済み')
        continue
    elif likeState == 'いいね!':
        print('  まだ「いいね」してないので「いいね」します')
        favbtn = likeIcon[0].find_element_by_xpath('./..')
        favbtn.click()
        continue
    else:
        print('  不明です')
else:
    print('  いいねボタンが見つかりませんでした')

いいねアイコンを押すのですが、いいねアイコンが、いいね済なのに押してしまうと、いいねが解除されてしまい本末転倒なので、いいね済みかどうかを”aria-label”属性をみて、判別します。

いいねしてくれた人を全部回るまでプログラムが走ったら、投稿URLの入力フェーズに戻ります。

他の投稿のいいねくれた人にも返したい場合は、つづけてURLを入力すれば、またいいねを返します。

ただ、いいね返しが、結構時間かかるので、のんびり待つしかないです。

time.sleepの値を短くすれば早いのですが、あまり早くすると制限されてしまうため、仕方がないですね。

昔は、インスタグラムもAPIが結構自由に開放されていたと思ったのですが、Facebookに買収されてから、なんというか、そのあたり難しくなりましたよね。

個人的には、Facebookの買収はまったく歓迎はできなかったのですが、案の定、オープンなWebという方向からは、離れてしまいました。

と、そんなことを言ってもしょうが無いのでみなさんのインスタライフの一助になれば幸いです。

funnydreamer
栃木生まれのミドルエイジ。フロントエンドとデザインの領域におりましたが、最近はマーケティングやライティングにPythonによる自動化など何でも屋になってきました。趣味は、ゲーム、アニメ、自転車(ポタリング)、カフェ巡り、お絵描きと自称多趣味。ケーキはショートケーキが好物。

ADD COMMENT

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください