find.elementとfind.elementsの使い分けには気をつけなければいけない


概要

pythonでSelenium BeautifulSoupを使ってスクレイピングの勉強をしています。
タイトルの通り、何度もしくじったのでメモがてらにアウトプット。seleniumとは?
みたいな基本や細かいところはすっ飛ばします。今回はメルカリを対象に書いてます。
soupも若干触れますが、基本はseleniumの記事です。

findの細かい種類や簡単な概要はこちら参考になると思います。
https://kurozumi.github.io/selenium-python/locating-elements.html

初期の設定はこんな感じで。ただメルカリのトップページを開くだけです。
余計なimportも沢山ありますがスルーしてください。

from selenium import webdriver
from time import sleep
import os
from bs4 import BeautifulSoup
import sys
import urllib.request
import chromedriver_binary

options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox') 
options.add_argument('--disable-dev-shm-usage')
browser = webdriver.Chrome('chromedriver',options=options)
url = "https://www.mercari.com/jp/"
browser.get(url)
ITEM = 'ウィスキー山崎'

find.element

対象の要素を一つだけ取得する。もし参照したいクラス等に重複があれば一番最初に参照するノードが取得される
なので参照する時に、同名のクラス名がないか先に確認しておくべきです(自戒)複数あっても一番最初に参照されるのであれば問題ないですけどね。

とりあえずトップページで検索ワードは何も入れずにただ検索ボタンをクリックしてみました。

browser.find_element_by_css_selector('.sc-exAgwC.bdHDSo').click()

とりあえずなんか表示されてますwww
新しく更新された順ですかね?
ちなみに検索ボタン押す前のトップページを検証ツールで見てみると

同名のクラスは1つしかない事がわかります。
なのでパスさえ間違えなければ、何も問題なく通りますね。
試しにわざとタイポしてみます

browser.find_element_by_css_selector('.sc-exAgwC.bdHDSoaaaaa').click()

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":".sc-exAgwC.bdHDSoaaaaa"}

そんな要素はねーよって言われてます。

余談ですけどsoupにして同様のミスをすると「None」で返ってきます。
この違いは注意した方が良いかも。

find.elements

対象の要素をlist型にして全て取得する。同名クラスが複数あって全て取得したい場合はこちらを使う事になります。例えば対象が3つあった場合は

[対象1,対象2,対象3]

こんな感じのlistで返ってきます。
あくまで個人的にですがseleniumでelementsは使い勝手があまり良くないなぁと感じでいます。対象データをスクレイピングして整形したりしたい時って主にsoupでやってしまうので、ブラウザ操作に使われがちなseleniumで全件取得が活躍した事が今のところあまりありません。

ちなみに今回の記事を執筆しようと思ったのは下記コードで詰まった時なのですが

search_word = browser.find_element_by_xpath("//input[@name='keyword']")

selenium.common.exceptions.ElementNotInteractableException: Message: element not interactable

見て察しがつくと思いますが、商品の検索窓にキーワードを入れるための要素を取得しようとしています。
私は当初これを普通にelementで取得しようとして上手くいきませんでした。
何故ならこの("//input[@name='keyword']")がまさかの対象が2件あったわけですw

今回欲しいのは2個目の要素なので、elementでやると1個目の要素が返ってきて、想定した場所でクリックができていないわけです。指定の仕方を変えればelementでもいけると思うのですが、今回は強引にこんな感じで指定します。

search_word = browser.find_elements_by_xpath("//input[@name='keyword']")[1]

'keyword'要素はlist型で二つ返ってきているので下記の様になっています。

[keyword0,keyword1]

ので、最後に[1]を書いてどこの要素を使うか指定する必要があります。

無事指定したワードが入力されました。
あとこれまた余談ですがsoupで指定を間違えると空リストで返ってきます。

selenium.common.exceptions.ElementNotInteractableException: Message: element not interactableがウザい

elementでミスっている時、このエラーで色々と探っていて、原因を気づくのにだいぶ時間がかかりました。
画面上に要素が表示されていないから、画面をデカくしろだの、chromedriverのバージョンが違うだの、マウスホバーしろだのあらゆる記事が出てきましたが、結局は私の通し方が間違えてただけですw

というかseleniumのエラーはほぼほぼfindの通す先が何らかの原因で間違えている事が99パーセントでした(圧倒的偏見)
初心者の方は気をつけてください。

検索窓に指定ワードを入れて検索ボタンクリックするコードだけ一応最後に貼っておきます。
以上です。

from selenium import webdriver
from bs4 import BeautifulSoup
import chromedriver_binary
from time import sleep

options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox') 
options.add_argument('--disable-dev-shm-usage')
browser = webdriver.Chrome('chromedriver',options=options)
url = "https://www.mercari.com/jp/"
browser.get(url)
sleep(2)
ITEM = 'ウィスキー山崎'

search_word = browser.find_elements_by_xpath("//input[@name='keyword']")[1]
search_word.send_keys(ITEM)
browser.find_element_by_css_selector('.sc-exAgwC.bdHDSo').click()
sleep(2)