質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.58%

VPS上での文章生成:素材文によってBad Gatewayが生じることがある

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 525

bunks

score 30

問題

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モジュール

コード

def search(request):
    if request.method == 'GET':
        query = request.GET.get('your_name').encode('utf-8')
        try:
            page_num = request.GET.get('page_num').encode('utf-8')
            page_num = int(page_num)
        except:
            page_num = 1

        s = Scraper(query, page_num)
        sample = s[0]
        urls = s[1]
        samples = s[2]

        chain = PrepareChain(sample)
        triplet_freqs = chain.make_triplet_freqs()
        chain.save(triplet_freqs, True)

        generator = GenerateText()
        try:
            sentence_num = request.GET.get('sentence_num').encode('utf-8')
            generator.n = int(sentence_num)
        except:
            generator.n = 15
        gen_txt = generator.generate()

        data = {
            'your_name': gen_txt,
            'urls': urls,
            'samples': samples,
        }

        return render(request, 'app/webapp.html', data)
from django.db import models
import sqlite3
import sys

#PrepareChain
import unittest
import re
import MeCab
from collections import defaultdict

#GenerateText
import os.path
import random

#Scraper
from bs4 import BeautifulSoup
import urllib3
import requests
import chardet
from urllib.parse import parse_qsl
from urllib.parse import urlparse
from google import google
import ssl
ssl._create_default_https_context = ssl._create_unverified_context


class Text(models.Model):
    query_text = models.CharField(max_length = 5000)

    def __str__(self):
        return self.query_text

class Query(models.Model):
    query_text = models.CharField(max_length = 40)

    def __str__(self):
        return self.query_text

def Scraper(query, page_num):
    #setup
    urls = []
    samples = []

    query = query.decode('utf-8')
    print(query)
    search_results = google.search(query, page_num)
    for result in search_results:
        urls.append(result.link)
        samples.append(result.description)

    sample = ''.join(samples)

    #Generating txt file
    return [sample, urls, samples]

class PrepareChain(object):

    BEGIN = "__BEGIN_SENTENCE__"
    END = "__END_SENTENCE__"

    DB_PATH = "chain.db"
    DB_SCHEMA_PATH = "schema.sql"

    def __init__(self, text):

        if isinstance(text, bytes):
            text = text.decode('utf-8')
        self.text = text

        # 形態素解析用タガー
        self.tagger = MeCab.Tagger('-Ochasen')

    def make_triplet_freqs(self):

        # 長い文章をセンテンス毎に分割
        sentences = self._divide(self.text)

        # 3つ組の出現回数
        triplet_freqs = defaultdict(int)

        # センテンス毎に3つ組にする
        for sentence in sentences:
            # 形態素解析
            morphemes = self._morphological_analysis(sentence)
            # 3つ組をつくる
            triplets = self._make_triplet(morphemes)
            # 出現回数を加算
            for (triplet, n) in list(triplets.items()):
                triplet_freqs[triplet] += n

        return triplet_freqs

    def _divide(self, text):

        # 改行文字以外の分割文字(正規表現表記)
        delimiter = "。|.|\."

        # 全ての分割文字を改行文字に置換(splitしたときに「。」などの情報を無くさないため)
        text = re.sub(r"({0})".format(delimiter), r"\1\n", text)

        # 改行文字で分割
        sentences = text.splitlines()

        # 前後の空白文字を削除
        sentences = [sentence.strip() for sentence in sentences]

        return sentences

    def _morphological_analysis(self, sentence):

        morphemes = []
        node = self.tagger.parseToNode(sentence)
        while node:
            if node.posid != 0:
                try:
                    morpheme = node.surface
                    morphemes.append(morpheme)
                except:
                    continue
            node = node.next
        return morphemes

    def _make_triplet(self, morphemes):

        # 3つ組をつくれない場合は終える
        if len(morphemes) < 3:
            return {}

        # 出現回数の辞書
        triplet_freqs = defaultdict(int)

        # 繰り返し
        for i in range(len(morphemes)-2):
            triplet = tuple(morphemes[i:i+3])
            triplet_freqs[triplet] += 1

        # beginを追加
        triplet = (PrepareChain.BEGIN, morphemes[0], morphemes[1])
        triplet_freqs[triplet] = 1

        # endを追加
        triplet = (morphemes[-2], morphemes[-1], PrepareChain.END)
        triplet_freqs[triplet] = 1

        return triplet_freqs

    def save(self, triplet_freqs, init=False):

        # DBオープン
        con = sqlite3.connect(PrepareChain.DB_PATH)

        # 初期化から始める場合
        if init:
            # DBの初期化
            with open(PrepareChain.DB_SCHEMA_PATH, "r") as f:
                schema = f.read()
                con.executescript(schema)

            # データ整形
            datas = [(triplet[0], triplet[1], triplet[2], freq) for (triplet, freq) in triplet_freqs.items()]

            # データ挿入
            p_statement = "insert into chain_freqs (prefix1, prefix2, suffix, freq) values (?, ?, ?, ?)"
            con.executemany(p_statement, datas)

        # コミットしてクローズ
        con.commit()
        con.close()

    def show(self, triplet_freqs):

        for triplet in triplet_freqs:
            print("|".join(triplet), "\t", triplet_freqs[triplet])


class GenerateText(object):
    """
    文章生成用クラス
    """

    def __init__(self):
        """
        初期化メソッド
        @param n いくつの文章を生成するか
        """
        self.n = 5

    def generate(self):
        """
        実際に生成する
        @return 生成された文章
        """
        # DBが存在しないときは例外をあげる
        if not os.path.exists(PrepareChain.DB_PATH):
            raise IOError("DBファイルが存在しません")

        # DBオープン
        con = sqlite3.connect(PrepareChain.DB_PATH)
        con.row_factory = sqlite3.Row

        # 最終的にできる文章
        generated_text = ""

        # 指定の数だけ作成する
        for i in range(self.n):
            text = self._generate_sentence(con)
            generated_text += text

        # DBクローズ
        con.close()

        return generated_text

    def _generate_sentence(self, con):
        """
        ランダムに一文を生成する
        @param con DBコネクション
        @return 生成された1つの文章
        """
        # 生成文章のリスト
        morphemes = []

        # はじまりを取得
        first_triplet = self._get_first_triplet(con)
        morphemes.append(first_triplet[1])
        morphemes.append(first_triplet[2])

        # 文章を紡いでいく
        while morphemes[-1] != PrepareChain.END:
            prefix1 = morphemes[-2]
            prefix2 = morphemes[-1]
            triplet = self._get_triplet(con, prefix1, prefix2)
            morphemes.append(triplet[2])

        # 連結
        result = "".join(morphemes[:-1])

        return result

    def _get_chain_from_DB(self, con, prefixes):
        """
        チェーンの情報をDBから取得する
        @param con DBコネクション
        @param prefixes チェーンを取得するprefixの条件 tupleかlist
        @return チェーンの情報の配列
        """
        # ベースとなるSQL
        sql = "select prefix1, prefix2, suffix, freq from chain_freqs where prefix1 = ?"

        # prefixが2つなら条件に加える
        if len(prefixes) == 2:
            sql += " and prefix2 = ?"

        # 結果
        result = []

        # DBから取得
        cursor = con.execute(sql, prefixes)
        for row in cursor:
            result.append(dict(row))

        return result

    def _get_first_triplet(self, con):
        """
        文章のはじまりの3つ組をランダムに取得する
        @param con DBコネクション
        @return 文章のはじまりの3つ組のタプル
        """
        # BEGINをprefix1としてチェーンを取得
        prefixes = (PrepareChain.BEGIN,)

        # チェーン情報を取得
        chains = self._get_chain_from_DB(con, prefixes)

        # 取得したチェーンから、確率的に1つ選ぶ
        triplet = self._get_probable_triplet(chains)

        return (triplet["prefix1"], triplet["prefix2"], triplet["suffix"])

    def _get_triplet(self, con, prefix1, prefix2):
        """
        prefix1とprefix2からsuffixをランダムに取得する
        @param con DBコネクション
        @param prefix1 1つ目のprefix
        @param prefix2 2つ目のprefix
        @return 3つ組のタプル
        """
        # BEGINをprefix1としてチェーンを取得
        prefixes = (prefix1, prefix2)

        # チェーン情報を取得
        chains = self._get_chain_from_DB(con, prefixes)

        # 取得したチェーンから、確率的に1つ選ぶ
        triplet = self._get_probable_triplet(chains)

        return (triplet["prefix1"], triplet["prefix2"], triplet["suffix"])

    def _get_probable_triplet(self, chains):
        """
        チェーンの配列の中から確率的に1つを返す
        @param chains チェーンの配列
        @return 確率的に選んだ3つ組
        """
        # 確率配列
        probability = []

        # 確率に合うように、インデックスを入れる
        for (index, chain) in enumerate(chains):
            for j in range(chain["freq"]):
                probability.append(index)

        # ランダムに1つを選ぶ
        chain_index = random.choice(probability)

        return chains[chain_index]
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

まだ回答がついていません

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る