Pythonで検索順位を自動保存(SEOのデータ分析に使える)|PythonでWebスクレイピング第05回

こんにちはキノコードです。
今回はWebスクレイピングの5回目のレッスンです。
3回目のレッスンで解説した、検索結果ページの情報を取得する方法を覚えているでしょうか。
このときは、"python"というように、あらかじめ1つの検索キーワードを設定し、その検索結果ページの情報を取得しました。
もちろんこの方法を学ぶだけでも、情報収集を随分効率化することができます。

ただ、同じジャンルの様々なキーワードで、検索結果ページを調査したいケースも多くあると思います。
例えばプログラミング言語であれば、"python"、"javascript"、"go"で検索した時の検索結果ページの情報を一気に取得できたら便利ですよね?
このWebスクレイピングの5回目のレッスンでは、こうした複数のキーワードの検索結果ページの情報を一気に取得する方法を学習します。

このレッスンには、1回目~4回目のレッスンで学んだ内容も多く出てきます。
もしこのレッスンで分からないところがあれば、1回目から4回目までのレッスンを見直してみてください。

キノコードではこの動画のほかに、たくさんのプログラミングのレッスンを配信しています。
チャンネル登録がまだの方は、チャンネルがどこにあるか分からなくならないように、是非チャンネル登録をお願いします。
それではレッスンスタートです。

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

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

keyword_list = ['python', 'ruby', 'javascript', 'c', 'java', 'go', 'kotlin']

続いてkeyword_listという変数に、検索するキーワードを代入します。
今回は’python’,'ruby','javascript','c','java','go','kotlin'の7つの言語を検索します。
各言語をカンマ区切りのリストとして変数に代入しましょう。
実行します。
keyword_listに検索するキーワードのリストが格納されました。

dict = {}

for k in range(len(keyword_list)):

    urls = []

    url = 'https://kino-code.work/?s={}'.format(keyword_list[k])

    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:
        pages.append(i.text)

    if not pages:
        url = 'https://kino-code.com/?s={}'.format(keyword_list[k])
        urls.append(url)
        dict[keyword_list[k]] = urls

    else:
        last_page = int(pages[-2])

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

dict
{'python': ['https://kino-code.work/?s=python&paged=1',
  'https://kino-code.work/?s=python&paged=2',
  'https://kino-code.work/?s=python&paged=3'],
 'ruby': ['https://kino-code.com/?s=ruby'],
 'javascript': ['https://kino-code.com/?s=javascript'],
 'c': ['https://kino-code.work/?s=c&paged=1',
  'https://kino-code.work/?s=c&paged=2',
  'https://kino-code.work/?s=c&paged=3',
  'https://kino-code.work/?s=c&paged=4',
  'https://kino-code.work/?s=c&paged=5'],
 'java': ['https://kino-code.work/?s=java&paged=1',
  'https://kino-code.work/?s=java&paged=2'],
 'go': ['https://kino-code.work/?s=go&paged=1',
  'https://kino-code.work/?s=go&paged=2',
  'https://kino-code.work/?s=go&paged=3'],
 'kotlin': ['https://kino-code.com/?s=kotlin']}

では、検索キーワードで検索し、検索結果のURLを取得するコードを書きましょう。
取得したURLを格納するために、dictという辞書型の変数を作成します。

こちらは以前のレッスンで書いたソースコードです。
検索結果ページのURLを取得するソースコードをそのまま使用しましょう。
検索する対象のサイトのURLをここで代入しています。
ここでは、キノコードのWebスクレイピング練習用のサイトにしています。
for文で、keyword_listの キーワードを順に検索します。

そして、作成した辞書dictのkeyにプログラミング言語、valueに検索結果ページのURLのリストを入れます。
dict角括弧の中にkeyword_list[k] イコール urlsとします。
実行します。
keyにプログラミング言語、valueに検索結果のページのURLのリストが入った辞書を作ることができました。

import datetime

続いてデータを取得した日時を取得するのにdatetimeをインポートします。

today = datetime.datetime.now()

todayという変数に、現在の日時を代入します。
datetimeドットdatetimeドットnow()で現在の日時を取得できます。
実行します。

print(today)
2021-06-07 11:38:50.311795

正しく現在の日時が取得できているか確認しておきましょう。
print丸括弧todayと書いて、実行します。
問題なく取得することができていますね。

print(today.strftime('%Y/%m/%d %H:%M:%S'))
2021/06/07 11:38:50

日時は見やすいように、書式文字列に変換しておきましょう。
変数todayドットstrftime、引数に書式文字列を指定します。
今回は年月日をスラッシュで、時分秒をコロンで区切って表示します。
%Yは西暦4けた、%mは月2けた、%dは日2けた、%Hは時、%Mは分、%Sは秒を表します。
実行します。
指定した書式文字列に変換することができました。

time_data = {}
link_data = {}
title_data = {}

for k in range(len(dict)):

    times = []
    links = []
    titles = []

    dict_value_list = dict[keyword_list[k]]

    for i in range(len(dict_value_list)):

        r = requests.get(dict_value_list[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_time = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
            times.append(get_list_time)

            # リンクを取得
            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)

    time_data[keyword_list[k]] = times
    link_data[keyword_list[k]] = links
    title_data[keyword_list[k]] = titles

ここからは、各プログラミング言語の検索結果ページの情報を取得します。
日時、リンク、タイトルを格納する辞書をそれぞれ、time_data、link_data、title_dataとします。

検索結果のURLごとに処理をしたいので、for文を使います。
for文で、検索結果のURLが入っているdictの個数分だけループ処理をします。
len関数でdictの個数をカウント、そしてrange関数で開始から終了までの数字を取得し、順に変数kに代入します。
日時、リンク、タイトルを格納するリストをそれぞれ、times、links、titlesとします。

変数dict_value_listにそれぞれのプログラミング言語の検索結果のページのURLのリストを辞書型に変換して入れます。

さらにfor文で、dict_value_listの個数分ループ処理をします。
len関数でURLのリストの個数をカウント、range関数で開始から終了までの数字を取得し、順に変数iに代入します。
ループ内で、変数rに、dict_value_listの要素であるWebページを読み込みます。
Webページが読み込まれる前に、次の処理に行かないよう、time.sleep(3)で3秒待機させます。
BeautifulSoup(r.text,'html.parser')で、読み込まれたWebページのHTMLを解析します。

それでは、リンク、タイトルを取得するため、検索結果ページのHMTL構造を見てみましょう。
検索結果の1つ目を見ると、「entry-card-wrap」のクラスが付与されているaタグ内に、リンクとタイトルの記述が見つかりました。
コードに戻りましょう。
find_allメソッドで「entry-card-wrap」のクラスが付与されているaタグをすべて取得します。
取得したaタグは検索結果の個数分あるはずです。
for文で1つずつ取り出し、リンクとタイトルを取得します。
リンクはhref属性、タイトルはtitle属性です。

リストtimesに現在の日時を、links、titlesに取得したリンク、タイトルをそれぞれappendメソッドで追加します。
そして、プログラミング言語ごとに、取得したリストを辞書time_data、link_data、title_dataに入れましょう。

time_data

変数time_dataの中身を確認してみましょう。
実行します。
それぞれのプログラミング言語ごとにデータを取得した日時のリストが取得できています。

link_data

次に変数link_dataの中身を確認してみましょう。
実行します。
それぞれのプログラミング言語ごとにURLのリストが取得できています。

title_data

最後に変数title_dataの中身を確認してみましょう。
実行します。
それぞれのプログラミング言語ごとにタイトルのリストが取得できています。

それでは取得した辞書型のデータをデータフレームに変換しましょう。
検索キーワードごとに、このようなデータフレームを作成します。
カラムは、検索順位、日時、タイトル、URLとします。

[i+1 for i in range(len(time_data[keyword_list[0]]))]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]

では、Pythonを例にしてみましょう。
検索順位をつけるために、リスト内包表記でリストを作成します。
ここでは、range関数だと0からはじまるので、1を足します。
len関数でtime_dataのPythonの要素をカウントします。
Pythonはkeyword_listの最初の要素なのでkeyword_list[0]とします。
実行して確認しましょう。
1から22まで表示されました。

df = pd.DataFrame({'検索順位':[i+1 for i in range(len(time_data[keyword_list[0]]))],
                  '日時':time_data[keyword_list[0]],
                  'タイトル':title_data[keyword_list[0]],
                  'URL':link_data[keyword_list[0]]})

これを検索順位のカラム、また、先ほど取得したリストから、日時、タイトル、URLを要素にデータフレームを作成します。
それぞれキーワードのPython、つまり0番目を指定ます。

df

作成したデータフレームを確認してみましょう。
データが格納されたことがわかります。

import os

ほかのプログラミング言語ごとのデータフレームを作成する前に、CSVファイルを保存するフォルダを作成しましょう。

まずosモジュールをインポートします。
osは、基本ソフトウェアの機能を使用するためのモジュールです。
ファイル名を取得したり、ディレクトリを作成したり、ファイル名やディレクトリを削除するために使用します。

os.mkdir('./result') # resultフォルダを作成します。

csvを保存するディレクトリを作成します。
ディレクトリの作成には、osライブラリのmkdirメソッドを使用します。
os.mkdirの引数にはディレクトリパスを記述し、シングルクォーテーションでくくります。

ここでは、現在のディレクトリの下の階層に、resultというディレクトリを作成します。
このドットは、現在のディレクトリを意味しています。
現在のディレクトリとはJupyterLabを使用している場合、JupyterLabが置いてある場所です。
この現在のディレクトリからスラッシュをつけると、下の階層という意味です。
実行します。
新しいディレクトリが作成されました。

for i in range(len(keyword_list)):
    df = pd.DataFrame({'検索順位':[i+1 for i in range(len(time_data[keyword_list[i]]))],
                      '日時':time_data[keyword_list[i]],
                      'タイトル':title_data[keyword_list[i]],
                      'URL':link_data[keyword_list[i]]})
    df.to_csv('./resukt/' + keyword_list[i] + '.csv', index=False)
    print(keyword_list[i] + '.csvファイルを保存しました。')

では、作成したディレクトリに、それぞれのプログラミング言語ごとの検索結果をCSVファイルで保存しましょう。
まずはデータフレームをfor文で作成します。

データフレームを作成するコードは、先ほどのPythonを例にしたコードを使用します。
Pythonを0番目で指定していた箇所を、iに変更することで、順に取得することができます。

CSVファイルの名前は、検索キーワードをそのまま使用し、作成したフォルダresultに保存します。
実行します。

フォルダを確認してみましょう。
保存できています。

CSVファイルの中を確認しましょう。
書き出せています。

レッスンは以上です。いかがでしたでしょうか。
キノコードでは分かりやすく飽きない動画づくりを意識しています。
今後はこのようなレッスン動画を予定しています。
レッスンの新着通知が行きますので、是非チャンネル登録をお願いします。
それではまた、次の動画でお会いしましょう。