見出し画像

【初心者向け】Pythonでスクレイピングする環境を作る⑧ クラスにまとめてみる

Naruhiko です。

Python どうですか?楽しんでますか?

これまで進めてきて、
1.Google検索結果からURLを取得する
2.取得したURLのサイトのタイトルを取得する
ここまでのコードを書くことができました。

関数を3つ作りましたね。
・get_robots_txt() … robots.txt の内容を確認して取得可能か確認する
・get_html() … html 情報を取得する
・get_search_url() … 検索エンジンで検索した結果の一覧を取得する

これらの関数を駆使することで目標通りのデータを取得することができました。

さて、突然ですがオブジェクト指向とか聞いたことありますか?
今まで作ったものと、オブジェクト思考で作ったものとは何が違うのでしょうか。

そのあたりも含めて、クラスというものを扱っていこうと思います。

前回までに作ったコードはこちらです。

import time
from urllib.robotparser import RobotFileParser
from urllib.parse import urlparse
from logging import basicConfig, getLogger, DEBUG

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"

# ログフォーマットを定義
formatter = "[%(asctime)s][%(name)s][%(levelname)s]%(message)s"
# ログレベル
basicConfig(level=DEBUG, format=formatter)

# robots.txt 用パーサー
rp = RobotFileParser()

def get_robots_txt(url):
    """ get_robots_txt
    url: robots.txt を確認するサイトURL
    """
    logger = getLogger("get_robots_txt()")
    try:
        # robots の url 取得
        parsed_url = urlparse(url)
        robots_url = "{0.scheme}://{0.netloc}/robots.txt".format(parsed_url)
        # robots.txt 取得
        rp.set_url(robots_url)
        rp.read()
        # 取得していいか確認
        return rp.can_fetch("*", url)
    except Exception as e:
        logger.warning(e)
        return False

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

def get_search_url(word, engine="google"):
    """ get_search_url
    word: 検索するワード
    [engine]: 使用する検索サイト(デフォルトは google)
    """
    logger = getLogger("get_search_url()")
    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:
                raise Exception("No Data")
        else:
            raise Exception("No Engine")
    except Exception as e:
        logger.warning(e)
        return None


try:
    urls = get_search_url("python", "google")
    if urls != None:
        for url in urls:
            if get_robots_txt(url):
                soup = get_html(url)
                print(soup.title.get_text())
            else:
                print("クロールが拒否されました [{}]".format(url))
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

オブジェクトとは

クラスの前に、まずはオブジェクトとはということから簡単に話します。

オブジェクトとは、概念です。

あるものに対して何者なのかという概念が定義されるとそれはオブジェクトという概念が盛り込まれます。

全然わかりませんよね。
プログラミングではだいたいざっくりデータのことを捉えます。
そのデータが何者なのかを定義するすることで実体となりオブジェクトになるんです。
まぁ、なにかしらの名前がついていればオブジェクトです。

"abc" という文字列があります。

この文字列は、大体のプログラミング言語では string 型になります。
この string 型に定義されていることでオブジェクトになります。

そして、オブジェクトにはさまざまな情報が入っています。
例えばボールは名前や形や色や動きなどの情報が入っていますよね。
それがオブジェクト化したデータに入れることが出来るんです。

変数 ball は string オブジェクトです。

ball = "baseball"

ball には "baseball" という文字列情報が入ります。

string 型には中の文字を操作するメソッドというものがあります。

ball.upper()

こうすると、ball は "BASEBALL" という情報になります。

こんな感じでオブジェクトという概念にはデータ(変数やプロパティ)やコード(関数やメソッド)といういろいろな情報があるんです。

オブジェクト指向プログラミングとは

これもよく聞きますがオブジェクト指向でプログラミングするとなにが違うのでしょうか。

通常、今まで作ったコードのように関数などを使って流れるようにコードを書きます。

オブジェクト指向プログラミングは後で説明する class を使うことでいろいろなメリットが出てくるんです。

まずはカプセル化です。
コードなどを一つにまとめることでコードがカプセル化されます。
カプセル化のメリットは変なデータを渡さないようにすることで、バグを少なくしたり出来ることです。
余計な情報はわたさないという秘匿主義な人です。
そして、オブジェクト自体にいろいろな機能を追加することができるんですね。

a = 5
b = a * 2
c = a * b

この計算を見てみましょう。
b,c を関数でまとめるとこうなりますね。

a = 5

def get_func(a):
    b = a * 2
    c = a * b
    return c

c = get_func(a)

c に計算結果が入りました。
オブジェクト指向というのは、この c 自体に独自の計算ができるすごい子を作り出すことをいいます。

それには class を使用します。
そして関数をメソッドというものにします。(ほとんどかわりません)

a = 5

class Func:

    def get_func(self, a):
        b = a * 2
        c = a * b
        return c

このクラスの使い方は、まず、インスタンス化したクラスオブジェクトを
変数 c に渡します

c = Func()

オブジェクトとインスタンスは同じものだと思ってもいいです。
そして、インスタンス c のメソッドを呼び出します。

c.get_func(a)

すると、インスタンス c に計算結果が格納されるんですね。

a = 5

class Func:

   def get_func(self, a):
       b = a * 2
       c = a * b
       return c

c = Func()
print(c.get_func(a))

コードが大きくなればなるほど、まとめやすくなり、使いやすくなります。

もう一つのメリットは継承です。

class を定義する時に他の class を指定することでそのクラスの能力を引き継ぐ事ができます。

class Fanc(Object):

こんな感じです。
クラス Func はクラス Object の能力を引き継げるんですね。

変数に代入するのではなく、
オブジェクトの能力を増やしたりまとめたりすることで、そのオブジェクトをいろいろな所で使いやすくすることができるのです。

他にもいろいろとメリットがありますが、それはまたの機会にしたいと思います。

Class をつかってみる

では、今まで書いてきたコードを class に変えて行きましょう。

まずは、class の宣言ですね。

class Crawler:

クラス名は、最初の文字は大文字、あとは小文字で表現します。
今回は継承はしないので、() は使いません。

次は、初期設定をします。
__init__ メソッドを使うことで、最初のクラスのインスタンス化の時に実行したいコードを書いておきます。
今回は、user_agent を指定しておきましょう。

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

それとどの検索サイトで検索するかを指定しておきます。
(今はまだ google しかありません)

def __init__(self, engine="google"):
    # User-Agent
    self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
                       AppleWebKit/537.36 (KHTML, like Gecko) \
                       Chrome/69.0.3497.100"
    # engine
    self.engine = engine

self はこのクラス自身を示すインスタンス変数になります。
メソッドの第一引数に必ず入れます。

オプションにある engine と self.engine は違うものです。

    self.engine = "検索エンジンは{}です。".format(engine)

e = Crawler()
print(e.engine)

としてみると、self.engine のほうが呼ばれます。

検索エンジンはgoogleです。

そして、インスタンスは複数作ることも、それぞれに違う値を入れることも可能です。

g = Crawler()
y = Crawler("yahoo")
n = Crawler("note")
print(g.engine)
print(y.engine)
print(n.engine)
検索エンジンはgoogleです。
検索エンジンはyahooです。
検索エンジンはnoteです。

ですので、google検索のインスタンス、yahoo検索のインスタンスなどと使い分けることが出来るようになります。

次は、get_robots_txt() をクラスに入れましょう。
一緒に RobotFileParser() も中に入れました。

def get_robots_txt(self, url):
    """ get_robots_txt
    url: robots.txt を確認するサイトURL
    """
    logger = getLogger("get_robots_txt()")
    try:
        # robots.txt 用パーサー
        rp = RobotFileParser()
        # robots の url 取得
        parsed_url = urlparse(url)
        robots_url = "{0.scheme}://{0.netloc}/robots.txt".format(parsed_url)

        # robots.txt 取得
        rp.set_url(robots_url)
        rp.read()
        # 取得していいか確認
        return rp.can_fetch("*", url)
    except Exception as e:
        logger.warning(e)
        return False

このメソッドではインスタンス変数(selfの変数)を使いませんでした。
すべてメソッド内で完結する変数です。

次は get_html() をクラスに入れます。

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

このメソッドもインスタンス変数は使いません。
引数に self を追加するだけでした。

最後に get_search_url() です。

def get_search_url(self, word):
    """ get_search_url
    word: 検索するワード
    [engine]: 使用する検索サイト(デフォルトは google)
    """
    logger = getLogger("get_search_url()")
    try:
        if self.engine == "google":
            # google 検索
            search_url = "https://www.google.co.jp/search"
            search_params = {"q": word}
            search_headers = {"User-Agent": self.user_agent}
            # データ取得
            soup = self.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:
                raise Exception("No Data")
        else:
            raise Exception("No Engine")
    except Exception as e:
        logger.warning(e)
        return None

ここで初めてインスタンス変数を使います。
self.engine で検索エンジンを、self.user_agent でユーザーエージェントを代入します。
self.get_html でクラス内のメソッドも実行できます。
引数に engine の指定は必要ないのでやめました。

これでクラス化が完成しました。
次に、このクラスを呼び出すところを修正しましょう。

# インスタンス化
crawler = Crawler()

まずはインスタンス化します。
既定値は「google」ですので、指定しなくても大丈夫ですね。
もし、他の検索エンジンを追加したのであれば、

# インスタンス化
crawler = Crawler("yahoo")

などと引数を渡します。

クラスからメソッドを呼び出してみましょう。

urls = crawler.get_search_url("python")

引数はキーワードだけにしましたね。

if crawler.get_robots_txt(url):
    soup = crawler.get_html(url)

インスタンスからメソッドを実行します。

まとめるとこの様になりました。

try:
    # インスタンス化
    crawler = Crawler()
    urls = crawler.get_search_url("python")
    if urls != None:
        for url in urls:
            if crawler.get_robots_txt(url):
                soup = crawler.get_html(url)
                print(soup.title.get_text())
            else:
                print("クロールが拒否されました [{}]".format(url))
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

実行すると同じ結果になりますが、
プログラムは構造が違ったものになりました。

最後に

オブジェクト指向でプログラムを書くことは大事なことです。
余計な変数のデータを表に出さないようにしたり、メソッドを継承したりなどいろいろなことが感覚的にできるようになります。

最後に、これまで作ったコードをのせておきます。

import time
from urllib.robotparser import RobotFileParser
from urllib.parse import urlparse
from logging import basicConfig, getLogger, DEBUG

import requests
from bs4 import BeautifulSoup


# ログフォーマットを定義
formatter = "[%(asctime)s][%(name)s][%(levelname)s]%(message)s"
# ログレベル
basicConfig(level=DEBUG, format=formatter)


class Crawler:
    """ Crawler
    self.user_agent : ユーザーエージェントの情報を設定
    self.engine : 検索サイトを設定

    get_robot_txt(url) : robots.txt の中身を精査
        url: robots.txt を確認するサイトURL
        return True, False

    get_html(url, params=None, headers=None) : サイトの情報を取得
        url: データを取得するサイトのURL
        [params]: 検索サイトのパラメーター {x: param}
        [headers]: カスタムヘッダー情報

    get_search_url(word, engine="google") : 検索サイトで検索結果の一覧を取得
        word: 検索するワード
        [engine]: 使用する検索サイト(デフォルトは google)
    """
    def __init__(self, engine="google"):
        # User-Agent
        self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
                           AppleWebKit/537.36 (KHTML, like Gecko) \
                           Chrome/69.0.3497.100"
        # engine
        self.engine = engine
    def get_robots_txt(self, url):
        """ get_robots_txt
        url: robots.txt を確認するサイトURL
        """
        logger = getLogger("get_robots_txt()")
        try:
            # robots.txt 用パーサー
            rp = RobotFileParser()
            # robots の url 取得
            parsed_url = urlparse(url)
            robots_url = "{0.scheme}://{0.netloc}/robots.txt".format(parsed_url)
            # robots.txt 取得
            rp.set_url(robots_url)
            rp.read()
            # 取得していいか確認
            return rp.can_fetch("*", url)
        except Exception as e:
            logger.warning(e)
            return False

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

    def get_search_url(self, word):
        """ get_search_url
        word: 検索するワード
        [engine]: 使用する検索サイト(デフォルトは google)
        """
        logger = getLogger("get_search_url()")
        try:
            if self.engine == "google":
                # google 検索
                search_url = "https://www.google.co.jp/search"
                search_params = {"q": word}
                search_headers = {"User-Agent": self.user_agent}
                # データ取得
                soup = self.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:
                    raise Exception("No Data")
            else:
                raise Exception("No Engine")
        except Exception as e:
            logger.warning(e)
            return None


try:
    # インスタンス化
    crawler = Crawler()
    urls = crawler.get_search_url("python")
    if urls != None:
        for url in urls:
            if crawler.get_robots_txt(url):
                soup = crawler.get_html(url)
                print(soup.title.get_text())
            else:
                print("クロールが拒否されました [{}]".format(url))
    else:
        print("取得できませんでした")
except Exception as e:
    print("エラーになりました")

---

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

note 連載目次

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

ここから先は

0字

¥ 100

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