最近同事在爬取某网站数据,想将爬取的数据保存为docx。在爬取数据过程中一切很顺利,但是在保存数据时却提示以下错误。
File "src\lxml\etree.pyx", line 1024, in lxml.etree._Element.text.__set__
File "src\lxml\apihelpers.pxi", line 747, in lxml.etree._setNodeText
File "src\lxml\apihelpers.pxi", line 735, in lxml.etree._createTextNode
File "src\lxml\apihelpers.pxi", line 1540, in lxml.etree._utf8
ValueError: 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 Document
document = Document()
#测试标题,注意这里忘了转为Unicode
heading = document.add_heading("测试标题", 0)
heading.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 居中显示
#测试内容,这里转为Unicode
document.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_ALIGNMENT
from docx import Document
import re
# 清理所有非utf-8的字符
def cleantxt(raw):
# utf-8字符集范围u4e00-u9fa5
fil = re.compile(u'[^0-9a-zA-Z\u4e00-\u9fa5.,,。?“”《》_()!;:]+', 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)
运行一切正常,接下来到了激动人心的时刻了。

运行结果
终于得到了想要的结果,在此记录遇到问题的场景和解决问题的思路和方法和大家共享。