見出し画像

みんなのフォトギャラリーの共有済み画像の整理、備忘録

作業がほぼ完了したので、ここにまとめておきたい。

整理の方針

共有済み画像のうち、使用履歴のないものを非共有とする。

例外として、使用履歴がなくてもその画像を扱った記事が生きている場合には、共有のままとする。

みんなのフォトギャラリー関連記事もほぼ全て下書きに戻し、非公開とする。

「フォトギャラリーで公開した画像が使用された時」のメール通知設定もオフにした。

ちなみに、共有することをやめてしまうと、それが使われている記事からタイトル画像が消えてしまうとのこと。

note.com の内容
この画像は××ノートで使用されています。
「みんなのフォトギャラリー」から削除すると、
それらのノートの画像も消えてしまいます。
本当によろしいですか?

自分が提供した画像とはいえ、勝手に消してしまうなんてできないと思う。

新規に使うことを停止するのが一番良い。サポートに聞いたら、それはできないとのことだった。

手で作業する場合

まず、準備として、note の「画像」を開く。

1.すべてをクリックする。
2.共有済みを選択する。
3.日時で絞り込むをクリックする。
4.期間を指定を選択する。
5.開始日を指定する(文字列のインプットすれば良い)
6.終了日を指定する(文字列をインプットすれば良い)
7.選択ボタンをクリックする。
8.スクロールバーのスライダーを下にドラッグして表示画像を増やす。9.全部表示されるまで8を繰り返す。(最大125コマ)
10.画像をクリックする。
11.使用履歴がなかったら、みんなのフォトギャラリーから削除する。
12.絞り込み前の画面に戻るので、1から繰り返す。

以上を、対象の画像がなくなるまで繰り返す。

問題点

画像を削除した後で、最初の画面に戻る。毎回、期間を設定し直す必要がある。面倒臭い。しかしそれが仕様ならば仕方がない。可能な限り自動化しよう。

自分が書いた関連記事

↓最初、こんなことを書いた。

Web Scraping でできたらよかったのだがうまくできなかったので、とりあえずマクロを使った。

突破できたと思ったが、マクロではできないことがあるのだった。自分が知らないだけかもしれないが、マクロをいろいろいじってみてやりたいことができないという結論を得たのだ。

Web Scraping に再挑戦

Mac OS X El Capitan
Python 3.8
Selenium
Chrome
ChromeDriver

開発環境 PyCharm

どこかのサイトで、ChromeDriverを使うときは次のオプション設定が必須みたいに言っていたので、それをそのまま使う。

# GoogleChrome のオプションの設定
op = Options()
op.add_argument("--disable-gpu");
op.add_argument("--disable-extensions");
op.add_argument("--proxy-server='direct://'");
op.add_argument("--proxy-bypass-list=*");
op.add_argument("--start-maximized");
# op.add_argument("--headless");
op.add_argument('--user-data-dir=' + profile_path)

headless はやめる。
ブラウザーの表示を見ながらデバッグしていかないと無理。

profile_path は、自分の環境にあったパスを設定(どこかよそのサイトで解説されているはず)
これを設定すれば、保存されたプロファイルが読み込まれるので、ID やパスワードを入力しなくて済む。Web Scraping するにあたって、IDとパスワードの入力までやるのは、結構面倒に感じる。

しかしこれには一つ良くないところがある。Chrome 起動済みでこれをやると、「すでに使われている」とエラーになってしまう。

ユーザープロファイルを使わなければこのようなエラーは発生しないが、代わりに ID と パスワードの入力を求められる。痛し痒しである。

ID と パスワードの自動入力は可能。だから、note を開くたびに ID とパスワードを入力すれば問題は解決する。しかし、パスワードの管理が問題だ。

そこで、必ず Chrome を終了してからスクリプトを実行する方法で妥協した。

プログラミングのポイント

自分がポイントと思ったところだけ記録。

「画像」のページを開く。

url = 'https://note.com/creator_gallery'
driver.get(url)

ダイレクトに開けた。

次に、エレメントをつかみながら、クリックしたり値を入力したりしていく。エレメントは、基本的に XPath で指定した。

XPath をゲットするのに、Chrome のデベロッパーツールを使った。

element = driver.find_element_by_xpath(XPathの記述)

全てまたは共有済みのボタン
//*[@id="image-gallery-container"]/div[2]/image-gallery-status-sort

共有済み
//*[@id="image-gallery-container"]/div[2]/image-gallery-status-sort/ul/li/div/ul/li[2]

日時で絞り込むボタン
//*[@id="image-gallery-container"]/div[2]/image-gallery-date-sort

期間を指定
//*[@id="image-gallery-container"]/div[2]/image-gallery-date-sort/ul/li/div/div

全て .click() で。

適当にウエイト(待機時間)を入れた。

開始日
//*[@id="start_date"]

終了日
//*[@id="end_date"]

これらに対しては、 .send_keys(yyyy年M月D日) とした。

年月日を指定するためにカレンダーをクリックする必要はなかった。直接文字をインプットすれば良い。

選択ボタン
//*[@id="image-gallery-container"]/masonry-layout/gallery-paging/ul/li/a

このボタンをクリックすれば画像が表示される。全てが表示されるには少し時間が掛かる。

画像
//*[@id="image-gallery-container"]/masonry-layout/div[1]/div[*]

画像は最後の div[*] と、アスタリスクを入れることで全ての画像を指定できる。

画像のXPath = '//*[@id="image-gallery-container"]/masonry-layout/div[1]/div[*]'
WebDriverWait(driver, 30).until(EC.visibility_of_all_elements_located((By.XPATH, 画像のXPath)))

これ、効いているかどうか、よく分からない。

この時点で、最初の画像(最大 25 コマ)が表示される。

残りの画像を表示するために、スクロールする。

スクロールには、Java Script を使う。

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

どこかのサイトに書いてあったのをそのまま。

全ての画像が表示されたら、リストに取り込む。

画像の XPath は前で設定したものと同じ。

elements = driver.find_elements_by_xpath(画像のXPath)

find_elements_by_ の結果は、エレメントのリストである。個数は

len(リスト)

125 個を超えると次のページを開かなければならない。処理が面倒になる。自分の場合、期間を1日に絞れば 125 以内に収まると思う。だから 125個まで処理できれば問題ない。

スクロール操作の工夫

画像数が 25 の倍数の時は、スクロールする。
25 の倍数でないときは、それ以上画像はないので、スクロールせずに次へ。
125個 に達したら、それ以上表示できないのでスクロールをやめて次へ。

1回スクロールするたびに、画像が表示されるのを待つ。

スクロール後、画像の表示が完了したことを判断するのに、visibility_of_all_elements_located などの condition を使ってやろうとしたが、うまくいかなかった。スクロールする前から画像が表示されているからだと思う。

そこで、読み込み中かどうかを見ることにした。

スクロール
読み込み中
読み込み完了
さらにスクロール

という流れである。

画像を読み込んでいるときに note のロゴの左上に現れるグルグルを捕まえる。

あの「ぐるぐる」は、デベロッパーツールで見ていると、現れたと思ったらすぐに消えてしまう。だから文字は読めないと思った。動画を撮ろうかと思ったが、面倒くさかった。、結局、目視で読み取った。

id = 'loading-bar-spinner'

スクリプトは、次のようにした。

try:
   el = driver.find_element_by_id('loading-bar-spinner')
   log_text = log_text + 'loading-bar-spinner found\n'
   loading = True
   loaded = False
   sleep(2)
except:
   log_text = log_text + 'loading-bar-spinner not found\n'
   loading = False
   images = driver.find_elements_by_xpath(XPath_for_images)
   image_count = len(images)

'loading-bar-spinner' があったら、画像読み込み中で、loading = True にセット。なかったら、読み込み中ではないから loading = False とする。

loaded というのは、全ての画像の読み込みが完了したことを表すフラグだ。前に述べた画像数で読み込み完了を判断すると、ぐるぐるが消える前に終わってしまったので、念のためにきっちりとぐるぐるが消えてから終わるようにした。

while not loaded:

で、ループ。

log_text というのは、画像の読み込みの進行状況を記録したもの。以下に例を示す。

Scroll
loading-bar-spinner found
0, 25
loading-bar-spinner not found
1, 50
Scroll
loading-bar-spinner found
2, 50
loading-bar-spinner found
3, 50
loading-bar-spinner not found
4, 65
Image loaded, image_count % 25 > 0
loading-bar-spinner not found
5, 65

上の記録の説明

まず最初のスクロール。
次に、ぐるぐるが現れる。
ループカウント0、画像数 25。
ぐるぐるが消えた。
ループカウント1、画像の数は 50 に変化。
スクロール。
ぐるぐるがあらわれた。
ループカウント2、画像数 50 で変化なし。
ぐるぐるはまだある。
ループカウント3、画像数 50 で変化なし。
ぐるぐるが消えた。
ループカウント4、画像数 65 に変化。
65 は 25 の倍数ではないので、ローディング完了。
ぐるぐるも消えた。
ループカウント5、画像数 65。

ぐるぐるが消えてすぐは画像の数が変わらないため、無駄にスクロールすることがあった。そこで、sleep(2) で2秒間の待ちを適当に入れた。

125 件に達したときは、さらに画像があるかもしれない。しかし次のページを処理するのが面倒臭かったので、期間を短縮してやり直す。期間は、スクリプトを手で書き換えることで変更する。

メッセージボックスも使ってみたが、PyCharm でメッセージを表示したところ Python ランチャーが起動したままになって強制終了しなければならない羽目に。いちいち強制終了するのは面倒なので、メッセージボックスは使わないことにした。デバッガーで Breakpoint を設定し、停止するようにした。

画像の表示が完了したら、画像のエレメントをリストに取り込む。

el_images = driver.find_elements_by_xpath(画像のXPath)

次に、画像を1個ずつクリックしていく。

for i in range(len(el_images)):
   image = el_images[i]
   image_count = i + 1

クリックはこれで

image.click()

画像をクリックすると次のようなダイアログが表示される。

画像1

で、各項目は li タグとなっている。目的のエレメントだけをつかみたいので、Class 名で指定。

WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CLASS_NAME, 'p-galleryModal__item')))
list = driver.find_elements_by_class_name('p-galleryModal__item')

まず全ての項目をリストに取り込んで、次にリストの項目を一つ一つ調べていく。

項目は次のとおり。

タイトル(上の画像では、※ 表示)
キーワード
画像の説明
使用履歴
みんなのフォトギャラリーから取り除く

使用履歴がない場合、エレメント自体が存在しない。

上の4つが dt タグ、最後のやつが a タグである。

dt をつかむために try: を使った。

try を使うと dt タグが存在しない場合にタイムアウトまで待たされる。予期しない動きはなさそうなので、個数を数えてインデックスで直接指定した方が早いかもしれない。

dt タグ内の文字は、element.text でゲットできた。

画像をアップした記事が公開されていない場合は、「タイトル」ではなく '※' が表示される。

使用履歴がない場合は、使用履歴の項目がなくなり、「みんなのフォトギャラリーから取り除く」が繰り上がる。

「みんなのフォトギャラリーから取り除く」だけが a タグ。これも念のため、try: を使ってエラーに備えた。

「みんなのフォトギャラリーから取り除く」エレメントには dt タグがない。dt タグをつかみに行った時、タイムアウトまで待つ必要がある。

この段階でエレメントは全て揃っているはず。読み込み待ちは必要ないので、タイムアウトを1秒に設定した。

driver.implicitly_wait(1)

一連の処理が終わったら、タイムアウトの設定を 30 秒に設定する。

使用履歴のデータを取り込む。

まず、ul タグをつかむ。

els_ul = elm_li.find_elements_by_tag_name('ul')

ul は2個ある。show か show でないか、で2つ。件数で分けているようだ。

show の方からデータをゲットする。show であることは、ng-show アトリビュートで確認。

if el_ul.get_attribute('ng-show') == 'show':

使用先は、li タグに収められている。 

els_li = el_ul.find_elements_by_tag_name('li')

これの要素1個ずつから、

使用された記事へのリンク
使用された記事のタイトル
使用したクリエイター

を取り出す。

記事へのリンクは、 a タグをつかんで、

エレメントa.get_attribute('href')

タイトルおよびクリエイターは、Calss 名を使ってつかんだ。

タイトルは、

タイトルのエレメント = aタグのエレメント.find_element_by_class_name('u-mrXXS')
タイトル = タイトルのエレメント.get_attribute('innerHTML')

クリエイターは、

クリエイターのエレメント = aタグのエレメント.find_element_by_class_name('u-textGrey')
クリエイター = クリエイターのエレメント.get_attribute('innerHTML')

文字列は、.text で取れると思ったら innerHTML だった。

画像の情報は、csv ファイルに保存。

with open(fname, 'w') as csvfile:
   writer = csv.writer(csvfile)
   writer.writerows(data)

data はリストで、次の形。

[image_count, title, creator, link, image_source]

使用履歴がない場合、画像情報のダイアログを表示したところで止まるので、「みんなのフォトギャラリーから取り除く」を手でクリックする。

止めるのは、デバッガーの Breakpoint を設定することによる。

別に、他人が使うスクリプトを作っているわけではないので、デバッグモードでも構わない。

みんなのフォトギャラリーから取り除いた後、シームレスに最後の画像まで処理を進めたかったが、自分には難しくてできなかった。

みんなのフォトギャラリーから取り除いた後は、デバッガーを停止して、最初からやり直す。

デバッガーを停止すると、Chrome が終了する。

画像をクリックするまでは、何度かスクロールして一旦全部表示する必要があるが、すでにチェックした画像は開く必要はない。途中で終わってしまった場合は、csv ファイルに最後の画像の番号が書かれている。そこで csv ファイルを見て、番号をスクリプトに転記することにした。

csv ファイルから読み込むのは簡単だが、まだデバッグ段階で、最初から実行したいときもあり、とりあえず手作業とした。

途中から再開することで、無駄なアクセスを減らした。

開始日、終了日、それから何番目の画像まで処理済みかは、スクリプトの冒頭に書くようにした。

start_date = '2020年6月30日'
end_date = '2020年7月31日'
completed_image_number = 0

全画像をループ処理できなかった件

画像を削除した後の処理がうまくできなかったため、一旦終了して再開という方法を取ることになった。

もしかしたら、Chrome を終了して、一からやり直すスクリプトを書けばできたのかもしれない。

画像情報のダイアログで「みんなのフォトギャラリーから削除する」をクリックすると、Exception が発生する。これをうまく処理する方法があるのかもしれないが、知識不足のため解決策がわからず、できるところまでで妥協した。

画像の説明のデータが読めなかった件

これも知識不足なのだろうが、画像の説明のデータを読見とることができなかった。画面上は文字が表示されているのだが、デベロッパーツールで調べても同じ文字列はどこにもなかった。結局、あきらめた。

画像の説明は読めない、画像情報に日付の情報が表示されない、画像ページで検索しても記事を検索しに行くので目的の画像に絞り込めない。なんとかならないか。ま、みんなのフォトギャラリーにおいて、画像提供者側の便宜を図ってもあまりメリットはないか。利用者が使いやすければそれで良いのだろう。

真面目に画像を管理する気もないので、ま、いっか。

画像が 25 コマしか表示できない問題の回避策

25 コマを超える画像がある場合に残りを表示するには、スクロールする必要がある。

しかし、自分の環境では、スクロールできない現象がたびたび発生している。見ると、スクロールバーのスライダーが最大サイズになっている。上下に余白がないからスライダーが動かない。つまりスクロールできない。

Web Scraping していて、回避策を見つけた。

ウィンドウを縮めるのだ。高さ方向に。

縮めたらスクロールできるようになった。

しかし、縮めたままだと都合が悪かった。そこで適宜ウインドウサイズを変更することにした。

絞り込み条件設定から、最初の画面が表示されるまでは、

driver.set_window_size(1396,1200)

次に、スクロールするときの設定は、

driver.set_window_size(1396, 640)

画像をクリックしてダイアログを表示するときは、

driver.set_window_size(1396, 1324)

拡大する。狭いと、ダイアログの下の方が切れてしまって、ボタンが隠れてクリックできなかったから。

横幅、1396 は適当だが、6コマが並ぶ設定。最初と最後は同じ設定でよかったかもしれない。

処理に掛かる時間

全て使用履歴がある画像だが、65件についてやってみたら、約 2分40秒かかった。

Selenium の Wait について

Web Scraping において、待つ事は大事だ。便利なものを Selenium が用意してくれているので、うまく使いたい。

ドキュメント

5. Waits として次の2種類について書かれている。

Explicit Waits
Implicit Waits

Explicit Wait というのは、明示的な待機と訳されるもの。

element = WebDriverWait(driver, 10).until(
       EC.presence_of_element_located((By.ID, "myDynamicElement"))

ある condition になるのを待つということ。つまり、何がどういう状態になるまで待つかを明言するということ。

これを使うには

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

expected_condition にはいくつかあるが、使えそうな気がしたのは、

visibility_of_element_located
element がページの DOM に存在し、表示されていることを確認します。
可視性とは、element が表示されているだけでなく、0 以上の高さと幅を持っていることを意味します。
locator を使用して element を見つけます。
element が配置され可視性が得られると WebElement を返します。
visibility_of_all_elements_located
すべての element がページの DOM に存在し、表示されていることを確認します。
可視性とは、element が表示されているだけでなく、0 より大きい高さと幅を持っていることを意味します。
locator を使用して element を見つけます。
element が配置され可視性が確認されると、WebElements のリストを返します。
invisibility_of_element_located
ある element が DOM 上で不可視であること、または、存在しないことを確認します。
(多分、locator を使用して element を見つける。)
staleness_of
element がDOMに接続されなくなるまで待ちます。
(多分、待機対象の element を指定する。)
element が DOM に接続されている場合は False を、そうでない場合は True を返します。

※ 和訳してみた。element および locator はあえて英語のまま。引用部分に怪しいものを感じた場合は、原文を確認すること。

参考:DOMとは Document Object Model の頭文字


5. Waits に関しては、こちらで日本語で読める。

「このサイトはあずみ.netが翻訳しております。」とのこと。

以上
t.koba


この記事が気に入ったらサポートをしてみませんか?