[关闭]
@nearby 2018-07-31T02:52:00.000000Z 字数 4474 阅读 1463

Python解析15年前、后的电子病历

Python xml html 解析


一、本院电子病历存储结构

按15年前、后进行区分,15年前,电子病历以XML格式存储在后台数据库;15年后,以HTML格式存在。

标准xml结构

标准的XML文档是一种树结构,从根元素直至最低端,形如:

  1. <root>
  2. <child>
  3. <subchild>.....</subchild>
  4. </child>
  5. </root>

root是树的根元素,root,child, subchild都称为标签tag.标签是成对存在的,作为起始点,即<root></root>。父、子以及同胞等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素成为同胞(兄弟或姐妹)。所有元素均可拥有文本内容和属性(类似 HTML 中)。
对xml树结构的认识,可参考http://www.w3school.com.cn/xml/xml_tree.asp

标准HTML结构

HTML也是一种树结构,与xml类似

  1. <html>
  2. <body>
  3. <h1>我的第一个标题</h1>
  4. <p>我的第一个段落。</p>
  5. </body>
  6. </html>

实际举例

(1)15年前xml格式举例
15前病历xml格式举例
例子中被蓝色方框标记的都是标签tag,在sublime中用红色进行区分,绿色部分为tag中的属性,白色为文本内容,即我们最终需要提取的内容部分。
需要提取的白色部分属于Content_Text标签的内容,因此思路就是对xml这种树结构,遍历到Content_Text标签 (不同的电子病历标签可能不一样,请结合实际情况)

(2)15年后html格式举例
15后病历html格式举例
由<>包围的都称作tag,标签为中文时,sublime显示成白色。结构基本与xml相似,但其中还包含段落排版。
我们需要提取的内容包含在段落标签<p></p>以及<span></span>(span标签表示不换行)中,同样地,对树结构遍历到该标签。

二、代码分析

安装必备包

环境:Windows7; Python3.6 (Python2.7?)
必备包:xml, pandas, bs4, HTMLParser
Python下安装包,进入到cmd命令行,pip install package_name
以下分析默认已满足上述要求
此处输入图片的描述

XML解析过程

  1. def getDetail(file):
  2. tree = ET.ElementTree(file=file)
  3. root = tree.getroot()
  4. records = root.findall('RECORD')
  5. all_info = []
  6. for record in records:
  7. xml_content = record.find('Content').text
  8. #此处Content的名字以从CDR中导出来的病历xml所属列列名为准
  9. if xml_content == None:
  10. continue
  11. info_root = ET.fromstring(xml_content)
  12. info = {}
  13. if info_root == None:
  14. continue
  15. for elem in info_root.iter(tag='DocObjContent'):
  16. #寻找标签值等于DocObjContent的标签,将该标签的PatNo属性放在info字典中存储
  17. info[u'住院号'] = elem.attrib['PatNo']
  18. for elem in info_root.iterfind('Section/NewCtrl'):
  19. #寻找Section标签下的NewCtrl标签,该标签的属性DisplayName作为需要提取的内容的列名,比如记录时间、主诉、病史;标签下Content_Text标签的text属性则含有要提取的内容
  20. info[elem.attrib['DisplayName']] = elem.find('Content_Text').text
  21. # 入院情况的NewCtrl没有父标签Section,因此单独查找
  22. for elem in info_root.iterfind('NewCtrl'):
  23. info[elem.attrib['DisplayName']] = elem.find('Content_Text').text
  24. #所有病人的信息加入list中,以备最终写入csv
  25. all_info.append(info)
  26. return all_info

写入csv

  1. def list2csv(info, output_file):
  2. # info为一个list,即上一个函数得到的all_info,output_file为输出csv的路径,根据自己的路径进行修改。
  3. with open(output_file, 'w', newline='') as f: #2018.7.31更新,增加newline
  4. w = csv.writer(f, dialect='excel')
  5. #2018.7.31更新,增加excel,不会每一行下面多出一行空白
  6. columns = info[0].keys()
  7. w.writerow(columns)
  8. for row in info:
  9. w.writerow(row.values())

HTML解析过程

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time: 2018/5/8 10:30
  4. import xml.etree.ElementTree as ET
  5. from bs4 import BeautifulSoup
  6. import re
  7. import HTMLParser
  8. import csv
  9. file = './原始数据/EMR_after150910.xml'
  10. output_file = './原始数据/EMR_after150910.csv'
  11. # 鉴于中文标签在输入至beautifulsoup时因为编码的问题,会被当成注释,因此先将中文标签替换成英文,需根据实际病历标签进行适当修改
  12. rep = {u'其他基本病史信息': 'Other_Info',
  13. u'主诉': 'Chief_Complaint',
  14. u'入院情况': 'Admit_Condition',
  15. u'既往史': 'History',
  16. u'初步诊断': 'Initial_Diag',
  17. u'诊疗经过': 'Treatment',
  18. u'出院情况': 'Discharge',
  19. u'出院诊断': 'Discharge_Diag',
  20. u'出院医嘱': 'Discharge_Order',
  21. u'医师签名': 'Signment',
  22. u'签署日期': 'Sign_Date'}
  23. tags = ['other_info', 'chief_complaint', 'admit_condition', 'history', 'initial_diag', 'treatment', 'discharge', 'discharge_diag', 'discharge_order', 'signment', 'sign_date']
  24. # 原始导出的html病历,会有&amp; &quot; &lt;等看起来像乱码的符号,实际为html的转义字符,代表的是& \ <等符号。在进行解析前,需要对这些转义字符进行替换。
  25. # 可以用HTMLParser进行替换,也可以自己写函数进行替换,以下为利用字符串的替换函数replace进行替换,此时可以不安装HTMLParser包。
  26. def replaceHTML(text):
  27. for k,v in rep.items():
  28. text = text.replace(k,v)
  29. return text.replace("&amp;","&").replace("&nbsp;", " ").replace("&quot;", "\"").replace("&lt;","<").replace("&gt;", ">")
  30. # 与replaceHTML一样的功能,只不过使用的HTMLParser包
  31. def parse(text):
  32. parser = HTMLParser.HTMLParser()
  33. text = parser.unescape(text)
  34. return text
  35. # 把list写入到csv
  36. def list2csv(info, output_file):
  37. with open(output_file, 'w') as f:
  38. w = csv.writer(f)
  39. columns = info[0].keys()
  40. w.writerow(columns)
  41. for row in info:
  42. w.writerow(row.values())
  43. def getDetailInfo(file):
  44. tree = ET.ElementTree(file=file) #CDR中导出15年后病历,保存为xml格式
  45. root = tree.getroot()
  46. records = root.findall('RECORD') # 所有病人病历html
  47. all_info = [] # 存放所有解析后的数据
  48. for record in records: # 遍历所有病人html
  49. res = {}
  50. # 导出的xml中有Inhosp_No这一列,所以这里读取出来,具体看导出的数据样式
  51. res['Inhosp_No'] = record.find('Inhosp_No').text
  52. # 病历放在Content这一列
  53. html_content = record.find('Content').text
  54. if html_content == None:
  55. continue
  56. html_content = replaceHTML(html_content)
  57. # replaceHTML与parse函数二选一
  58. # html_content = parse(html_content)
  59. # html病历放入BeautifulSoup函数
  60. soup = BeautifulSoup(html_content, 'html.parser')
  61. for tag in tags:
  62. # tags为提前替换成英文的标签
  63. if soup.find(tag) == None:
  64. continue
  65. # 找到标签下包含的内容,要层层遍历直至找到所有段落标签<p>
  66. temps = (soup.find(tag).html.body).find_all('p')
  67. #temps包含多个段落,每一个p标签包含的内容
  68. # res_list = [] (2018.7.31更新,不使用list,使用字符直接串联起来)
  69. res_list = ""
  70. for temp in temps:
  71. # p标签下还有span标签
  72. for i in temp.find_all('span'): #在每个p标签中找到包含的span标签,并放入一个list中,其中span里的内容空格替换成非空格,方便后续处理
  73. #res_list.append(i.string.replace(' ','')) #span的内容添加到list中
  74. res_list = res_list + i.string
  75. res[tag] = res_list #把所有的span跟tag对应起来
  76. all_info.append(res)
  77. return all_info
  78. if __name__ == '__main__':
  79. all_info = getDetailInfo(file)
  80. list2csv(all_info, output_file)
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注