#はじめに
この記事は、Pubmedで検索で引っかかった文献データ(xml形式)をpythonで読み込む方法についての自分用メモです。
お気付きの点がありましたらご指摘いただけますと幸いです。
#処理したいデータ
一件のデータは以下のような感じです。実際は複数件のデータを処理したいですが、まずは一件ずつ処理できるようにします。
<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を読むためのライブラリーを読み込みます。
import xml.etree.ElementTree as ET
ファイルからxmlデータを読み込みます。
複数のデータが改行2つ区切りで並んでいるようなので、splitで分割してリストにします。
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オブジェクトであることがわかります。
root = ET.fromstring(records[0])
type(root)
#<class 'xml.etree.ElementTree.Element'>
root.tagでtagを確認できるとのことです。確認してみます。
root.tag
#'PubmedArticle'
1つのデータについては、ざっくり言えば以下のような形になっています。root.tagで、一番外側のtagにアクセスできました。
<PubmedArticle>
<MedlineCitation>
</MedlineCitation>
<PubmedData>
</PubmedData>
</PubmedArticle>
<PubmedArticle>の内側には2つの要素(MedlineCitationとPubmedData)があり、これには添字を使ってアクセスできます。添字を使ってアクセスし、さらにtypeを調べます。
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つまたは複数指定できます。タグ全体をクォーテーションで囲み、タグを複数指定する場合はタグ間をスラッシュで区切ります。
- find('tag1/tag2')
- findall('tag1/tag2')
- 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のデータの取り扱い方がわかりました。