数据预处理之抽取文本信息(2)
大数据技术与我们日常生活越来越紧密,要做大数据,首要解决数据问题。原始数据存在大量不完整、不一致、有异常的数据,严重影响到数据建模的执行效率,甚至可能导致模型结果的偏差,因此要数据预处。数据预处理主要是将原始数据经过文本抽取、数据清理、数据集成、数据处理、数据变换、数据降维等处理后,不仅提高了数据质量,而且更好的提升算法模型性能。数据预处理在数据挖掘、自然语言处理、机器学习、深度学习算法中起着重要的作用。(本文原创,转载必须注明出处.)
Python数据预处理之抽取文本信息(2)
白宁超 2018年12月28日10:28:48
摘要:大数据技术与我们日常生活越来越紧密,要做大数据,首要解决数据问题。原始数据存在大量不完整、不一致、有异常的数据,严重影响到数据建模的执行效率,甚至可能导致模型结果的偏差,因此要数据预处。数据预处理主要是将原始数据经过文本抽取、数据清理、数据集成、数据处理、数据变换、数据降维等处理后,不仅提高了数据质量,而且更好的提升算法模型性能。数据预处理在数据挖掘、自然语言处理、机器学习、深度学习算法中起着重要的作用。(本文原创,转载必须注明出处.)
1 数据类型与数据采集
通常说的数据指的的数字、图表信息这些。在大数据领域所谓的数据总体包括结构化数据、半结构化数据和非结构化数据。
结构化数据
结构化的数据是指可以使用关系型数据库表示和存储,表现为二维形式的数据。一般特点是:数据以行为单位,一行数据表示一个实体的信息,每一行数据的属性是相同的。比如:
id | name | age | gender |
---|---|---|---|
1 | 张三 | 12 | 男 |
2 | 李花 | 13 | 女 |
3 | 王五 | 18 | 男 |
- 数据特点:关系模型数据,关系数据库表示。
- 常见格式:比如MySQL、Oracle、SQL Server等。
- 应用场合:数据库、系统网站、数据备份、ERP等。
- 数据采集:DB导出、SQL等方式。
结构化的数据的存储和排列是很有规律的,这对查询和修改等操作很有帮助。但是,它的扩展性不好。
半结构化数据
半结构化数据是结构化数据的一种形式,它并不符合关系型数据库或其他数据表的形式关联起来的数据模型结构,但包含相关标记,用来分隔语义元素以及对记录和字段进行分层。因此,它也被称为自描述的结构。半结构化数据,属于同一类实体可以有不同的属性,即使他们被组合在一起,这些属性的顺序并不重要。常见的半结构数据有XML如下:
<person>
<name>李花</name>
<age>13</age>
<gender>女</gender>
</person>
- 数据特点:非关系模型数据,还有一定的格式。
- 常见格式:比如Email、HTML、XML、JSON等。
- 应用场合:邮件系统、档案系统、新闻网站等。
- 数据采集:网络爬虫、数据解析等方式。
不同的半结构化数据的属性的个数是不定的。有些人说半结构化数据是以树或者图的数据结构存储的数据,上面的例子中,标签是树的根节点,和标签是子节点。通过这样的数据格式,可以自由地表达很多有用的信息,包括自我描述信息(元数据)。所以,半结构化数据的扩展性是很好的。
非结构化数据
就是没有固定结构的数据。各种文档、图片、视频/音频等都属于非结构化数据。对于这类数据,我们一般直接整体进行存储,而且一般存储为二进制的数据格式。如下所示:
- 数据特点:没有固定格式的数据
- 常见格式:Word、PDF、PPT、图片、音视频等。
- 应用场合:图片识别、人脸识别、医疗影像、文本分析等。
- 数据采集:网络爬虫、数据存档等方式。
2 常见的文本抽取方法
针对数据不同形式,通过特定方式的数据采集方式(文档下载、数据库导出、网络爬虫、语音收集、图片解析等等)获取数据,无论是结构化的数据库文件、半结构化的网页数据,还是非结构化的图片、音视频。我们最终的目的都是将数据传入到电脑之中,通过算法模型挖掘其潜在的价值,为最终的AI技术做支撑。不同的是,在结构化和半结构化数据数据集成过程中,我们可以提取相关文本信息,做进一步的数据预处理;而非结构化的图片、音视频我们采用一定的技术手段,获取其对应的数据点矩阵。这一点不太容易理解,我们比如说想解析一张图片的数据,我们知道图片是有长宽高组成的,还包括红蓝绿三种基本色。那么我们就找到对应的多维特征,采用数据点占位表示,比如:
图片名 | 长(bit) | 宽(bit) | 红 | 绿 | 蓝 |
---|---|---|---|---|---|
猫1 | 12 | 100 | 0 | 0 | 1 |
狗2 | 101 | 234 | 1 | 1 | 1 |
猪3 | 202 | 24 | 0 | 1 | 0 |
上面就数据表示猫1这张图片,长宽位点(12,100)处只有蓝色构成;狗2这张图片,长宽位点(101,234)处有红绿蓝3中色构成;猪3这张图片,长宽位点(202,24)处只有绿构成。这就是非结构数据图片转化为数值型数据的原理。完整流程数据挖掘的流程图如下所示:
我根据不同的数据类型,采用对应的数据采集方式获取目标数据。这时候的数据质量很差,存在文本格式不同,数据表示形式不同等诸多问题。这里我们单纯的考虑文本信息的处理,就文本信息而言,你采集的数据可能是网页、数据库文件、pdf文档、word文档等等。我们想去处理这些数据,还需要对数据进行集成即转化为统一的数据格式,这里我们就需要文本信息抽取,常见的抽取方式包括以下几个内容:在线格式转换工具、office内置格式转换、自己开发文本抽取工具。详见下图:
经过实际操作会发现采用在线格式转换工具存在几个弊端,其限制文件转化的数据,要么就是收费的;而采用本地的office自带文档,一个个另存为文本,肯定不现实。基于上述情况,我们对工具抽取的弊端总结如下:
- 格式转换后,识别乱码较多
- 不支持或者限制支持批量处理
- 批量转化收费问题
- 格式转换后的txt文件存在编码问题
- 生成文件名一堆数字乱码
- 操作不够灵活便捷
我们针对以上问题,就去寻求解决方式,那就是自己动手丰衣足食,我们自己去打造批量文本抽取问题,我们期待效果是:
- 支持PDF/Word等多格式文本抽取
- 自动过滤不符合指定格式的文件
- 生成的目标文件与原文件目录一致
- 生成文档采用统一的编码格式保存(如:UTF-8 )
- 支持默认保存路径和自定义保存路径
3 抽取Word文档文本
做word文档抽取工作,我们运行环境是在win10-64bit下,python3.5,Anaconda4.4版本下执行的,所使用的插件是win32com。下载地址:https://pan.baidu.com/s/1-2BsiTs8XjMIe5Gnh_GFjw 密码: 7j3t
预装完win32com以后,以下代码便完成抽取word文本信息。
算法思路:
- 定义文件路径和转存路径:split
- 修改新的文件名:fnmatch
- 设置完整的保存路径:join
- 启动应用程序格式转换:Dispatch
- 保存文本:SaveAs
算法流程:
代码实现:
#coding=utf-8
"""
Description: Word文件转化TXT文本
Author:伏草惟存
Prompt: code in Python3 env
Install package: pip install pypiwin32
"""
import os,fnmatch
from win32com import client as wc
from win32com.client import Dispatch
\'\'\'
功能描述:word文件转存txt文件,默认存储当前路径下;用户可以指定存储文件路径。
参数描述:1 filePath:文件路径 2 savePath: 指定保存路径
\'\'\'
def Word2Txt(filePath,savePath=\'\'):
# 1 切分文件上级目录和文件名
dirs,filename = os.path.split(filePath)
# print(dirs,\'\n\',filename)
# 2 修改转化后的文件名
new_name = \'\'
if fnmatch.fnmatch(filename,\'*.doc\'):
new_name = filename[:-4]+\'.txt\'
elif fnmatch.fnmatch(filename,\'*.docx\'):
new_name = filename[:-5]+\'.txt\'
else: return
print(\'->\',new_name)
# 3 文件转化后的保存路径
if savePath==\'\': savePath = dirs
else: savePath = savePath
word_to_txt = os.path.join(savePath,new_name)
print(\'->\',word_to_txt)
# 4 加载处理应用,word转化txt
wordapp = wc.Dispatch(\'Word.Application\')
mytxt = wordapp.Documents.Open(filePath)
mytxt.SaveAs(word_to_txt,4)
mytxt.Close()
if __name__==\'__main__\':
filepath = os.path.abspath(r\'../dataSet/filename.doc\')
# savepath = \'\'
Word2Txt(filepath)
4 抽取PDF文档文本
算法思路:
- 定义文件路径和转存路径:split
- 修改新的文件名:fnmatch
- 设置完整的保存路径:join
- 启动应用程序格式转换:Dispatch
- 保存文本:SaveAs
算法流程:
代码实现:
# coding=utf-8
"""
Description: PDF文件转化TXT文本
Author:伏草惟存
Prompt: code in Python3 env
"""
import os,fnmatch
from win32com import client as wc
from win32com.client import Dispatch,gencache
\'\'\'
功能描述:pdf文件转化txt文本
参数描述:1 filePath:文件路径 2 savePath: 指定保存路径
\'\'\'
def Pdf2Txt(filePath,savePath=\'\'):
# 1 切分文件上级目录和文件名
dirs,filename = os.path.split(filePath)
# print(\'目录:\',dirs,\'\n文件名:\',filename)
# 2 修改转化后的文件名
new_name = ""
if fnmatch.fnmatch(filename,\'*.pdf\') or fnmatch.fnmatch(filename,\'*.PDF\'):
new_name = filename[:-4]+\'.txt\' # 截取".pdf"之前的文件名
else: return
print(\'新的文件名:\',new_name)
# 3 文件转化后的保存路径
if savePath=="": savePath = dirs
else: savePath = savePath
pdf_to_txt = os.path.join(savePath,new_name)
print(\'保存路径:\',pdf_to_txt)
# 4 加载处理应用,pdf转化txt
wordapp = wc.Dispatch(\'Word.Application\')
mytxt = wordapp.Documents.Open(filePath)
mytxt.SaveAs(pdf_to_txt,4)
mytxt.Close()
if __name__==\'__main__\':
# 使用绝对路径
filePath = os.path.abspath(r\'../dataSet/Corpus/pdftotxt/2018年世界新闻自由日.pdf\')
# savePath = r\'E:\\\'
Pdf2Txt(filePath)
5 文本抽取工具与编码
算法思路:
- 定义文件夹路径和转存夹路径:split
- 修改新的文件名:TranType(filename, typename)、fnmatch
- 设置完整的保存路径:join
- 启动应用程序格式转换:Dispatch
- 保存文本:SaveAs
代码实现
#coding=utf-8
"""
Description: 多文档格式转换工具
Author:伏草惟存
Prompt: code in Python3 env
"""
import os,fnmatch
from win32com import client as wc
from win32com.client import Dispatch,gencache
\'\'\'
功能描述:抽取文件文本信息
参数描述:1 filePath:文件路径 2 savePath: 指定保存路径
\'\'\'
def Files2Txt(filePath,savePath=\'\'):
try:
# 1 切分文件上级目录和文件名
dirs,filename = os.path.split(filePath)
# print(\'目录:\',dirs,\'\n文件名:\',filename)
# 2 修改转化后的文件名
typename = os.path.splitext(filename)[-1].lower() # 获取后缀
new_name = TranType(filename,typename)
# print(\'新的文件名:\',new_name)
# 3 文件转化后的保存路径
if savePath=="": savePath = dirs
else: savePath = savePath
new_save_path = os.path.join(savePath,new_name)
print(\'保存路径:\',new_save_path)
# 4 加载处理应用
wordapp = wc.Dispatch(\'Word.Application\')
mytxt = wordapp.Documents.Open(filePath)
mytxt.SaveAs(new_save_path,4)
mytxt.Close()
except Exception as e:
pass
\'\'\'
功能描述:根据文件后缀修改文件名
参数描述:1 filePath:文件路径 2 typename 文件后缀
返回数据:new_name 返回修改后的文件名
\'\'\'
def TranType(filename,typename):
# 新的文件名称
new_name = ""
if typename == \'.pdf\' : # pdf->txt
if fnmatch.fnmatch(filename,\'*.pdf\') :
new_name = filename[:-4]+\'.txt\' # 截取".pdf"之前的文件名
else: return
elif typename == \'.doc\' or typename == \'.docx\' : # word->txt
if fnmatch.fnmatch(filename, \'*.doc\') :
new_name = filename[:-4]+\'.txt\'
elif fnmatch.fnmatch(filename, \'*.docx\'):
new_name = filename[:-5]+\'.txt\'
else: return
else:
print(\'警告:\n您输入[\',typename,\']不合法,本工具支持pdf/doc/docx格式,请输入正确格式。\')
return
return new_name
if __name__ == \'__main__\':
filePath1 = os.path.abspath(r\'../dataSet/Corpus/wordtotxt/一种改进的朴素贝叶斯文本分类方法研究.doc\')
filePath2 = os.path.abspath(r\'../dataSet/Corpus/pdftotxt/改进朴素贝叶斯文本分类方法研究.pdf\')
filePath3 = os.path.abspath(r\'../dataSet/Corpus/wordtotxt/科技项目数据挖掘决策架构.docx\')
Files2Txt(filePath3)
遍历读取文件
- 遍历文件的类TraversalFun : TraversalDir、 AllFiles
- 遍历目录文件TraversalDir : AllFiles(self.rootDir)
- 递归遍历文件AllFiles: AllFiles(self,rootDir)
- 判断是否为文件isfile :打印出文件名
- 判断是否是目录isdir :递归遍历
遍历文件源码实现
# coding=utf-8
"""
Description: 遍历读取文件名
Author:伏草惟存
Prompt: code in Python3 env
"""
import os,time
\'\'\'
功能描述:遍历目录处理子文件
参数描述: 1 rootDir 目标文件的根目录
\'\'\'
class TraversalFun():
# 1 初始化
def __init__(self,rootDir):
self.rootDir = rootDir # 目录路径
# 2 遍历目录文件
def TraversalDir(self):
TraversalFun.AllFiles(self,self.rootDir)
# 3 递归遍历所有文件,并提供具体文件操作功能
def AllFiles(self,rootDir):
# 返回指定目录包含的文件或文件夹的名字的列表
for lists in os.listdir(rootDir):
# 待处理文件夹名字集合
path = os.path.join(rootDir, lists)
# 核心算法,对文件具体操作
if os.path.isfile(path):
print(os.path.abspath(path))
# 递归遍历文件目录
elif os.path.isdir(path):
TraversalFun.AllFiles(self,path)
if __name__ == \'__main__\':
time_start=time.time()
# 根目录文件路径
rootDir = r"../dataSet/Corpus/EnPapers"
tra=TraversalFun(rootDir) # 默认方法参数打印所有文件路径
tra.TraversalDir() # 遍历文件并进行相关操作
time_end=time.time()
print(\'totally cost\',time_end-time_start,\'s\')
6 实战案例:遍历文件批量抽取新闻文本内容
算法思路
- 引用外部文本抽取模块:import ExtractTxt as ET
- 参数方法使用:TraversalFun(rootDir,ET.Files2Txt,saveDir)
- 创建保存根目录:os.path.abspath
- 递归遍历文件:func(path, save_dir)
源码实现
# coding=utf-8
"""
Description: 批量文档格式自动转化txt
Author:伏草惟存
Prompt: code in Python3 env
"""
import ExtractTxt as ET
import os,time
\'\'\'
功能描述:遍历目录,对子文件单独处理
参数描述:1 rootDir 根目录 2 deffun:方法参数 3 saveDir: 保存路径
\'\'\'
class TraversalFun():
# 1 初始化
def __init__(self,rootDir,func=None,saveDir=""):
self.rootDir = rootDir # 目录路径
self.func = func # 参数方法
self.saveDir = saveDir # 保存路径
# 2 遍历目录文件
def TraversalDir(self):
# 切分文件上级目录和文件名
dirs,latername = os.path.split(self.rootDir)
# print(rootDir,\'\n\',dirs,\'\n\',latername)
# 保存目录
save_dir = ""
if self.saveDir=="": # 默认文件保存路径
save_dir = os.path.abspath(os.path.join(dirs,\'new_\'+latername))
else: save_dir = self.saveDir
# 创建目录文件
if not os.path.exists(save_dir): os.makedirs(save_dir)
print("保存目录:\n"+save_dir)
# 遍历文件并将其转化txt文件
TraversalFun.AllFiles(self,self.rootDir,save_dir)
# 3 递归遍历所有文件,并提供具体文件操作功能
def AllFiles(self,rootDir,save_dir=\'\'):
# 返回指定目录包含的文件或文件夹的名字的列表
for lists in os.listdir(rootDir):
# 待处理文件夹名字集合
path = os.path.join(rootDir, lists)
# 核心算法,对文件具体操作
if os.path.isfile(path):
self.func(os.path.abspath(path),os.path.abspath(save_dir))
# 递归遍历文件目录
if os.path.isdir(path):
newpath = os.path.join(save_dir, lists)
if not os.path.exists(newpath):
os.mkdir(newpath)
TraversalFun.AllFiles(self,path,newpath)
if __name__ == \'__main__\':
time_start=time.time()
# 根目录文件路径
rootDir = r"../dataSet/Corpus/EnPapers"
# saveDir = r"./Corpus/TxtEnPapers"
tra=TraversalFun(rootDir,ET.Files2Txt) # 默认方法参数打印所有文件路径
tra.TraversalDir() # 遍历文件并进行相关操作
time_end=time.time()
print(\'totally cost\',time_end-time_start,\'s\')
7源码获取
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0 }
body { font-family: “microsoft yahei”, Helvetica, arial, freesans, clean, sans-serif; font-size: 16px; line-height: 1.5; color: rgba(63, 63, 63, 1); background-color: rgba(255, 255, 255, 1); padding: 20px; margin: 0 auto; word-break: break-all }
body>*:first-child { margin-top: 0 !important }
body>*:last-child { margin-bottom: 0 !important }
a { color: rgba(65, 131, 196, 1); text-decoration: none }
a:active, a:hover, a:hover, a:visited { color: rgba(202, 12, 22, 1); text-decoration: underline }
p, blockquote, ul, ol, dl, table, pre { margin: 13px 0 }
h1, h2, h3, h4, h5, h6 { font-family: “PingFang SC”, “Microsoft YaHei”, SimHei, Arial, SimSun; margin: 20px 0 10px; padding: 0; font-weight: bold; -webkit-font-smoothing: antialiased }
h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { font-size: inherit }
h1 { font-size: 24px; color: rgba(0, 0, 0, 1) }
h2 { font-size: 20px; border-bottom: 2px solid rgba(63, 63, 63, 1); color: rgba(0, 0, 0, 1); font-weight: bold; -webkit-font-smoothing: antialiased }
h3 { font-size: 16px; font-weight: bold; -webkit-font-smoothing: antialiased }
h4 { font-size: 15px }
h5 { font-size: 14px }
h6 { color: rgba(119, 119, 119, 1); font-size: 13px }
body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child { margin-top: 0; padding-top: 0 }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 { margin-top: 0; padding-top: 0 }
h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { margin-top: 10px }
ul, ol { padding-left: 30px }
ul li>:first-child, ol li>:first-child, ul li ul:first-of-type, ol li ol:first-of-type, ul li ol:first-of-type, ol li ul:first-of-type { margin-top: 0 }
ul ul, ul ol, ol ol, ol ul { margin-bottom: 0 }
dl { padding: 0 }
dl dt { font-size: 14px; font-weight: bold; font-style: italic; padding: 0; margin: 15px 0 5px }
dl dt:first-child { padding: 0 }
dl dt>:first-child { margin-top: 0 }
dl dt>:last-child { margin-bottom: 0 }
dl dd { margin: 0 0 15px; padding: 0 15px }
dl dd>:first-child { margin-top: 0 }
dl dd>:last-child { margin-bottom: 0 }
p code { color: rgba(181, 42, 29, 1) }
pre, code, tt { font-family: Consolas, “Liberation Mono”, Courier, monospace }
code, tt { margin: 0 2px; padding: 0 8px; white-space: nowrap; border: 1px solid rgba(234, 234, 234, 1); background-color: rgba(248, 248, 248, 1); border-radius: 3px }
pre>code { margin: 0; padding: 0; white-space: ; border: none; background: rgba(0, 0, 0, 0); word-wrap: break-word; overflow: auto; word-break: break-all }
pre { background-color: rgba(249, 249, 249, 1); border: 1px solid rgba(204, 204, 204, 1); font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px; border-radius: 3px }
pre code, pre tt { background-color: rgba(0, 0, 0, 0); border: none }
kbd { -moz-border-bottom-colors: none; -moz-border-left-colors: none; -moz-border-right-colors: none; -moz-border-top-colors: none; background-color: rgba(221, 221, 221, 1); background-image: linear-gradient(rgba(241, 241, 241, 1), rgba(221, 221, 221, 1)); background-repeat: repeat-x; border-top: 1px solid rgba(221, 221, 221, 1); border-right: 1px solid rgba(204, 204, 204, 1); border-bottom: 1px solid rgba(204, 204, 204, 1); border-left: 1px solid rgba(221, 221, 221, 1); border-image: none; border-radius: 2px; font-family: “Helvetica Neue”, Helvetica, Arial, sans-serif; line-height: 10px; padding: 1px 4px }
blockquote { padding: 15px 20px; border-left: 10px solid rgba(241, 241, 241, 1); background-color: rgba(249, 249, 249, 1); border-radius: 0 5px 5px 0 }
blockquote>:first-child { margin-top: 0 }
blockquote>:last-child { margin-bottom: 0 }
hr { clear: both; margin: 15px 0; height: 0; overflow: hidden; border-top: none; border-right: none; border-bottom: 4px solid rgba(221, 221, 221, 1); border-left: none; background: rgba(0, 0, 0, 0); padding: 0 }
table { font-family: Helvetica, arial, freesans, clean, sans-serif; padding: 0; border-collapse: collapse; border-spacing: 0; font-size: 1em; border: 0 }
tbody { margin: 0; padding: 0; border: 0 }
table tr { border-top: 1px solid rgba(204, 204, 204, 1); border-right: 0; border-bottom: 0; border-left: 0; background-color: rgba(255, 255, 255, 1); margin: 0; padding: 0 }
table tr:nth-child(2n) { background-color: rgba(248, 248, 248, 1) }
table tr th, table tr td { font-size: 1em; border: 1px solid rgba(204, 204, 204, 1); margin: 0; padding: 0.5em 1em }
table tr th { font-weight: bold; background-color: rgba(240, 240, 240, 1) }
img { max-width: 1000px }
strong, b { padding: 0 4px }
{ background: rgba(255, 255, 255, 1) }
.highlight .c { color: rgba(153, 153, 136, 1); font-style: italic }
.highlight .err { color: rgba(166, 23, 23, 1); background-color: rgba(227, 210, 210, 1) }
.highlight .k { font-weight: bold }
.highlight .o { font-weight: bold }
.highlight .cm { color: rgba(153, 153, 136, 1); font-style: italic }
.highlight .cp { color: rgba(153, 153, 153, 1); font-weight: bold }
.highlight .c1 { color: rgba(153, 153, 136, 1); font-style: italic }
.highlight .cs { color: rgba(153, 153, 153, 1); font-weight: bold; font-style: italic }
.highlight .gd { color: rgba(0, 0, 0, 1); background-color: rgba(255, 221, 221, 1) }
.highlight .gd .x { color: rgba(0, 0, 0, 1); background-color: rgba(255, 170, 170, 1) }
.highlight .ge { font-style: italic }
.highlight .gr { color: rgba(170, 0, 0, 1) }
.highlight .gh { color: rgba(153, 153, 153, 1) }
.highlight .gi { color: rgba(0, 0, 0, 1); background-color: rgba(221, 255, 221, 1) }
.highlight .gi .x { color: rgba(0, 0, 0, 1); background-color: rgba(170, 255, 170, 1) }
.highlight .go { color: rgba(136, 136, 136, 1) }
.highlight .gp { color: rgba(85, 85, 85, 1) }
.highlight .gs { font-weight: bold }
.highlight .gu { color: rgba(170, 170, 170, 1) }
.highlight .gt { color: rgba(170, 0, 0, 1) }
.highlight .kc { font-weight: bold }
.highlight .kd { font-weight: bold }
.highlight .kp { font-weight: bold }
.highlight .kr { font-weight: bold }
.highlight .kt { color: rgba(68, 85, 136, 1); font-weight: bold }
.highlight .m { color: rgba(0, 153, 153, 1) }
.highlight .s { color: rgba(221, 17, 68, 1) }
.highlight .na { color: rgba(0, 128, 128, 1) }
.highlight .nb { color: rgba(0, 134, 179, 1) }
.highlight .nc { color: rgba(68, 85, 136, 1); font-weight: bold }
.highlight .no { color: rgba(0, 128, 128, 1) }
.highlight .ni { color: rgba(128, 0, 128, 1) }
.highlight .ne { color: rgba(153, 0, 0, 1); font-weight: bold }
.highlight .nf { color: rgba(153, 0, 0, 1); font-weight: bold }
.highlight .nn { color: rgba(85, 85, 85, 1) }
.highlight .nt { color: rgba(0, 0, 128, 1) }
.highlight .nv { color: rgba(0, 128, 128, 1) }
.highlight .ow { font-weight: bold }
.highlight .w { color: rgba(187, 187, 187, 1) }
.highlight .mf { color: rgba(0, 153, 153, 1) }
.highlight .mh { color: rgba(0, 153, 153, 1) }
.highlight .mi { color: rgba(0, 153, 153, 1) }
.highlight .mo { color: rgba(0, 153, 153, 1) }
.highlight .sb { color: rgba(221, 17, 68, 1) }
.highlight .sc { color: rgba(221, 17, 68, 1) }
.highlight .sd { color: rgba(221, 17, 68, 1) }
.highlight .s2 { color: rgba(221, 17, 68, 1) }
.highlight .se { color: rgba(221, 17, 68, 1) }
.highlight .sh { color: rgba(221, 17, 68, 1) }
.highlight .si { color: rgba(221, 17, 68, 1) }
.highlight .sx { color: rgba(221, 17, 68, 1) }
.highlight .sr { color: rgba(0, 153, 38, 1) }
.highlight .s1 { color: rgba(221, 17, 68, 1) }
.highlight .ss { color: rgba(153, 0, 115, 1) }
.highlight .bp { color: rgba(153, 153, 153, 1) }
.highlight .vc { color: rgba(0, 128, 128, 1) }
.highlight .vg { color: rgba(0, 128, 128, 1) }
.highlight .vi { color: rgba(0, 128, 128, 1) }
.highlight .il { color: rgba(0, 153, 153, 1) }
.pl-c { color: rgba(150, 152, 150, 1) }
.pl-c1, .pl-mdh, .pl-mm, .pl-mp, .pl-mr, .pl-s1 .pl-v, .pl-s3, .pl-sc, .pl-sv { color: rgba(0, 134, 179, 1) }
.pl-e, .pl-en { color: rgba(121, 93, 163, 1) }
.pl-s1 .pl-s2, .pl-smi, .pl-smp, .pl-stj, .pl-vo, .pl-vpf { color: rgba(51, 51, 51, 1) }
.pl-ent { color: rgba(99, 163, 92, 1) }
.pl-k, .pl-s, .pl-st { color: rgba(167, 29, 93, 1) }
.pl-pds, .pl-s1, .pl-s1 .pl-pse .pl-s2, .pl-sr, .pl-sr .pl-cce, .pl-sr .pl-sra, .pl-sr .pl-sre, .pl-src, .pl-v { color: rgba(223, 80, 0, 1) }
.pl-id { color: rgba(181, 42, 29, 1) }
.pl-ii { background-color: rgba(181, 42, 29, 1); color: rgba(248, 248, 248, 1) }
.pl-sr .pl-cce { color: rgba(99, 163, 92, 1); font-weight: bold }
.pl-ml { color: rgba(105, 58, 23, 1) }
.pl-mh, .pl-mh .pl-en, .pl-ms { color: rgba(29, 62, 129, 1); font-weight: bold }
.pl-mq { color: rgba(0, 128, 128, 1) }
.pl-mi { color: rgba(51, 51, 51, 1); font-style: italic }
.pl-mb { color: rgba(51, 51, 51, 1); font-weight: bold }
.pl-md, .pl-mdhf { background-color: rgba(255, 236, 236, 1); color: rgba(189, 44, 0, 1) }
.pl-mdht, .pl-mi1 { background-color: rgba(234, 255, 234, 1); color: rgba(85, 165, 50, 1) }
.pl-mdr { color: rgba(121, 93, 163, 1); font-weight: bold }
.pl-mo { color: rgba(29, 62, 129, 1) }
.task-list { padding-left: 10px; margin-bottom: 0 }
.task-list li { margin-left: 20px }
.task-list-item { list-style-type: none; padding-left: 10px }
.task-list-item label { font-weight: 400 }
.task-list-item.enabled label { cursor: pointer }
.task-list-item+.task-list-item { margin-top: 3px }
.task-list-item-checkbox { display: inline-block; margin-left: -20px; margin-right: 3px; vertical-align: 1px }