<jupyter_start><jupyter_text>全国書誌データを分析してみよう！！国立国会図書館では全国書誌データと呼ばれる書誌データを作成し、誰でも利用可能な形で提供しています。全国書誌データは以下のような特色があります。>全国書誌データは、国立国会図書館が網羅的に収集した国内出版物の標準的な書誌情報です。  >書店で一般に購入できる書籍などの納入率は、95％以上です。  >官庁出版物や地方自治体出版物など一般に流通しにくいものも多く含みます。  >刊行された出版物が国立国会図書館に届いてから、おおむね4日後に新着書誌情報として提供し、1か月程度で完成した書誌情報を提供しています。   [全国書誌データ提供](https://www.ndl.go.jp/jp/data/data_service/jnb/index.html)全国書誌データの利用は**無償**で、かつ**申請等も不要**です。データの取得方法としては2019年6月現在、以下の4種類が提供されています。(※[全国書誌データ提供サービス一覧](https://www.ndl.go.jp/jp/data/data_service/jnb/faq.html)も参照のこと。)* 検索用API図書館システムの検索画面等から、国立国会図書館サーチの書誌データを検索し、その結果を取得・表示することができます。  [検索用API](https://www.ndl.go.jp/jp/data/data_service/jnb/ndl_search.htmliss01)  [APIのご利用について](https://iss.ndl.go.jp/information/api/)* ハーベスト用API国立国会図書館サーチからOAI-PMHにより書誌データを取得できます。全件収集等、大量のデータをまとめて取得することができます。  [ハーベスト用API](https://www.ndl.go.jp/jp/data/data_service/jnb/ndl_search.htmliss02)  [国立国会図書館サーチが提供するOAI-PMH](https://iss.ndl.go.jp/information/api/api-lists/oai-pmh_info/)  * RSS新着書誌情報、全国書誌及び全国書誌（電子書籍・電子雑誌編）を、国立国会図書館サーチの機能を用いてRSS形式（RSS2.0）で提供しています。  [国立国会図書館サーチが提供するRSS](https://iss.ndl.go.jp/information/api/api-lists/rss_info/2)* TSVファイル全国書誌（電子書籍・電子雑誌編）をTSVファイル（タブ区切り形式のテキストファイル）で提供しています。  [全国書誌（電子書籍・電子雑誌編）TSVファイル一覧](https://www.ndl.go.jp/jp/data/data_service/jnb/ebej_tsv.html)今回はこの全国書誌を利用して、1. ハーベスト用APIを利用した全件取得  2. 取得したデータの整形とクレンジング3. 結果の可視化  4. 応用編(グラフをアニメーションにする)  を行っていきます。  基本的には上から順番にctrl+Enterを押していけば実行できます。<jupyter_code>#必要なライブラリ群のインストール
!pip install pycurl tqdm datetime pandas matplotlib seaborn<jupyter_output><empty_output><jupyter_text>1. ハーベスト用APIを利用した全件取得適当なハーベスタを用意して必要な断面を全件収集してみましょう。  pythonで簡単なハーベスタを書いておきましたので、自己責任でご利用ください。<jupyter_code>#(ハーベスタ)rdf/xmlを扱うための前準備
import os
import xml.etree.ElementTree as ET
import pycurl

import time
import codecs
from io import StringIO,BytesIO

#XMLの名前空間
OAI='{http://www.openarchives.org/OAI/2.0/}'
dc ='{http://purl.org/dc/elements/1.1/}'
dcndl='{http://ndl.go.jp/dcndl/terms/}'
dcterms='{http://purl.org/dc/terms/}'
rdf='{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
rdfs='{http://www.w3.org/2000/01/rdf-schema#}'
foaf='{http://xmlns.com/foaf/0.1/}'

#ElementTree(xmlを解析するライブラリ)にも名前空間を登録
ET.register_namespace('',"http://www.openarchives.org/OAI/2.0/")
ET.register_namespace('rdf', "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
ET.register_namespace('rdfs', "http://www.w3.org/2000/01/rdf-schema#")
ET.register_namespace('dc',"http://purl.org/dc/elements/1.1/")
ET.register_namespace('dcterms',"http://purl.org/dc/terms/")
ET.register_namespace('dcndl',"http://ndl.go.jp/dcndl/terms/")
ET.register_namespace('xsi',"http://www.w3.org/2001/XMLSchema-instance")
ET.register_namespace('schemaLocation',"http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd")
ET.register_namespace('oai_dc',"http://www.openarchives.org/OAI/2.0/oai_dc/")
ET.register_namespace('foaf',"http://xmlns.com/foaf/0.1/")
ET.register_namespace('owl',"http://www.w3.org/2002/07/owl#")


#ハーベスタ本体
from datetime import datetime, date, timedelta
from tqdm import tqdm
import time
import os
import xml.etree.ElementTree as ET
import pycurl

import time
import codecs
from io import StringIO,BytesIO
import urllib.request

class OAI_harvester:
    def __init__(self, outputxmlpath="xml_all.xml", prefixname="dcndl"):
        self.outputxml = outputxmlpath
        self.prefixname = prefixname
        self.resumptiontoken = None
        self.datasize = None
        with open(self.outputxml, 'wb') as f:
            print("initialize file")

    def _parse_xml_ndl(self):
        tree = ET.parse('oaitmp.xml')
        root = tree.getroot()
        with codecs.open(self.outputxml, 'a', "utf-8") as f:
            es_item = root.find(OAI + 'ListRecords').findall(OAI + 'record')
            for item in es_item:  # OAI-PMHは「id <xml>」のようになっているので不要なid部分を消す
                if item.find(OAI + 'metadata') is None:
                    continue
                item2 = item.find(OAI + 'metadata').find(rdf + 'RDF')
                item_id = item.find(OAI + 'metadata').find(rdf + 'RDF').find(dcndl + "BibAdminResource").attrib[
                    rdf + "about"].split("/")[-1]
                item_str = ET.tostring(item2, encoding='utf8', method='xml').decode()
                item_str = item_str.replace("\n", "")
                f.write(item_str + "\n")
        if root.find(OAI + 'ListRecords') is None:
            self.resumptiontoken = None
            return
        token = root.find(OAI + 'ListRecords').find(OAI + "resumptionToken")
        if token is None or token.text is None:
            self.resumptiontoken = None
            return
        self.datasize = token.attrib["completeListSize"]
        self.resumptiontoken = token.text

    def _download_xml(self, fromdate):
        # b = io.BytesIO()
        with open("oaitmp.xml", 'wb') as f:
            url = "http://iss.ndl.go.jp/api/oaipmh?verb=ListRecords"
            if self.resumptiontoken is not None:
                url += "&resumptionToken=" + self.resumptiontoken
            else:
                url += "&from=" + fromdate + "&metadataPrefix=" + self.prefixname + "&set=" + self.setname
                print(url)
            # print(url)
            try:
                data = urllib.request.urlopen(url)
                f.write(data.read())
                http_code = data.getcode()
                if http_code == 200:
                    retval = True
                else:
                    retval = False
            except Exception as e:
                print(str(e))
                retval = False
        return retval

    def getxml(self, setname, fromdate=None):
        self.setname = setname
        self.resumptiontoken = None
        if fromdate is None:
            # 最初の200件は条件を指定して取得する。fromとuntilで年度の期間を取得できるが、最大1年分
            today = datetime.today()
            fromdate = datetime.strftime(today - timedelta(days=364), '%Y-%m-%d')
        self._download_xml(fromdate)
        self._parse_xml_ndl()
        print(self.datasize + "件見つかりました。200件ずつ取得します")
        if self.resumptiontoken is not None:
            self._parse_xml_ndl()
            for index in tqdm(range(int(self.datasize) // 200 + 1)):
                while not self._download_xml(fromdate):
                    print("retry")
                    time.sleep(1)
                # print("downloading  file_count:",index)
                self._parse_xml_ndl()
                if self.resumptiontoken is None:
                    break
        else:
            print("エラーです。set名を確認してください")<jupyter_output><empty_output><jupyter_text>例えば「小説・物語」(日本十進分類法で913)に分類される全国書誌(iss-ndl-opac-national)の書誌データは以下のようにして全件取得できます。   **注意**：  「小説・物語」の場合、実行に2時間程度かかります。 取得済の断面をhttp://lab.ndl.go.jp/dataset/xml_913.zipからダウンロードできるようにしてあります。<jupyter_code>oai=OAI_harvester(outputxmlpath="xml_913.xml")
#実行時に下のコメントを外してください(誤操作防止)
#oai.getxml(setname="iss-ndl-opac-national:913",fromdate="2018-06-19")<jupyter_output><empty_output><jupyter_text>2. 取得したデータの整形とクレンジング1で取得したデータは1行に1書誌のxmlが収まった形式をしています。  まずは書誌がどんなデータ構造をしているのか覗いてみましょう。<jupyter_code>from xml.dom import minidom
with codecs.open("xml_novel.xml", "r","utf-8") as f:
    xmlsample=f.readline()
    xmlstr = minidom.parseString(xmlsample).toprettyxml(indent="   ")
    print(xmlstr)<jupyter_output><empty_output><jupyter_text>書誌データの中身を見ると、タイトルや著者などのほか、「出版社」や「出版年」といった情報がわかります。  今回は出版社名に注目して、  **「小説・物語の分野で多くの本を出版しているのはどの出版社なのか、また出版年代ごとに変化はあるのか」**  調べてみましょう。    上で表示した書誌データを見る限り、  出版社は```                           ```を使うとよさそうです。  出版年は``````を使ってみましょう。  また、XMLのままではデータの取り回しが不便なので、抽出したデータはpandasのデータフレームで管理します。<jupyter_code>#書誌データから出版社名と出版年だけ取り出してデータフレームに加工する
import pandas as pd


with codecs.open("xml_novel.xml", "r","utf-8") as f:
    xmlsample=f.readline()
    publisherList=[]
    dateList=[]
    cnt=0
    while xmlsample:
        cnt+=1
        #if cnt%10000==0:
        #    print(cnt)
        tree = ET.fromstring(xmlsample)
        #print(tree)
        #root = tree.getroot()
        publisher=tree.find(dcndl+'BibResource').find(dcterms+'publisher')
        publishername=publisher.find(foaf+'Agent').find(foaf+'name')
        publishdate=tree.find(dcndl+'BibResource').find(dcterms+'date')
        #cleandate=publishdate.text.replace(".*([0-9\.]+).*",r"\1",regex=True)
        #print(publishername.text,cleandate)
        #tmp_se = pd.Series( [ publishername.text, publishdate.text], index=analysis_df.columns )
        #analysis_df = analysis_df.append( tmp_se, ignore_index=True )
        publisherList.append(publishername.text)
        dateList.append(publishdate.text)
        xmlsample=f.readline()
    analysis_df = pd.DataFrame({'publisher':publisherList,'date':dateList})                                     
print(analysis_df)<jupyter_output><empty_output><jupyter_text>このままでは出版年の中に「制作」や「19--」や\[2011\]のような変則的な表記が含まれてしまい、数値としての大小がわかりません。  出版年が西暦4桁の数値だけを抽出して持つようにデータをきれいにしましょう(このような処理を「クレンジング」と呼びます)。<jupyter_code>#データのクレンジングをする
cleandf=analysis_df.copy()
#西暦4桁が含まれていれば抽出、含まれていなければ欠損値とする
cleandf['date']=cleandf['date'].str.extract('([0-9]{4})')
#欠損値を含む書誌を削除
cleandf=cleandf.dropna(how='any')
#残った書誌データの出版年を数値にする
cleandf['date']=cleandf['date'].astype("int")


print("書誌データの出版年の分布")
print(cleandf.describe())
print("\nきれいになった書誌データ")
print(cleandf.head())
#csvとして書き出す
#cleandf.to_csv("clean_novel.csv")<jupyter_output><empty_output><jupyter_text>3. 結果の可視化データをきれいにしたので、いよいよ可視化をしてみましょう。出版年を追った時の出版数の推移を折れ線グラフで表してみます。<jupyter_code>#ここから始めたい人
#cleandf=pd.read_csv("clean_novel.csv")

#出版年ごとに集計してグラフにしてみる
df = pd.DataFrame(cleandf.groupby('date').count())
df.columns=["count"]
print(df["count"].sum())
#多い順ベスト10
print(df.nlargest(10, columns='count'))
df.plot.line(title=u'小説・物語の出版数年次推移')
#日本語文字化け対策
plt.rcParams['font.family'] = 'Yu Mincho'
#出版社ごとに集計して多い順に表にしてみる
grp_df=cleandf.groupby('publisher').count()

grp_df.columns=["count"]

print(grp_df.nlargest(20, columns='count'))

grp_df.nlargest(20, columns='count').plot.bar(alpha=0.6, figsize=(15,8))
plt.title(u'小説・物語の出版数ランキング', size=16)<jupyter_output><empty_output><jupyter_text>特定の出版年に絞り込んだランキングも出力可能です。1990年のランキングを見てみましょう。<jupyter_code>year=2000
#year年に出版された書籍を出版社ごとに集計して多い順に表にしてみる
grp_df=cleandf[cleandf['date']==year].groupby('publisher').count()
grp_df.columns=["count"]
#トップ20
print(grp_df.nlargest(20, columns='count'))

grp_df.nlargest(20, columns='count').plot.bar(alpha=0.6, figsize=(8,8))
plt.title(u'小説・物語の出版数ランキング(%d)'% year, size=16)<jupyter_output><empty_output><jupyter_text>4. 応用編(グラフをアニメーションにする) 最後に、グラフをアニメーションにしてみましょう。2000年以降、出版数でトップ10に入ったことのある出版社の出版数推移をアニメーションにしてみます。<jupyter_code>%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

publisherlist=[]
for year in range(2000,2018):
    grp_df=cleandf[cleandf['date']==year].groupby('publisher').count()
    grp_df.columns=["count"]
    x=grp_df.nlargest(20, columns='count')
    publisherlist.extend(list(x.index))

#重複を取り除く
publisherlist=list(set(publisherlist))
print(publisherlist)

#描画の準備
fig,ax= plt.subplots(figsize=(12, 10))
ims = []

for year in range(2000,2019):
    #ttl = plt.text(0.5, 1.01, year, horizontalalignment='center', verticalalignment='bottom', transform=ax.transAxes)
    #txt = plt.text(year,year,year)
    
    grp_df=cleandf[cleandf["date"]==year].groupby("publisher").count().reset_index()
    grp_df.columns=["publisher","count"]
    grp_df=grp_df[grp_df["publisher"].isin(publisherlist)]
    #x=grp_df.nlargest(20, columns='count')
    #print(x["count"])
    im = plt.barh(list(grp_df["publisher"]),grp_df["count"].values)
    #ax.text(.8,.8, "{}".format(year), transform=ax.transAxes)
    plt.title(u'小説・物語の出版数推移(2000年から2018年)', size=16)
    ims.append(im)

ani = animation.ArtistAnimation(fig, ims,  blit=False,interval=500)
ani.save('出版推移.gif',writer='pillow')
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15158447720725302, 'fraction_numerical': 0.020589730554143367, 'mean_word_length': 4.140679442508711, 'pattern_counts': {'":': 0, '<': 38, '<?xml version=': 0, '>': 42, 'https://': 8, 'lorem ipsum': 0, 'www.': 16, 'xml': 45}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '293386', 'license': 'CC-BY-4.0', 'n_tokens_mistral': 6004, 'n_tokens_neox': 5742, 'n_words': 490, 'path': 'analysis.ipynb', 'repo_name': 'ndl-lab/ndlsearch-oaipmh-sample', 'size': '11802'}