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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

if

if文とは様々なプログラミング言語で使用される制御構文の一種であり、条件によって処理の流れを制御します。

selenium

Selenium(セレニウム)は、ブラウザをプログラムで作動させるフレームワークです。この原理を使うことにより、ブラウザのユーザーテストなどを自動化にすることができます。

Q&A

解決済

3回答

1168閲覧

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

kaori_oka

総合スコア176

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

if

if文とは様々なプログラミング言語で使用される制御構文の一種であり、条件によって処理の流れを制御します。

selenium

Selenium(セレニウム)は、ブラウザをプログラムで作動させるフレームワークです。この原理を使うことにより、ブラウザのユーザーテストなどを自動化にすることができます。

0グッド

0クリップ

投稿2020/01/14 03:04

前提・実現したいこと

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

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

該当のソースコード

ruby

1newsUrl.each do |url| 2 driver.navigate.to(url) 3 puts "#{url}に移動します" 4 sleep 1 5 fileCount = fileCount + 1 6 begin 7 while true do 8 if driver.find_element(:xpath, '/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a') 9 file = driver.find_element(:xpath, "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{fileCount}]/dt/a") 10 fileHref = news.attribute('href') 11 filePaths << fileHref 12 elsif driver.find_element(:xpath, '/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a') 13 file = driver.find_element(:xpath, "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a") 14 fileHref = news.attribute('href') 15 filePaths << fileHref 16 else 17 puts "#{url}のファイルは空です。" 18 end 19 end 20 rescue Selenium::WebDriver::Error::NoSuchElementError 21 puts "url無し" 22 end 23end 24

試したこと

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

ruby

1./demo.rb:103:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

taichi730

2020/01/14 04:54 編集

./demo.rb:103 は具体的には、どこを指すのでしょうか? fileCount = fileCount + 1 の事でしょうか?
kaori_oka

2020/01/14 05:13

ご回答ありがとうございます。 おっしゃるとおり、./demo.rbの:103行目が```./demo.rb:103:in```となります
taichi730

2020/01/14 05:16

fileCount の定義はどうなっているのでしょうか? attr_reader :fileCount とか attr_accessor :fileCount の様になっているのでしょうか?
taichi730

2020/01/14 05:21

もう一点。 fileCount は newsUrl のインデックスとか通し番号の認識で合っていますでしょうか?
kaori_oka

2020/01/14 05:22

fileCountは ```fileCount = fileCount + 1```として、index代わりに使用していまして、 attr_accessor :fileCount のようには使用していません。 他の処理にて```index = index + 1```を使用しているため、 indexの代わりとなる変数名を「fileCount」として使用しています。
kaori_oka

2020/01/14 05:51

①記事内にpdfやWordが複数あるバージョン(xpath指定) ```/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a``` >いくつのファイルが添付されているかは不明 ②記事内にpdfやWordが単品のバージョン(xpath指定) >添付されているファイルは1点のみ ```html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a``` ③記事内に添付ファイルがない newsUrlのとび先では、上記3種の掲載パターンがあります。 newsUrlのすべてのURLを巡回し、添付されているファイルURLの取得と、添付されているファイルの総数を知りたいです。 newsUrlは現在調査しているサイトの記事すべてのURLが入っていて、それに添付されているファイルと、そのファイルのURLを抜き出したいと思っています。 いろいろと後追いでの情報提供ですみません...
taichi730

2020/01/14 06:28

> 記事内にpdfやWordが複数あるバージョン(xpath指定) ある URL に /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[*]/dt/a で一致する要素が複数個あるということでしょうか?
kaori_oka

2020/01/14 06:34

はいそうです。 複数のファイルが紐づく記事は下記のような指定で、すべてのファイルの情報を取得したいです。 /html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{fileCount}]/dt/a
taichi730

2020/01/14 06:40

1 つめの添付ファイル "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" 2 つめの添付ファイル "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl1/dt/a" 3 つめの添付ファイル "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl2/dt/a" という事でしょうか?
kaori_oka

2020/01/14 06: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 06:55 編集

"/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 06: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が存在しないページが添付のない記事です
guest

回答3

0

ベストアンサー

ruby

1newsUrl.each do |url| 2 driver.navigate.to(url) 3 puts "#{url}に移動します" 4 sleep 1 5 6 file_count = 1 7 loop do 8 file_patterns = [] 9 file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a" if file_count == 1 10 file_patterns << "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl[#{file_count}]/dt/a" 11 12 elements = file_patterns.flat_map { |pattern| driver.find_elements(:xpath, pattern) } 13 if !elements.empty? 14 filePaths << url.attribute('href') 15 file_count += 1 16 else 17 break 18 end 19 end 20end

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

ruby

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

で対応しています。

ruby

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

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

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

で処理してます。

投稿2020/01/14 07:10

taichi730

総合スコア318

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kaori_oka

2020/01/14 07:33 編集

ご提案ありがとうございます! すみません、下記のようなエラーが出たのですが、 ``` 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 ``` 長々とすみません。。。
kaori_oka

2020/01/15 06: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 ```
taichi730

2020/01/15 06:43

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

2020/01/15 06:50

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

2020/01/24 01:44

@taichi730さま 友人に手伝ってもらい、下記で動作しました!ありがとうございます! ``` csv_import_url.each do |url| driver.navigate.to(url) sleep 1 elements = driver.find_elements(:xpath, "/html/body/div[3]/div[1]/div[2]/div[2]/div[2]/div/dl/dt/a") if !elements.empty? elements.each do |e| filePaths << e.attribute('href') end end end ```
guest

0

ruby

1filePaths << url.attribute('href')

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

ruby

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

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

投稿2020/01/14 07:37

編集2020/01/14 07:38
taichi730

総合スコア318

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kaori_oka

2020/01/15 03:07

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

0

newsUrl と fileCount の関係が、

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

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

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

ruby

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

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

投稿2020/01/14 05:27

taichi730

総合スコア318

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kaori_oka

2020/01/14 05: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 ```
taichi730

2020/01/14 05: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
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問