問題
VPS上でGoogleの検索結果のdescriptionを元に文章を作るプログラムを以下のようなコードで作成しているのですが,queryによってエラーが発生します。
Scraperでdescriptionを取得し,PrepareChainおよびGenerateTextで文章を作るのですが,
例えば,queryが
・1単語(ex. ”りんご”)→問題なし
・2単語(ex. ”りんご 製品”)→ほぼ問題なし
・3単語(ex. ”りんご 製品 良い”)→502 Bad Gateway多発
・サイト内検索(ex. ”site:yahoo.co.jp りんご”)→502 Bad Gateway多発
のようになります。
queryによってエラーの多寡が変わるので,views.pyのchain = PrepareChain(sample)より下,文章生成の部分をコメントアウトしてみたのですが,Scraperでurlやdescriptionをとるところまでは問題がないようでした。(どのqueryでもエラーなし)
これより,問題はPrepareChainやGenerateTextを実行するにあたって生じているのかと考えています。
いずれのqueryにおいても抽出されるdescriptionに大差はないと思うのですが,なぜqueryによってエラーが出たり出なかったりするのでしょうか?
対策と合わせて原因を教えていただきたいです。
なおローカルでrunserverする場合は,ほぼどのパターンでも問題なく,VPSより早く動きます。
環境
・さくらVPS
・Django
・MeCab
・googleモジュール
コード
views
1def search(request): 2 if request.method == 'GET': 3 query = request.GET.get('your_name').encode('utf-8') 4 try: 5 page_num = request.GET.get('page_num').encode('utf-8') 6 page_num = int(page_num) 7 except: 8 page_num = 1 9 10 s = Scraper(query, page_num) 11 sample = s[0] 12 urls = s[1] 13 samples = s[2] 14 15 chain = PrepareChain(sample) 16 triplet_freqs = chain.make_triplet_freqs() 17 chain.save(triplet_freqs, True) 18 19 generator = GenerateText() 20 try: 21 sentence_num = request.GET.get('sentence_num').encode('utf-8') 22 generator.n = int(sentence_num) 23 except: 24 generator.n = 15 25 gen_txt = generator.generate() 26 27 data = { 28 'your_name': gen_txt, 29 'urls': urls, 30 'samples': samples, 31 } 32 33 return render(request, 'app/webapp.html', data) 34
models
1from django.db import models 2import sqlite3 3import sys 4 5#PrepareChain 6import unittest 7import re 8import MeCab 9from collections import defaultdict 10 11#GenerateText 12import os.path 13import random 14 15#Scraper 16from bs4 import BeautifulSoup 17import urllib3 18import requests 19import chardet 20from urllib.parse import parse_qsl 21from urllib.parse import urlparse 22from google import google 23import ssl 24ssl._create_default_https_context = ssl._create_unverified_context 25 26 27class Text(models.Model): 28 query_text = models.CharField(max_length = 5000) 29 30 def __str__(self): 31 return self.query_text 32 33class Query(models.Model): 34 query_text = models.CharField(max_length = 40) 35 36 def __str__(self): 37 return self.query_text 38 39def Scraper(query, page_num): 40 #setup 41 urls = [] 42 samples = [] 43 44 query = query.decode('utf-8') 45 print(query) 46 search_results = google.search(query, page_num) 47 for result in search_results: 48 urls.append(result.link) 49 samples.append(result.description) 50 51 sample = ''.join(samples) 52 53 #Generating txt file 54 return [sample, urls, samples] 55 56class PrepareChain(object): 57 58 BEGIN = "__BEGIN_SENTENCE__" 59 END = "__END_SENTENCE__" 60 61 DB_PATH = "chain.db" 62 DB_SCHEMA_PATH = "schema.sql" 63 64 def __init__(self, text): 65 66 if isinstance(text, bytes): 67 text = text.decode('utf-8') 68 self.text = text 69 70 # 形態素解析用タガー 71 self.tagger = MeCab.Tagger('-Ochasen') 72 73 def make_triplet_freqs(self): 74 75 # 長い文章をセンテンス毎に分割 76 sentences = self._divide(self.text) 77 78 # 3つ組の出現回数 79 triplet_freqs = defaultdict(int) 80 81 # センテンス毎に3つ組にする 82 for sentence in sentences: 83 # 形態素解析 84 morphemes = self._morphological_analysis(sentence) 85 # 3つ組をつくる 86 triplets = self._make_triplet(morphemes) 87 # 出現回数を加算 88 for (triplet, n) in list(triplets.items()): 89 triplet_freqs[triplet] += n 90 91 return triplet_freqs 92 93 def _divide(self, text): 94 95 # 改行文字以外の分割文字(正規表現表記) 96 delimiter = "。|.|." 97 98 # 全ての分割文字を改行文字に置換(splitしたときに「。」などの情報を無くさないため) 99 text = re.sub(r"({0})".format(delimiter), r"\1\n", text) 100 101 # 改行文字で分割 102 sentences = text.splitlines() 103 104 # 前後の空白文字を削除 105 sentences = [sentence.strip() for sentence in sentences] 106 107 return sentences 108 109 def _morphological_analysis(self, sentence): 110 111 morphemes = [] 112 node = self.tagger.parseToNode(sentence) 113 while node: 114 if node.posid != 0: 115 try: 116 morpheme = node.surface 117 morphemes.append(morpheme) 118 except: 119 continue 120 node = node.next 121 return morphemes 122 123 def _make_triplet(self, morphemes): 124 125 # 3つ組をつくれない場合は終える 126 if len(morphemes) < 3: 127 return {} 128 129 # 出現回数の辞書 130 triplet_freqs = defaultdict(int) 131 132 # 繰り返し 133 for i in range(len(morphemes)-2): 134 triplet = tuple(morphemes[i:i+3]) 135 triplet_freqs[triplet] += 1 136 137 # beginを追加 138 triplet = (PrepareChain.BEGIN, morphemes[0], morphemes[1]) 139 triplet_freqs[triplet] = 1 140 141 # endを追加 142 triplet = (morphemes[-2], morphemes[-1], PrepareChain.END) 143 triplet_freqs[triplet] = 1 144 145 return triplet_freqs 146 147 def save(self, triplet_freqs, init=False): 148 149 # DBオープン 150 con = sqlite3.connect(PrepareChain.DB_PATH) 151 152 # 初期化から始める場合 153 if init: 154 # DBの初期化 155 with open(PrepareChain.DB_SCHEMA_PATH, "r") as f: 156 schema = f.read() 157 con.executescript(schema) 158 159 # データ整形 160 datas = [(triplet[0], triplet[1], triplet[2], freq) for (triplet, freq) in triplet_freqs.items()] 161 162 # データ挿入 163 p_statement = "insert into chain_freqs (prefix1, prefix2, suffix, freq) values (?, ?, ?, ?)" 164 con.executemany(p_statement, datas) 165 166 # コミットしてクローズ 167 con.commit() 168 con.close() 169 170 def show(self, triplet_freqs): 171 172 for triplet in triplet_freqs: 173 print("|".join(triplet), "\t", triplet_freqs[triplet]) 174 175 176class GenerateText(object): 177 """ 178 文章生成用クラス 179 """ 180 181 def __init__(self): 182 """ 183 初期化メソッド 184 @param n いくつの文章を生成するか 185 """ 186 self.n = 5 187 188 def generate(self): 189 """ 190 実際に生成する 191 @return 生成された文章 192 """ 193 # DBが存在しないときは例外をあげる 194 if not os.path.exists(PrepareChain.DB_PATH): 195 raise IOError("DBファイルが存在しません") 196 197 # DBオープン 198 con = sqlite3.connect(PrepareChain.DB_PATH) 199 con.row_factory = sqlite3.Row 200 201 # 最終的にできる文章 202 generated_text = "" 203 204 # 指定の数だけ作成する 205 for i in range(self.n): 206 text = self._generate_sentence(con) 207 generated_text += text 208 209 # DBクローズ 210 con.close() 211 212 return generated_text 213 214 def _generate_sentence(self, con): 215 """ 216 ランダムに一文を生成する 217 @param con DBコネクション 218 @return 生成された1つの文章 219 """ 220 # 生成文章のリスト 221 morphemes = [] 222 223 # はじまりを取得 224 first_triplet = self._get_first_triplet(con) 225 morphemes.append(first_triplet[1]) 226 morphemes.append(first_triplet[2]) 227 228 # 文章を紡いでいく 229 while morphemes[-1] != PrepareChain.END: 230 prefix1 = morphemes[-2] 231 prefix2 = morphemes[-1] 232 triplet = self._get_triplet(con, prefix1, prefix2) 233 morphemes.append(triplet[2]) 234 235 # 連結 236 result = "".join(morphemes[:-1]) 237 238 return result 239 240 def _get_chain_from_DB(self, con, prefixes): 241 """ 242 チェーンの情報をDBから取得する 243 @param con DBコネクション 244 @param prefixes チェーンを取得するprefixの条件 tupleかlist 245 @return チェーンの情報の配列 246 """ 247 # ベースとなるSQL 248 sql = "select prefix1, prefix2, suffix, freq from chain_freqs where prefix1 = ?" 249 250 # prefixが2つなら条件に加える 251 if len(prefixes) == 2: 252 sql += " and prefix2 = ?" 253 254 # 結果 255 result = [] 256 257 # DBから取得 258 cursor = con.execute(sql, prefixes) 259 for row in cursor: 260 result.append(dict(row)) 261 262 return result 263 264 def _get_first_triplet(self, con): 265 """ 266 文章のはじまりの3つ組をランダムに取得する 267 @param con DBコネクション 268 @return 文章のはじまりの3つ組のタプル 269 """ 270 # BEGINをprefix1としてチェーンを取得 271 prefixes = (PrepareChain.BEGIN,) 272 273 # チェーン情報を取得 274 chains = self._get_chain_from_DB(con, prefixes) 275 276 # 取得したチェーンから、確率的に1つ選ぶ 277 triplet = self._get_probable_triplet(chains) 278 279 return (triplet["prefix1"], triplet["prefix2"], triplet["suffix"]) 280 281 def _get_triplet(self, con, prefix1, prefix2): 282 """ 283 prefix1とprefix2からsuffixをランダムに取得する 284 @param con DBコネクション 285 @param prefix1 1つ目のprefix 286 @param prefix2 2つ目のprefix 287 @return 3つ組のタプル 288 """ 289 # BEGINをprefix1としてチェーンを取得 290 prefixes = (prefix1, prefix2) 291 292 # チェーン情報を取得 293 chains = self._get_chain_from_DB(con, prefixes) 294 295 # 取得したチェーンから、確率的に1つ選ぶ 296 triplet = self._get_probable_triplet(chains) 297 298 return (triplet["prefix1"], triplet["prefix2"], triplet["suffix"]) 299 300 def _get_probable_triplet(self, chains): 301 """ 302 チェーンの配列の中から確率的に1つを返す 303 @param chains チェーンの配列 304 @return 確率的に選んだ3つ組 305 """ 306 # 確率配列 307 probability = [] 308 309 # 確率に合うように、インデックスを入れる 310 for (index, chain) in enumerate(chains): 311 for j in range(chain["freq"]): 312 probability.append(index) 313 314 # ランダムに1つを選ぶ 315 chain_index = random.choice(probability) 316 317 return chains[chain_index] 318

あなたの回答
tips
プレビュー