【ラクしてデータ収集】キーワード検索した結果ページのデータ取得する方法|PythonでWebスクレイピング

挨拶

こんにちは。キノコードです。

前回のレッスンでは、該当サイトのページの下の階層に1000ページあったとして、その1000ページに記載しているURLを取得する方法について説明しました。
大量のデータを収集したいときに便利ですよね。
3回目のこのレッスンでは、該当サイトで文字列検索をして、その検索結果の出てきたページのタイトル、リンク先URL、説明文を取得する方法について解説をします。
この方法を知っていれば、特定のワードで検索をして、その検索結果に出たページはなんなのか?検索順位はなんなのか?
そういったことを定期的にチェックしている方にはうってつけですよね。
またこの動画では、検索結果が複数あった場合にどうすればいいのか?ということも説明いたしました。
以前紹介したPythonのプログラムを自動実行するcronやタスクスケジューラを使えば、自動で検索結果や順位を取得してくれるので業務が楽になるはずです。
ぜひマスターしてみてください。
また、この動画3回目です。このレッスンでわからないことがあれば前回のレッスンに戻ってみてください。
もしわからないことがあれば、過去の2回の動画に戻ってみてください。
わからないことの解説になるかもしれません。
キノコードでは、この動画で話した内容の文字書き起こし、ソースコードもキノコードのウェブサイト、キノブログでもアップしています。
そちらもぜひチェックしてみてください。
それではレッスンスタートです。

【ラクしてデータ収集】キーワード検索した結果ページのデータ取得する方法|PythonでWebスクレイピング

ライブラリインポート

from bs4 import BeautifulSoup
import requests
import pandas as pd
import time

では、ライブラリをインポートしていきます。
bs4のパッケージの中にあるBeautifulSoupを読み込みます。
BeautifulSoupは、複雑なHTMLの構造を解析し、必要な部分を取り出すことができるライブラリです。
HTTP接続するために、requestsをインポートします。
requestsはPythonの標準のurllibモジュールよりもシンプルに実装でき、APIに関する機能も備わっています。
取得したデータをCSVに書き出すために、pandasをインポートします。
asを記述するとライブラリ名を好きな名前で使うことができます。
さらに、HTTPに接続した後、すぐに次の処理が実行されないようにするためtimeをインポートします。
実行します。
インポートが完了しました。

keyword = 'python'

検索バーに入力する文字を変数key_wordに代入します。
今回は"python"とします。

keyword

key_wordの中身を確認しましょう。
実行します。
データが格納されているようです。

Webスクレイピング練習用のキノコードのサイト、テスト版キノブログに行ってpythonという文字列を検索してみましょう。
現在はこのようなURLとなっています。
"kino-code.com"ではなく、"kino-code.work"です。
検索をすると、このURLが変化します。
Pythonという文字をキノブログの検索バーから検索をしてみましょう。
エンターを押します。
そうすると、"https://kino-code.work/?s=python" となりました。
つまり、このキノブログの場合、このURLを実行、つまりこのURLを呼び出せば、Pythonという文字列で検索した結果が返ってくることがわかります。

# 検索バーのurlに変数key_wordを追加する
url = 'https://kino-code.work/?s={}'.format(keyword)

では、先ほど代入したkey_wordの変数を使って、このURLを作成してみましょう。
文字列に変数を追加するには、format関数を使えばいいです。
丸括弧の中に、追加したい文字列を渡します。
先ほど変数key_wordに代入した文字列は「python」です。
urlは、このように ( https://kino-code.com/?s=python ) となるはずです。
実行します。

# 実行コード
url

urlの中身を確認しましょう。
実行します。
データが格納されているようです。

# リンクを呼び出すコード
r = requests.get(url)
time.sleep(3)

requestsのgetメソッドで、指定したurlを呼び出す記述をします。
引数には、先ほど設定したurlを渡します。
Webページが読み込まれる前に、次の処理に行かないように、time.sleep(3)で、3秒待機させます。

soup = BeautifulSoup(r.text,'html.parser')
page_na = soup.find(class_="pagination")
page_num = page_na.find_all(class_="page-numbers")

検索結果ページが1ページだけではなく、複数ある場合があります。
その場合は、次のページに遷移する必要があります。
その場合の解説をします。
次のページにいくには、ページ下部にあるページング機能を操作する必要があります。
現在が1ページ目だとすると、「2」をクリックすると次のページにいけますよね。

そこで、この部分のHTML構造がどのようになっているか調べみましょう。
調べるには、前回のレッスンで使ってGoogleChromeの「検証」を使います。
「2」の上で右クリックをして検証を押します。
そうすると、このページング機能は、HTMLのクラス「pagination」であることがわかります。
この中のクラス「page-numbers」の数だけページ数があります。

そこでBeautifulSoupを使って、検索結果ページの全体のデータを取得。
次に、findメソッドでクラスpaginationをデータ取得。
そして、最後に、クラスpage-numbersのデータ取得します。

page_num

page_numの中身を確認しましょう。
実行します。
データが格納されているようです。
またページ番号らしきものも確認できます。
もう少し調べてみましょう。

キノブログの検索結果ページをみてみましょう。
全部で2ページしかありません。

page_num[0]

このリストの1番目、添え字でいう0はどのようになっているでしょうか?
実行します。

page_num[0].text

これのテキストはどのようになっているでしょうか?
実行します。
1です。
つまりページ数です。

page_num[1].text

それでは、2ページ目をみてみましょう。

page_num[2].text

それでは、3ページ目をみてみましょう。
空になっています。
これが送りボタン(>)の部分です。
なので、最後が’’で文字が入っていません。
このようにHTMLには何かしらの規則性があります。
Webサイトにあわせて分析、調査をしてみてください。

pages = []
for i in page_num:
    print(i.text)
    pages.append(i.text)

それでは、ページ番号noリストを作成してみましょう。
リストpagesを作成し、forループでページ番号を追加していきましょう。
実行します。

pages

変数pagesをみてみましょう。
実行します。
1、2、最後は空になっています。
つまり、検索結果の全部のページ数を取得するには、最後から2番目の値を取ってくればいいですよね。
では、最後から2番目の値をとってくるにはどうしたらいいでしょうか?

x = [1,2,3,4,5,6,7]
x[-2]

リストの最後から2番目を取り出すには、リスト角かっこ−2と記述すればよいです。
忘れた方はPython超入門コースのリストをご覧ください。
このように−2とすれば、後ろから2番目の6が取得できるはずです。
実行します。
6が返ってきました。

pages[-2]

ただし、これはシングルクオテーションで括られているのでわかると思いますが、文字列です。
したがって、これを数字型に変換してあげましょう。

int(pages[-2])

数値型に変換するにはintで括ってあげればよいです。
実行します。
数値型になりました。

urls = []

last_page = int(pages[-2])

for i in range(1,last_page+1):
    url = 'https://kino-code.work/?s={}'.format(keyword) + '&paged={}'.format(i)
    urls.append(url)

では、この最後のページをlast_pageという変数に代入しましょう。

range丸括弧の第一引数に1、第二引数に変数last_pageを記述します。
しかし、このようにすると、1までしかカウントされません。
従って、last_pageプラス1とすることで,1〜2までカウントされるようになります。

また、順番は逆になりますが、その上に空のリストを用意し、変数urlsとします。
各ページのURLを作成し、リストに格納する記述をします。
これを、forループで1〜2まで回数分実行しましょう。
各ページのurlは「https//:kino-code.work/?=」 のイコールの後に検索ワード、そして「&paged=」のイコールの後にページ番号が入ります。
先ほど取得した、検索ワードとページ数は.format関数で追加します。
最後に、urlsドットappendの引数に変数urlを渡し、作成したリンクをリストurlsへ追加します。
実行します。
これでurlsに検索結果のページのURLが追加されたはずです。

# 実行コード
urls

urlsを確認してみましょう。
実行します。
1ページ目から2ページ目まで取得できています。

keyword = 'python'

url = 'https://kino-code.work/?s={}'.format(keyword)

r = requests.get(url)
time.sleep(3)

soup = BeautifulSoup(r.text,'html.parser')
page_na = soup.find(class_="pagination")
page_num = page_na.find_all(class_="page-numbers")

pages = []
for i in page_num:
    print(i.text)
    pages.append(i.text)

urls = []

last_page = int(pages[-2])

for i in range(1,last_page+1):
    url = 'https://kino-code.work/?s={}'.format(keyword) + '&paged={}'.format(i)
    urls.append(url)

urls

それでは今まで書いたソースコードをまとめてみましょう。
検索結果のページのURLが取得できていますね。

from IPython.display import Image
Image("WebSc03_03.png")

次にjavascriptで検索してみます。
ページング機能がありませんよね。
この場合、どうなるでしょうか?

keyword = 'javascript'

url = 'https://kino-code.work/?s={}'.format(keyword)

r = requests.get(url)
time.sleep(3)

soup = BeautifulSoup(r.text,'html.parser')
page_na = soup.find(class_="pagination")
page_num = page_na.find_all(class_="page-numbers")

pages = []
for i in page_num:
    print(i.text)
    pages.append(i.text)

urls = []

last_page = int(pages[-2])

for i in range(1,last_page+1):
    url = 'https://kino-code.work/?s={}'.format(keyword) + '&paged={}'.format(i)
    urls.append(url)

urls

ではkeywordをjavascriptにして検索してみましょう。
実行します。
エラーになります。
これはリストpagesが空のためです。
つまり、検索結果が複数ページないため、ページング機能が表示されず空になるのです。
このようなケースは実際の仕事でも想定されるケースです。
では、このようなどうすればいいでしょうか?

keyword = 'javascript'

url = 'https://kino-code.work/?s={}'.format(keyword)

r = requests.get(url)
time.sleep(3)

soup = BeautifulSoup(r.text,'html.parser')
page_na = soup.find(class_="pagination")
page_num = page_na.find_all(class_="page-numbers")

pages = []
for i in page_num:
    print(i.text)
    pages.append(i.text)

urls = []

if not pages:
    urls = ['https://kino-code.com/?s={}'.format(keyword)]
else:
    last_page = int(pages[-2])

    for i in range(1,last_page+1):
        url = 'https://kino-code.com/?s={}'.format(keyword) + '&paged={}'.format(i)
        urls.append(url)

urls

その場合はif文で条件によって処理をわけましょう。
まず、if文を使用し先ほど取得したリストpagesの中身が空かどうか判定します。
if not pagesでリストが空かどうかの判定ができます。
ちなみに、この書き方は「if pages == []:」と同じです。
そして、pagesが空の場合は、リストurlsイコール角括弧( https://kino-code.com/?s={} ) に.format関数を追加して引数に、変数keywordを格納します。
urlsがリストなので、代入するURLも全体を角括弧でくくり、リストにしましょう。

pagesに要素が入っている場合は、else以下の処理に移動します。
else以下の処理は先程説明した通りです。

links = []
titles = []
snippets = []
for i in range(len(urls)):

    r = requests.get(urls[i])
    time.sleep(3)
    soup = BeautifulSoup(r.text, 'html.parser')
    # find_allの引数に、HTMLタグが持つCSSのクラスで検索をかける。
    get_list_info = soup.find_all("a", class_="entry-card-wrap")

    for n in range(len(get_list_info)):
        # リンクを取得
        get_list_link = get_list_info[n].attrs['href']
        links.append(get_list_link)

        # タイトルを取得
        get_list_title = get_list_info[n].attrs['title']
        titles.append(get_list_title)

        # 説明文を取得
        get_list_snippet = get_list_info[n].find(class_="entry-card-snippet").text
        snippets.append(get_list_snippet)

次に、リストurlsのリンクを1ずつ読み込み、各ページ内の情報を取得してみましょう。
タイトル、リンク、説明文を格納するリストをそれぞれ、links、titles、snippetsとします。
for分で、len丸括弧の中にurlsを記述し、urlsの個数をカウントし、range関数で開始から終了までの数字を取得します。
ループ内で、変数rに、リストurlの要素であるWebページを読み込んでいきます。
Webページが読み込まれる前に。次の処理に行かないように、time.sleep(3)で3秒待機させます。
BeautifulSoup(r.text,'html.parser')で、読み込まれたWebページのHTMLを解析します。

次に、検索結果のページに表示されている、リスト部分のクラス「entry-card-wrap」タグ内のHTMLを解析していきます。
「entry-card-wrap」クラス内にある、aタグを全て取得します。
find_all丸括弧の第一引数に"a"、第二引数にclass_イコール"entry-card-wrap"と記述します。
クラス「entry-card-wrap」内に、複数のaタグがあるので、forループでさらに、1つずつ取り出していきます。
aタグの個数分をカウントさせて、aタグを1つづつ取り出します。
タグ内にある、href属性とtitle属性を取得します。
リストlinksとtitlesにappendで取得した値をそれぞれ入れていきます。

タイトルの下にある、動画の説明文を取得します。
説明文は,クラス「entry-card-wrap」内にあるdivタグのクラス「entry-card-snippet」にあるテキストを取得していきます。
find丸括弧の中にclass_イコール"entry-card-snippet"で、ドットtextを追加して、テキストのみを取得します。
リストsnippetsにappendで取得した値を入れていきます。

# リストを辞書型に変換する
result = {
    'title': titles,
    'link': links,
    'snippet': snippets
}

次に取得したデータを辞書型にまとめておきましょう。
波括弧の中にキーと値を設定することで辞書型を作成できます。
ここでは、変数resultに、辞書型を代入する記述をします。
タイトルのキーを'title'、リンクのキーを'links'、説明分のキーを'snippet'とします。
値は、先ほど取得したリストを設定します。

# データフレームを作成する
df = pd.DataFrame(result)

取得した辞書型のデータをデータフレームにしましょう。
イコール、pd.DataFrame丸括弧で、データフレームを用意します。
作成したデータフレームに、先ほど変数resultで取得したデータを格納します。

# 実行コード
df.head(10)

データフレームの中身を確認してみましょう。
実行します。
データが格納できているようです。

df.to_csv('result.csv', index=False, encoding='utf-8')

出力されたデータフレームをCSVへ書き出しましょう。
pandasのto_csvメソッドを使って、CSVへ書き出す記述をします。
第一引数には、任意のCSVファイル名を記述し、シングルコーテーションでくくります。
ここでは、result.csvとします。
indexイコールFalseとすることで、インデックスをつけない設定にします。
インデックスとは、データフレームにした際に自動的に出力される行番号のことです。
最後に、文字化けを避けるために、endodingで文字コードを指定します。
ここではutf-8とします。
実行します。
list_info.csという、CSVファイルを作成できました。

こちらを、Excelまたはスプレッドシートにも書き出すことができます。。
Excelに出力したい場合は、この動画をご覧ください。

Pandas入門講座|06.CSV・Excelファイルの読み込み・書き出し、データベースとの接続方法【PythonのライブラリPandas】

レッスンで使ったファイルはこちら

■保存方法

Mac:右クリック⇒「リンク先を別名で保存」

Windows:右クリック⇒「名前を付けてリンク先を保存」

Jupyter Labのファイルはこちら