( 白宁超 7月16日14:47:41 )
导读:随着大数据的快速发展,自然语言处理、数据挖掘、机器学习技术应用愈加广泛。针对大数据的预处理工作是一项庞杂、棘手的工作。首先数据采集和存储,尤其高质量数据采集往往不是那么简单。采集后的信息文件格式不一,诸如pdf,doc,docx,Excel,ppt等多种形式。然而最常见便是txt、pdf和word类型的文档。本文主要对pdf和word文档进行文本格式转换成txt。格式一致化以后再进行后续预处理工作。笔者采用一些工具转换效果都不理想,于是才出现本系统的研究与实现。(,转载必须注明出处: 数据分析:基于Python的自定义文件格式转换系统)
1 本文概述
1.1 背景介绍
为什么要文件格式转换?
无论读者现在是做数据挖掘、数据分析、自然语言处理、智能对话系统、商品推荐系统等等,都不可避免的涉及语料的问题即大数据。数据来源无非分为结构化数据、半结构化数据和非结构化数据。其中结构化数据以规范的文档、数据库文件等等为代表;半结构化数据以网页、json文件等为代表;非结构化数据以自由文本为主,诸如随想录、中医病症记录等等。遗憾的是现实生活中半结构化和非结构化数据居多,而且往往还需要自己去收集。
读者试想以下情况:
你的技术主管交给你一堆数据文件,让你做数据分析工作。你打开一看文件格式繁杂,诸如pdf、doc、docx、txt、excel等。更悲催的是有些pdf文件还是加密的,或者是图片格式的等复杂情况。此刻你采用什么方法做数据分析与预处理工作呢?
上面情况算你幸运,隔几天技术主管直接给你一堆网站,让你自己去采集信息。你或许会惊喜的说的,那不简单,使用爬虫技术不就可以啦?恭喜你思路完全正确,可是爬取过程中遇到一些网页是pdf格式的情况,你不能直接抓取页面了。你此刻如何去采集信息呢?
现有工具的转换效果如何
针对以上典型的情况,自定义插件PDFMiner、win2com等将派上用场(本文主要讲述文件格式转化,网络爬虫解析读者自行研究)。首先我们看看常规方式的处理,比如我下载个格式转化软件或者在线格式转化软件,具体如下所示:
在线格式转换工具1页面效果如下:
图1-1 在线格式转换工具1
pdf格式转化为txt后的效果如下:
图1-2 工具1pdf格式转化为txt结果
上面转换效果读者是否满意?是否因为某一个在线转换工具不完备,那我们再尝试一个,在线格式转换工具2页面效果如下:
图1-3 在线格式转换工具2
pdf格式转化为txt后的效果如下:
图1-4 工具2pdf格式转化为txt结果
继续我们的格式转换工作,我们这次采用offic软件内带的pdf另存为效果如下:
图1-5 offic软件内带的pdf另存为效果
总结
通过上面现有常规的方法,我们总结出以下问题:
1、 格式转换后,识别乱码较多。
2、 不支持或者限制支持批量处理。
3、 格式转换后的txt文件存在编码问题。
4、 生成目标文件的标题跟原标题不一致。
5、 操作不够灵活便捷。
1.2 基于自定义格式转换介绍
预期效果
1、 将带有嵌套的目录放在一个根目录文件下,只需要传入文件名即可自动转化。
2、 自动过滤掉不符合指定格式的文件。
3、 对处理的pdf文件不能识别的(加密文件等)给出日志记录其路径。
4、 生成目标文件的标题跟原文件目录标题保持一致。
5、 生成的文件按照统一的utf-8编码格式保存。
6、 支持默认保存路径与自定义保存路径。
预期效果展示
待处理语料数据如下:
图1-6 处理语料数据集
处理后默认自动保存的结果(支持自定义指定保存目录):
图1-7 默认自动保存转化后的数据集
基于自定义插件的文本转化效果:
图1-8 自定义插件的文本转化效果
基于pdfminer插件的运行效果
图1-9 整体运行效果图
2 基础配置工作
2.1 基础准备工作
运行环境
1、windows7以上64bit操作系统
2、sublime运行环境
3、python3.0+
需要插件
1、 pdfminer插件: 链接: /s/1p7X430bvBpjJ-qGNO-Fmcg 密码: v5th
或者:pip install pdfminer3k
图2-1 pip方式安装pdfminer插件
2、 win2com 插件:链接: /s/1-2BsiTs8XjMIe5Gnh_GFjw 密码: 7j3t
pip install pypiwin32
图2-2 pip方式安装win2com插件
2.2 类库重构
算法基础类库重构
重构又称高度代码封装,旨在代码重用和面向对象编程。本文将相关基本方法封装在一个类库中供外部类调用,提高代码复用性和可读性。具体重构文件结构如下:
重构文件名:BaseClass.py
'''
功能描述:遍历目录,对子文件单独处理
参数描述:
1 rootdir:待处理的目录路径
2 deffun: 方法参数,默认为空
3 savepath: 保存路径
'''
class TraversalFun():
TraversalDir:遍历目录文件方法
creat_savepath:支持默认和自定义保存目录方法
AllFiles:递归遍历所有文件,并提供具体文件操作功能
TranType:通过指定关键字操作,检查文件类型并转化目标类型
filelogs:记录文件处理日志方法
cleardir:清空目录文件方法
writeFile:文件的写操作方法
readFile:文件的读操作方法
mkdir:创建目录方法
'''
功能描述:提供全局变量类
作 者:白宁超
时 间:10月24日15:07:38
'''
class Global(object):提高各个公共全局变量
'''
功能描述:测试类
作 者:白宁超
时 间:10月24日15:07:38
'''
def TestMethod(filepath,newpath):方法测试类
核心方法详解
1 TraversalFun类方法:
def __init__(self,rootdir,deffun=None,savedir=""):
self.rootdir = rootdir # 目录路径
self.deffun = deffun # 参数方法
self.savedir = savedir # 保存路径
''' 遍历目录文件'''
def TraversalDir(self,defpar='newpath'):
try:
# 支持默认和自定义保存目录
newdir = TraversalFun.creat_savepath(self,defpar)
# 递归遍历word文件并将其转化txt文件
TraversalFun.AllFiles(self,self.rootdir,newdir)
except Exception as e:
raise e
'''支持默认和自定义保存目录'''
# @staticmethod
def creat_savepath(self,defpar):
# 文件路径切分为上级路径和文件名('F:\\kjxm\\kjt', '1.txt')
prapath,filename = os.path.split(self.rootdir)
newdir = ""
if self.savedir=="":
newdir = os.path.abspath(os.path.join(prapath,filename+"_"+defpar))
else:
newdir = self.savedir
print("保存目录路径:\n"+newdir)
if not os.path.exists(newdir):
os.mkdir(newdir)
return newdir
'''递归遍历所有文件,并提供具体文件操作功能。'''
def AllFiles(self,rootdir,newdir=''):
# 返回指定目录包含的文件或文件夹的名字的列表
for lists in os.listdir(rootdir):
# 待处理文件夹名字集合
path = os.path.join(rootdir, lists)
# 核心算法,对文件具体操作
if os.path.isfile(path):
self.deffun(path,newdir) # 具体方法实现功能
# TraversalFun.filelogs(rootdir) # 日志文件
# 递归遍历文件目录
if os.path.isdir(path):
newpath = os.path.join(newdir, lists)
if not os.path.exists(newpath):
os.mkdir(newpath)
TraversalFun.AllFiles(self,path,newpath)
''' 通过指定关键字操作,检查文件类型并转化目标类型'''
def TranType(filename,typename):
# print("本方法支持文件类型处理格式:pdf2txt,代表pdf转化为txt;word2txt,代表word转化txt;word2pdf,代表word转化pdf。")
# 新的文件名称
new_name = ""
if typename == "pdf2txt" :
#如果不是pdf文件,或者是pdf临时文件退出
if not fnmatch.fnmatch(filename, '*.pdf') or not fnmatch.fnmatch(filename, '*.PDF') or fnmatch.fnmatch(filename, '~$*'):
return
# 如果是pdf文件,修改文件名
if fnmatch.fnmatch(filename, '*.pdf') or fnmatch.fnmatch(filename, '*.PDF'):
new_name = filename[:-4]+'.txt' # 截取".pdf"之前的文件名
if typename == "word2txt" :
#如果是word文件:
if fnmatch.fnmatch(filename, '*.doc') :
new_name = filename[:-4]+'.txt'
print(new_name)
if fnmatch.fnmatch(filename, '*.docx'):
new_name = filename[:-5]+'.txt'
# 如果不是word文件,或者是word临时文件退出
else:
return
if typename == "word2pdf" :
#如果是word文件:
if fnmatch.fnmatch(filename, '*.doc'):
new_name = filename[:-4]+'.pdf'
if fnmatch.fnmatch(filename, '*.docx'):
new_name = filename[:-5]+'.pdf'
#如果不是word文件:继续
else:
return
return new_name
'''记录文件处理日志'''
def filelogs(rootdir):
prapath,filename = os.path.split(rootdir)
# 创建日志目录
dirpath = prapath+r"/"+filename+"_logs"
TraversalFun.mkdir(dirpath)
# 错误文件路径
errorpath = dirpath+r"/errorlogs.txt"
# 限制文件路径
limitpath = dirpath+r"/limitlogs.txt"
# 错误文件日志写入
TraversalFun.writeFile(errorpath,'\n'.join(Global.error_file_list))
# # 限制文件日志写入
TraversalFun.writeFile(limitpath,'\n'.join(Global.limit_file_list))
'''清空目录文件'''
def cleardir(dirpath):
if not os.path.exists(dirpath):
TraversalFun.mkdir(dirpath)
else:
shutil.rmtree(dirpath)
TraversalFun.mkdir(dirpath)
''' 文件的写操作'''
def writeFile(filepath,strs): #encoding="utf-8"
with open(filepath,'wb') as f:
f.write(strs.encode())
''' 文件的读操作'''
def readFile(filepath):
isfile = os.path.exists(filepath)
readstr = ""
if isfile:
with open(filepath,"r",encoding="utf-8") as f:
readstr = f.read()
else:
return
return readstr
''' 创建目录 '''
def mkdir(dirpath):
# 判断路径是否存在
isExists=os.path.exists(dirpath)
# 判断结果
if not isExists:
os.makedirs(dirpath)
print(dirpath+' 创建成功')
else:
pass
2 TestMethod测试类
'''
功能描述:测试类
作 者:白宁超
时 间:10月24日15:07:38
'''
def TestMethod(filepath,newpath):
if os.path.isfile(filepath) :
print("this is file name:"+filepath)
else:
pass
3 利用测试类方法运行方法参数效果图
方法的调用:传达参数分别是跟目录和测试类中的方法参数
t1=time.time()
# 根目录文件路径
rootDir = r"../../Corpus/DataSet"
tra=TraversalFun(rootDir,TestMethod) # 默认方法参数打印所有文件路径
tra.TraversalDir() # 遍历文件并进行相关操作
t2=time.time()
totalTime=Decimal(str(t2-t1)).quantize(Decimal('0.0000'))
print("耗时:"+str(totalTime)+" s"+"\n")
input()
运行结果如图所示:
图2-3 测试方法参数判断是文件的打印文件路径
3 基于pdfminer插件的pdf批量格式转换代码实现
pdfminer原理介绍
图3-1 pdfminer解析原理
由于解析PDF是一件非常耗时和内存的工作,因此PDFMiner使用了一种称作lazy parsing的策略,只在需要的时候才去解析,以减少时间和内存的使用。要解析PDF至少需要两个类:PDFParser 和 PDFDocument,PDFParser 从文件中提取数据,PDFDocument保存数据。另外还需要PDFPageInterpreter去处理页面内容,PDFDevice将其转换为我们所需要的。PDFResourceManager用于保存共享内容例如字体或图片。
图3-2 LTpage解析原理
LTPage :表示整个页。可能会含有LTTextBox,LTFigure,LTImage,LTRect,LTCurve和LTLine子对象。
LTTextBox:表示一组文本块可能包含在一个矩形区域。注意此box是由几何分析中创建,并且不一定表示该文本的一个逻辑边界。它包含TTextLine对象的列表。使用 get_text()方法返回的文本内容。
LTTextLine :包含表示单个文本行LTChar对象的列表。字符对齐要么 水平或垂直,取决于文本的写入模式。
get_text()方法返回的文本内容。
LTAnno:在文本中实际的字母表示为Unicode字符串(?)。需要注意的是,虽然一个LTChar对象具有实际边界,LTAnno对象没有,因为这些是“虚拟”的字符,根据两个字符间的关系(例如,一个空格)由布局分析后插入。
LTImage:表示一个图像对象。嵌入式图像可以是JPEG或其它格式,但是目前PDFMiner没有放置太多精力在图形对象。
LTLine:代表一条直线。可用于分离文本或附图。
LTRect:表示矩形。可用于框架的另一图片或数字。
LTCurve:表示一个通用的 Bezier曲线
pdfminer学习文献
英文官方:https://euske.github.io/pdfminer/index.html 中文:/robolinux/article/details/43318229
pdfminer代码实现
# pdfminer库的地址 /pypi/pdfminer3k
# 下载后,用cmd执行命令 setup.py install
from pdfminer.pdfparser import PDFParser,PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal,LAParams
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
from decimal import Decimal
import time,fnmatch,os,re,sys
from BaseClass import * #全局变量
# from BaseClass import TraversalFun # 文件遍历处理基类函数
# 清除警告
import logging
logging.Logger.propagate = False
logging.getLogger().setLevel(logging.ERROR)
'''pdf文件格式转换为txt'''
def PdfToText(filepath,newdir=''):
# 文件路径切分为上级路径和文件名
prapath,filename = os.path.split(filepath)
new_txt_name=TraversalFun.TranType(filename,"pdf2txt") # 更改文件名
if new_txt_name ==None:
return
newpath = os.path.join(newdir,new_txt_name) # 文件保存路径
print ("->格式转换后保留路径:\n"+newpath)
try:
praser = PDFParser(open(filepath, 'rb')) # 创建一个pdf文档分析器
doc = PDFDocument() # 创建一个PDF文档
praser.set_document(doc) # 连接分析器 与文档对象
doc.set_parser(praser)
doc.initialize() # 提供初始化密码,如果没有密码 就创建一个空的字符串
# 检测文档是否提供txt转换,不提供就忽略
if not doc.is_extractable:
Global.error_file_list.append(filepath)
return
rsrcmgr = PDFResourceManager() # 创建PDf 资源管理器管理共享资源
laparams = LAParams() # 创建一个PDF设备对象
device = PDFPageAggregator(rsrcmgr, laparams=laparams)
interpreter = PDFPageInterpreter(rsrcmgr, device) # 创建一个PDF解释器对象
pdfStr = "" # 存储解析后的提取内容
# 循环遍历列表,每次处理一个page的内容
for page in doc.get_pages(): # doc.get_pages()获取page列表
interpreter.process_page(page)
layout = device.get_result() # 接受该页面的LTPage对象
# 这里layout是一个LTPage对象 里面存放着 这个page解析出的各种对象 一般包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 想要获取文本就获得对象的text属性,
for x in layout:
if (isinstance(x, LTTextBoxHorizontal)):
pdfStr = pdfStr + x.get_text()
TraversalFun.writeFile(newpath,pdfStr) # 写文件
# 限制文件列表
filesize = os.path.getsize(newpath)
if filesize < Global.limit_file_size :
Global.limit_file_list.append(newpath+"\t"+ str(Decimal(filesize/1024).quantize(Decimal('0.00'))) +"KB")
os.remove(newpath)
else :
Global.all_FileNum+=1
except Exception as e:
Global.error_file_list.append(filepath)
return
if __name__ == '__main__':
t1=time.time()
rootDir = r"../../Corpus/DataSet" # 默认处理路径
TraversalFun.cleardir(r'../../Corpus/DataSet_newpath') # 每次加载清空目录
print ('【批量生成的文件:】')
tra=TraversalFun(rootDir,PdfToText) # 默认方法参数打印所有文件路径
tra.TraversalDir()
# 写入日志文件
TraversalFun.filelogs(rootDir)
print ('共处理文档数目:'+str(Global.all_FileNum+len(Global.error_file_list)+len(Global.limit_file_list))+' 个,其中:\n \
1) 筛选文件(可用)'+str(Global.all_FileNum)+'个.\n \
2) 错误文件(不能识别)'+ str(len(Global.error_file_list)) +'个.\n \
3) 限制文件(<5K)'+ str(len(Global.limit_file_list))+'个.' )
t2=time.time()
totalTime=Decimal(str(t2-t1)).quantize(Decimal('0.0000'))
print("耗时:"+str(totalTime)+" s"+"\n")
input()
解析pdf文件用到的类:
PDFParser:从一个文件中获取数据
PDFDocument:保存获取的数据,和PDFParser是相互关联的
PDFPageInterpreter处理页面内容
PDFDevice将其翻译成你需要的格式
PDFResourceManager用于存储共享资源,如字体或图像。
pdfminer页面结果:
图3-3 pdfminer运行效果
pdfminer转化结果
图3-4 pdfminer转化结果
实验结论
错误分析,打开日志文件查看
图3-5 pdfminer转化限制文件
错误原因分析:因为我们在全局变量中限制了最小文件读取1KB,该文件0KB不符合要求故而过滤出来。打开查看发现该pdf是一张图片转换出来的,没有成功识别。但是,通过技术研究是可以实现的,本文没有深入进行。还有以下结论:
1 可以支持批量文本和单文本转化。
2 编码格式一致,默认utf-8。
3 生成文件名誉原始处理文件名保存一致。
4 生成的文本信息相对比较规范。
支持多方式转化,其他案例读者自行研究。
扩展学习
在解析有些PDF的时候会报这样的异常:pdfminer.pdfdocument.PDFEncryptionError: Unknown algorithm: param={'CF': {'StdCF': {'Length': 16, 'CFM': /AESV2, 'AuthEvent': /DocOpen}}, 'O': '\xe4\xe74\xb86/\xa8)\xa6x\xe6\xa3/U\xdf\x0fWR\x9cPh\xac\xae\x88B\x06_\xb0\x93@\x9f\x8d', 'Filter': /Standard, 'P': -1340, 'Length': 128, 'R': 4, 'U': '|UTX#f\xc9V\x18\x87z\x10\xcb\xf5{\xa7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'V': 4, 'StmF': /StdCF, 'StrF': /StdCF}
如上是加密的PDF,所以无法解析 ,但是如果直接打开PDF却是可以的并没有要求输密码什么的,原因是这个PDF虽然是加过密的,但密码是空,所以就出现了这样的问题。
解决这个的问题的办法是通过qpdf命令来解密文件(要确保已经安装了qpdf),要想在python中调用该命令只需使用call即可:
1 from subprocess import call
2 call('qpdf --password=%s --decrypt %s %s' %('', file_path, new_file_path), shell=True)
其中参数filepath是要解密的PDF的路径,newfile_path是解密后的PDF文件路径,然后使用解密后的文件去做解析就OK了
4 基于win32com插件的代码实现
导入相关包
from win32com import client as wc
from win32com.client import Dispatch, constants, gencache
import os,fnmatch,time,sys
from decimal import Decimal
from BaseClass import * # 自定义类库
word的doc或docx文件转化pdf文本
1 代码实现
'''
功能名称:word的doc或docx文件转化pdf文本
功能描述:输入一个doc或docx文件路径,自动转化为pdf文件,并存储在当前路径下。
用户可以指定存储文件路径。
参数描述:
1 filepath:单个文件路径
2 newdir: 指定保存路径
测试路径: F:\corper\kjt\1.docx
'''
def doc2pdf(filepath,newDir=''):
# 文件路径切分为上级路径和文件名
prapath,filename = os.path.split(filepath)
# 单文件处理使用
if newDir=='':
newDir = prapath
else:
newDir =newDir
new_txt_name=TraversalFun.TranType(filename,"word2pdf")
if new_txt_name ==None:
return
else:
print(new_txt_name)
newpath = os.path.join(newDir,new_txt_name)
word = wc.DispatchEx("Word.Application")
worddoc = word.Documents.Open(filepath,ReadOnly = 1)
worddoc.SaveAs(newpath, FileFormat = 17)
worddoc.Close()
Global.all_FileNum+=1
2 单个word转换pdf
主程序运行代码:
# 单个word转换pdf
filepath=os.path.abspath(r"../../Corpus/DataSet//科技项目数据挖掘决策架构.docx")
doc2pdf(filepath)
控制台打印效果:
科技项目数据挖掘决策架构.pdf
共处理文档数目:1 个
耗时:3.6121 s
结果:
图4-1 word文档单文件转化结果
打开显示:
图4-2 转化后规范的pdf文件
3 批量word转换pdf
主程序运行代码:
rootDir =os.path.abspath(r"../../Corpus/DataSet")
# 1 批量的word转换pdf
tra=TraversalFun(rootDir,doc2pdf) # 默认方法参数打印所有文件路径
tra.TraversalDir('word2pdf')
控制台打印效果:
保存目录路径:
F:\AllNote\AllProject\TechDataMining\Corpus\DataSet_word2pdf
科技项目数据挖掘决策架构.pdf
科技项目数据挖掘决策架构.pdf
共处理文档数目:2 个
耗时:7.1494 s
结果:
图4-3 word文档批量转化结果
word的doc或docx文件转化txt文本
1 代码实现
'''
功能名称:单个word的doc或docx文件转化txt文本
'''
def WordTranslate(filepath,newDir=''):
# 文件路径切分为上级路径和文件名
prapath,filename = os.path.split(os.path.abspath(filepath))
if newDir=='':
newDir = prapath
else:
newDir =newDir
new_txt_name=TraversalFun.TranType(filename,'word2txt')
if new_txt_name == None:
return
else:
word_to_txt = os.path.join(newDir,new_txt_name)
print ("格式转换后保留路径:\n"+word_to_txt)
#加载处理应用
wordapp = wc.Dispatch('Word.Application')
doc = wordapp.Documents.Open(filepath)
#为了让python可以在后续操作中r方式读取txt和不产生乱码,参数为4
doc.SaveAs(word_to_txt,4)
doc.Close()
# print(word_to_txt)
Global.all_FileNum += 1
2 单个word转换txt
# 单个word转换txt
filepath=os.path.abspath(r"../../Corpus/DataSet//科技项目数据挖掘决策架构.docx")
WordTranslate(filepath)
3 批量的word转换txt
# 批量的word转换txt
tra=TraversalFun(rootDir,WordTranslate) # 默认方法参数打印所有文件路径
tra.TraversalDir('word2txt')
4 批量的word转换txt结果
图4-4 批量的word转换txt结果
pdf文件转化txt文本
1 代码实现
'''
功能名称:pdf文件转化txt文本
功能描述:输入一个pdf文件路径,自动转化为txt文件,并存储在当前路径下。
用户可以指定存储文件路径。
参数描述:
1 filepath:单个文件路径
2 newdir: 指定保存路径
测试路径: F:\corper\kjt\申报书.pdf
'''
def PdfTranslate(filepath,newDir=''):
# 文件路径切分为上级路径和文件名
prapath,filename = os.path.split(filepath)
if newDir=="":
newDir = prapath
else:
newDir = newDir
new_txt_name=TraversalFun.TranType(filename,"pdf2txt")
if new_txt_name ==None:
return
else:
word_to_txt = os.path.join(newDir,new_txt_name)
# print(word_to_txt)
#加载处理应用
wordapp = wc.Dispatch('Word.Application')
doc = wordapp.Documents.Open(filepath)
#为了让python可以在后续操作中r方式读取txt和不产生乱码,参数为4
doc.SaveAs(word_to_txt,4)
doc.Close()
Global.all_FileNum += 1
2 单个pdf文件转化txt文本
# 单个pdf转换txt
filepath=os.path.abspath(r"../../Corpus/DataSet//改进朴素贝叶斯文本分类方法研究.pdf")
PdfTranslate(filepath)
3 批量pdf文件转化txt文本
# 3 批量的pdf转换txt
tra=TraversalFun(rootDir,PdfTranslate) # 默认方法参数打印所有文件路径
tra.TraversalDir("pdf2txt")
4 批量pdf文件转化txt文本结果
图4-5 批量pdf文件转化txt文本结果
5 完整代码下载
机器学习和自然语言QQ群:436303759
【微信公众号:datathinks】
源码请进QQ群文件下载:
图5-1 完整项目文件
参考文献
/~euske/python/pdfminer/programming.html
/jamespei/p/5339769.html
/u011389474/article/details/60139786
/u010983763/article/details/78654651
/zyc121561/article/details/77879831
/zyc121561/article/details/77877912?locationNum=7&fps=1
声明