質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.89%

【ruby】sereniumのfind_elementがあれば動作するif文

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 95

kaori_oka

score 124

前提・実現したいこと

rubyにて情報提供系サイトにてpdfやWordファイルがいくつ存在しているか(添付されているか)を調査するため、
seleniumにて簡易クローラー作成しようとしています。

発生している問題・エラーメッセージ

掲載パターンが3パターンありif文で分岐させています。
①記事内にpdfやWordが複数あるバージョン(xpath指定)

/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a

②記事内にpdfやWordが単品のバージョン(xpath指定)

html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a

③記事内に添付ファイルがない

該当のソースコード

newsUrl.each do |url|
    driver.navigate.to(url)
    puts "#{url}に移動します"
    sleep 1
    fileCount = fileCount + 1
    begin
        while true do
            if driver.find_element(:xpath, '/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a')
                file = driver.find_element(:xpath, "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{fileCount}]/dt/a")
                fileHref = news.attribute('href')
                filePaths << fileHref
            elsif driver.find_element(:xpath, '/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a')
                file = driver.find_element(:xpath, "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a")
                fileHref = news.attribute('href')
                filePaths << fileHref
            else
                puts "#{url}のファイルは空です。"
            end
        end
    rescue Selenium::WebDriver::Error::NoSuchElementError
        puts "url無し"
    end
end

試したこと

上記のようにif文で書き実行してみたのですが
下記のようなエラーが出ます。
他の方法ご存知の方いたら教えてほしいです。

./demo.rb:103:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • kaori_oka

    2020/01/14 15:44

    おっしゃる通りです。
    下記のようにすると取得できます。
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[1]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[2]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[3]/dt/a"

    キャンセル

  • taichi730

    2020/01/14 15:48 編集

    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[1]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[2]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[3]/dt/a"

    この例は、添付ファイルが3つある場合ということでしょうか?
    添付ファイルが2つの場合は、

    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[1]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[2]/dt/a"

    で取得できて、1つの場合は、

    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a"

    で取得できるということでしょうか?
    添付ファイルの個数と、期待される xpath の一覧の対応がわかると、ありがたいです。

    キャンセル

  • kaori_oka

    2020/01/14 15:56

    その通りです!
    そのパターンに加え、添付ファイルのない記事もあります。

    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a"
    "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a"
    上記のようなxpathが存在しないページが添付のない記事です

    キャンセル

回答 3

checkベストアンサー

+1

newsUrl.each do |url|
  driver.navigate.to(url)
  puts "#{url}に移動します"
  sleep 1

  file_count = 1
  loop do
    file_patterns = []
    file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" if file_count == 1
    file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{file_count}]/dt/a"

    elements = file_patterns.flat_map { |pattern| driver.find_elements(:xpath, pattern) }
    if !elements.empty?
      filePaths << url.attribute('href')
      file_count += 1
    else
      break
    end
  end
end

上記ののような感じでしょうか?
1つ目の添付ファイルの場合、1つだけ添付されている場合と、複数個添付されている場合で、パターンが異なるので、

file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" if file_count == 1
file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{file_count}]/dt/a"

で対応しています。

elements = file_patterns.flat_map { |pattern| driver.find_elements(:xpath, pattern) }

で一致する要素を取り出して、

  • 一致する要素がある: elements が空ではない
  • 一致する要素がない: elements が空
  • この時点でループを抜ける

で処理してます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/14 16:32 編集

    ご提案ありがとうございます!
    すみません、下記のようなエラーが出たのですが、

    ```
    https://****/21074/に移動します
    Traceback (most recent call last):
    4: from ./demo.rb:100:in `<main>'
    3: from ./demo.rb:100:in `each'
    2: from ./demo.rb:106:in `block in <main>'
    1: from ./demo.rb:106:in `loop'
    ./demo.rb:113:in `block (2 levels) in <main>': undefined method `attribute' for "https://****/21074/":String (NoMethodError)
    ```

    ./demo.rb:113:in `block (2 levels) in <main>': undefined method `attribute' for "https://****/21074/":String となっているので、データ型を変更する必要があるということでしょうか。

    下記が移植したソースコードなのですが、putsは動作しているので102行目までは問題なさそうです。

    ```
    100 newsUrl.each do |url|
    101 driver.navigate.to(url)
    102 puts "#{url}に移動します"
    103 sleep 1
    104
    105 file_count = 1
    106 loop do
    . file_patterns = []
    . file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" if file_count == 1
    . file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{file_count}]/dt/a"
    .
    . elements = file_patterns.flat_map { |pattern| driver.find_elements(:xpath, pattern) }
    . if !elements.empty?
    113 filePaths << url.attribute('href')
    . file_count += 1
    . else
    . break
    . end
    . end
    . end
    ```

    長々とすみません。。。

    キャンセル

  • 2020/01/15 15:22

    ご提案いただいたコードを下記のように修正しまして正常に動作しました!

    loop doですと取り出す要素がなくなった際に「Selenium::WebDriver::Error::NoSuchElementError」となりますので、もともと動かしていたdigin ~ rescue ~ endと移植して解決しました!

    ```
    csv_import_url.each do |url|
    begin
    driver.navigate.to(url)
    # puts "#{url}に移動します"
    sleep 1

    file_count = 1
    while true do
    file_patterns = []
    file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" if file_count == 1
    file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{file_count}]/dt/a"

    elements = file_patterns.flat_map { |pattern| driver.find_elements(:xpath, pattern) }
    if !elements.empty?
    filePaths << elements[0].attribute('href')
    file_count += 1
    else
    break
    end
    end
    rescue Selenium::WebDriver::Error::NoSuchElementError
    end
    end
    ```

    キャンセル

  • 2020/01/15 15:43

    https://www.rubydoc.info/gems/selenium-webdriver/Selenium/WebDriver/SearchContext#find_elements-instance_method
    によれば、find_elements の場合は、NoSuchElementError は起こらないとなっています。
    なので、NoSuchElementError は起きないはずなんですが。
    何か別の問題があるのかもしれません。
    (例外を制御に使うのはお行儀がよろしくおないので、find_element の代わりに find_elements にしました。)

    キャンセル

  • 2020/01/15 15:50

    もしかしたら、たくさんのURLの中に違うパターンのものがあるかもしれないので、再度提案いただいたコードを実行して
    原因究明してみます!

    キャンセル

0

newsUrl と fileCount の関係が、

  • newsUrl[0]/fileCount: 0
  • newsUrl[1]/fileCount: 1
  • newsUrl[2]/fileCount: 2

であるならば、each のかわりに each_with_index を使うと良いと思います。

fileCount = fileCount + 1 の行を削除して、

newsUrl.each_with_index do |url, fileCount|
  # 以下略
end

としてみてはどうでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/14 14:37

    前段階の処理にて、
    ```newsUrl```に1000件くらいのurlを入れまして、

    その1000件の掲載パターンはバラバラとなっています

    newsUrl[0]/ fileCount: 0 OR fileCount: 1 OR fileCount: 2
    newsUrl[1]/ fileCount: 0 OR fileCount: 1 OR fileCount: 2
    以下繰り返しといった具合です

    ご提案いただいた下記の記述で上記のパターンの処理できますでしょうか??
    ```
    newsUrl.each_with_index do |url, fileCount|
    # 以下略
    end
    ```

    キャンセル

  • 2020/01/14 14:45

    #each_with_index はインデックスとともに、配列の中身を走査するメソッドなので、上記の用途では使えないかと。

    #find_element の引数のパターンが、以下の4種類のうちのどれかで、それを while で回しながら検査しているということでしょうか?

    * /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a
    * /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl1/dt/a
    * /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl2/dt/a
    * /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl3/dt/a

    キャンセル

0

filePaths << url.attribute('href')

if 文の中身の上記のコードは、元々のコードを持ってきています。
selenium については知らないので、間違っているかもしれませんが、

filePaths << elements[0].attribute('href')

とするのが、正しいように見えます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/01/15 12:07

    現在検証中でして、少々お待ちください
    長々とお待たせしてしまって申し訳ございません。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.89%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる