見出し画像

【初心者向け】Pythonでスクレイピングする環境を作る⑤ Google検索をしてみる

Naruhiko です。

サイトのデータの取得を簡単ですが説明しました。
Python の詳しい内容は専門書などにおまかせしますが、
前回までの方法をそのまま行えばスクレイピングが出来るようになっていると思います。

今回は、Google検索の結果を元にサイトのデータを取得してみます。

前回作成したコードはこれですね。

import requests
from bs4 import BeautifulSoup

try:
    # urlを代入
    url = "https://www.yahoo.co.jp/"
    # データ取得
    resp = requests.get(url)
    # 要素の抽出
    soup = BeautifulSoup(resp.text, "html.parser")
    tags = soup.find_all("a") 
    # タグ内テキスト
    for tag in tags:
        print(tag.get_text())
except:
    print("取得できませんでした")

Google検索をしてみる

Google検索をするためには、検索用のサイトにアクセスする必要があります。

# google search
google_search_url = "https://www.google.co.jp/search"

次に検索するパラメーターを設定する必要があります。
検索ワードとかですね。

search_params = {"q": "python"}

上記の例では「python」を検索文字としてパラメーターを送ります。

そのパラメーターと url を requests.get に渡していきます。

resp = requests.get(google_search_url, params=search_params)

とりあえず、検索結果ページのタイトルを表示させてみます。

# 要素の抽出
soup = BeautifulSoup(resp.text, "html.parser")
print(soup.title)

ここまでまとめると、このようなコードになります。

import requests
from bs4 import BeautifulSoup

try:
    # google search
    google_search_url = "https://www.google.co.jp/search"
    search_params = {"q": "python"}
    resp = requests.get(google_search_url, params=search_params)

    # 要素の抽出
    soup = BeautifulSoup(resp.text, "html.parser")
    print(soup.title)
except:
    print("取得できませんでした")
root@e973f742a9b6:/workspaces/Workspaces/crawler# python crawler.py
<title>python - Google 検索</title>

取得に成功すると、検索結果のページタイトルが表示され、
失敗すると、「取得できませんでした」と表示されます。

リクエスト部分を関数化する

このまま同じ try 内で書いてもいいですが、
複数の箇所で同じようなコードが必要になってくるかもしれません。

そのようなときは、まとまった部分を関数化することを検討します。
関数化とは様々な処理を一つにまとめることで、その処理を使いやすくします。
さらに同じ処理が複数ある場合にもまとめることでメンテナンスがしやすくなります。

今回、まとめるところはここにしましょう。
ここまでが1セットになりますね。

# google search
google_search_url = "https://www.google.co.jp/search"
search_params = {"q": "python"}
# データ取得
resp = requests.get(google_search_url, params=search_params)
# 要素の抽出
soup = BeautifulSoup(resp.text, "html.parser")

ではリクエストがきちんと帰ってくればその値を返し、
帰ってこなければ、None を返すものを作っていくことにします。

python では def を使って関数にまとめます。

def get_html():
    """ get_html
    """
    # google search
    url = "https://www.google.co.jp/search"
    params = {"q": "python"}
    # データ取得
    resp = requests.get(url, params=params)
    # 要素の抽出
    soup = BeautifulSoup(resp.text, "html.parser")
    return soup

get_html という関数を作成しました。
get_html() を呼び出すと、リクエストし、要素を抽出した後、return で値を戻します。

「"""」でダブルクォーテーションを3つ繋げているところがありますが、
これはドキュメンテーション文字列といって、この関数がどういったものなのかを記述しておくところです。
今後この関数のボリュームが増えてきたときに、このドキュメンテーション文字列もメンテナンスしていくことになります。

エラーになったときの処理も書いておきましょう。
エラーになったときは None を返すようにすることに決めましたね。
ここらへんはどうしていくかは、各プロジェクトで変わってくると思います。
PMの支持に従ってくださいね。

エラー時の処理を追加したコードはこちらです。

def get_html():
    """ get_html
    """
    try:
        # google search
        url = "https://www.google.co.jp/search"
        params = {"q": "python"}
        # データ取得
        resp = requests.get(url, params=params)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except:
        return None

except ですが、本当はどんなエラーがあるかを予測し、そのエラーだけを取り出すのがいいのですが、そこのところは今回は省きます。
もし、ログにエラー内容を保存したいなと思ったときなどは、以下のようにしてエラー内容を保管しておきます。

except Exception as e:

e という変数にエラーの内容が保存されますので、それを使ってエラー画面にデータを渡したり、ログに保存したりするのがいいと思います。
バグを特定するためのツールですので、エラーログは本当に大事です。

ここまで関数化しましたが、これでは検索URLや検索文字列を変えることができませんね。
それに、検索条件などのパラメーターも指定することができません。

ですので、この関数に引数を作って呼び出したときに値を渡せるように作り変えます。

渡したい値は以下のとおりです。
・検索するサイト
・検索するときのパラメーター(検索文字列もここに入る)

これに従ってそれぞれ、url、params とします。
もし、パラメーターのないサイトを取得する場合は params の引数を指定しなくても大丈夫なようにデフォルトで None を設定しておきます。

def get_html(url, params=None):

中身もこのパラメーターを使うように変えましょう。

こう作り変えました。

def get_html(url, params=None):
    """ get_html
    url: データを取得するサイトのURL
    [params]: 検索サイトのパラメーター {x: param}
    """
    try:
        # データ取得
        resp = requests.get(url, params=params)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except Exception as e:
        return None

これで引数付きの関数呼び出しでリクエスト文字列を返すようになります。

この関数を呼び出すにはこうします。

soup = get_html("https://www.google.co.jp/search", {"q": "python"})

値は変数に入れておきましょう。

search_url = "https://www.google.co.jp/search"
search_params = {"q": "python"}
soup = get_html(search_url, search_params)

これまでのコードはこうなりました。

import requests
from bs4 import BeautifulSoup

def get_html(url, params=None):
    """ get_html
    url: データを取得するサイトのURL
    [params]: 検索サイトのパラメーター {x: param}
    """
    try:
        # データ取得
        resp = requests.get(url, params=params)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except Exception as e:
        return None


try:
   # urlを代入
   search_url = "https://www.google.co.jp/search"
   search_params = {"q": "python"}
   # データ取得
   soup = get_html(search_url, search_params)
   print(soup.title)
except Exception as e:
   print("取得できませんでした")

しかし、これでは困ったことになります。
この時点で「取得できませんでした」と表示されたとしても、get_html() でエラーになったか、それ以外のところでエラーになったのかわかりません。

リクエストでデータが取得できなかった場合に
None が返されるので、その None を活用します。

get_html() でエラーになったのなら、「取得できませんでした」
その後にエラーになったら「エラーになりました」
というふうに作ってみましょう。

こういうときには、if 文を使用します。
if 文を使うことで、条件によって分岐することが可能になります。

if soup != None:
    print(soup.title)
else:
    print("取得できませんでした")

get_html() で取得したデータを soup に入れますが、
もし、その soup の中身が None 以外だったら、タイトルを表示し、
None だったら、「取得できませんでした」を表示するように変えました。

ちなみに、Python は if 文や for 文などを1行にまとめることが可能です。
上の if 文は下のように記述することもできます。

print(soup.title) if soup != None else print("取得できませんでした")

あとは、エラーになったときの print 文を変更しておきます。

print("エラーになりました")

ここまでにコードはこうなりました。

import requests
from bs4 import BeautifulSoup

def get_html(url, params=None):
    """ get_html
    url: データを取得するサイトのURL
    [params]: 検索サイトのパラメーター {x: param}
    """
    try:
        # データ取得
        resp = requests.get(url, params=params)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except Exception as e:
        return None


try:
    # urlを代入
    search_url = "https://www.google.co.jp/search"
    search_params = {"q": "python"}
    # データ取得
    soup = get_html(search_url, search_params)
    if soup != None:
        print(soup.title)
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

検索結果のサイトURLを取得する

では、今度は取得したデータを元に結果先サイトのURLを取得してみましょう。

その前に、Google の html コードは、ブラウザによって変化します。
ですので、固定してしまいましょう。
具体的には、ヘッダー情報に User-Agent を指定します。
windows10 の chrome ではこの様な文字列です。

"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100"

バージョンによって変わってきますが、これを使います。
まずは、user-agent 情報を用意します。
1行が79字以上になりましたので、PEP8に従って途中で「\」を使って
改行しています。

# User-Agent
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
              AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100"

※user_agent については、運用時にはきちんと自分が bot であると明記すべきです。このような偽造した user_agent はもし何かあった場合には問題になりやすいです。この部分については、パッケージ化する時に再度説明し修正してきます。

カスタムヘッダー情報を指定します。
今回は、User-Agent のみの設定です。

search_headers = {"User-Agent": user_agent}

データを渡す引数に追加します。

soup = get_html(search_url, search_params, search_headers)

get_html() 関数の引数も追加します。
デフォルトで None を設定しておけば、引数を入れなかった場合は None を自動で入れてくれるので便利です。

def get_html(url, params=None, headers=None):

requests.get で引数を追加します。

resp = requests.get(url, params=params, headers=headers)

こうすることで、同じ場所でもクラス名が違っていたところが、
ブラウザと同じクラス名になります。

例)
追加前:<div class="kCrYT">
追加後:<div class="r">

追加後のクラス名がブラウザでも確認できます。

headers の追加後のコードはこうなりました。

import requests
from bs4 import BeautifulSoup

# User-Agent
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
             AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100"

def get_html(url, params=None, headers=None):
   """ get_html
   url: データを取得するサイトのURL
   [params]: 検索サイトのパラメーター {x: param}
   [headers]: カスタムヘッダー情報
   """
   try:
       # データ取得
       resp = requests.get(url, params=params, headers=headers)
       # 要素の抽出
       soup = BeautifulSoup(resp.text, "html.parser")
       return soup
   except Exception as e:
       return None


try:
   # urlを代入
   search_url = "https://www.google.co.jp/search"
   search_params = {"q": "python"}
   search_headers = {"User-Agent": user_agent}
   # データ取得
   soup = get_html(search_url, search_params, search_headers)
   if soup != None:
       print(soup.title)
   else:
       print("取得できませんでした")
except Exception as e:
   print("エラーになりました")

では、タイトルを取得している部分を変更していきます。

print(soup.title)

この部分を変更していきましょう。

ブラウザで検索したときに、どの様に表示されるのでしょうか。
実際にブラウザで見てみましょう。

画像1

class="r" の下の a にURLがあります。
このURLを取得してみましょう。
Beautiful soup の select メソッドは、css を知っているとわかりやすいのでこれを使ってみます。

tags = soup.select(".r > a")

取得したデータそれぞれのリンクアドレスを取得します。
この tags を for で回してそれぞれの tag データから get("href") していきます。

for tag in tags:
    print(tag.get("href"))

こうすることで、URLの一覧を取得できました。

root@e973f742a9b6:/workspaces/Workspaces/crawler# python crawler.py
https://www.python.jp/
https://www.python.org/
https://ja.wikipedia.org/wiki/Python
https://prog-8.com/languages/python
https://www.javadrive.jp/python/
https://japan.zdnet.com/article/35149096/
https://japan.zdnet.com/article/35149142/
https://www.atmarkit.co.jp/ait/articles/1904/02/news024.html
https://qiita.com/tags/python

最後に、今回の最終的なコードはこのようになりました。

import requests
from bs4 import BeautifulSoup

# User-Agent
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
              AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100"

def get_html(url, params=None, headers=None):
    """ get_html
    url: データを取得するサイトのURL
    [params]: 検索サイトのパラメーター {x: param}
    [headers]: カスタムヘッダー情報
    """
    try:
        # データ取得
        resp = requests.get(url, params=params, headers=headers)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except Exception as e:
        return None

try:
    # urlを代入
    search_url = "https://www.google.co.jp/search"
    search_params = {"q": "python"}
    search_headers = {"User-Agent": user_agent}
    # データ取得
    soup = get_html(search_url, search_params, search_headers)
    if soup != None:
        tags = soup.select(".r > a")
        for tag in tags:
            print(tag.get("href"))
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

URL取得部分を関数化する

最後に、URL取得する部分を関数化しておきます。
関数化しておくところはここです。

# urlを代入
search_url = "https://www.google.co.jp/search"
search_params = {"q": "python"}
search_headers = {"User-Agent": user_agent}
# データ取得
soup = get_html(search_url, search_params, search_headers)
if soup != None:
    tags = soup.select(".r > a")
    for tag in tags:
        print(tag.get("href"))
else:
    print("取得できませんでした")

残り全てですね(笑)

このコードは Google 検索専用です。
今後、もし別の検索サイトを使わないといけなくなった場合、ここのコードは使えなくなってしまいます。
ですので、切り離して Google検索と他の検索サイトと使い分ける関数を作ってしまいましょう。

まずは、関数名です。
「get_search_url()」にしましょう。
引数は何を渡すのがいいのかというと、
・検索ワード
・検索エンジン(デフォルトは google)
くらいですね。

def get_search_url(word, engine="google"):
    """ get_search_url
    word: 検索するワード
    [engine]: 使用する検索サイト(デフォルトは google)
    """

こんな感じで、検索ワードと検索サイトを引数に入れました。

次は、検索サイトによって分岐させます。
簡単ですね。if 文を使うだけです。

if engine == "google":

検索ワードを引数よりもらいます。

search_params = {"q": word}

取得したURLをリストに入れて return します。
python にはリスト作成に便利な一行の for 文があります。

urls = [tag.get("href") for tag in tags]
return urls

失敗したときは None を返します。

return None

これらを try/except で囲んでおきます。

get_search_url() 関数はこうなりました。

def get_search_url(word, engine="google"):
    """ get_search_url
    word: 検索するワード
    [engine]: 使用する検索サイト(デフォルトは google)
    """
    try:
        if engine == "google":
            # google 検索
            search_url = "https://www.google.co.jp/search"
            search_params = {"q": word}
            search_headers = {"User-Agent": user_agent}
            # データ取得
            soup = get_html(search_url, search_params, search_headers)
            if soup != None:
                tags = soup.select(".r > a")
                urls = [tag.get("href") for tag in tags]
                return urls
            else:
                return None
        else:
            return None
    except Exception as e:
        return None

気になるところは None が返ってきたときにどこでデータが取れなかったか分かりづらいところですね。

これでは本当にデータがなかったのか、エラーになったからなのかわかりませんね。
おすすめはきちんとエラーを返し、それをきちんと処理してあげることですが、それは次に回すことにします。

では、この関数を呼ぶものをつくってしまいましょう。

関数の呼び出しは一度やりましたからわかりますよね。

try:
    result = get_search_url("python")
    if result != None:
        print(result)
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")
root@e973f742a9b6:/workspaces/Workspaces/crawler# python crawler.py
['https://www.python.jp/', 'https://ja.wikipedia.org/wiki/Python', 'https://www.python.org/', 'https://prog-8.com/languages/python', 'https://www.atmarkit.co.jp/ait/articles/1904/02/news024.html', 'https://www.javadrive.jp/python/', 'https://japan.zdnet.com/article/35149142/', 'https://japan.zdnet.com/article/35149096/', 'https://qiita.com/tags/python']

URLの一覧がリスト形式で取得できました。

最終的なコードはこのようになりました。

import requests
from bs4 import BeautifulSoup

# User-Agent
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
              AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100"

def get_html(url, params=None, headers=None):
    """ get_html
    url: データを取得するサイトのURL
    [params]: 検索サイトのパラメーター {x: param}
    [headers]: カスタムヘッダー情報
    """
    try:
        # データ取得
        resp = requests.get(url, params=params, headers=headers)
        # 要素の抽出
        soup = BeautifulSoup(resp.text, "html.parser")
        return soup
    except Exception as e:
        return None

def get_search_url(word, engine="google"):
    """ get_search_url
    word: 検索するワード
    [engine]: 使用する検索サイト(デフォルトは google)
    """
    try:
        if engine == "google":
            # google 検索
            search_url = "https://www.google.co.jp/search"
            search_params = {"q": word}
            search_headers = {"User-Agent": user_agent}
            # データ取得
            soup = get_html(search_url, search_params, search_headers)
            if soup != None:
                tags = soup.select(".r > a")
                urls = [tag.get("href") for tag in tags]
                return urls
            else:
                return None
        else:
            return None
    except Exception as e:
        return None


try:
    result = get_search_url("python")
    if result != None:
        print(result)
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

次は、このリストのURL先のデータを取得したいと思います。

その前に、スクレイピングに関しての注意事項を次回に説明して置こうと思います。

---

気に入っていただけたら、フォローや好きをお願いします!

連載目次

【初心者向け】Pythonでスクレイピングする環境を作る① はじめに
【初心者向け】Pythonでスクレイピングする環境を作る② Dockerの使い方
【初心者向け】Pythonでスクレイピングする環境を作る③ VSCodeでDocker環境を構築する
【初心者向け】Pythonでスクレイピングする環境を作る④ requestsでデータを取得してみる
【初心者向け】Pythonでスクレイピングする環境を作る⑤ Google検索をしてみる
【初心者向け】Pythonでスクレイピングする環境を作る⑥ スクレイピングでの注意事項
【初心者向け】Pythonでスクレイピングする環境を作る⑦ 検索結果のページのタイトルを取得する
【初心者向け】Pythonでスクレイピングする環境を作る⑧ クラスにまとめてみる
【初心者向け】Pythonでスクレイピングする環境を作る⑨ テストしてみる
【初心者向け】Pythonでスクレイピングする環境を作る⑩ crawler と scraper を分ける

続きをみるには

残り 0字

¥ 100

期間限定 PayPay支払いすると抽選でお得に!

この記事が気に入ったらサポートをしてみませんか?