Webスクレイピングをpythonであれやこれやとやってみました。お勉強のため、なにかオリジナルなプクレイピングをやりたいなあと思い、いつもwpのビュー数を眺めて一喜一憂しているのですが、そうだ!これを全記事さらって出力してみたらどうだろうか!と。
wpのテーマ「COCOON」を利用させていただいているのですが、そのアクセス集計機能をオンにしていると出るみたいです。
https://wp-cocoon.com/access-aggregate/
https://sec.ayaito.net/cocoon/setting/17348/
で、pythonが(というかJupiter Notebookが)ちょっと一時的に使いない状況なので、今回はエクセルVBAでseleniumを利用してやってみることにしました。
出力結果をエクセルに出せば見やすいし。
なお、だらだらと試行錯誤しながら徐々に作成していく過程を綴っているので、いいから最終コードだけ知りたいというかたは、すべてすっとばして 4(6) を見てください。
1.エクセルでSeleniumを使えるようにする準備
エクセルVBAでseleniumを利用するにはseleniumのインストールと、Google Chrome Driverのインストール、.NET フレームワークのインストール、およびvbaでの参照設定が必要です。
(1)Selenium Basic と Google Chrome Driver と .NET フレームワークのインストール
すみません。こちらは割愛しますので、エクセル VBA seleniumなどで検索してseleniumのダウンロードなどを行ってみてください。
私はpythonでseleniumを利用していたため、すでに上記のインストールはやっていたため、今回は新たにインストールは行いませんでした。
こちらのサイト等、わかりやすかったです。
https://akira55.com/seleniumbasic_googlechrome/
https://powervbadesktop.com/web0/
実際はさて、やってみよう~としたら、エラーが出て、どうもGoogle Chromeのバージョンが上がっていたようでChrome Driverを再度インストールしなおしました。(上書きする)
アセアセしながらこちらの記事をみて行いました。
https://powervbadesktop.com/web4/
さて、いよいよVBAでSelenium。順を追って、チマチマと矯めつ眇めつ確認しながら行ったのでその通りに記載していきます。そんなんいいから最終的にどうなの?と手っ取り早く知りたい方は一番最後のあたりに最終的なコードを記載していますのでそちらをどうぞ。
2.Google Chrome の起動
(1)参照設定
VBAのエディターを開きます。
Selenium Type Library を探して、チェックを付けて「OK」を押します。
Seleniumを使うにはこれが必要です。
(2)Sleepを使えるようにする呪文を冒頭に書く
WEBページを読み込みしている最中に次の処理に進んでしまわないように、待ち時間を入れることがあります。VBAには「○秒なにもしないでまって」というコードがないそうで、「○秒なにもしないでまって」=Sleepを使うためにwindowAPIを利用するために必要な呪文です。
APIとは。。。ザックリいうとどこぞのだれかが親切に用意してくれたプログラム・・・。を、所定の申込を記入すると使えるようになる、というところでしょうか。(だうぶちがう?イメージなので突っ込んはナシで)
ExcelVBAでSeleniumを使うには、という記事でコードをコピペしたときに上部に入っていたので一緒にコピペしました。
最初、エラーになって利用できなかったのですが、エクセルに64ビット版と32ビット版とあるらしく、コピペしてきたものが32ビット版のもので、64ビット版の書き方に直したらエラーが出なくなりました。
'64ビット版 Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal ms As LongPtr)
'32ビット版 Private Declare Sub Sleep Lib "kernel32" (ByVal ms As Long)
モジュールの最上部に書きます。
こちらのサイトを参考にさせていただきました。
https://3rdcom.biz/b/%E3%80%90vba%E3%80%91sleep%E3%82%92%E4%BD%BF%E3%81%88%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B/
https://happy-tenshoku.com/__trashed/
(3)サイトを開く
さて、まず手始めにヤホーでも開いてみましょう。
Private Declare PtrSafe Sub Sleep Lib "KERNEL32" (ByVal dwMilliseconds As Long)
Sub test()
Dim Driver As New Selenium.WebDriver
Driver.Start "chrome" 'クロームブラウザを立ち上げます。
Driver.Get "https://www.yahoo.co.jp/" 'Yahoo!サイトを開きます。
MsgBox "Yahoo Japanを表示しました"
End Sub
まずDriverを宣言します。
Dim Driver As New Selenium.WebDriver
クロームブラウザを立ちあげます。
Driver.Start “chrome”
サイトを開きます。Driver.Get の後に表示したいURLを記載します。
Driver.Get “https://www.yahoo.co.jp/”
開けましたね!
メッセージボックスの「はい」を押すと同時に、クロームも閉じてしまいます。モジュールが終了すると同時にクロームも閉じてしまうのですね。
3.WPのビュー数出力マクロを作成する(テーマCocoon)
(1)WPにログイン
さて、いよいよ本題。WPの自身のサイトにログインしてサイトを開く、という部分をやります。ログインした状態でないと、ビュー数は表示されないみたいです。
https://sec.ayaito.net/cocoon/setting/17348/
手順としては以下のようになりましょう。
①サイトを開く
②IDを入力
③パスワードを入力
④ログインボタンクリック
⑤ちょっと待つ
Sub test()
'Chromeを立ち上げる
Dim Driver As New Selenium.WebDriver
Driver.Start "Chrome"
'自身のサイトのURL。おしりに/wp-adimin/つける
Driver.Get "https://xxxx.com/wp-admin/"
Sleep 3000
'ID入力
Driver.FindElementByXPath("//input[@id='user_login']").SendKeys "xxxxxxxxx"
'パスワード入力
Driver.FindElementByXPath("//input[@id='user_pass']").SendKeys "password"
'ログインボタンクック
Driver.FindElementByXPath("//input[@id='wp-submit']").Click
Sleep 3000
End Sub
ステップ実行しながらやっていきます
まず、Chromeを立ち上げるところは同じです。
Dim Driver As New Selenium.WebDriver
Driver.Start “Chrome”
自分のブログのサイトを開きます。ブログのURLのおしりにwp-adminを付けるのを忘れずに。
Driver.Get “https://xxxx.com/wp-admin/”
こうなりました。
IDとパスワードを入力したいです。
手順としてIDを入力する欄をぐっととらまえて、Send Keys でIDを入力します。
ID欄のところで右クリック→検証をクリックします。
ハイライトされた部分がユーザー名インプットの部分です。この要素をとらまえて、そこにIDの文字列を入力します。
要素をつかむには、SeleniumnではFindElementBy〇〇を使います。
いろんな種類があります。
FindElementById
FindElementByCss
FindElementByTag
FindElementByName
FindElementByClass
FindElementByXPath
FindElementByLinkText
FindElementByPartialLinkText
以下のサイトを参考とさせていただきました。
https://nkmrdai.com/vba-selenium-reference/
以前自分でやったSeleniumの記事も読み返しつつやりました(すっかり忘れている。。。)。Pythonだけど、基本的な考え方は同じなので。
https://mwkexcelfriend.com/python-selenium2/
https://mwkexcelfriend.com/python-selenium/
CSSでほぼすべてできるでしょ!という方もいればXPath一択でしょ!という方もいます。
なんとなくXpathが響きがかっこいいので、極力Xpathでやるようにしようかと思います。
XPathとは、HTML(XML)の指定したい場所を取得するための簡易言語、とのことです。タグの構造をたどって、目的の場所にたどりつく道筋をしめすもの(説明というか、あくまで私の中のイメージ)
たとえば
<html>
<head>
<title>タイトルだよ</titile>
</head>
</html>
のタイトルを取得したいとき、Xpathで記述すると
/html/head/title
で、
<title>タイトルだよ</titile>
が取得できます。
省略して書くこともできて
//title
属性を指定する場合は[@属性=属性値]で指定します。例えば以下の例だと
<a id=”test1″ class=”test2″ href=”http://aiueo.com”>あいうえお</p>
idで指定したい場合は
//a[@id=”test1″]
classで指定したい場合は
//a[@class=”test2″]
とするそうです。
そのほか奥深く子要素を取得とか孫要素を取得とか〇〇を含むとかいろいろあります。
Xpathについてはこちらを参考とさせていただきました。
https://hirachin.com/post-9067/
https://ai-inter1.com/xpath/
さて、話題を戻して、ユーザー名入力の要素をつかみたい。htmlから読み取るのは大変なので、手っ取り早い方法。検証で表示させた開発ツールのユーザー名の部分を右クリック→Copy→Copy XPath クリップボードにコピーされた情報をとりあえずメモ帳かなんかに貼り付けして、眺めます。
Xpathを使わずCSSをつかう場合はCopy Selector をクリックすると、CSSセレクターが取得できます。
そのままつかってもよいのですが、なんとなくinputタグだよと明示して以下のように書きました。
Driver.FindElementByXPath(“//input[@id=’user_login’]”).SendKeys “ここにユーザー名”
inputタグでidがuser-loginなのですね。idってページに一つだけって決まってるから迷いがなくていいですね。
SendKeysで入力する文字列を入れることができます。SendoKeysの後に””でかこってユーザー名を入力します。ユーザー名は変数に入れておいて、その変数をSendKeysの後ろにおいてもいいですね。
同様にパスワードの部分も開発ツールで確認して、要素を指定し、SendKeysでパスワードを入力します。(パスワードは自身のパスワード)
Driver.FindElementByXPath(“//input[@id=’user_pass’]”).SendKeys “password”
次はログインボタンをクリックです。
クリックするには要素を指定して.Clickとするとクリックできます。
ボタンかと思ったらinputタグなのですね。
Driver.FindElementByXPath(“//input[@id=’wp-submit’]”).Click
このあとにSleepで3000入れています。3000ミリ秒、ということなので3秒?
これでいったん実行してみましょう。プロシージャが終了するとブラウザも閉じてしまうのでログインボタンクリックの後にStop入れて実行したほうが分かりやすいです。
できましたでしょうか?!
(2)サイトを表示する
さて、これで管理画面が表示されるので、サイトを表示してビュー数が見れる画面を表示したいと思います。
いつも左上の家のマークのところをクリックしてブログのホーム画面に移っている。開発ツールで見てみるとhref属性にURLがあるので、この要素を取得しhref属性のURLを取得して、Get URL で画面遷移するという作戦で行く。
Dim href As String, elem As WebElement
Set elem = Driver.FindElementByXPath("//li[@id='wp-admin-bar-site-name']/a")
href = elem.Attribute("href")
Driver.Get href
Driver.Wait 1000
ログインの続きに上記のコードをつなげます。
文字列型の変数hrefを用意しここにURLを収めたいと思う。今思うと変数名をURLにした方がよかったと思う。それはさておき
つかんだ要素を用意した変数elemに代入し、elem.Attribute(“href”)でその要素のhref属性の値を取得して、変数hrefに入れる。
属性を取得したい場合、要素.Attribute(“属性名”)とするらしい。
変数hrefにURLが入っているはずなので、Dirver.Get でブログを表示する。
ロード待ちにDriver.waitというのもあるそうなので、Sleepの代わりに使ってみました。
さて、これでブログは表示されるはず。
(3)ビュー数を取得する
さて、ここからが正念場です。
カード状に並んでいるそれぞれの記事からタイトルとビュー数を取ってきたいですね。
開発者用ツールを見てみると、aタグが並んでいて、これが一つ一つのカード状のあれっぽいですね。
一番上のaタグの中を見てみましょう。わーっといろいろあるけど、today-pvなどがビュー数ですね。これが欲しい!赤枠で囲った<div>タグを取得すればよさそうですね。<h2>もその下に入っているし。
その下も同じ構造になっているようなので、classがentry-card-content… のdivタグを取得すればいいようです。
Copy XPath でやってみると //*[@id=”post-2680″]/div となった。
しかしpost-2680は一つのカードにしか当てはまらない。次のカードでは違う番号になってしまう。
なので、カードすべてに共通する要素で指定したいので、「classがentry-card-contentを含むdivタグ」という指定方法にしたいと思います。
2022.12.10.上記の書き方だとほかの部分も該当してしまうようになったので、コードを以下のように変更しました。
Set elems = Driver.FindElementsByXPath(“//div[contains(@class,’entry-card-content card-content e-card-content’)]”)
含むではなくイコールでも行けます。
Set elems = Driver.FindElementsByXPath(“//div[@class=’entry-card-content card-content e-card-content’]”)
Dim elems As WebElement
Set elems = Driver.FindElementsByXPath("//div[contains(@class,'entry-card-content card-content e-card-content')]")
Debug.Print elems.FindElementByTag("h2").Text
「classがentry-card-contentを含むdivタグ」
属性が〇〇を含む、と指定したいときは
[contains(@class,’〇〇’)]
のように指定します。便利ですね。
クラス名が3つあったのでそれを全部指定してもよかったのですが、長かったので。。。
ちなみに
Set elems = Driver.FindElementByClass(“entry-card-content”)
としても大丈夫でした。
変数elemsを用意しておいて、目的のdivタグの中身を入れ込みました。elemsと複数形にしているのはあとで対象のdivタグをまとめて取ろうとしているからです。。。今はちょっと試しに一つだけとってきています。FindElementsとすると、合致する対象をすべて取得します。今はFindElementなので、合致する対象の一番上のタグを持ってきています。
elemsのなかをFindElementして、h2タグを取得し、.Textとしてそのテキストを取得します。
ためしにDebug.Printでイミディエイトウインドウに出力します。
タイトルがでた!感動しますね(*‘∀‘)
つづけてビュー数を取得します。
本日は today-pv-count、週は week-pv-count、月はmonth-pv-count、全体はall-pv-count、とクラスで指定できそうですね。1とか14とかを表示したいので.Textで取得したspanタグの内容を取得します。
Debug.Print "本日:" & elems.FindElementByClass("today-pv-count").Text
Debug.Print "今週:" & elems.FindElementByClass("week-pv-count").Text
Debug.Print "今月:" & elems.FindElementByClass("month-pv-count").Text
Debug.Print "全体:" & elems.FindElementByClass("all-pv-count").Text
でました!素敵!ビュー数は素敵じゃないけど。。。
(4)ページに表示されているすべての記事カードのビュー数を表示する
よし、いけそうだ!ということで,FindElementsですべてのカードを取得し、for eachでひとつひとつビュー数を表示していきます。
Dim elems As WebElements
Set elems = Driver.FindElementsByClass("entry-card-content")
Dim e As WebElement
For Each e In elems
Debug.Print e.FindElementByTag("h2").Text
Debug.Print e.FindElementByClass("today-pv-count").Text
Debug.Print e.FindElementByClass("week-pv-count").Text
Debug.Print e.FindElementByClass("month-pv-count").Text
Debug.Print e.FindElementByClass("all-pv-count").Text
Next
先ほどとの違いは、変数elemsの型をWebElementからWebElementsと複数形にしたところ。
Driver.FindElementsByClass(“entry-card-content”) とこちらも複数取得するメソッドにしたところ。
さて、複数の要素がelemsに入っているので For Each でひとつずつ取り出して処理します。
変数eを用意してelemsの中身をeに入れていきます。
h2のテキスト、各ビュー数のテキストを取得する部分は、先ほどelemsとしていたところをeに変更します。
やってみましょう。イミディエイトウインドウに出力する形にしているのでだーーーっとでてくるはずです。
でました!もっと長いんですけどね。本日とか入れるのを忘れましたね。
(5)次へボタンで次のページへ
さて、ここまでできました。でも、まだ、次のページ、次のページと最後のページまで行きたいです。
クラスpagination-nextのdivタグの下のaタグに次のページのurlが入っているようですね。
このURLを取得してGet url で次のページへ遷移すればよさそうです。
先ほどのコードの続きに試しに書いてみて実行してみましょう。ブラウザが閉じないようにStopも入れておきましょう。
DIm href_next As String
href_next = Driver.FindElementByXPath("//div[@class='pagination-next']/a").Attribute("href")
Driver.Get href_next
Driver.Wait 3000
Stop
変数href_next を用意しておいて、ここにhref属性を入れましょう。
クラスがpagination-nextのdivタグの下のaタグから.Attribute(“href”)でhref属性を取得します。
Driver.Get でhref_nextのURLを開いて、3秒待ちます。
よっし。行った(*‘∀‘)
(6)最後のページまで繰り返し処理 Excelへ出力
さて、いよいよ。最終段階。
最終の手順
①サイトを開く
②IDを入力
③パスワードを入力
④ログインボタンクリック
⑤ちょっと待つ
⑥各記事のカードのエレメント取得
⑦ ForEachで⑥の要素をひとつずつ処理
・タイトル出力
・ビュー数出力
⑧次へボタンで次のページへ遷移
⑥⑦の処理
⑨次へボタンがない→処理終了
補記事項としては
⑧で「次へボタン」があるかないかを調査しつつ行います。
また、最初にエクセルシートをきれいにしたり、項目名を出力しておいたりの処理を付け加えます。
Sub printWpView()
'Excel側の用意
'大きめの配列を用意しておく(ここに一旦取り込みしてからエクセルに出力する)
Dim arrData(1000, 5) As Variant
'wpというシート名のシートに出力(適宜変更してください)
Dim ws As Worksheet
Set ws = Worksheets("wp")
'カラム名
Const ColumnName As String = "タイトル|日付|本日|今週|今月|全体"
Dim arrColumnName() As String
arrColumnName = Split(ColumnName, "|")
'出力するシートをクリアし、項目名を1行目に出力する
Dim i As Long
With ws
.Cells.Clear
For i = LBound(arrColumnName) To UBound(arrColumnName)
.Cells(1, i + 1).Value = arrColumnName(i)
Next i
End With
'webスクレイピング
'Chromeを立ち上げてGoogleを立ち上げる
Dim Driver As New Selenium.WebDriver
Driver.Start "Chrome"
Driver.Get "https://xxxxxx.com/wp-admin/"
Driver.Wait 3000
'ログイン(userid,passwordは自身のものに変更)
Driver.FindElementByXPath("//input[@id='user_login']").SendKeys "userid"
Driver.FindElementByXPath("//input[@id='user_pass']").SendKeys "password"
Driver.FindElementByXPath("//input[@id='wp-submit']").Click
Sleep 3000
'サイトを表示
Dim href As String, elem As WebElement
Set elem = Driver.FindElementByXPath("//li[@id='wp-admin-bar-site-name']/a")
href = elem.Attribute("href")
Driver.Get href
Driver.Wait 1000
'ループ
Dim elems As WebElements, e As WebElement, href_next As String
Dim myBy As New By
Dim idx As Long: idx = 0
Do
Set elems = Driver.FindElementsByXPath("//div[contains(@class,'entry-card-content card-content e-card-content')]")
For Each e In elems
Sleep 1000
arrData(idx, 0) = e.FindElementByTag("h2").Text
arrData(idx, 1) = e.FindElementByClass("post-date").Text
arrData(idx, 2) = e.FindElementByClass("today-pv-count").Text
arrData(idx, 3) = e.FindElementByClass("week-pv-count").Text
arrData(idx, 4) = e.FindElementByClass("month-pv-count").Text
arrData(idx, 5) = e.FindElementByClass("all-pv-count").Text
idx = idx + 1
Next
'次へボタン
If Driver.IsElementPresent(myBy.Class("pagination-next")) Then
href_next = Driver.FindElementByXPath("//div[@class='pagination-next']/a").Attribute("href")
Driver.Get href_next
Driver.Wait 3000
Else
Exit Do
End If
Loop
Sleep 3000
With ws
.Range("A2:F1002").Value = arrData
End With
End Sub
配列arrDataを用意して、ここにいったん情報を蓄積し、最後にエクセルシートに一気に出力する作りとしています。一つ一つワークシートに出力するより処理速度が圧倒的に早いので。
配列arrDataは1次元目の要素を自分の記事数より大きめに用意しておきます。2次元目の要素はカラム数が6なので、5とします(0始まりのなので6つ入れるには5でOK)
取得する項目は「記事タイトル、投稿日付(あった方がいいかなと付け足しました)、本日のビュー数、今週のビュー数、今月のビュー数、全体のビュー数」の6つです。
最後にエクセルシートに配列の中身を出力するときは、配列と同じ大きさのセル範囲を指定します。配列は0始まりなので、横は6つ分、A~F列。縦は1行目は項目名なので2行目から配列の大きさを1000としていたら二つ足して1002とします。
現在の記事数は150ちょいなので、1000はかなり大きめだけど大きい分にはいいか、と。
「次のページ」ボタンの要素があるかどうかを調べるために IsElementPresent を使っています。
Driver.IsElementPresent(myBy.Class(“pagination-next”))
検査する要素を入れて、あったらTrue,なければFalseが返るそうです。
合わせてByというのを使います。
いまいち説明ができないので詳しくはこちらのサイトをご参照ください。はっきりいって、こちらのサイトからコードをコピペさせていただき、理解せずに使っています。(ありがとうございます)
https://powervbadesktop.com/web3/#toc13
で、その要素があったら、href属性を取得し次のページへ行きLoop処理を続ける。
なければLoopをExit Doで抜けて処理を終了する、という流れにしています。
出力結果のイメージはこんな感じです。見なければよかった。。。というさみしいビュー数。。。
ビュー数が多い順に並べ替えなんかしてみてもいいですね。
GooGleアナリティクスやサーチコンソールも使っているのですが、機能が立派すぎてよくわからなくて。。。がっとビュー数が見れるといいなと思っていたので、スクレイピングの練習もできて一石二鳥!
試行錯誤とともにだらだらと書いてしまいました。Seleniumもスクレイピングも初心者ですのでこれがよいコードであるとは限りません。もっとスマートな良い方法があるかもしれません。
お読みいただきありがとうございました!
コメント