Pubmedの.xmlデータをpythonで処理する

#はじめに
この記事は、Pubmedで検索で引っかかった文献データ(xml形式)をpythonで読み込む方法についての自分用メモです。

お気付きの点がありましたらご指摘いただけますと幸いです。

#処理したいデータ
一件のデータは以下のような感じです。実際は複数件のデータを処理したいですが、まずは一件ずつ処理できるようにします。

```001.xml
<PubmedArticle>
    <MedlineCitation Status="Publisher" Owner="NLM">
        <PMID Version="1">12345678</PMID>
        <DateRevised>
            <Year>2020</Year>
            <Month>03</Month>
            <Day>27</Day>
        </DateRevised>
        <Article PubModel="Print-Electronic">
            <Journal>
                <ISSN IssnType="Electronic">1873-3700</ISSN>
                <JournalIssue CitedMedium="Internet">
                    <PubDate>
                        <Year>2020</Year>
                        <Month>Mar</Month>
                    </PubDate>
                </JournalIssue>
                <Title>Journal of XXX</Title>
            </Journal>
            <ArticleTitle>Identification of XXX.</ArticleTitle>
            <AuthorList CompleteYN="Y">
                <Author ValidYN="Y">
                    <LastName>Sendai</LastName>
                    <ForeName>Shiro</ForeName>
                    <Initials>S</Initials>
                    <AffiliationInfo>
                        <Affiliation>Sendai, Japan.</Affiliation>
                    </AffiliationInfo>
                </Author>
                <Author ValidYN="Y">
                    <LastName>Tohoku</LastName>
                    <ForeName>Taro</ForeName>
                    <Initials>T</Initials>
                    <AffiliationInfo>
                        <Affiliation>Miyagi, Japan.</Affiliation>
                    </AffiliationInfo>
                </Author>
            </AuthorList>
            <Language>eng</Language>
            <PublicationTypeList>
                <PublicationType UI="D016428">Journal Article</PublicationType>
            </PublicationTypeList>
            <ArticleDate DateType="Electronic">
                <Year>2020</Year>
                <Month>03</Month>
                <Day>23</Day>
            </ArticleDate>
        </Article>
        <CitationSubset>IM</CitationSubset>
    </MedlineCitation>
    <PubmedData>
        <PublicationStatus>aheadofprint</PublicationStatus>
        <ArticleIdList>
            <ArticleId IdType="pubmed">32213359</ArticleId>
            <ArticleId IdType="pii">S0031-9422(19)30971-9</ArticleId>
            <ArticleId IdType="doi">10.1016/j.phytochem.2020.112349</ArticleId>
        </ArticleIdList>
    </PubmedData>
</PubmedArticle>

```
#基本的な使い方の理解

xmlを読むためのライブラリーを読み込みます。

```001.py
import xml.etree.ElementTree as ET
```
ファイルからxmlデータを読み込みます。
複数のデータが改行2つ区切りで並んでいるようなので、splitで分割してリストにします。

```002.py
test_data = open("./xxxx/pubmed.xml", "r")
contents = test_data.read()
records = contents.split('\n\n')
```

1つ目の文献データ(records[0])をET.fromstring()で読み込んで変数rootにしまいます。
rootをtype()で調べると、Elementオブジェクトであることがわかります。

```003.py
root = ET.fromstring(records[0])
type(root)
#<class 'xml.etree.ElementTree.Element'>
```

root.tagでtagを確認できるとのことです。確認してみます。

```004.py
root.tag
#'PubmedArticle'
```
1つのデータについては、ざっくり言えば以下のような形になっています。root.tagで、一番外側のtagにアクセスできました。

```002.xml
<PubmedArticle>
    <MedlineCitation>
    </MedlineCitation>
    <PubmedData>
    </PubmedData>
</PubmedArticle>
```
\<PubmedArticle\>の内側には2つの要素(MedlineCitationとPubmedData)があり、これには添字を使ってアクセスできます。添字を使ってアクセスし、さらにtypeを調べます。

```005.py
root[0]
#<Element 'MedlineCitation' at 0x10a9d5b38>
type(root[0])
#<class 'xml.etree.ElementTree.Element'>

root[1]
#<Element 'PubmedData' at 0x10aa78868>
type(root[1])
#<class 'xml.etree.ElementTree.Element'>
```
どちらもElementオブジェクトであることがわかります。

要するに、全部のノードがElementオブジェクトということのようです。
Elementオブジェクトはイテレーションができ、子ノードを1つずつ取り出して処理できます。

```.py
for i in root:
    print(i.tag)
```
Elementのタグは、.tagで調べることができ、.attribでそのtagにつけられた属性と属性値を調べられます。

```.py
root[0].tag
#'MedlineCitation'

root[0].attrib
#{'Status': 'Publisher', 'Owner': 'NLM'}
# root[0]のタグ回りは以下のようになっている。
#    <MedlineCitation Status="Publisher" Owner="NLM">


type(root[0].attrib)
#<class 'dict'> #辞書クラス
```

#Elementオブジェクトへのアクセス方法
3つありそうです。いずれも、tagを1つまたは複数指定できます。タグ全体をクォーテーションで囲み、タグを複数指定する場合はタグ間をスラッシュで区切ります。
1. find('tag1/tag2')
2. findall('tag1/tag2')
3. iter('tag1/tag2')

1の場合はElementオブジェクトが返りますが、2の場合はElementオブジェクトのlistが、3の場合はイテレーション用のオブジェクト？が返ります。確認してみます。

```.py
root.find('MedlineCitation/DateRevised/Year')
#<Element 'Year' at 0x10a9f8ae8>

root.findall('MedlineCitation')
#[<Element 'MedlineCitation' at 0x10a9d5b38>]

root.iter('Author')
#<_elementtree._element_iterator object at 0x10aa65990>

#for文でイテレーションしてみます。
for i in root.iter('Author'):
    print(i)
#<Element 'Author' at 0x10aa6e9f8>
#<Element 'Author' at 0x10aa6ec28>

```
findall()では、Elementオブジェクトの子ノードのみを調べ、iter()では、Elementオブジェクトのすべての子ノード、孫ノード、ひ孫ノード...を調べるようです。

#Elementオブジェクトの値へのアクセス
Elementオブジェクトは2つ値を持っています。属性値とテキストデータです。属性値はElementオブジェクトに対して.get('*プロパティ名*')で得ることができます。あるいは、.attrib['*プロパティ名*']でも良いようです。

```.py
#.get()を使うか、
root.find('MedlineCitation').get('Status')
#'Publisher'

#.attrib()を使うか、
root.find('MedlineCitation').attrib['Status']
#'Publisher'
```
またElementオブジェクトに対して.textとすると、テキストデータが取得できます。

ここでテキストデータと言っているのは、タグで囲まれた部分、下の例で言えば2020がそうです。
\<Year\>2020\</Year\>

find()でElementオブジェクトへのパスを指定して値を取得してみます。

```.py
root.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Year').text
#'2020'
```
複数いるAuthorについての情報を得るには、findall()で得たリストをイテレーションします。
>著者所属が複数ある場合を考慮して、修正しました(2020年3月31日)。

```.py
for x in root.findall('MedlineCitation/Article/AuthorList/Author'):
    x.find('LastName').text   #著者の苗字
    x.find('ForeName').text    #著者の名
    for y in x.findall('AffiliationInfo'):
        y.find('Affiliation').text

```



doi(ドキュメント識別子)については、タグELocationIDに記述されていますが、タグELocationIDには、いくつか属性値をとるものがあり、EIdType="doi"の場合のテキストデータを得る必要があります。

```.py
for x in root.findall('MedlineCitation/Article/ELocationID'):
    if(x.get('EIdType') == 'doi'):
        x.text
```
レコードが、ReviewなのかJournal Articleなのか、区別する必要がありますがこれは、PublicationTypeに書いてあります。ただしPublicationTypeは複数あることが普通で、その中に値がReviewであるものがあればReviewということのようです。

例えばReviewのレコードを見ると以下のようあります。

```.xml
<PublicationTypeList>
    <PublicationType UI="D016428">Journal Article</PublicationType>
    <PublicationType UI="D016454">Review</PublicationType>
    <PublicationType UI="D013485">Research Support, Non-U.S. Gov't</PublicationType>
</PublicationTypeList>
```

なので、reviewなのかどうかは、

```.py
isReview = False
for x in root.findall('MedlineCitation/Article/PublicationTypeList'):
    if (x.text == 'Review'):
        isReview = TRUE
```
とかすると良いと思います。


その他取得したいかもしれない情報も含め、ここまでをまとめると

```.py
import xml.etree.ElementTree as ET

test_data = open("./pubmed.xml", "r")
contents = test_data.read()
records = contents.split('\n\n')
root = ET.fromstring(records[0])#とりあえず1件目のみ。

# 著者情報
for x in root.findall('MedlineCitation/Article/AuthorList/Author'):
    x.find('LastName').text   #著者の苗字
    x.find('ForeName').text    #著者の名
    for y in x.findall('AffiliationInfo'):
        y.find('Affiliation').text#修正しました。

# Reviewかどうかの判定
isReview = False
for x in root.findall('MedlineCitation/Article/PublicationTypeList'):
    if (x.text == 'Review'):
        isReview = TRUE

# doi
for x in root.findall('MedlineCitation/Article/ELocationID'):
    if(x.get('EIdType') == 'doi'):
        x.text

#PMID
root.find('MedlineCitation/PMID').text
#論文タイトル
root.find('MedlineCitation/Article/ArticleTitle').text
#ジャーナル名
root.find('MedlineCitation/Article/Journal/Title').text
#出版年
root.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Year').text
#出版月
root.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Month').text
#言語
root.find('MedlineCitation/Article/Language').text

```
とすれば良いと思います。上のコードでは一件だけの処理ですが、

```.py
for record in records:
    root = ET.fromstring(record)
    #処理を記述
```
としてやればいいですね。

これでxmlデータがあれば、一気に必要な情報を抜き出すことができるようになりました。あとはどう整形するか、考えるだけですね。

これでxmlのデータの取り扱い方がわかりました。