千葉県Go To EAT向けに店舗検索BOT(AI LINE BOT)を作った話(3)【python環境構築】の続きです。
今回は赤字の部分のデータ収集とAIモデル作成について記載します。
データ集め1(店舗情報)
Go To Eatクーポン券が利用可能なお店をサジェストするためには、お店データを集める必要があります。今回は、Go To Eat千葉のWEBサイトにてお店一覧がpdfファイルで記載されていたのでそれを利用しました。
↑2020/11/27日現在ダウンロード出来なくなっています。
↓以下のやり方で情報が集められるようです。
↓ダウンロード出来た一覧はこんな感じ(黒いところに店舗名、住所、電話番号が載っていました)
pdf → CSVの変換はいろいろやり方あるかと思いますが、今回は以下の方法を参考にさせていただきました。
データ集め2(ジャンル情報)
今回は料理のジャンルからお店をサジェストしたいので、グルメサイトの情報を利用させていただきました。検索エンジンにてお店の名前を検索し、出てきた食べログページのジャンル情報をスクレイピングしました。プログラムは以下です。
https://github.com/Haradasn/linebot
import csv
import time
import pprint
import requests
import urllib3
import pandas as pd
from bs4 import BeautifulSoup
import re
from requests.exceptions import Timeout
def connect_url(url,df,idx):
stand_by_sec = 5
try:
print(f"\nurl: {url}\tへ接続開始...")
u = requests.get(url, timeout=3.5)
soup = BeautifulSoup(u.content, "lxml")
elems = soup.find_all("a",href=re.compile('^.*tabelog.com/chiba')) #食べログ千葉のURLを抽出する
if not elems:
print("Nothing")
else:
for elem in elems:
if re.search(r'^https://.*/[0-9]{8}/',elem['href'][7:]) :
print(re.search(r'^https://.*/[0-9]{8}/',elem['href'][7:]).group())
urlName = re.search(r'^.*/[0-9]{8}/',elem['href'][7:]).group()
df[df.columns[7]][idx]= urlName
url = requests.get(urlName)
soup = BeautifulSoup(url.content, "lxml")
elems = soup.find_all("span",class_="linktree__parent-target-text")
for index,elem in enumerate(elems):
print(elem.text)
df[df.columns[8+index]][idx]= elem.text
break
except requests.exceptions.Timeout:
# タイムアウトした時は2秒待機して再実行
print(f"\rタイムアウトしました...\n再接続待機中...{stand_by_sec}秒後に再実行します", end="", flush=True)
time.sleep(stand_by_sec)
connect_url(url=url,df=df,idx=idx)
except requests.exceptions.ConnectionError:
# タイムアウトした時は2秒待機して再実行
print(f"\rタイムアウトしました...\n再接続待機中...{stand_by_sec}秒後に再実行します", end="", flush=True)
time.sleep(stand_by_sec)
connect_url(url=url,df=df,idx=idx)
except requests.exceptions.HTTPError:
# タイムアウトした時は2秒待機して再実行
print(f"\rタイムアウトしました...\n再接続待機中...{stand_by_sec}秒後に再実行します", end="", flush=True)
time.sleep(stand_by_sec)
connect_url(url=url,df=df,idx=idx)
except urllib3.exceptions.ReadTimeoutError:
# タイムアウトした時は2秒待機して再実行
print(f"\rタイムアウトしました...\n再接続待機中...{stand_by_sec}秒後に再実行します", end="", flush=True)
time.sleep(stand_by_sec)
connect_url(url=url,df=df,idx=idx)
else:
# 成功時の処理
return u,elems
finally:
# 後始末
pass
df = pd.read_csv('#千葉県の店舗一覧のCSVをロードする', header=None)
#元のcsvデータとは別に列を追加する
df = df.assign(url=0)
df = df.assign(elm1=0)
df = df.assign(elm2=0)
df = df.assign(elm3=0)
df = df.assign(elm4=0)
print(df['elm3'][11])
for idx,row in df.iterrows():
urlName = "https://www.google.com/search?q=" + re.sub(" ","+",re.sub("&","&",re.sub("-","",row[4])))+ "+" + row[6] + "+食べログ"
url,elems = connect_url(urlName,df,idx)
df.to_csv("#保存先を記載する")
print("終わり!")
上記のプログラムで既存のcsvに最寄り駅、ジャンル情報を追加しています。
最終的に以下のカラムを持つcsvデータを作成しました。
市 | 区 | 紙クーポン可否 | 電子クーポン可否 | 店名 | 住所 | 電話番号 | 食べログURL | 最寄り駅 | ジャンル1 | ジャンル2 | ジャンル3 | ジャンル4 |
---|
AIモデルの作成
お店をサジェストするAIは、gensimのdoc2vecを用いて作成しました。
作成に当たっては以下のページを参考にさせていただきました。
mecabにて形態素解析をして単語に分割したのちに、doc2vecに入力して学習させます。
店舗名をIDにジャンルや最寄り駅情報を紐づけて学習させています。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pandas as pd
import os
import sys
import MeCab
import collections
from gensim import models
from gensim.models.doc2vec import TaggedDocument
import re
import time
OUTPUT_MODEL = 'doc2vec.model'
# 文章から単語に分解して返す
def split_into_words(doc, name=''):
mecab = MeCab.Tagger("-Ochasen")
lines = mecab.parse(doc).splitlines()
words = []
for line in lines:
chunks = line.split('\t')
if len(chunks) > 3 and (chunks[3].startswith('形容詞') or (chunks[3].startswith('名詞') and not chunks[3].startswith('名詞-数'))):
words.append(chunks[0])
return TaggedDocument(words=words, tags=[name])
# データから単語のリストを取得
def corpus_to_sentences(corpus):
for idx,doc in enumerate(corpus.iterrows()):
sys.stdout.write('\r前処理中 {} / {}'.format(idx, len(corpus)))
# yield split_into_words(str(df[2][idx])+re.sub("駅|京成","",str(df[9][idx]))+str(df[11][idx])+str(df[12][idx])+str(df[13][idx])+str(df[5][idx]), df[5][idx])
yield split_into_words(str(df[2][idx])+re.sub("駅|京成","",str(df[9][idx]))+str(df[11][idx])+str(df[12][idx])+str(df[13][idx])+str(df[14][idx])[:5]+str(df[5][idx]), df[5][idx])
# 学習
def train(sentences):
model = models.Doc2Vec(dm=1,vector_size=300, sample=5e-5, min_count=0, workers=15,window=1,epochs=600,hs=1,negative=5)
model.build_vocab(sentences)
model.train(sentences, total_examples = model.corpus_count, epochs = model.epochs)
ranks = []
for doc_id in range(int(sum([len(sentence) for (sentence) in (sentences)])/2)):
inferred_vector = model.infer_vector(sentences[doc_id].words)
sims = model.docvecs.most_similar([inferred_vector], topn=len(model.docvecs))
rank = [docid for docid, sim in sims].index(sentences[doc_id].tags[0])
ranks.append(rank)
print(collections.Counter(ranks))
return model
if __name__ == '__main__':
df = pd.read_csv('#CSV情報', header=None)
sentences = list(corpus_to_sentences(df))
start = time.time()
model = train(sentences)
elapsed_time = time.time() - start
model.save(OUTPUT_MODEL)
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
モデル作成後に、検索ワードのベクトルを読み込ませ、動作を確認します。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import MeCab
import gensim.models.doc2vec as doc2vec
from gensim import models
from gensim.models.doc2vec import TaggedDocument
model = models.Doc2Vec.load('doc2vec.model')
def trim_doc(doc):
lines = doc.splitlines()
valid_lines = []
is_valid = False
horizontal_rule_cnt = 0
break_cnt = 0
for line in lines:
if horizontal_rule_cnt < 2 and '-----' in line:
horizontal_rule_cnt += 1
is_valid = horizontal_rule_cnt == 2
continue
if not(is_valid):
continue
if line == '':
break_cnt += 1
is_valid = break_cnt != 3
continue
break_cnt = 0
valid_lines.append(line)
return ''.join(valid_lines)
def split_into_words(doc):
mecab = MeCab.Tagger("-Ochasen")
valid_doc = doc
lines = mecab.parse(valid_doc).splitlines()
words = []
#print(lines)
for line in lines:
chunks = line.split('\t')
if len(chunks) > 3 and (chunks[3].startswith('形容詞') or (chunks[3].startswith('名詞') and not chunks[3].startswith('名詞-数'))):
words.append(chunks[0])
print("words=",words)
return words
# 似た文章を探す
def search_similar_texts(words):
x = model.infer_vector(words)
#print(x)
most_similar_texts = model.docvecs.most_similar([x])
for similar_text in most_similar_texts:
print(similar_text)
if __name__ == '__main__':
print('文字列入力:')
search_str = input()
words = split_into_words(search_str)
search_similar_texts(words)
文字列入力: ハンバーガー 船橋
('モスバーガー船橋本町店', 0.5699790716171265)
('船橋酒場ふなぞう', 0.5292383432388306)
('フレッシュネスバーガー船橋店', 0.5287526249885559)
('モスバーガー\u3000アリオ市原店', 0.4948973059654236)
('モスバーガー四街道店', 0.4904986619949341)
('フレッシュネスバーガーセブンパークアリオ柏店', 0.4869162440299988)
('ビッグテキサス船橋', 0.4765006899833679)
('和幸\u3000松戸西口店', 0.4758237600326538)
('モスバーガー\u3000イオン新浦安店', 0.4637131094932556)
('サーティワンアイスクリーム船橋イトーヨーカドー店', 0.4555884003639221)
なんとなくそれっぽい結果をサジェストしてくるようになりました!
これでAIモデル作成は終了です。