python: webスクレイピングで検索結果をお気に入り登録する

プログラミング
スポンサーリンク




こんにちは、Eviです。

前回、検索結果を全件CSVファイルに出力する機能を作成しました。

しかし、これだといらないものまで出力されてしまい、見返す時に目当てのものを見つけにくくなるので実用性に欠けます。

ですので今回は、検索結果のうち気に入ったもののみをCSVファイルに記録する、いわゆる「お気に入りボタン」を実装したいと思います。

スポンサーリンク

機能の説明

検索欄に知りたいことを入力し、searchボタンを押すと

検索結果のうち上位数件がWEBブラウザで表示され、検索欄の下にお気に入りボタンが生成されます。(ボタンのテキストはwebページのタイトルと対応しています)

お気に入りボタンをクリックすると、CSVファイルにwebページのタイトルとURLが出力されます。

ソースコード

大きく変わった点は、太字になっています。

・ファイル構造

・Main.py

import tkinter as tk

from src.Model import Model
from src.View import View
from src.Controller import Controller


class Application(tk.Frame):
    def __init__(self, root):
        # スーパークラスのコンストラクタを呼び出し
        super().__init__(root)

        # モデルをインスタンス化
        self.model = Model(root)

        # ビューをインスタンス化
        self.view = View(root, self.model)

        # コントローラーをインスタンス化
        self.controller = Controller(root, self.model, self.view)

        # Viewにメソッドをセット
        self.view.btn_search["command"] = lambda: self.controller.call_btn_clicked()

        # 画面の設定
        root.geometry(str(self.model.width) + "x" + str(self.model.height))
        root.title(self.model.title)


def main():
    root = tk.Tk()
    window = Application(root)
    window.mainloop()


if __name__ == "__main__":
    main()

・Model.py

import configparser as c
import tkinter as tk
import os

# ---------------------------------
# スクレイピング用のモジュールをインポート
# ---------------------------------
# ブラウザ表示用
import urllib
import webbrowser
# HTMLダウンロード用
import requests
# HTML解析用
import bs4

# ---------------------------------
# csv操作用のモジュールをインポート
# ---------------------------------
import csv


class Model(object):
    def __init__(self, root):
        # ---------------------------------
        # 初期設定
        # ---------------------------------
        self.root = root

        # configファイルを読み込み
        self.config = c.ConfigParser()
        self.config.read("config.ini")

        # 画面タイトルを取得
        self.title = self.config["DISPLAY_INFO"]["title"]

        # 画面サイズを取得
        self.width = int(self.config["DISPLAY_INFO"]["width"])
        self.height = int(self.config["DISPLAY_INFO"]["height"])

        # 表示ページ数を取得
        self.pages = self.config["SETTING"]["pages"]

        # 画面パーツに入力した値を格納する変数を宣言
        self.ent_search_text = tk.StringVar()

        # そのた変数を生成
        self.quant = self.pages
        # 検索結果のタイトルとURLを格納するリストを生成する
        self.list_title = []
        self.list_url = []
        # CSVファイルの情報を生成
        # ファイルパス
        self.result_path = "search_result.csv"
        # ヘッダー
        self.column_names = ["title", "url"]

    # ボタンクリック時起動メソッド
    def btn_clicked(self):

        # 検索結果一覧のwebページのHTMLを取得する
        search_result = requests.get("https://www.google.com/search?q={}".format((self.ent_search_text.get()))).content

        # 取得したHTMLを使用してBeautifulSoupオブジェクトを生成する
        result_soup = bs4.BeautifulSoup(search_result, "html.parser")

        # 検索結果のtitleを含むdivタグのリストを取得
        title_elems = result_soup.select('div[class="kCrYT"] > a > .BNeawe.vvjwJb.AP7Wnd')

        # 検索結果のurlを含むaタグのリストを取得
        url_elems = result_soup.select('div[class="kCrYT"] > a')

        # 表示する件数を取得
        self.quant = min(int(self.pages), len(url_elems))

        # リストを初期化
        self.list_title = []
        self.list_url = []

        # ブラウザを生成
        browser = webbrowser.get()

        # 指定された件数分検索結果をリストに格納する
        for idx in range(0, self.quant):
            # divタグからタイトルを取得
            title = title_elems[idx].text

            # aタグからurlを取得(日本語はエンコーディングされているので、デコードする)
            url = urllib.parse.unquote(url_elems[idx].get('href').split('&sa=U&')[0].replace('/url?q=', ''))

            # リスト要素を追加
            self.list_title.append(title)
            self.list_url.append(url)

            # urlを指定してwebページを表示
            browser.open(url)

        # 画面のサイズを変更
        self.root.geometry(str(self.width) + "x" + str(self.height + 22 * self.quant))

    # お気に入りボタンクリック時起動メソッド
    def btn_favorite_callback(self, title, url):
        def add_csv():
            # 検索結果ファイルが存在しない場合、作成する
            if not os.path.exists(self.result_path):
                with open(self.result_path, "w") as f:
                    # CSV書き込み用オブジェクトを生成
                    writer = csv.DictWriter(f, self.column_names)

                    # ヘッダーを書き込み
                    writer.writeheader()

            # csvファイルにデータを書き込む
            with open(self.result_path, "a") as f:
                # CSV書き込み用オブジェクトを生成
                writer = csv.DictWriter(f, self.column_names)

                # 要素を書き込み
                writer.writerow({"title": title, "url": url})

        return add_csv

・View.py

import tkinter as tk


class View(object):
    def __init__(self, root, model):
        self.root = root
        self.model = model

        # ---------------------------------
        # 画面の部品を生成
        # ---------------------------------
        # 検索欄ラベル
        self.lbl_search = tk.Label(
            root,
            text="word"
        )

        # 検索欄
        self.ent_search = tk.Entry(
            root,
            textvariable=model.ent_search_text,
            width=30
        )

        # 検索ボタン
        self.btn_search = tk.Button(
            root,
            text="search",
            command=lambda: model.btn_clicked()
        )

        # お気に入りボタン完了リスト
        self.list_favorite_btn = []

        # ---------------------------------
        # 画面の部品を配置
        # ---------------------------------
        self.lbl_search.grid(row=0, column=0)
        self.ent_search.grid(row=0, column=1)
        self.btn_search.grid(row=0, column=2)

・Controller.py

import tkinter as tk


class Controller(object):
    def __init__(self, root, model, view):
        self.root = root
        self.model = model
        self.view = view

    # 検索ボタンクリック時起動メソッドを定義
    def call_btn_clicked(self):
        # 検索結果を表示
        self.model.btn_clicked()

        # 既存のお気に入りボタンをリセット
        for f_btn in self.view.list_favorite_btn:
            f_btn.destroy()

        # お気に入りボタンリストをリセット
        self.view.list_favorite_btn = []

        # 新しいボタンを作成する処理
        for i in range(self.model.quant):
            # ボタンを作成
            btn_favorite = tk.Button(
                self.root,
                text=self.model.list_title[i],
                command=self.model.btn_favorite_callback(self.model.list_title[i], self.model.list_url[i]),
                width=40,
                anchor="w"
            )

            # リストにボタンを追加
            self.view.list_favorite_btn.append(btn_favorite)

            # 新しいボタンを表示
            btn_favorite.grid(row=i + 1, column=0, columnspan=3)

・config.ini

[DISPLAY_INFO]
title = "検索結果取得"
width = 380
height = 30

[SETTING]
pages = 3

追加した処理

・お気に入りボタンを動的に生成する処理

お気に入りボタンの追加は、検索結果を表示する処理の後に行います。

画面に要素を追加する処理ですので、画面制御を行うController.pyに処理を記述します。

ソースコード(抜粋)

# ①既存のお気に入りボタンをリセット
for f_btn in self.view.list_favorite_btn:
    f_btn.destroy()

# ②お気に入りボタンリストをリセット
self.view.list_favorite_btn = []

# ③新しいボタンを作成する処理
for i in range(self.model.quant):
    # ボタンを作成
    btn_favorite = tk.Button(
        self.root,
        text=self.model.list_title[i],
        command=self.model.btn_favorite_callback(self.model.list_title[i], self.model.list_url[i]),
        width=40,
        anchor="w"
    )

    # ④リストにボタンを追加
    self.view.list_favorite_btn.append(btn_favorite)

    # ⑤新しいボタンを表示
    btn_favorite.grid(row=i + 1, column=0, columnspan=3)

・お気に入りボタンに関数を設定する処理

ボタンを動的に生成し、関数を割り当てる処理の実装には、少しコツが必要です。

ソースコード(抜粋)

# ボタンを作成
btn_favorite = tk.Button(
    self.root,
    text=self.model.list_title[i],
    command=self.model.btn_favorite_callback(self.model.list_title[i], self.model.list_url[i]),
    width=40,
    anchor="w"
)
# お気に入りボタンクリック時起動メソッド
def btn_favorite_callback(self, title, url):
    def add_csv():
        # 検索結果ファイルが存在しない場合、作成する
        if not os.path.exists(self.result_path):
            with open(self.result_path, "w") as f:
                # CSV書き込み用オブジェクトを生成
                writer = csv.DictWriter(f, self.column_names)

                # ヘッダーを書き込み
                writer.writeheader()

        # csvファイルにデータを書き込む
        with open(self.result_path, "a") as f:
            # CSV書き込み用オブジェクトを生成
            writer = csv.DictWriter(f, self.column_names)

            # 要素を書き込み
            writer.writerow({"title": title, "url": url})

    return add_csv

 

↓参考

http://memopy.hatenadiary.jp/entry/2017/06/11/220452

次回

次回は、作成したプログラムをexeファイルに変換する方法について紹介したいと思います。

コメント

タイトルとURLをコピーしました