Instagramハッシュタグ検索の結果をスクレイピングしていきます。
ただ、それなりの難易度です。
Pythonはもちろん、Seleniumにも精通しておく必要があります。
でも、安心してください。
コピペで動くサンプルコードを載せています。
これを使えば、ハッシュタグ検索の結果を一気に取得できます。
本記事の内容
- Instagramのスクレイピングについて
- ハッシュタグ検索結果のスクレイピング仕様
- ハッシュタグ検索結果をスクレイピングするサンプルコード
それでは、上記に沿って解説していきます。
Instagramのスクレイピングについて
次の記事をご覧ください。
Instagramのスクレイピングの可否などについて説明しています。
スクレイピングは、問題のある技術だと言われることがあります。
でも、上の記事内ではそれを真っ向から否定しています。
スクレイピングに関して、経験や知識のない方は是非とも上記記事を事前にご覧ください。
そして、その内容を理解してください。
おそらく、そうしないとこれ以降の説明も理解できないかもしれません。
「急いでスクレイピングをしたい」と言う方は、注意事項に気を付けてサンプルコードを利用してください。
ハッシュタグ検索結果のスクレイピング仕様
今回は、「それ以降の10件」を取得します。
つまり、スクロールして初めて表示される投稿のことです。
詳細を知りたい場合は、上記参考記事をご覧ください。
やはり、InstagramはTwitterと同じようにスクレイピング対策を熱心にしてきています。
そのため、Seleniumの知識が必要です。
PythonからSeleniumを使って、スクロールをするのです。
そうすることにより、「それ以降の10件」が表示されます。
ここまでをまとめます。
例えば、「#家系ラーメン」でハッシュタグ検索を実施したとします。
総数が280709件(2020年09月20日時点)です。
この時点では、画面内(htmlソース)には33件(9件ぐらいしか実際には見えない)の投稿(htmlタグ)が表示されています。
そして、それ以降の投稿を取得するためにスクロールします。
約4行、12件の投稿数分だけのスクロールとなります。
この時点で33件+12件で、45件の投稿データを取得できます。
あとは、これを繰り返すだけです。
基本的には、1スクロールで12個のデータが追加されていきます。
以上でスクレイピング仕様は終わりです。
ここで説明したスクロールを伴うスクレピングを次の記事でも解説しています。
ハッシュタグ検索結果をスクレイピングするサンプルコード
コピペで動くサンプルコードです。
「#家系ラーメン」でハッシュタグ検索した結果をスクレイピングするコードです。
2020年09月20日時点では、モリモリと動いています。
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains import urllib.parse import re import bs4 import json import time INSTAGRAM_DOMAIN = "https://www.instagram.com" MIN_COUNT = 100 KEYWORD = "家系ラーメン" CHROMEDRIVER = "chromedriver.exeのパス" # driver取得 def get_driver(): # ヘッドレスモードでブラウザを起動 options = Options() #options.add_argument('--headless') # ブラウザーを起動 driver = webdriver.Chrome(CHROMEDRIVER, options=options) return driver # 対象ページ取得 def get_text_from_target_page(driver, first_flg, url): # ターゲット driver.get(url) if first_flg: # articleタグが読み込まれるまで待機(最大10秒) WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.TAG_NAME, 'article'))) else: driver.implicitly_wait(10) # 見つからないときは、10秒まで待つ text = driver.page_source return text # 正規表現で値を抽出 def get_search_value(ptn, str): result = re.search(ptn, str) if result: return result.group(1) else: return None # info取得 def get_info_from_text(text): soup = bs4.BeautifulSoup(text, features='lxml') try: info = {} # 投稿(v1Nh3 kIKUG _bz0w) elems = soup.find_all(class_="v1Nh3") for elem in elems: a_elem = elem.find("a") href = a_elem["href"] url = INSTAGRAM_DOMAIN + href post_id = get_search_value("\/p\/(.*)\/", href) # img情報 img_elem = elem.find("img") alt = img_elem["alt"] src = img_elem["src"] post_dic = {} post_dic["url"] = url post_dic["alt"] = alt post_dic["src"] = src info[post_id] = post_dic return info except: return None # 最後の要素までスクロール def scroll_to_elem(driver, footer_move_flg): try: if footer_move_flg: last_elem = driver.find_element_by_id("fb-root") actions = ActionChains(driver); actions.move_to_element(last_elem); actions.perform(); else: # 最後の要素の一つ前までスクロール elems = driver.find_elements_by_class_name("weEfm") last_elem = elems[-1] actions = ActionChains(driver); actions.move_to_element(last_elem); actions.perform(); return True except: return False # 投稿件数取得 def get_post_count(text): try: json_str = get_search_value("window._sharedData = (.*);<\/script>", text) dict = json.loads(json_str) post_count = dict["entry_data"]["TagPage"][0]["graphql"]["hashtag"]["edge_hashtag_to_media"]["count"] return post_count except: return MIN_COUNT if __name__ == '__main__': # url url = "https://www.instagram.com/explore/tags/" + urllib.parse.quote(KEYWORD) + "/" # ブラウザーを起動 driver = get_driver() # 対象ページのhtmlソース取得 text_0 = get_text_from_target_page(driver, True, url) post_count = get_post_count(text_0) print("合計:" + str(post_count)) info_all = {} count_info = 0 buf_count_info = 0 while count_info < MIN_COUNT: # スクロール後対象ページのhtmlソース取得 text_1 = driver.page_source info = get_info_from_text(text_1) if not None: info_all.update(info) count_info = len(info_all) time.sleep(1) print(count_info) if buf_count_info==count_info: time.sleep(3) result_flg = scroll_to_elem(driver, False) buf_count_info = count_info if post_count <= count_info: break driver.quit()
詳細はコードを見てください。
このレベルのPythonを理解できない方は、利用するのはやめておいた方がよいでしょう。
スクレイピングは、やりかたによっては相手サーバーに負荷を与えてしまいますので。
そのため、理解できないコードは利用すべきではありません。
ただ、Seleniumに関する部分は理解必須とは言えません。
これを機に理解してみるか程度でよいかと思います。
あと、以下の定数を説明しておきます。
MIN_COUNT = 100 KEYWORD = "家系ラーメン" CHROMEDRIVER = "chromedriver.exe"
MIN_COUNT
必要最低限取得したい投稿数です。
ハッシュタグによっては、何百万件も投稿があります。
制限がないと何百万件も取得し続けるわけです。
そのための制限でもあると考えてください。
現状は、100を設定しています。
よって、100件以上取得した段階でプログラムは終了します。
KEYWORD
これは説明不要でしょうね。
ハッシュタグのキーワードです。
CHROMEDRIVER
Seleniumに関する部分です。
詳細は、次の記事を確認してください。
まとめ
あとは、printに関して補足で説明しておきます。
このコードを実行すると、以下のような結果が表示されます。
合計:280709 33 45 57 69 81
合計の「280709」は、ハッシュタグ検索の結果の総件数です。
それ以外の数字は、スクロールする度に取得した投稿の総件数を表示しています。
12件づつスクレイピングできているのが、わかると思います。
なお、スクロールは基本的に1秒間間隔で行っています。
現時点では、InstagramからIP制限を受けていません。
1秒間間隔のアクセスなら、問題ないレベルということでしょう。
そもそも、人間が手動でスクロールする方が速いはずです。
指でシュッシュッとスクロールしますよね。
短時間においてなら、プログラムより人間の方がサーバーへの負荷が大きいのでは?
以上より、適切に考慮すれば、スクレイピングは何も危険ではありません。