LoginSignup
6
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-29

はじめに

この記事は、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つずつ取り出して処理できます。

for i in root:
    print(i.tag)

Elementのタグは、.tagで調べることができ、.attribでそのtagにつけられた属性と属性値を調べられます。

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の場合はイテレーション用のオブジェクト?が返ります。確認してみます。

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['プロパティ名']でも良いようです。

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

#.attrib()を使うか、
root.find('MedlineCitation').attrib['Status']
#'Publisher'

またElementオブジェクトに対して.textとすると、テキストデータが取得できます。

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

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

root.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Year').text
#'2020'

複数いるAuthorについての情報を得るには、findall()で得たリストをイテレーションします。

著者所属が複数ある場合を考慮して、修正しました(2020年3月31日)。

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"の場合のテキストデータを得る必要があります。

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

レコードが、ReviewなのかJournal Articleなのか、区別する必要がありますがこれは、PublicationTypeに書いてあります。ただしPublicationTypeは複数あることが普通で、その中に値がReviewであるものがあればReviewということのようです。

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

<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なのかどうかは、

isReview = False
for x in root.findall('MedlineCitation/Article/PublicationTypeList'):
    if (x.text == 'Review'):
        isReview = TRUE

とかすると良いと思います。

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

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

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

for record in records:
    root = ET.fromstring(record)
    #処理を記述

としてやればいいですね。

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

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

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5