@nearby
2018-07-31T02:52:00.000000Z
字数 4474
阅读 1463
Python xml html 解析
按15年前、后进行区分,15年前,电子病历以XML格式存储在后台数据库;15年后,以HTML格式存在。
标准的XML文档是一种树结构,从根元素直至最低端,形如:
<root><child><subchild>.....</subchild></child></root>
root是树的根元素,root,child, subchild都称为标签tag.标签是成对存在的,作为起始点,即<root></root>。父、子以及同胞等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素成为同胞(兄弟或姐妹)。所有元素均可拥有文本内容和属性(类似 HTML 中)。
对xml树结构的认识,可参考http://www.w3school.com.cn/xml/xml_tree.asp
HTML也是一种树结构,与xml类似
<html><body><h1>我的第一个标题</h1><p>我的第一个段落。</p></body></html>
(1)15年前xml格式举例
例子中被蓝色方框标记的都是标签tag,在sublime中用红色进行区分,绿色部分为tag中的属性,白色为文本内容,即我们最终需要提取的内容部分。
需要提取的白色部分属于Content_Text标签的内容,因此思路就是对xml这种树结构,遍历到Content_Text标签 (不同的电子病历标签可能不一样,请结合实际情况)
(2)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解析过程
def getDetail(file):tree = ET.ElementTree(file=file)root = tree.getroot()records = root.findall('RECORD')all_info = []for record in records:xml_content = record.find('Content').text#此处Content的名字以从CDR中导出来的病历xml所属列列名为准if xml_content == None:continueinfo_root = ET.fromstring(xml_content)info = {}if info_root == None:continuefor elem in info_root.iter(tag='DocObjContent'):#寻找标签值等于DocObjContent的标签,将该标签的PatNo属性放在info字典中存储info[u'住院号'] = elem.attrib['PatNo']for elem in info_root.iterfind('Section/NewCtrl'):#寻找Section标签下的NewCtrl标签,该标签的属性DisplayName作为需要提取的内容的列名,比如记录时间、主诉、病史;标签下Content_Text标签的text属性则含有要提取的内容info[elem.attrib['DisplayName']] = elem.find('Content_Text').text# 入院情况的NewCtrl没有父标签Section,因此单独查找for elem in info_root.iterfind('NewCtrl'):info[elem.attrib['DisplayName']] = elem.find('Content_Text').text#所有病人的信息加入list中,以备最终写入csvall_info.append(info)return all_info
写入csv
def list2csv(info, output_file):# info为一个list,即上一个函数得到的all_info,output_file为输出csv的路径,根据自己的路径进行修改。with open(output_file, 'w', newline='') as f: #2018.7.31更新,增加newlinew = csv.writer(f, dialect='excel')#2018.7.31更新,增加excel,不会每一行下面多出一行空白columns = info[0].keys()w.writerow(columns)for row in info:w.writerow(row.values())
HTML解析过程
#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time: 2018/5/8 10:30import xml.etree.ElementTree as ETfrom bs4 import BeautifulSoupimport reimport HTMLParserimport csvfile = './原始数据/EMR_after150910.xml'output_file = './原始数据/EMR_after150910.csv'# 鉴于中文标签在输入至beautifulsoup时因为编码的问题,会被当成注释,因此先将中文标签替换成英文,需根据实际病历标签进行适当修改rep = {u'其他基本病史信息': 'Other_Info',u'主诉': 'Chief_Complaint',u'入院情况': 'Admit_Condition',u'既往史': 'History',u'初步诊断': 'Initial_Diag',u'诊疗经过': 'Treatment',u'出院情况': 'Discharge',u'出院诊断': 'Discharge_Diag',u'出院医嘱': 'Discharge_Order',u'医师签名': 'Signment',u'签署日期': 'Sign_Date'}tags = ['other_info', 'chief_complaint', 'admit_condition', 'history', 'initial_diag', 'treatment', 'discharge', 'discharge_diag', 'discharge_order', 'signment', 'sign_date']# 原始导出的html病历,会有& " <等看起来像乱码的符号,实际为html的转义字符,代表的是& \ <等符号。在进行解析前,需要对这些转义字符进行替换。# 可以用HTMLParser进行替换,也可以自己写函数进行替换,以下为利用字符串的替换函数replace进行替换,此时可以不安装HTMLParser包。def replaceHTML(text):for k,v in rep.items():text = text.replace(k,v)return text.replace("&","&").replace(" ", " ").replace(""", "\"").replace("<","<").replace(">", ">")# 与replaceHTML一样的功能,只不过使用的HTMLParser包def parse(text):parser = HTMLParser.HTMLParser()text = parser.unescape(text)return text# 把list写入到csvdef list2csv(info, output_file):with open(output_file, 'w') as f:w = csv.writer(f)columns = info[0].keys()w.writerow(columns)for row in info:w.writerow(row.values())def getDetailInfo(file):tree = ET.ElementTree(file=file) #CDR中导出15年后病历,保存为xml格式root = tree.getroot()records = root.findall('RECORD') # 所有病人病历htmlall_info = [] # 存放所有解析后的数据for record in records: # 遍历所有病人htmlres = {}# 导出的xml中有Inhosp_No这一列,所以这里读取出来,具体看导出的数据样式res['Inhosp_No'] = record.find('Inhosp_No').text# 病历放在Content这一列html_content = record.find('Content').textif html_content == None:continuehtml_content = replaceHTML(html_content)# replaceHTML与parse函数二选一# html_content = parse(html_content)# html病历放入BeautifulSoup函数soup = BeautifulSoup(html_content, 'html.parser')for tag in tags:# tags为提前替换成英文的标签if soup.find(tag) == None:continue# 找到标签下包含的内容,要层层遍历直至找到所有段落标签<p>temps = (soup.find(tag).html.body).find_all('p')#temps包含多个段落,每一个p标签包含的内容# res_list = [] (2018.7.31更新,不使用list,使用字符直接串联起来)res_list = ""for temp in temps:# p标签下还有span标签for i in temp.find_all('span'): #在每个p标签中找到包含的span标签,并放入一个list中,其中span里的内容空格替换成非空格,方便后续处理#res_list.append(i.string.replace(' ','')) #span的内容添加到list中res_list = res_list + i.stringres[tag] = res_list #把所有的span跟tag对应起来all_info.append(res)return all_infoif __name__ == '__main__':all_info = getDetailInfo(file)list2csv(all_info, output_file)