アイドルグループのTwitterアカウントをデータベース化する [Python][PostgreSQL][beautifulsoup]
つくるもの
主成分分析するために、アイドルグループのTwitterアカウントをデータベース化する。
▲データベースの構造
処理の流れ
おおまかな流れとしては、
- 2000年代以降の日本の女性アイドルグループの一覧 - WikipediaをPythonでスクレイピング
→ Wikipediaのグループ個別ページのURLをデータベース化
→ TwitterアカウントのURLをデータベース化
なお、アイドルグループには1から順にidを割り振り管理する。
テーブルは後から項目を追加しやすいように4つに分割した。
テーブルの説明
データベース名 : iddata
- idol_group_name
アイドルグループの名前を管理するテーブル
- idol_group_wiki_url
アイドルグループのwikipediaのURLを管理するテーブル
- not_idol_group_wiki_url
アイドルグループのwikipediaのURLでないものを管理するテーブル
再度スクレイピングするときに、このURLを弾くようにする
- idol_group_twitter_url
アイドルグループのTwitterのURLを管理するテーブル
公式アカウントのaccount_typeを「official」とし、それ以外は「other」とする
「official」はグループの代表アカウントにするため、1つのみとする
準備
Python環境設定
- Python3系をインストール (Anacondaを使うとよい)
- Pythonの各種ライブラリをインストール
- psycopg2 : pythonでPostgreSQLを扱うライブラリ
- BeautifulSoup : Webページから情報をスクレイピングするライブラリ
方法
データベースを作成
「iddata」という名前のデータベースを作成する
コマンドプロンプトで以下のように入力する。
psql -U postgres pass #postgreSQLインストール時に設定したPASSを入力 create database iddata;
もしくは、下記のdatabase.pyのif __name__ == '__main__':以下のコメントアウトを外して実行する。
データベースの確認
pgadmin 4を開いて、以下のようにiddataというデータベースが作成されていることを確認する。
スクリプトを作成
「iddata」など適当に名前をつけてフォルダを作成し、直下に以下のスクリプトを入れる
- iddata
- database.py
- wiki_crawler.py
database.py
- これはpythonからpostgreSQLへ接続するときに使用する。
- password="" には、postgreSQLインストール時のものを入力しておく。
#!/usr/bin/env python # -*- coding: utf-8 -*- import psycopg2 class DB(): def __init__(self, dbname): # データベースに接続 self.conn = psycopg2.connect(host="localhost", dbname=dbname, user="postgres", password="password") self.cur = self.conn.cursor() # データベースを新規作成 def create_db(self, new_dbname): # ※データベース新規作成時は、dbname="postgres"などの既存のデータベースに接続すること self.conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) self.cur = self.conn.cursor() self.cur.execute('CREATE DATABASE %s' % new_dbname) self.conn.commit() # SQL実行処理 (CREATE, DROP, DELETE, UPDATE,..など) def execute_sql(self, sql): self.cur.execute(sql) self.conn.commit() # リスト型データの挿入処理 # 例 insert('INSERT INTO twiurl (idol_group_id,twitter_name,twitter_url,account_type) VALUES (%s,%s,%s,%s)', [1002, 'myidol', 'https://hoge.com', 'other']) def insert(self, sql, list): self.cur.execute(sql, list) self.conn.commit() # リスト型データの取得処理 # 例 select('SELECT ota_follow_id, follow_num FROM otafollow WHERE idol_group_id = %s ORDER BY follow_num DESC LIMIT 100' % (idol_group_id)) def select(self, sql): self.cur.execute(sql) # tupleをlistに変換した形式で返す [(a,b),(c,d),..] => [[a,b],[c,d],..] rows = [list(i) for i in self.cur.fetchall()] # 1次元のリストならネストを削除 [[1],[2],[3]] => [1, 2, 3] if len(rows) >= 1: if len(rows[0]) == 1: values = list() for l in rows: values.append(l[0]) return values return rows # データベースを閉じる処理 def close(self): self.cur.close() self.conn.close() if __name__ == '__main__': """ データベース新規作成時に実行 db = DB("postgres") db.create_db("iddata") db.close() """
wiki_crawler.py
これは、WikipediaからアイドルグループのWikipediaURLとTwitterURLをスクレイピングする
WikiClawl()クラスをインスタンス化し、その中の以下のメソッドを実行している
- idol_group_wiki_url()で個別アイドルグループのWikipediaURLを取得
- アイドルグループのリストが入っている<div>タグをclass名を指定して抜き出し、さらにそこから<a>タグを抜き出す
- <a>タグからURLとタイトルを抜き出し、データベースへ挿入
- ひとつずつ手作業で確認し、y (登録)/ n(登録しない) / skip (スキップ)で処理を選択
- idol_group_twitter_url()で個別アイドルグループのTwitterURLを取得
#!/usr/bin/env python # -*- coding: utf-8 -*- import requests from bs4 import BeautifulSoup import re import difflib from database import DB from urllib.parse import urljoin class WikiCrawl(): def __init__(self): pass # アイドルグループのwikipediaのURLを取得 def idol_group_wiki_url(self): db = DB('iddata') # 女性アイドルグループのURL base_url = 'https://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E3%81%AE%E5%A5%B3%E6%80%A7%E3%82%A2%E3%82%A4%E3%83%89%E3%83%AB%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%81%AE%E4%B8%80%E8%A6%A7' r = requests.get(base_url) content = r.content soup = BeautifulSoup(content, 'html.parser') # すべての該当クラスの<div>タグをリストで返す ex. [<div class="hoge">~</div>, <div>~</div>,...,<div>~</div>] divs = soup.find_all('div', class_='div-col columns column-count column-count-2') # 各<div>タグの要素から<a>タグを抜き出し、グループコード,グループ名,URLを抜き出す(80,90年代はパス) for div in divs[2:]: idol_groups = div.find_all('a') for idol_group in idol_groups: # 相対パスを絶対パスに変換して取得 url = urljoin(base_url, idol_group.get('href')) name = idol_group.text # データベースに登録済みか確認 pass_url = list() pass_url.extend(db.select('SELECT url FROM idol_group_wiki_url;')) pass_url.extend(db.select('SELECT url FROM not_idol_group_wiki_url;')) if url in pass_url: continue # idol_group_idを設定 max_id = db.select('SELECT MAX(idol_group_id) FROM idol_group_name;')[0] if max_id is None: id = 1 else: id = max_id + 1 # データベースへの登録処理 print(id, name, url) command = input('新規アイドルグループに登録しますか? (y/n/skip) >>') if command is 'y': db.insert('INSERT INTO idol_group_name (idol_group_id, idol_group_name) VALUES (%s,%s)', [id, name]) db.insert('INSERT INTO idol_group_wiki_url (idol_group_id, url) VALUES (%s,%s)', [id, url]) print('登録しました') elif command is 'n': db.insert('INSERT INTO not_idol_group_wiki_url (not_idol_group_name, url) VALUES (%s,%s)', [name, url]) print('URLを除外リストに挿入しました') else: print('スキップしました') db.close() # アイドルグループのtwitterのURLを取得 def idol_group_twitter_url(self): db = DB('iddata') id_name_wikiurls = db.select('SELECT N.idol_group_id, N.idol_group_name, W.url FROM idol_group_name AS N INNER JOIN idol_group_wiki_url AS W ON N.idol_group_id = W.idol_group_id') for id_name_wikiurl in id_name_wikiurls: # wikipediaの個別アイドルグループのURLをクロール res = requests.get(id_name_wikiurl[2]) content = res.content soup = BeautifulSoup(content, 'html.parser') # twitter.comを含む<a>タグをlistで取得 twitter_a = soup.find_all('a', href=re.compile("twitter.com")) # <a>タグからTwitterURLとツイッター名を取得しlist化 twitter_name_urls = list() for a in twitter_a: twitter_url = a.get('href') twitter_name = a.text # すでにURLがDBに登録されていたらスキップ db_twitter_url = db.select("SELECT url FROM idol_group_twitter_url WHERE idol_group_id = %s AND url = '%s'" % (id_name_wikiurl[0], twitter_url)) if len(db_twitter_url) > 0: continue # URLに特定の文字列が含まれていれば、スキップ if '/status' in twitter_url: continue twitter_name_urls.append([twitter_name, twitter_url]) # 追加twitter_name_urlsが空ならスキップ if len(twitter_name_urls) == 0: continue # twitter_name_urlsリストの各先頭にtargetsリストのマッチ度を挿入 targets = ["公式", "運営", "オフィシャル", "スタッフ", "staff","OFFICIAL"] targets.append(id_name_wikiurl[1]) for i, name_url in enumerate(twitter_name_urls): match_ratio = 0 for target in targets: match_ratio += difflib.SequenceMatcher(None, name_url[0], target).ratio() twitter_name_urls[i].insert(0, match_ratio) # [[match_ratio, idol_group_name, twitter_url],..]のリストをマッチ度の高い順にソート twitter_match_name_urls = twitter_name_urls twitter_match_name_urls.sort(reverse=True) print(id_name_wikiurl[0], id_name_wikiurl[1]) print('データベースのTwitterURLリスト') print(db.select('SELECT idol_group_id, twitter_name, url, account_type FROM idol_group_twitter_url WHERE idol_group_id = %s' % id_name_wikiurl[0])) print('追加するTwitterURLリスト') print(twitter_match_name_urls) command = input('%sをofficialにしますか?(y/n) >>' % (twitter_match_name_urls[0])) if command is 'y': # データベース内の特定アイドルグループのtwitterURLのアカウントタイプをすべてotherに更新 db.execute_sql("UPDATE idol_group_twitter_url SET account_type = 'other' WHERE idol_group_id = %s" % id_name_wikiurl[0]) # TwitterURLを挿入 for count, match_name_url in enumerate(twitter_match_name_urls): # マッチ度が高いURLはofficialにして挿入 if count == 0: db.insert('INSERT INTO idol_group_twitter_url (idol_group_id, twitter_name, url, account_type) VALUES (%s,%s,%s,%s)', [id_name_wikiurl[0], match_name_url[1], match_name_url[2], 'official']) continue db.insert('INSERT INTO idol_group_twitter_url (idol_group_id, twitter_name, url, account_type) VALUES (%s,%s,%s,%s)', [id_name_wikiurl[0], match_name_url[1], match_name_url[2], 'other']) print('officialで登録しました') else: # TwitterURLをotherにして挿入 for match_name_url in twitter_match_name_urls: db.insert( 'INSERT INTO idol_group_twitter_url (idol_group_id, twitter_name, url, account_type) VALUES (%s,%s,%s,%s)', [id_name_wikiurl[0], match_name_url[1], match_name_url[2], 'other']) print('otherで登録しました') # テーブルの作成処理 def create_table(): db = DB('iddata') db.execute_sql("CREATE TABLE idol_group_name (idol_group_id integer PRIMARY KEY, idol_group_name varchar(255)) WITH OIDS;") db.execute_sql("CREATE TABLE idol_group_wiki_url (idol_group_id integer PRIMARY KEY, url varchar(255)) WITH OIDS;") db.execute_sql("CREATE TABLE not_idol_group_wiki_url (not_idol_group_name varchar(255), url varchar(255)) WITH OIDS;") db.execute_sql("CREATE TABLE idol_group_twitter_url (idol_group_id integer, twitter_name varchar(255), url varchar(255), account_type varchar(255)) WITH OIDS;") db.close() if __name__ == '__main__': # テーブルの作成処理(初回のみ実行する) create_table() # WikipediaからグループのWikipedia個別URLをスクレイピングする crawl = WikiCrawl() crawl.idol_group_wiki_url() # WikipediaからTwitterURLをスクレイピングする crawl.idol_group_twitter_url()
実行例
idol_group_wiki_url()
1 ハロー!プロジェクト https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%AD%E3%83%BC!%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88 新規アイドルグループに登録しますか? (y/n/skip) >>n URLを除外リストに挿入しました 1 モーニング娘。 https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E5%A8%98%E3%80%82 新規アイドルグループに登録しますか? (y/n/skip) >>y 登録しました 2 タンポポ https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%B3%E3%83%9D%E3%83%9D_(%E3%83%8F%E3%83%AD%E3%83%BC!%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88) 新規アイドルグループに登録しますか? (y/n/skip) >> スキップしました
idol_group_twitter_url()
15 カントリー・ガールズ データベースのTwitterURLリスト [] 追加するTwitterURLリスト [[1.125, 'カントリー・ガールズ', 'https://twitter.com/countrygirls_uf']] [1.125, 'カントリー・ガールズ', 'https://twitter.com/countrygirls_uf']をofficialにしますか?(y/n) >>y officialで登録しました 22 ℃-ute データベースのTwitterURLリスト [] 追加するTwitterURLリスト [[1.2, '℃-ute', 'https://twitter.com/Cute_upfront']] [1.2, '℃-ute', 'https://twitter.com/Cute_upfront']をofficialにしますか?(y/n) >>y officialで登録しました
後処理
アイドルグループ名やURLを直接編集する場合はpgadminからテーブルを開き、直接編集することができる。
このとき、左上のSaveを押さなければ保存されないので注意。
まとめ
このように、半手作業ではあるがアイドルグループのアカウントをデータベース化できた。
次回以降は、このデータを使ってアイドルグループの主成分分析をする。