Aidemy 2020/12/3
はじめに
こんにちは、んがょぺです!バリバリの文系ですが、AIの可能性に興味を持ったのがきっかけで、AI特化型スクール「Aidemy」に通い、勉強しています。ここで得られた知識を皆さんと共有したいと思い、Qiitaでまとめています。以前のまとめ記事も多くの方に読んでいただけてとても嬉しいです。ありがとうございます!
今回は、レコメンデーション発展の1つ目の投稿になります。どうぞよろしくお願いします。
*本記事は「Aidemy」での学習内容を「自分の言葉で」まとめたものになります。表現の間違いや勘違いを含む可能性があります。ご了承ください。
今回学ぶこと
・検索エンジンの作成
・SQLについて
・文章を単語に分ける
検索エンジンの作成:概要
手順など
・レコメンデーション発展では、Chapter3までで__検索エンジンを作成__していく。検索エンジンは__ページに含まれる単語や単語の位置__から__検索したい内容に的したものをレコメンドする__システムである。
・実装方法としては、文章から全ての単語を抜き出し、抜き出したものをキーとして検索をする__「転置インデックス」__を使う。また、レコメンデーションの分類としては、ページの情報を使うので、__コンテンツベース__であると言える。
・今回は、検索用データをスクレイピングで取得し、最も推薦度の高いURLを示すところまでを実装する。大まかな流れは以下の通り。
①Webページから__データを取得__し、データセットを作成する
②__sql__でデータセットを__データベース__に保存する
③データベースから検索した単語に適応するサイトのURLを表示可能にする
④表示の優先順位をデータから分析する
⑤推薦度の高いURLを表示する
①Webページからデータを取得し、データセットを作成する
Webページの情報取得
・スクレイピングにより、検索対象となるWebサイトのデータを取得する。ネット上のリンクを辿ってWebサイトを巡回し、Webページ上の情報を保存すること__を「クローリング」という。
・スクレイピングには「urllib」「BeautifulSoup」と言うモジュールを使用する。urllibはURLからHTMLを取得するためのライブラリであり、BeautifulSoupはurllibで取得した情報を操作するためのものである。
・以下はこの2つのライブラリを使ってURLからHTMLを取得し、BeautifulSoupで表示している。「urlopen()」でHTMLを取得し、「BeautifulSoup()」でクラスを変換し、プログラム上で本文を出力できるようにしている。引数の「parser」は変換する方法を指定するもので、HTMLの場合は「html.parser」__とすれば良い。
HTMLのタグ情報を取得する
・スクレイピングの際に__「タグ情報」も取得する必要がある。タグには、段落にされた文章を格納する「divタグ」や、他URLへのリンクを格納する「aタグ」がある。
・タグの取得は、前項で作成したBeautiful Soup型のデータ「soup」に対して「find_all('a')」のように指定することで行える。find_all()では指定した全てのタグを取得し、リストの形で保存される。また、指定したタグの最初の1つのみ取得したい時は「find()」__を使う。
タグの属性を取得する
・前項ではタグを取得したが、それぞれのタグには__「class」や「href」などの「属性」__と呼ばれるものがセットで含まれている。例えばhrefにはリンクが代入されている。このような特定の属性のみを取得したい時は、以下のように取得すれば良い。
クローラーの作成
・ここではaタグのhref属性を取得して、Webページの文章をスクレイピングしていく__「クローラー」を作成する。その手順は以下の通り。
1:出発点となる__URL__をプログラムに入力しておく
2:出発点のURLにはられているリ__ンクから新しくURLの情報を入手する
3:取得したURLを重複なくまとめ、Webページの文章をスクレイピングしていく
・ここで取得するURLについて、URLには__絶対URL__と__相対URL__がある。
・絶対URL__はWebページの情報を集約した__完全なURL__である。具体的には、「http」「https」から始まるものである。一方、相対URL__については、「別のURL(現在地:基底URL)を基準にして、新しい位置を示すURL」で、かけている部分が存在し、そこを別のURLで補う必要があるものである。URLとは別だが、基底URLはカレントディレクトリ、相対URLはそれを基準にした下位のディレクトリと同様のものであると言える。
・今回は__相対URLから絶対URLを作成する。「urllib.parse.urljoin()」で行える。第一引数は「基底URL」を指定し、第二引数には「相対URL」を指定する。
・また、URLには「#」__がつくことがあり、これを__アンカーリンク__という。URLは基本的にページの先頭に移動するが、アンカーリンクを指定することで、ページの好きな位置にアクセスすることができる。
・これらのことを考慮したクローラーは以下のようにして実装できる。
・__「crawl()」__について、引数には出発点となるURL(基底URL)と、URLをどこまで深く探索するかを指定するdepthがある。このdepthの回数分、以下のことを繰り返す。
・まずは条件を満たす__絶対URLを格納する「newpages」を定義する。引数で渡された「pages」のそれぞれ「page」について、前項までで行なったように、htmlを取得し、そのaタグを取得する(links)。このそれぞれ(link)について、「urllib.parse.urljoin()」__に、基底URLとしてpageを、相対URLとして__link['href']を渡して絶対URLに変換する。
・絶対パスに変換した「url」について、アンカーリンク「#」は「split()」で取り除き、同様に「''」__も取り除く必要がある(詳しくはChapter2参照)ので、こちらも除外する。最終的には、絶対パスのうち「http」のもので、かつ追加されていないものをnewpagesに格納する。そして、これをそのままpages_listに格納し、返す。これが以降で使うデータセットとなる。
データベース関連
SQLについて
・__SQL__とは、データベース言語__の一つである。SQLを使うことで、処理したデータの保存・出力が簡単に行える。今回は、分析しやすくするために、スクレイピングの結果をデータベースに保存する。pythonで使えるSQLである「sqlite3」を利用する。
・SQLの各名称について、データを一括でまとめた全体を「データベース」といい、それぞれのデータを保存したものを「テーブル」__という。
・SQLのデータベースを操作する流れは次のようになる。
1:データベースの作成
2:pythonとデータベースを繋げる
3:SQLでテーブルを作成する
4:テーブルにデータを入れる
5:テーブルからデータを取り出す
1:データベースの作成/2:pythonとデータベースを繋げる
・データベースは__「conn=sqlite3.connect()」で作成でき、以降は「conn」を操作することでデータベースを扱うことができるようになる。引数にはデータベースの名前を入力する。ちなみに、すでに同じ名前のデータベースが存在していた場合は、そのデータベースを呼び出す役割もある。また、データベースの名前につける拡張子はなんでも良い。
・プログラムを終了するときは「conn.close()」__のようにして接続を閉じる。
3:SQLでテーブルを作成する/4:テーブルにデータを入れる
・pythonでSQLを操作するときには、専用の__「SQL文」というもので記述__し、それを__「execute」メソッドで読み込ませる必要がある。ちなみに、executeメソッドの中で記述するSQL文は「""」で括る__。
・今回はこのSQL文を使って、__「テーブルとカラムの作成」と「カラムに値を代入」__を行う。__カラム__はテーブルの中にある、実際に値が代入される要素__そのものである。(テーブルがフォルダだとしたら、カラムはファイル)
・「テーブルとカラムの作成」は「conn.execute()」のなかで、「create table テーブル名(カラム名)」と記述することで行う。「カラムに値を代入」は「insert into テーブル名 values(値)」で行う。最後に、「conn.commit()」で値を更新する。
・また、テーブルを削除したいときは「drop table テーブル名」__とすれば良い。
5:テーブルからデータを取り出す
・テーブルからデータを取り出すときは「conn.execute()」のなかで__「select カラム from テーブル名」とする。条件を指定したい時は、テーブル名の後に__where__を入れる。
・カラムについて、全てのカラムを選択したい時は「*」を使う。また、取り出したデータについて、リスト化して取得したい時は「fetchall()」を使えば良い。取り出したデータの一番上の要素だけを取得したい時は「fetchone()」__を使う。
・以下では、前項までで作成したtestテーブルのうち、「カラムbの値が5である行のカラムa,c」の値を取得している。
実際にテーブルを作成
・今回の検索エンジンに使う__テーブルを4つ作成__する。一つ目が__「urllist」というテーブルで、カラムはスクレイピングしたページのURLを保存する「url」とする。二つ目が「wordlist」で、ページ内の単語を保存する「word」をカラムとする。三つ目が「wordlocation」で、カラムとしてはwordのあったURLを保存する「url」と、wordを保存する「word」に加え、wordのあった位置を保存する「location」を作成する。4つ目が「link」で、参照したページのURLを保存する「url_from」と、参照先のURLを保存する「url_to」をカラムとする。
・後者2つはページの重み付けをするときに使用する。
・pythonのindexに対応するものとして、「rowid」がある。値の識別番号のようなもので、値を設定すると自動的に振り分けられる。確認するには「select rowid from テーブル名」__のようにすれば良い。
単語を文章に分ける
HTMLから文章のみを抽出する
・BeautifulSoupによってスクレイピングすることは学習したが、今回は、そのうち「タグ」や「属性」のない文章のみを抽出するということを行う。
・今回は文章のみをstr型で取得する__「text」メソッドを使う。そのほかのメソッドとして、タグ内の要素が一つのもののみを取得する「string」、初めの要素のみ本文を取り出し、それ以降はタグ付きで取得する「contents」__がある。
正規表現
・前項で本文のみを抽出したが、出力結果を見ても__意味のなさそうな文字列が多い__。このような場合は__正規表現__を使って意味のある単語のみを抜き出すということを行う。
・日本語の場合は__分かち書き__を行う必要がある。今回は簡単化のため、英語のページでおこなう。
・正規表現を使った単語の抽出は__「re.split("正規表現",text)」で行う。正規表現部分に指定した文字を区切り文字としている。以下では「\W+」と指定することで、英数字以外の文字(無意味な文字)を__区切り文字__として単語のリストを作っている。また、各単語について、「lower()」__で小文字にしており、一文字の単語は除外している。
正規表現でコンパイル
・プログラミング言語から機会が理解できる言語に変換することを__コンパイル__と言い、今回はこれを正規表現で行う。前項と同じように「英数字以外を区切り文字として単語を抽出する」とき、正規表現部分を__「pattern=re.compile("正規表現")」__で別にコンパイルしておくことで、re.split()部分を「pattern.split(文章)」とするだけで良くなる。
まとめ問題
・最後に、ここまでで作成したものを関数化(separete_wordとcrawl)し、これらを使ってURLから単語を抽出し、出現頻度を数えて高い順に並び替えるということを行う。ここで取得した__出現頻度の高い単語__をキーに含めた場合、多くの記事が検索結果として出力されてしまい、望んだ検索結果が得られにくくなってしまうので、これらの単語は除外することとする。
・コードについて、まずは__crawl()関数を使うことで__URLから単語を取得__し、リスト(word_list)として返している。次に、この「word_list」の各単語(w)について、「words.extend(w)」とすることで、新しいリスト「words」に格納している。このwords内の各単語wordについて、{word:出現回数}__とする辞書common_wordに入っていなければ値を「1」とし、すでに入っていれば値を「+1」する。これにより、単語の出現回数__が数えられる。
・あとは、この出現回数順に__ソート__すれば良い。「sorted(辞書.items(), key=lambda x:-x[1])」__とすることで出現回数順にソートできる。最後に、出現回数の多いものから順に20番目までの単語を出力している。
まとめ
・検索エンジンの作成は、URLから取得したデータ(HTML)のaタグから、他サイトへのリンク(を含むデータ)を取得し、それについて、'href'属性を抜き出して、完全にリンクだけを抽出する。これを__絶対URL__に変換し、不要な情報を取り除くことで、適した検索結果が返される。
・また、次回以降で使用する__データベース__については、__「sqlite3.connect()」で作成することができる。また、「conn.execute()」のなかでSQL文を記述することで、データベースからデータを取得したり、テーブルを追加したりできる。
・タグや属性のない、単純な文章のみを取得するには、「text」__メソッドを使う。また、これで取得した文字列から、__正規表現__を使って意味のある文字のみを抽出すると良い。
今回は以上です。最後まで読んでいただき、ありがとうございました。