一.简介
在做FASPell复现时候需要多搜集一些训练语料。我把目光转向了SIGHAN举行的CSC比赛当中,其中SIGHAN14和SIGHAN15提供了共5000条训练语句,而且他们的格式一样,使用了相同的SGML语言来标记。
二.提醒
网络上较多的是使用sgmllib包中的SGMLParser来进行SGML文件的解析,但由于sgmillib只在Python2中可用,且sgmllib包的下载也成问题,因此最后选择使用HTMLParser。
三.HTMLParser使用
HtmlParser是一个类,在使用时一般继承它然后重载它的方法,来达到解析所需数据的目的。
1.常用属性
lasttag,保存上一个解析的标签名,是字符串。
2.常用方法:
handle_starttag(tag, attrs) ,处理开始标签,比如<div>这里的attrs获取到的是属性列表,属性以元组的方式展示handle_endtag(tag) ,处理结束标签,比如</div>。
handle_startendtag(tag, attrs) ,处理自己结束的标签,如<img />。
handle_data(data) ,处理数据,标签之间的文本。
handle_comment(data) ,处理注释,<!-- -->之间的文本。
3.基本使用
HTMLParse官方代码示例
from html.parser import HTMLParser class MyHTMLParser(HTMLParser): def handle_starttag(self, tag, attrs): print("Encountered a start tag:", tag) def handle_endtag(self, tag): print("Encountered an end tag :", tag) def handle_data(self, data): print("Encountered some data :", data) parser = MyHTMLParser() parser.feed('<html><head><title>Test</title></head>' '<body><h1>Parse me!</h1></body></html>')
输出为
Encountered a start tag: html Encountered a start tag: head Encountered a start tag: title Encountered some data : Test Encountered an end tag : title Encountered an end tag : head Encountered a start tag: body Encountered a start tag: h1 Encountered some data : Parse me! Encountered an end tag : h1 Encountered an end tag : body Encountered an end tag : html
4.实用案例
以下的实用案例均在上面的代码中修改对应函数,每个实例都是单独的。
解析的sgml文件如下:
<ESSAY title="给卫生局的信-有机蔬菜的食品安全"> <TEXT> <PASSAGE id="B2-1443-1">我希望以他们的安全保証书来解决这个问题。不能提出安全保証书的话,应该停止营业。</PASSAGE> </TEXT> <MISTAKE id="B2-1443-1" location="11"> <WRONG>保証书</WRONG> <CORRECTION>保证书</CORRECTION> </MISTAKE> </ESSAY>
- 获取属性的函数,是个静态函数,新增的。直接定义在类中,返回属性名对应的属性
def _attr(attrlist, attrname): for attr in attrlist: if attr[0] == attrname: return attr[1] return None
- 获取所有WRONG标签的文本,最简单方法只修改handle_data
def handle_data(self, data): # 此处标签不区分大小写,统一用小写字母即可,大写字母会报错 if self.lasttag == 'wrong': print("Encountered WRONG data :", data)
- 获取PASSAGE编号(id)为B2-1443-1的PASSAGE标签的文本。使用了案例1,增加一个实例属性作为标志,选取需要的标签
def __init__(self): HTMLParser.__init__(self) self.flag = False def handle_starttag(self, tag, attrs): if tag == 'passage' and _attr(attrs, 'id') == 'B2-1443-1': self.flag = True def handle_data(self, data): if self.flag == True: print("Encountered PASSAGE OF B2-1443-1 data :", data)
- 获取MISTAKE标签的属性列表
def handle_starttag(self, tag, attrs): if tag == 'mistake': print("Encountered MISTAKE attrs :", attrs)
- 获取PASSAGE标签的id属性
def handle_starttag(self, tag, attrs): if tag == 'passage' and _attr(attrs, 'id'): print("Encountered p class :", _attr(attrs, 'id'))
- 获取TEXT标签下的PASSAGE标签的文本
def __init__(self): HTMLParser.__init__(self) self.in_text = False def handle_starttag(self, tag, attrs): if tag == 'text': self.in_text = True def handle_data(self, data): if self.in_text == True and self.lasttag == 'passage': print("Encountered PASSAGE data :", data)
四. 完整代码
这是用来处理SIGHAN15中SGML数据的完整代码,主要是解析SGML文件将他们转化成FASPell中使用的训练数据格式。
待处理sgml文件格式
""" <ESSAY title="给卫生局的信-有机蔬菜的食品安全"> <TEXT> <PASSAGE id="B2-1443-1">我希望以他们的安全保証书来解决这个问题。不能提出安全保证书的话,应该停止营业。</PASSAGE> </TEXT> <MISTAKE id="B2-1443-1" location="11"> <WRONG>保証书</WRONG> <CORRECTION>保证书</CORRECTION> </MISTAKE> </ESSAY> """
目标格式
num wrong_string correct_string 1 我希望以他们的安全保証书来解决这个问题。不能提出安全保证书的话,应该停止营业。 我希望以他们的安全保证书来解决这个问题。不能提出安全保证书的话,应该停止营业。
代码块
from html.parser import HTMLParser import re
def attr(attrlist, attrname): for attr in attrlist: if attr[0] == attrname: return attr[1] return None
class MyHTMLParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.in_text = False self.in_mistake = False self.passage = {} self.wrong = {} self.correction = {} self.listid = [] self.id = "" def handle_starttag(self, tag, attrs): if tag == 'text': self.in_text = True if tag == 'passage' and attr(attrs, 'id'): self.id = attr(attrs, 'id') if self.passage.get(self.id) == None: self.passage[self.id] = [] if tag == 'mistake': self.in_mistake = True if tag == 'mistake' and attr(attrs, 'id'): self.id = attr(attrs, 'id') if self.wrong.get(self.id) == None: self.wrong[self.id] = [] if self.correction.get(self.id) == None: self.correction[self.id] = [] def handle_data(self, data): if self.in_text == True and self.lasttag == 'passage': if len(str(data)) >= 2: self.passage[self.id].append(data) if self.in_mistake == True and self.lasttag == 'wrong': if len(str(data)) >= 2 and str(data) not in self.wrong[self.id]: self.wrong[self.id].append(data) if self.in_mistake == True and self.lasttag == 'correction': if len(str(data)) >= 2 and str(data) not in self.correction[self.id]: self.correction[self.id].append(data) print(self.correction) def handle_endtag(self, tag): if tag == 'text': self.in_text = False self.id = "" if tag == 'mistake': self.in_mistake = False self.id = ""
def write_to_file(correction,wrong,passage): with open('median_file.txt','a',encoding='utf-8') as file: for k,v in passage.items(): origin_string = str(v[0]) wrong_list = [] correct_list = [] if k in wrong and k in correction: wrong_list.extend(wrong[k]) correct_list.extend(correction[k]) count = len(wrong_list) if count == 0: continue if count == len(correct_list): tmp_string = origin_string for i in range(count): corr_string = re.sub(wrong_list[i],correct_list[i],tmp_string) tmp_string = corr_string num = 0 if len(corr_string) != len(origin_string): continue for i in range(len(corr_string)): if corr_string[i] != origin_string[i]: num += 1 file.write('{}\t{}\t{}\n'.format(num,origin_string,corr_string))
if __name__ == "__main__": files = open('sighan15.sgml','r', encoding='utf-8') myhtmlparser = MyHTMLParser() myhtmlparser.feed(files.read()) write_to_file(myhtmlparser.correction,myhtmlparser.wrong,myhtmlparser.passage)