レコメンデーション発展　検索エンジンの作成2

[Aidemy](https://aidemy.net/)　2020/12/3
#はじめに
　こんにちは、んがょぺです！バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます！
　今回は、レコメンデーション発展の2つ目の投稿になります。どうぞよろしくお願いします。

＊本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。

今回学ぶこと
・データベースにデータを保存する
・クラスを作成する
・検索部分を作成する

#②sqlでデータセットをデータベースに保存する
##取得したデータを保存する
・Chapter1では検索エンジン作成の流れ①で、スクレイピングにより__データセットを作成した__。ここでは、そのデータセットを__データベースのテーブルに保存する__。
・カラムに値を追加する関数__「add_list()」__について、まずは渡されたsoupに__separete_words()__を使用して、単語（words）ごとに分ける。次に、テーブルの__カラムに値を入れていく__。「urllist」テーブルの__url__には、渡されたurlをそのまま入れれば良い。復習になるが、値の代入は__「conn.execute("insert into テーブル(カラム) values(値)")」__で行える。ちなみに今回は「値」の部分に__％記法__を使っていく。％記法とは、__％を使って文字列中に変数を代入する方法__である。「url」は文字列なので、__「%s」__のようにする。
・また、wordsの各単語「word」のうち、Chapter1の最後で作成した__「ignore_words」__でないものについて、「wordlist」テーブルの__wordカラム__に値として代入する。同様に、wordsのインデックスを「i」とすると、「wordlocation」テーブルについても「urlid,wordid,location」カラムについて__「url,word,i」__を値として代入する。
・最後に__「conn.commit()」__とすることで値を更新、保存する。

・コード![スクリーンショット 2020-11-30 15.30.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/c29280d3-7763-971a-33d2-8cf5408fab4c.png)

・実行結果（一部のみ）![スクリーンショット 2020-11-30 15.29.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/88a8b66f-3172-e9b9-6aa1-d7935f1911fa.png)

##URLの対応関係をデータベースに保存する
・ここでは、前項の「urllist」テーブルに、新たに__リンク先のURL（url_to）__を格納する。また、「link」テーブルの__url_from__と__url_to__カラムに、それぞれリンク元のURL、リンク先のURLを格納する。
・リンク元のURL、リンク先のURLを渡すことでこれを行う関数__「add_url_list()」__を実装する。linkテーブルの方は、url_fromとurl_toが同じの場合は格納しないようにする。
・また、Chapter1ではリンクの取得の際に__「'」__を取り除いていたが、もし「'」を含むURLがあったとした場合、SQL文の中で__「'('の入ったリンク)'」__となり、「'」が3つになると切れ目がなくなってしまい、エラーになるためである。

・コード![スクリーンショット 2020-11-30 15.56.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/e75f1d3c-4934-d093-5933-82ee936b504b.png)

##データが重複していないかを確かめる
・urllistとwordテーブルについて、__要素は全て重複してはいけない__のだが、現段階ではこれを考慮していなかったため、重複が生じてしまっている。そこで、ここでは重複を確認する関数__「check()」__を実装する。
・重複の確認はSQL文の__「select」__メソッドで行う。復習であるが__「select カラム from テーブル where 条件式」__で条件式に一致するデータのみを抜き出せる。%記法で「value」に対応する「%s」の部分については、今回のvalueはURLか単語である（str型である）ので、__「''」__を忘れないようにする。
・上記を重複しているかどうかの__flag__とし、重複していなければ、渡されたテーブルのカラムにその値を格納する。(重複していればそのまま何もせずに返す)

・コード(%記法でない場合)![スクリーンショット 2020-11-30 17.08.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/889cda5b-8bf3-94aa-5a5b-9e5b6163f276.png)

・コード（%記法の場合）![スクリーンショット 2020-11-30 17.09.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/e5694f7e-2885-aba1-6395-48e132bf0f90.png)

##crawl関数を再定義
・現時点での__crawl__関数は、前項までで作成した「add_list()」関数と「add_url_list()」関数についての記載がなかったので、前者は「soup」を作成したタイミングで、引数に「conn,page,soup」を渡して実装し、各データセットをデータベースに加える。後者はURLの処理が終わり、newpagesにurlを格納するときに、引数で「conn,page,url」を渡して実装し、URLと単語の関係をデータベースに加える。

・コード![スクリーンショット 2020-11-30 17.39.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/1e5acd77-0f28-7988-a50c-8d8f552ae3bb.png)

#クローラーにクラスをまとめる
##クラスの使い方
・pythonでは、__クラス__を使うことで__いくつかのモジュール（関数）を一つにまとめる__ことができる。使い方は、単に__「class クラス名:」__のようにして、その中に関数を記述していけば良い。
・クラス内で定義した関数を、クラス内で使用したい時は__「self.関数()」__のようにする必要がある。共通の変数を使用する際も同様に、__「self.変数名=処理」__のように記述する。
・クラスを使用する際は__「C=クラス()」__のように、入れ物を作成する必要がある。この時の「C」を__「オブジェクト」__という。また、クラス内の関数を使用するときは__「オブジェクト.関数」__のように呼び出す。
・加えて、クラスには__「\_\_init\_\_()」__という特別な関数がある。これは__インスタンスメソッド__で、オブジェクトが生成された時に__必ず実行されるもの__である。よって、__全てのオブジェクトに共通して実行したい処理__はここに記載する。
・以下のコードは数字を計算するクラス__「calculate」__を作成したものである。「init」では「self.num」をクラスに渡される数字とすることを定義している。あとは四則演算の関数を作成している。ちなみに、各関数の引数にある「self」はinitにより定義される、クラスに渡された数字が入るので、新たに指定する必要はない。

##初期値の設定
・__「\_\_init\_\_()」__のような特別な関数は他にもある。一例として、同じクラスのオブジェクト同士で「＋」を使用したときに呼び出される__「\_\_add\_\_()」__、同様に「\*」を使用したときに呼び出される__「\_\_mul\_\_()」__、また、オブジェクトが削除されるときに呼び出される__「\_\_del\_\_()」__がある。
・以下では、「「\_\_init\_\_()」でデータベースへのコネクションを作成し、「\_\_del\_\_()」を使って__オブジェクトが削除されたときに接続を閉じる__ようにしている。

![スクリーンショット 2020-11-30 19.18.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/96cf3184-78ef-6603-1c93-c4a5d02298ba.png)

##関数をクラスに入れてまとめる
・ここでは、クローラーに関する関数はクラスにまとめることとする。具体的には「separate_word()」「check()」「add_list()」「add_url_list()」「crawl()」「create_table()」を、__「crawlerクラス」__に格納する。
・それぞれの第一引数は「self」とし、共通の変数を使用するときは「self」をつけるのを忘れないようにする。引数の方については、selfで呼び出される「init」関数で「conn」を作成しているので、元の引数にconnがある場合はその代わりにselfを入れる。

・コード![スクリーンショット 2020-11-30 21.39.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/5f61d5b3-4a52-7152-1ea5-a69dce4332a2.png)

#検索部分の作成
##クエリが含まれるURLを探す
・ここでは入力されたクエリを使って、クエリが含まれているURLを返す関数を作成していく。
・SQL文で、複数のテーブルから欲しいデータを取り出すには、select文について以下のように指定する。
__「select テーブル1.カラム テーブル2.カラム from テーブル1,テーブル2」__
また、from部分のテーブルの後ろに__別名を指定する__ことで、その名前の__同じテーブルを複製する__ことができる。以下では、wordlocationテーブルをそれぞれ__「w0」「w1」__とし、w0では「python」を指定し、w1では「recommend」を指定することで、その両方のwordを含むurlを抜き出している。

![スクリーンショット 2020-12-01 16.46.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/e7ba322f-6a79-c013-9b80-9d639c86ecc3.png)

・クエリが含まれるURLを返す関数__「get_match_rows()」__を実装する。流れは以下の通り。
　1:クエリを__単語に分割する__
　2:__単語が含まれているか__を判断するSQL文を作成する
　3:SQL文を実行してURLとクエリの単語の位置のリストを受け取る

・このうち「2」について、抜き出したいカラムを設定する「columnlist」、対象となるテーブルを設定する「tablelist」、条件文を設定する「conditionlist」を3つを作成することで、汎用的なSQL文を作成することができる。

・「1」について、まずは渡されたクエリ「q」について、__スペース区切りで単語ごとに分割__し、その各単語について、データベースのwordlistテーブルに存在していれば、その単語を「word_list」に格納する。そして、tablelistのwordlocationの部分には、「w + tablenumber」という名前で保存し、columnlistにはこのテーブルのlocationを追加し、conditionlistには、このテーブルのwordと現在のwordが一致しているという意味の条件文を格納する。また、二つ目以降の単語が追加された時は、conditionlistとtablelistを重複がないようにするために更新する。
・「2」について、__「select columnlist from tablelist where conditionlist」__というSQL文を作成することで、単語が含まれているかを判断するSQL文が記述できる。
・「3」について、このSQL文を実行することで、URLとクエリの単語の位置のリスト「rows」を受け取ることができる。また、words_listにない単語が入力された場合エラーになってしまうので、通常の場合は__try文__で記述し、それ以外は__except文__で、エラーが起こらないように、警告文とともに、rowsを空にして返すようにする。


・コード![スクリーンショット 2020-12-01 17.59.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/0a7d0a0c-6570-0bae-5bf3-57ad343a6b0c.png)

・結果![スクリーンショット 2020-12-01 18.00.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698700/cc9b951c-07f0-d551-3b93-254ec61361e5.png)

・ちなみに、この段階ではtablelistを通じて、wordlocationからURLを持ってきているので、__URLが重複してしまう__。Chapter3ではレコメンドに重みをつけ、URLも重複しないようにしていく。

#まとめ
・Chapter1で作成したデータセットを、__データベースのテーブルに保存する__。それぞれのテーブルのカラムにあったやり方で、データを分け、格納していく（例えばwordカラムなら単語を格納したいので、Chapter1の最後の方で行ったやり方で単語を分割し、それを格納する）。
・カラムへの値の代入は__「insert into テーブル(カラム) values(値)」__で行える。今回は「値」の部分で__％記法__を使って変数を代入している。
・関数が大量にできたら、整理するために__「クラス」__を使用すると良い。クラス内でクラスの関数を使用するときや、__「\_\_init\_\_」__で作成した共通の変数を使用するときは__「self」__をつける。それ以外は普通に関数を定義すれば良い。
・今回最後に作成した、クエリを単語に分割し、それを参照してURLを返す関数__「get_match_rows()」__はChapter3でも使用する。

今回は以上です。最後まで読んでいただき、ありがとうございました。