Python
自然言語処理
NLP
Webスクレイピング
COTOHA

NTT40年の自然言語処理技術を結集して「いつどこで誰が何をどうしたゲーム」を作った 【Python & Webスクレイピング & COTOHA API】

やりたいこと

  • Python素人なので勉強したい。
  • Webスクレイピング素人なので実装してみたい。
  • 自然言語処理(NLP)素人なので使ってみたい。

→PythonでWebスクレイピングしてその結果を言語処理するアプリケーションを作る。

作ったもの

スクリプトに引数として単語を与えると、その単語のWikipedia記事を解析。
「いつ」「どこで」「だれが」「なにを」「どうした」に分解し、ランダムにそれらを組み合わせて表示する。

動作例 (豊臣秀吉のページ解析)

$ python cotoha_api_wikipedia.py "豊臣秀吉"
word:豊臣秀吉
text_number:170
100%|███████████████████████████████████████████████████████████| 170/170 [00:40<00:00,  4.73it/s]
------------------いつどこで誰が何をどうした------------------
9月4日 、長浜 で豊国神社 は文治派 に分か る 。
3月21日 には茶器 と滝川一益 も出 で あ る と継 ぎ 、
慶長2年秀吉 に日本軍 は 、大敗 を応じ な かった
3月21日 には戦い で囲碁 は 、12万 石 が滅ぼ さ れ た 。
天正15年家康 に大友宗麟 が自害 を派遣 し た 。
早朝 、本能寺の変 でする の は兵糧 を( 中国 大 返し ) 。
12月 、大仏殿 裏手 に自分 はこと が回 り 、
慶長2年妙法寺 には 、もの は敵 を支持 し 、
永禄11年政権 に秀吉 自身 は危篤 を受け た が 、
生涯 は味方 に豊国神社 が環境 の控え て お い て くださ い 。
以後関白 に滝川一益 は 、これ を用例 で あ り 、
被 成 御 煩 候 内 に支配下 に硬化 さ せ る の は 、城郭 群 を本領安堵 、
天正11年陣 で1つ は居城 ・ 桑名城 を応じ な かった
天文23年勝手 にポルトガル 本国 が全国 を著 し た
7月11日 には 、雑 器 に秀吉 自身 は与力 と与え 、
21日 、中 で 、援軍 が全権 を挙げ る 。
2月28日 、秘密 に秀吉 は虐待 し て い る 」 と推測 し て い る 。
きっかけ として 、陣 でルソン壷 が 、売買 さ れ て い た の を恵まれ な かった が 、
後 に投稿 内容 が秀吉 は 、世 を有名 で あ る が 、
12月 、「 お前 に光秀 は説明 が勝 る よう に な っ た の で あ る 。
--------------------------------------------------------------

1割くらい味わい深いのがでてくる。

環境

Mac OS X 0.13.6
Python 3.7.0

Webスクレイピング

【BeautifulSoupを使ってWikipediaのテキストを抽出する】のページを参考にした。
BeautifulSoupというライブラリを使うと簡単にできるらしい。

Wikipedia記事をスクレイピング

試しに「MeCab」についての記事で、<p>タグで囲まれているものを抜き出してみる。

インストール (クリックで展開)
$ pip install beautifulsoup4

コード (クリックで展開)
scraping.py
import urllib.parse as parser
import urllib.request
from bs4 import BeautifulSoup

link = "https://ja.wikipedia.org/wiki/"
with urllib.request.urlopen(link + parser.quote_plus("MeCab")) as response:
    html = response.read().decode("utf-8")
soup = BeautifulSoup(html,'html.parser')
p_tags = soup.find_all("p") #<p>タグを全て取り出す
for sub in p_tags:
    print(sub.get_text()) #<p>タグ内のテキストのみprintする

出力結果 (クリックで展開)
$ python scraping.py 
MeCabはオープンソースの形態素解析エンジンで、奈良先端科学技術大学院大学出身、現GoogleソフトウェアエンジニアでGoogle 日本語入力開発者の一人である工藤拓[1][2]によって開発されている。名称は開発者の好物「和布蕪(めかぶ)」から取られた。

開発開始当初はChaSenを基にし、ChaSenTNGという名前で開発されていたが、現在はChaSenとは独立にスクラッチから開発されている。ChaSenに比べて解析精度は同程度で、解析速度は平均3-4倍速い。

品詞情報を利用した解析・推定を行うことができる。MeCabで利用できる辞書はいくつかあるが、ChaSenと同様にIPA品詞体系で構築されたIPADICが一般的に用いられている。

MeCabはGoogleが公開した大規模日本語n-gramデータの作成にも使用された[3]。

Mac OS X v10.5及びv10.6のSpotlightやiPhone OS 2.1以降の日本語入力にも利用されている[4][5]。


簡単にできた。

自然言語処理

【自然言語処理を簡単に扱えると噂のCOTOHA APIをPythonで使ってみた】の記事を参考にした。
NTTコミュニケーションズ株式会社の提供しているCOTOHA APIというものを使う。
NTTグループの40年以上にわたる研究成果、らしい。継ぎ足しのタレ感ある。

適当な文章を構文解析

試しに「田中さんは昨日草原ではちみつを食べた」という文章をParse APIで構文解析してみる。

コード (クリックで展開) (注. 上記記事の「config.ini」というファイルが別途必要)
cotoha_api_python3_test.py
# -*- coding:utf-8 -*-

import os
import sys
import urllib.parse as parser
import urllib.request
import json
import configparser
import codecs
import re
from bs4 import BeautifulSoup

# COTOHA API操作用クラス
class CotohaApi:
    # 初期化
    def __init__(self, client_id, client_secret, developer_api_base_url, access_token_publish_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.developer_api_base_url = developer_api_base_url
        self.access_token_publish_url = access_token_publish_url
        self.getAccessToken()

    # アクセストークン取得
    def getAccessToken(self):
        # アクセストークン取得URL指定
        url = self.access_token_publish_url

        # ヘッダ指定
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }

        # リクエストボディ指定
        data = {
            "grantType": "client_credentials",
            "clientId": self.client_id,
            "clientSecret": self.client_secret
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()

        # リクエスト生成
        req = urllib.request.Request(url, data, headers)

        # リクエストを送信し、レスポンスを受信
        res = urllib.request.urlopen(req)

        # レスポンスボディ取得
        res_body = res.read()

        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)

        # レスポンスボディからアクセストークンを取得
        self.access_token = res_body["access_token"]


    # 構文解析API
    def parse(self, sentence):
        # 構文解析API URL指定
        url = self.developer_api_base_url + "v1/parse"
        # ヘッダ指定
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        # リクエストボディ指定
        data = {
            "sentence": sentence
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()
        # リクエスト生成
        req = urllib.request.Request(url, data, headers)
        # リクエストを送信し、レスポンスを受信
        try:
            res = urllib.request.urlopen(req)
        # リクエストでエラーが発生した場合の処理
        except urllib.request.HTTPError as e:
            # ステータスコードが401 Unauthorizedならアクセストークンを取得し直して再リクエスト
            if e.code == 401:
                print ("get access token")
                self.access_token = getAccessToken(self.client_id, self.client_secret)
                headers["Authorization"] = "Bearer " + self.access_token
                req = urllib.request.Request(url, data, headers)
                res = urllib.request.urlopen(req)
            # 401以外のエラーなら原因を表示
            else:
                print ("<Error> " + e.reason)

        # レスポンスボディ取得
        res_body = res.read()
        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)
        # レスポンスボディから解析結果を取得
        return res_body

if __name__ == '__main__':
    # ソースファイルの場所取得
    APP_ROOT = os.path.dirname(os.path.abspath( __file__)) + "/"

    # 設定値取得
    config = configparser.ConfigParser()
    config.read(APP_ROOT + "config.ini")
    CLIENT_ID = config.get("COTOHA API", "Developer Client id")
    CLIENT_SECRET = config.get("COTOHA API", "Developer Client secret")
    DEVELOPER_API_BASE_URL = config.get("COTOHA API", "Developer API Base URL")
    ACCESS_TOKEN_PUBLISH_URL = config.get("COTOHA API", "Access Token Publish URL")

    # COTOHA APIインスタンス生成
    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)

    # 解析対象文
    sentence = "田中さんは昨日草原ではちみつを食べた"

    # 構文解析API実行
    result = cotoha_api.parse(sentence)

    # 出力結果を見やすく整形
    result_formated = json.dumps(result, indent=4, separators=(',', ': '))
    print (codecs.decode(result_formated,'unicode-escape'))

出力結果 (クリックで展開)
$ python cotoha_api_python3_test.py
sentence:田中さんは昨日草原ではちみつを食べた
{
    "result": [
        {
            "chunk_info": {
                "id": 0,
                "head": 4,
                "dep": "D",
                "chunk_head": 1,
                "chunk_func": 2,
                "links": []
            },
            "tokens": [
                {
                    "id": 0,
                    "form": "田中",
                    "kana": "タナカ",
                    "lemma": "田中",
                    "pos": "名詞",
                    "features": [
                        "固有",
                        "姓",
                        "組織"
                    ],
                    "attributes": {}
                },
                {
                    "id": 1,
                    "form": "さん",
                    "kana": "サン",
                    "lemma": "さん",
                    "pos": "名詞接尾辞",
                    "features": [
                        "名詞"
                    ],
                    "dependency_labels": [
                        {
                            "token_id": 0,
                            "label": "name"
                        },
                        {
                            "token_id": 2,
                            "label": "case"
                        }
                    ],
                    "attributes": {}
                },
                {
                    "id": 2,
                    "form": "は",
                    "kana": "ハ",
                    "lemma": "は",
                    "pos": "連用助詞",
                    "features": [],
                    "attributes": {}
                }
            ]
        },
        {
            "chunk_info": {
                "id": 1,
                "head": 4,
                "dep": "D",
                "chunk_head": 0,
                "chunk_func": 0,
                "links": []
            },
            "tokens": [
                {
                    "id": 3,
                    "form": "昨日",
                    "kana": "サクジツ",
                    "lemma": "昨日",
                    "pos": "名詞",
                    "features": [
                        "日時"
                    ],
                    "dependency_labels": [],
                    "attributes": {}
                }
            ]
        },
        {
            "chunk_info": {
                "id": 2,
                "head": 4,
                "dep": "D",
                "chunk_head": 0,
                "chunk_func": 1,
                "links": []
            },
            "tokens": [
                {
                    "id": 4,
                    "form": "草原",
                    "kana": "ソウゲン",
                    "lemma": "草原",
                    "pos": "名詞",
                    "features": [],
                    "dependency_labels": [
                        {
                            "token_id": 5,
                            "label": "case"
                        }
                    ],
                    "attributes": {}
                },
                {
                    "id": 5,
                    "form": "で",
                    "kana": "デ",
                    "lemma": "で",
                    "pos": "格助詞",
                    "features": [
                        "連用"
                    ],
                    "attributes": {}
                }
            ]
        },
        {
            "chunk_info": {
                "id": 3,
                "head": 4,
                "dep": "D",
                "chunk_head": 0,
                "chunk_func": 1,
                "links": []
            },
            "tokens": [
                {
                    "id": 6,
                    "form": "はちみつ",
                    "kana": "ハチミツ",
                    "lemma": "蜂蜜",
                    "pos": "名詞",
                    "features": [],
                    "dependency_labels": [
                        {
                            "token_id": 7,
                            "label": "case"
                        }
                    ],
                    "attributes": {}
                },
                {
                    "id": 7,
                    "form": "を",
                    "kana": "ヲ",
                    "lemma": "を",
                    "pos": "格助詞",
                    "features": [
                        "連用"
                    ],
                    "attributes": {}
                }
            ]
        },
        {
            "chunk_info": {
                "id": 4,
                "head": -1,
                "dep": "O",
                "chunk_head": 0,
                "chunk_func": 1,
                "links": [
                    {
                        "link": 0,
                        "label": "agent"
                    },
                    {
                        "link": 1,
                        "label": "time"
                    },
                    {
                        "link": 2,
                        "label": "place"
                    },
                    {
                        "link": 3,
                        "label": "object"
                    }
                ],
                "predicate": [
                    "past"
                ]
            },
            "tokens": [
                {
                    "id": 8,
                    "form": "食べ",
                    "kana": "タベ",
                    "lemma": "食べる",
                    "pos": "動詞語幹",
                    "features": [
                        "A"
                    ],
                    "dependency_labels": [
                        {
                            "token_id": 1,
                            "label": "nsubj"
                        },
                        {
                            "token_id": 3,
                            "label": "nmod"
                        },
                        {
                            "token_id": 4,
                            "label": "nmod"
                        },
                        {
                            "token_id": 6,
                            "label": "dobj"
                        },
                        {
                            "token_id": 9,
                            "label": "aux"
                        }
                    ],
                    "attributes": {}
                },
                {
                    "id": 9,
                    "form": "た",
                    "kana": "タ",
                    "lemma": "た",
                    "pos": "動詞接尾辞",
                    "features": [
                        "終止"
                    ],
                    "attributes": {}
                }
            ]
        }
    ],
    "status": 0,
    "message": ""
}


簡単にできた。

意味関係解析

上記そのままだと「いつ」「どこで」等を抜き出してくるのが難しい。
COTOHA API:構文解析の出力結果をグラフ化してみたの記事を参考にして、
上記の構文解析の結果を係り受け解析してみる。

コード (クリックで展開)
    #関数定義追加
    def extract_dependency_info(self,jsonfile):
        chunkid_text_dict = dict()
        dependency_info = list()

        # 解析結果(json)から係り受け情報を抽出
        for chunk in jsonfile["result"]:
            chunk_id = chunk["chunk_info"]["id"]
            tokens = [token["form"] for token in chunk["tokens"]]
            chunkid_text_dict[chunk_id] = " ".join(tokens)
            for link in chunk["chunk_info"]["links"]:
                dependency_info.append([chunk_id,link["link"],link["label"]])

        return dependency_info,chunkid_text_dict


if __name__ == '__main__':
    # ソースファイルの場所取得
    APP_ROOT = os.path.dirname(os.path.abspath( __file__)) + "/"

    # 設定値取得
    config = configparser.ConfigParser()
    config.read(APP_ROOT + "config.ini")
    CLIENT_ID = config.get("COTOHA API", "Developer Client id")
    CLIENT_SECRET = config.get("COTOHA API", "Developer Client secret")
    DEVELOPER_API_BASE_URL = config.get("COTOHA API", "Developer API Base URL")
    ACCESS_TOKEN_PUBLISH_URL = config.get("COTOHA API", "Access Token Publish URL")

    # COTOHA APIインスタンス生成
    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)

    # 解析対象文
    sentence = "田中さんは昨日草原ではちみつを食べた"

    #コマンドライン引数があればそっちをセンテンスに設定
    args = sys.argv
    if len(args) == 2:
        sentence = str(args[1])
    print ("sentence:"+sentence)

    # 構文解析API実行
    result = cotoha_api.parse(sentence)

    # 出力結果を見やすく整形
    result_formated = json.dumps(result, indent=4, separators=(',', ': '))
    #print (codecs.decode(result_formated,'unicode-escape'))

    #chunkとlink抽出
    chunkid_text_dict = dict()
    dependency_info = list()
    dependency_info,chunkid_text_dict = cotoha_api.extract_dependency_info(result)
    print(chunkid_text_dict)
    print(dependency_info)

結果 (クリックで展開)
$ python cotoha_api_python3.py
sentence:田中さんは昨日草原ではちみつを食べた
{0: '田中 さん は', 1: '昨日', 2: '草原 で', 3: 'はちみつ を', 4: '食べ た'}
[[4, 0, 'agent'], [4, 1, 'time'], [4, 2, 'place'], [4, 3, 'object']]

chunkに分けられた結果と、その関係性がでてくる。
ラベルの意味は意味関係ラベル一覧に書いてある。
例えば[4, 0, 'agent']の意味は、
4番目のchunkである'食べ た'が動作 (どうした)、
0番目のchunkである'田中 さん は'がその主体 (誰が)、
ということなのだろう。多分。
同様に、'time'で時間(いつ)、'place'で場所 (どこで)、'object'で目的語 (何を)が取れるので、
それらをそれぞれlistとして格納してランダムで出力することで、目的のものを作る。

(結果)Webスクレイピング結果を自然言語処理してみる

最終的に全てまとめたコードが以下。
進捗表示用にtqdmというライブラリを入れたので追加でインストールが必要。
第1引数に解析したいWikipediaの単語、第2引数に生成する文章の数を入力する。

インストール (クリックで展開)
$ pip install beautifulsoup4 #再掲
$ pip install tqdm

コード (クリックで展開)
cotoha_api_wikipedia.py
# -*- coding:utf-8 -*-

import os
import sys
import urllib.parse as parser
import urllib.request
import json
import configparser
import codecs
import re
import random
from bs4 import BeautifulSoup
from tqdm import tqdm

# COTOHA API操作用クラス
class CotohaApi:
    # 初期化
    def __init__(self, client_id, client_secret, developer_api_base_url, access_token_publish_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.developer_api_base_url = developer_api_base_url
        self.access_token_publish_url = access_token_publish_url
        self.getAccessToken()

    # アクセストークン取得
    def getAccessToken(self):
        # アクセストークン取得URL指定
        url = self.access_token_publish_url

        # ヘッダ指定
        headers={
            "Content-Type": "application/json;charset=UTF-8"
        }

        # リクエストボディ指定
        data = {
            "grantType": "client_credentials",
            "clientId": self.client_id,
            "clientSecret": self.client_secret
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()

        # リクエスト生成
        req = urllib.request.Request(url, data, headers)

        # リクエストを送信し、レスポンスを受信
        #res = urllib.request.urlopen(req)
        try:
            with urllib.request.urlopen(req) as res:
                res_body = res.read()
                res_body = json.loads(res_body)
                self.access_token = res_body["access_token"]
        except urllib.error.HTTPError as err:
            print(err.code)
        except urllib.error.URLError as err:
            print(err.reason)

    # 構文解析API
    def parse(self, sentence):
        # 構文解析API URL指定
        url = self.developer_api_base_url + "v1/parse"
        # ヘッダ指定
        headers={
            "Authorization": "Bearer " + self.access_token,
            "Content-Type": "application/json;charset=UTF-8",
        }
        # リクエストボディ指定
        data = {
            "sentence": sentence
        }
        # リクエストボディ指定をJSONにエンコード
        data = json.dumps(data).encode()
        # リクエスト生成
        req = urllib.request.Request(url, data, headers)
        # リクエストを送信し、レスポンスを受信
        try:
            res = urllib.request.urlopen(req)
        # リクエストでエラーが発生した場合の処理
        except urllib.request.HTTPError as e:
            # ステータスコードが401 Unauthorizedならアクセストークンを取得し直して再リクエスト
            if e.code == 401:
                print ("get access token")
                self.access_token = getAccessToken(self.client_id, self.client_secret)
                headers["Authorization"] = "Bearer " + self.access_token
                req = urllib.request.Request(url, data, headers)
                res = urllib.request.urlopen(req)
            # 401以外のエラーなら原因を表示
            else:
                print ("<Error> " + e.reason)
                return -1

        # レスポンスボディ取得
        res_body = res.read()
        # レスポンスボディをJSONからデコード
        res_body = json.loads(res_body)
        # レスポンスボディから解析結果を取得
        return res_body

    def extract_dependency_info(self,jsonfile,when,where,who,whom,dowhat):
        chunkid_text_dict = dict()
        dependency_info = list()

        # 解析結果(json)から係り受け情報を抽出
        for chunk in jsonfile["result"]:
            chunk_id = chunk["chunk_info"]["id"]
            tokens = [token["form"] for token in chunk["tokens"]]
            chunkid_text_dict[chunk_id] = " ".join(tokens)
            for link in chunk["chunk_info"]["links"]:
                #dependency_info.append([chunk_id,link["link"],link["label"]])
                if link["label"] == "agent":
                    #print("主語:"+chunkid_text_dict[link["link"]])
                    who.append(chunkid_text_dict.get(link["link"]))
                    #print("述語:"+chunkid_text_dict[chunk_id])
                    dowhat.append(chunkid_text_dict.get(chunk_id))
                elif link["label"] == "object":
                    #print("目的語:"+chunkid_text_dict[link["link"]])
                    whom.append(chunkid_text_dict.get(link["link"]))
                elif link["label"] == "time":
                    #print("時間:"+chunkid_text_dict[link["link"]])
                    when.append(chunkid_text_dict.get(link["link"]))
                elif link["label"] == "place":
                    #print("場所:"+chunkid_text_dict[link["link"]])
                    where.append(chunkid_text_dict.get(link["link"]))



if __name__ == '__main__':
    # ソースファイルの場所取得
    APP_ROOT = os.path.dirname(os.path.abspath( __file__)) + "/"

    # 設定値取得
    config = configparser.ConfigParser()
    config.read(APP_ROOT + "config.ini")
    CLIENT_ID = config.get("COTOHA API", "Developer Client id")
    CLIENT_SECRET = config.get("COTOHA API", "Developer Client secret")
    DEVELOPER_API_BASE_URL = config.get("COTOHA API", "Developer API Base URL")
    ACCESS_TOKEN_PUBLISH_URL = config.get("COTOHA API", "Access Token Publish URL")

    # COTOHA APIインスタンス生成
    cotoha_api = CotohaApi(CLIENT_ID, CLIENT_SECRET, DEVELOPER_API_BASE_URL, ACCESS_TOKEN_PUBLISH_URL)

    #解析結果格納用リスト
    itu = list()
    darega = list()
    dokode = list()
    naniwo = list()
    dousita = list()

    #引数チェック。第一引数:wiki単語(defailt:MeCab)、第二引数:生成する文章の数(default:20)
    link = "https://ja.wikipedia.org/wiki/"
    WORD = "MeCab"
    TEXTNUM = 20
    args = sys.argv
    if len(args) >= 2:
        WORD = str(args[1])
    if len(args) >= 3:
        TEXTNUM = int(args[2])

    print("word:"+ WORD)
    with urllib.request.urlopen(link + parser.quote_plus(WORD)) as response:
        html = response.read().decode("utf-8")

    #解析してテキスト抜き出し
    soup = BeautifulSoup(html,'html.parser')
    p_tags = soup.find_all("p") #<p>タグを全て取り出す
    print("text_number:"+str(len(p_tags)))

    #テキストを一つずつAPIに投げていく
    for sub in tqdm(p_tags):
        #注釈の大かっこを除いたテキストを構造解析
        result = cotoha_api.parse(re.sub('\[.+\]',"",sub.text))
        if result == -1:
           continue
        cotoha_api.extract_dependency_info(result,itu,dokode,darega,naniwo,dousita)

    #すべてのリストに一個は入っていることが必要
    if not itu or not dokode or not darega or not naniwo or not dousita:
        print ("単語数が十分でありません。別の単語でやり直してください")
        sys.exit() 


    #いつどこでだれがなにをしたゲーム
    print("------------------いつどこで誰が何をどうした------------------")
    for var in range (0, TEXTNUM):
        print(random.choice(itu)+
              random.choice(dokode)+
              random.choice(darega)+
              random.choice(naniwo)+
              random.choice(dousita))    
    print("--------------------------------------------------------------")

    #結果表示
    print("■いつ")
    print(itu)
    print("■どこで")
    print(dokode)
    print("■誰が")
    print(darega)
    print("■何を")
    print(naniwo)
    print("■どうした")
    print(dousita)

出力結果 (クリックで展開)
$ python cotoha_api_wikipedia.py "グリニッジ" 15 
word:グリニッジ
text_number:21
100%|████████████████████████████████████████████████████████████████████████████████████████████████| 21/21 [00:04<00:00,  4.84it/s]
------------------いつどこで誰が何をどうした------------------
予定 で あ っ た 。南 には 、建造物 は( fan   museum )   が登録 さ れ て い る 。
( 1966年  グリニッジ 公園内 にfoundation )( greenwich market )( en:greenwich playhouse )   で あ る 。
予定 で あ っ た 。グリニッジ はjames athenian stuart が西・北・東 を位置 し て い る 。
最近 はグリニッジ は基準 は教会 が知らせ て い る 。
ノース ・ グリニッジ north  南 には 、呼び名 で あ っ た )( greenwich foot tunnel )   が呼 ば れ る 。
協定 世界 時 に時間 計測 に( greenwich millennium village )   がグリニッジ 子午線 が描 い た
毎日西部 に( the   old   royal   naval   college )   は 、密接 に存在 する 。
予定 で あ っ た 。12 番地 に建 っ て い る の が復元 が登録 さ れ て い る 。
毎日南 には 、ここ は基礎 を建て ら れ た 。
最近 はグリニッジ 公園内 にカンタベリー大主教アルフィージ がクイーンズ・ハウス を用い ら れ て い た 。
最近 は西部 に基準 は-   1967年 ) を貸 さ れ て い る が 、
地域 ( 現在ところ に( nicholas hawksmoor )( greenwich foot tunnel )   が呼 ば れ る 。
週末 には南 には 、頃 まで は基準 を管理 し て お り 、
( 1966年  グリニッジ は建造物 は( fan   museum )   が世界一周
2012年4月25日 、12 番地 にfranc is   chichester )   が一般公開 が手がけ た
--------------------------------------------------------------
■いつ
['最近 は', '地域 ( 現在', 'ノース ・ グリニッジ north  ', '協定 世界 時 に', '毎日', '予定 で あ っ た 。', '2012年4月25日 、', '( 1966年  ', '週末 には']
■どこで
['町 で 、', 'グリニッジ は', '西部 に', '時間 計測 に', 'グリニッジ 公園内 に', '公園 には', '12 番地 に', '角 に', 'グリニッジ には', '乾ドック で', '大幅 に', 'そば には 、', '( island gardens )   に', '川沿い に 、', 'ところ に', '南 には 、', '西側 に', '場所 は 、', '観光客 で']
■誰が
['頃 まで は', 'グリニッジ は', 'グリニッジ ) は', '呼び名 で あ っ た ) は', '歴史的 建造物 群 は 、', '置き換え ら れ る まで は 、', 'グリニッジ は', 'いまなお 報 時 球 は', '博物館 が', '( the   old   royal   naval   college )   は 、', 'ここ は', 'foundation )   が', '建造物 は', '( james thornhill )   が', 'james athenian stuart が', 'ガイド 付き ツアー が', 'もうひとつ は', '火災 事故 が', '「 カティーサーク 」 は', 'franc is   chichester )   が', '「 ジプシー ・ モス 4 世 号 」 ( gipsy moth iv )   も', 'カナ レット が', 'ミレニアム ・ ドーム は 、', 'これ は', '( greenwich millennium village )   が', '建 っ て い る の が', 'カンタベリー大主教アルフィージ が', '( nicholas hawksmoor )   が', '教会 は', '世界 遺産 は', '基準 は']
■何を
['由緒', '経度 0 o   0 \'   0 " に', 'グリニッジ 中心 街', '西・北・東 を', '密接 に', 'グリニッジ天文台 が', '天文台 を', 'グリニッジ 子午線 が', '基礎 を', 'グリニッジ 平均 時 が', '天体 観測 を', '午後1時 を', '海事 クロノメーター を', '航海術 に', '道具 を', '敷地 を', 'クイーンズ・ハウス を', '( national   maritime   museum )   が', '天文台 は 、', '建築 物 は', 'こと が', 'the   king   charles   block は 、', '内装 は', '扇 を', '( fan   museum )   が', '博物館 は', '( greenwich theatre )   が', '劇場 が', '2008年9月 に', '復元 が', '一般公開 が', '-   1967年 ) を', '航海 歴 を', '修繕 の', '( greenwich foot tunnel )   が', '出口 は', '眺め を', 'こと が', '( joseph rené bellot )   を', 'オベリスク が', '敷地 に', '教会 が', '( greenwich market )   が', '基準 を', '満た し た と', '登録 が']
■どうした
['表記 さ れ た 。', '位置 し て い る 。', '呼 ば れ る 。', '呼 ば れ る 。', '登録 さ れ て い る 。', '用い ら れ て い た 。', '場 では な い が 、', '知らせ て い る 。', '併設 さ れ て い る 。', '位置 し て い る 。', '管理 し て お り 、', '管理 し て お り 、', '貸 さ れ て い る が 、', '塗装 し た', '手がけ た ) も 、', '出発 し て い る', '( en:greenwich playhouse )   で あ る 。', '発生 、', '宣言 さ れ た 。', '世界一周', '展示 さ れ て い た が 、', '描 い た', '建て ら れ た 。', '隣接 し 、', '存在 する 。', 'セント・アルフィージ教会', '殺害 さ れ た', '手がけ た', 'もの で あ る 。', '満た し た と', '引用 で あ る ) 。']

PythonでWebスクレイピングしてその結果を言語処理するアプリケーションを作れた。

まとめ

Webスクレイピングと日本語解析を組み合わせて遊べた。

一方で、解析結果にまだまだゴミが多い。
例えば「どこで」の中に「〇〇に」がたくさん混ざるので、
「◯◯で」だけでフィルタリングするなど後処理を入れたほうが面白くなりやすいかもしれない。

あと、今回は4種類の意味ラベルしか使ってないが、
その他の意味ラベルもいろいろ使うことでもっと複雑な文生成もできるかもしれない。

将来的には、一つの記事からだけじゃなくたくさんの文から学習させて、
なおかつユーザのフィードバックをもらって面白そうなワードを重みづけて
優先的に出力させるようにすると面白いアプリケーションができるかも。

(おまけ) 生成してみた文の例

せっかくなので「いつどこで誰が何をどうした」をいろいろ作ってみた。

のち も日本 国内 で( ぐんまけん ) は 、飛行機 会社 と知 ら れ て い る 。 (Word:群馬県)
知らなかった

「 あと工業 的 にカール・ベンツ はガソリン エンジン と明らか に 異な る 」 (Word:石油)
せやな

翌日 、西方 でハンニバル 自身 は騎兵 2000 を破 っ た 。 (Word:ザマの戦い)
ハンニバル無双

天正18年 ( 1590 年 )馬上 で豊久 は覚悟 を相続 し た 。 (Word:島津豊久)
覚悟はいいか?俺は相続できてる

7月3日 、正親町天皇 に信長 は第六天魔王 と任じ ら れ る (Word:織田信長)
魔王は役職

8月25日 には高野山 奥の院 に信長 はパウロ三木 を献上 し 、 (Word:豊臣秀吉)
パウロ三木を献上する魔王

天正17年家康 に家康 は 、家康 を家康 で あ る (Word:徳川家康)
家康リーチ
(「いつ」が「家康 死後 も」とかだったら家康ビンゴだった)

20世紀 以降 に上空 でカルト 団体 ヘヴンズ・ゲート が増光 ( アウトバースト ) を( meteorologica ) 。 (Word:彗星)
意味不明でも「増光 ( アウトバースト ) 」で大体それっぽくなるのでずるい。

近年 では中国語 でサントリー は 、永谷園 と知 ら れ て い る (Word:烏龍茶)
まじかよ