最近同事在爬取某网站数据,想将爬取的数据保存为docx。在爬取数据过程中一切很顺利,但是在保存数据时却提示以下错误。
File "srclxmletree.pyx", line 1024, in lxml.etree._Element.text.__set__ File "srclxmlapihelpers.pxi", line 747, in lxml.etree._setNodeText File "srclxmlapihelpers.pxi", line 735, in lxml.etree._createTextNode File "srclxmlapihelpers.pxi", line 1540, in lxml.etree._utf8ValueError: All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters
数据保存使用的是python-docx模块。大致意思是字符不兼容,所有字符串必须与XML兼容。
那么接下来面向百度或谷歌编程,根据百度搜索结果。大致意思是python-docx与中文字符不兼容,需要将字符转为Unicode。让我们先来看看字符的数据内容,和字符数据格式。
def save(doc_title, doc_content_list): document = Document() # 测试标题 heading = document.add_heading(doc_title, 0) # 居中显示 heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER #打印字符集 print(chardet.detect(doc_content_list.encode())) #打印数据内容 print(doc_content_list) # 测试内容,这里转为Unicode document.add_paragraph(doc_content_list) # 字符分割,用于保存文件名 t_title = doc_title.split()[0] # 运行 document.save('下载-%s.docx' % t_title)
运行结果如下所示
数据内容好像没问题,字符集为utf-8。
难道是python-docx与中文字符真的不兼容?着手写了一个测试如下
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT # 用来居中显示标题from docx import Documentdocument = Document()#测试标题,注意这里忘了转为Unicodeheading = document.add_heading("测试标题", 0)heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中显示#测试内容,这里转为Unicodedocument.add_paragraph(u'测试内容')document.save('测试文档.docx')
最后运行一切正常,保存成功。
在写测试标题的时候忘了将字符转为Unicode,但是也能够正常保存,说明python-docx是能够支持utf-8字符集。而测试内容转为了Unicode,但是在文档中也能正常显示,说明python-docx在保存Unicode的时候会默认转为utf-8。
按理论上了来说,python-docx在保存数据的时候是没有问题的,那为什么会报错呢?那我们将网站上爬取的数据转为Unicode试试。大致代码如下
def save(doc_title, doc_content_list): document = Document() # 测试标题 heading = document.add_heading(doc_title, 0) # 居中显示 heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 测试内容,这里转为Unicode document.add_paragraph(json.dumps(doc_content_list)) # 字符分割,用于保存文件名 t_title = doc_title.split()[0] # 在当前脚本路径存储docx文件 document.save('下载-%s.docx' % t_title)
使用json.dumps将字符串转为Unicode,加上这一步操作后,运行过程中没有任何异常,但是运行结果却不是我们所想要的。大致运行结果如下图所示
当场我就纳闷了,怎么标题保存没问题,但是内容保存却是Unicode,按理论来说内容应该会直接转为utf-8啊。
懵逼之后,我整理了下思路:
- 数据可以打印,说明数据获取没问题
- 数据格式为utf-8
- python-docx可以直接保存utf-8数据集,也可以保存Unicode格式
- python-docx将数据保存为Unicode不报错,但是显示有问题
- python-docx将数据直接以utf-8保存,报错
整理思路,说明我们的数据可能有问题。我们回过头来看下错误提示
All strings must be XML compatible: Unicode or ASCII, no NULL bytes or control characters
所有字符串必须与XML兼容:Unicode或ASCII,不能是空字节或控制字符。
接下来我们尝试将得到的数据清理下,将所有非utf-8的字符去掉。大致代码如下
from docx.enum.text import WD_PARAGRAPH_ALIGNMENTfrom docx import Documentimport re# 清理所有非utf-8的字符def cleantxt(raw): # utf-8字符集范围u4e00-u9fa5 fil = re.compile(u'[^0-9a-zA-Z一-龥.,,。?“”《》_()!;:]+', re.UNICODE) return fil.sub(' ', raw)def save(doc_title, doc_content_list): document = Document() # 测试标题 heading = document.add_heading(doc_title, 0) # 居中显示 heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 测试内容,清理异常数据 document.add_paragraph(cleantxt(doc_content_list)) # 字符分割,用于保存文件名 t_title = doc_title.split()[0] # 在当前脚本路径存储docx文件 document.save('下载-%s.docx' % t_title)
运行一切正常,接下来到了激动人心的时刻了。
终于得到了想要的结果,在此记录遇到问题的场景和解决问题的思路和方法和大家共享。