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

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

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

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

1回答

3009閲覧

非同期処理の挙動で混乱しています

peimish

総合スコア17

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2019/06/09 05:40

編集2019/06/10 08:23

前提・実現したいこと

画面を遷移させたあとに、遷移先のDOMを取得しようとしています。
取得したいページでスクレイピングの処理をする分にはうまく取得できます。
ただ、2ワード以上検索して情報を取得するために、ページ遷移する処理とスクレイピング処理を単純にfor文で実行しようとすると、検索したいワード群の最後のワードのページをひたすらスクレイピングしてしまいます。
そのため、ページ遷移が完了するまで待機するように、イベントハンドラを用いてhtmlの読み込みが完了したらresolveを返すようにしてみたのですが、そうするとどうもスクレイピングの処理まで到達しなくなってしまいました。
どのようにすれば意図通り、画面遷移が完了したあとにそのページでスクレイピング処理が実行されるようにできるでしょうか?
非同期処理の扱いに慣れておらず、非常に困っております。
どうかお力をお貸しいただければ幸いです。
よろしくお願いいたします。

発生している問題

ページを遷移させる関数の最初のlogが出るのみで、その先の処理が実行されません。

該当のソースコード

javascript

1// ==UserScript== 2// @name sample 3// @namespace sample 4// @author peimish 5// @version 1.0 6// @match https://www.webstagram.one/search* 7// ==/UserScript== 8 9(async () => { 10 const movePage = word => { 11 return new Promise(resolve => { 12 //検索欄にワードを入力 13 let searchBox = document.getElementById('searchText'); 14 searchBox.value = word; 15 //検索ボタンクリック 16 console.log('click: ' + word); 17 document.getElementById('searchButton').click(); 18 //----------ここで処理が終わってしまいます----------- 19 //htmlが読み込まれるまで待機 20 window.addEventListener('DOMContentLoaded', word => { 21 console.log('遷移完了: ' + word) 22 //指摘いただいた箇所の修正 23 //resolve; 24 resolve(); 25 }); 26 }); 27 } 28 29 const collectHashtags = () => { 30 let result = []; 31 if (location.href === 'https://www.webstagram.one/search/instagram') { 32 return result; 33 } else { 34 console.log('スクレイピング開始'); 35 //出力されたハッシュタグのhtmlCollectionを取得 36 let targets = document.getElementsByClassName('searchTags')[0].getElementsByTagName('li'); 37 //配列に変換 38 targets = Array.from(targets); 39 targets.forEach(function(target,index){ 40 if (typeof(target.getElementsByTagName('strong')[0]) === 'undefined') { 41 return; 42 } 43 else { 44 let tag = target.getElementsByTagName('strong')[0].innerText; 45 //先頭に#のある文字列だけ保存 46 const regExp = /^#/; 47 if(regExp.test(tag)){ 48 let posts = target.getElementsByClassName('tagMediaCout')[0].innerText; 49 const hashtag = {}; 50 hashtag.hashtag = tag; 51 hashtag.posts = posts; 52 result.push(hashtag); 53 console.log('hashtag:' + hashtag.hashtag + ' 投稿件数:' + hashtag.posts); 54 }; 55 } 56 }); 57 console.log('スクレイピング完了'); 58 return result; 59 } 60 } 61 //-----------------処理開始----------------- 62 alert('start'); 63 let hashtags = []; 64 const searchWords = ['ruby', 'java']; 65 for (let word of searchWords) { 66 await movePage(word); 67 const result = collectHashtags(); 68 hashtags = hashtags.concat(result); 69 }; 70 console.log('finish'); 71 72 console.log('取得結果'); 73 console.log('------------------------------------------------------'); 74 hashtags.forEach(value => console.log('hashtag:' + value.hashtag + ' 投稿件数:' + value.posts)); 75 console.log('------------------------------------------------------'); 76 77 alert('done'); 78})();

補足情報(FW/ツールのバージョンなど)

実行環境: tampermonkey

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

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

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

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

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

karamarimo

2019/06/09 05:55

resolve() ではなく resolve となっているのは typo ですか?
peimish

2019/06/09 07:28

ご指摘ありがとうございます! ご指摘の通り修正したところ、ひとまずスクレイピングの処理も実行されました。 ですが、意図した通りの挙動にはならず、最後のワードのページのみをスクレイピングしてしまいます。 ソースの例ですと、下の方にある変数searchWordsに['ruby', 'java']と入れているのですが、 rubyの検索結果は取得せず、javaのページの情報を2回取得してしまっています。 どこが悪いのかもご教示いただけないでしょうか? よろしくお願いいたします。
guest

回答1

0

ベストアンサー

tampermonkeyについては知りませんが、ページが読み込まれるまで待つにはDOMContentLoadedのイベントリスナー内でresolveする必要があります。

js

1 return new Promise(resolve => { 2 3 // ... 4 5 window.addEventListener('DOMContentLoaded', word => { 6 console.log('遷移完了: ' + word) 7 resolve(); 8 }); 9 });

追記

スクレイピングしようとしているサイトがSPAではないので、ページ遷移するとtampermonkeyのuserscriptも最初から実行されることになります。解決策としては以下のようなものが考えられます。

どうしてもブラウザー上のスクリプトからやりたいのであれば、ブラウザ自体に情報を保存できるlocalStorage(or IndexedDB)を使用する方法が考えられます。例えば、searchWordsの何番目まで処理し終わったのかをlocalStorageに保存し、userscriptの最初でlocalStorageをチェックしてどのwordを処理するか決定できます。

スクレイピングはブラウザー外のプログラムで行うのが一般的であり、特に複数ページに渡ってスクレイピングしたい場合はこちらのほうが簡単だと思います。プログラムからHTTPリクエストしてレスポンスをパースし情報を取り出すタイプと、ブラウザーをシミュレートあるいは本物のブラウザーを外から操作してページを読み込み、情報を取り出すタイプの2つが主にあります。

前者はJavaScriptを実行できないため、ページ内の欲しい部分がJavaScriptで動的に生成されている場合は使えません。しかし今回スクレイピングしようとしているページのレスポンスのHTMLを見ると欲しい情報がすでに含まれているので、問題ありません。だいだいどのプログラミング言語でもHTTPリクエスト、HTMLパーサーライブラリを使えば簡単にできます。

後者はNode.jsではPuppeteer、PhantomJS、PythonであればSeleniumなどのライブラリがあります。

投稿2019/06/09 08:09

編集2019/06/10 09:35
karamarimo

総合スコア2551

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

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

peimish

2019/06/09 10:41

回答いただきありがとうございます! コールバックの外に出ていることに気が付きませんでした。 ですが修正して動かしたみたところ、最初のワードのページ遷移で止まり、 '遷移完了'のログも出ず終了してしまいます... 引き続き何かミスや改善すべきところを発見しましたら、お教えいただけると幸いです!
karamarimo

2019/06/09 11:05

そもそもページ遷移後に DOMContentLoaded イベントが発生するのでしょうか? console.log('遷移完了: ' + word) の部分は出力されていますか?
peimish

2019/06/09 13:53

はっ!そういうことなのでしょうか! console.log('遷移完了: ' + word) の部分は出力されなかったです。 ではloadイベントでしょうか...? 私の認識では画面遷移後に、htmlの読み込みが完了するとDOMContentLoadedイベントが発生し、その後Javascriptなどを含めた全ての読み込みが完了するとloadイベントが発生すると思っているのですが...
peimish

2019/06/09 14:07

loadもダメで、とりあえずclickイベントなら反応するだろうと思い変えてみたのですが、 console.log('遷移完了: ' + word) の部分は出力されませんでした... もしかしたら、リスナーの書き方・書き場所が間違えているのかもしれません...
karamarimo

2019/06/09 15:46

どのサイトで実行しようとしているか知らないのであくまで予想ですが、SPAのサイトだと思われます。 SPAでは擬似的にページ遷移しているように見せるため、DOMContentLoadedイベントは最初の一回以外発生しません。 逆にSPAでないならば、ページ遷移のたびにtampermonkeyのjsも最初から実行されなおすことになると思うので質問文のコードのような方法では実現できないと思います。 ではSPAではどのように遷移後のページが完成したタイミングを検出するのかですが、MutationObserverなどでDOM Elementを監視するか、適当な時間待つか程度の方法しか思いつきません。 puppeteerなどのChrome自体を操作するツールを使えば多少やりやすいかもしれません。
peimish

2019/06/10 04:06

このサイトなのですが... https://www.webstagram.one/ SPAについて詳しくないのですが、URLが変わるため普通に遷移しているのだと思っていたのですが、 少し調べてみると、非同期でURLを書き換えることもできるのですね... tampermonkey上では/search配下のページすべてに対してスクリプトを実行するように設定しています。 →この設定も要するにページ遷移する度に最初から実行されるということになるのですよね...? (何度も質問にお答えいただき本当にありがとうございます!大変助かっておりますm_m)
karamarimo

2019/06/10 06:33

> 非同期でURLを書き換えることもできるのですね. 非同期ではありません。 そのサイトを見てみたところSPAではありませんでした。質問文のコードを実行したところ無限に遷移が繰り返されるので peimish さんの状況とは異なる気がするのですが、原因は分かりますか?tampermonkey についてよく知らないのですが、ヘッダーの部分(==UserScript==で囲まれた所)が関係しているかもしれないので質問文に載せてもらえますか?
peimish

2019/06/10 08:20

わざわざtampermonkeyを入れて試してくださったんですね...! ありがとうございます!! 以下が使用中のUserScriptです。 でも処理が失敗?するとまた再度最初からスクリプトは実行されます。 処理が全て正常に終了すれば最後にtopページにでも飛ばして無限ループからは抜けられるのですが... もしかしたら以下のUserScriptでもkaramarimoさんと同様の状態になるかもしれませんが、よろしくお願いいたしますm_m // ==UserScript== // @name getHashtagInfo // @namespace sample.com // @author peimish // @version 1.0 // @match https://www.webstagram.one/search* // ==/UserScript==
karamarimo

2019/06/10 09:42

自分でリンクをクリックして無限ループから抜けるということでしょうか? どちらにせよ今の方法ではできそうにないので、別の方法について追記しました。
peimish

2019/06/10 11:32

詳しく追記いただきありがとうございます! なるほど、ローカルストレージを使えば良かったのですね!! 今回は極端に環境構築のハードルを低くする必要がありこのようなやり方にこだわっておりました。 あっという間にに実装できるとたかをくくっていたのですが、謎の挙動に泣かされました... 長いことやり取りにお付き合い下さり、本当にありがとうございました!! (もしローカルストレージの実装で泣かされたらまたご質問してもよろしいでしょうか...?笑 いや、最大限のアドバイスを頂いたので、最後は自力でなんとかしてみせます。笑) この度は本当に助かりました!ありがとうございましたm_m
karamarimo

2019/06/10 12:16

> もしローカルストレージの実装で泣かされたらまたご質問してもよろしいでしょうか...? いいですが、別の質問として投稿したほうがいいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問