使用Python的Zeep库调用SOAP服务时遇到XML格式错误问题求助
看起来你遇到的是Zeep和NuSOAP服务的兼容性问题——从报错里的NuSOAP: Br***stservice标题能看出来,这个服务是基于NuSOAP开发的,它对SOAP请求的格式要求比普通服务更严格,而Zeep默认的请求结构和它不匹配,所以返回了错误页面(HTML)而非预期的SOAP XML响应。
我之前也碰到过类似的场景,给你几个针对性的解决方案,按从简到繁的顺序来:
1. 先排查Zeep发送的请求结构(开启调试日志)
首先得确认Zeep生成的请求和SoapUI里的正确请求差异在哪里,可以开启Zeep的调试日志,看看实际发送的XML:
import logging import zeep import json # 开启Zeep调试日志,查看实际发送的请求XML logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger('zeep') logger.setLevel(logging.DEBUG) def getToken1(): wsdl2 = 'http://trx.*********ast.co.id/Webservice/b******ervice?wsdl' client2 = zeep.Client(wsdl2) parameters = { 'username':'n**l***s', 'password': 'ra8*******PeSw', 'counterpart':'***', 'ipAddress':'127.0.0.1' } info = client2.service.requestToken(parameters) json_result = zeep.helpers.serialize_object(info, dict) return json_result print(getToken1())
运行后看日志里的请求XML,和SoapUI的对比,重点关注:
- 命名空间是否正确(比如
urn:SOAPServerWSDL、urn:***stservice的引用) - 是否包含
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"属性 - 参数的嵌套结构是否一致(SoapUI里参数是包裹在
<parameters>节点里的)
2. 调整Zeep请求的Headers和参数类型
NuSOAP服务通常要求请求的Content-Type是text/xml(Zeep默认可能用application/soap+xml),而且需要强类型的参数对象,不是普通字典。试试这个修改:
import zeep from zeep.transports import Transport import json def getToken1(): wsdl2 = 'http://trx.*********ast.co.id/Webservice/b******ervice?wsdl' # 指定Content-Type为text/xml,适配NuSOAP的格式要求 transport = Transport(headers={'Content-Type': 'text/xml; charset=utf-8'}) client2 = zeep.Client(wsdl2, transport=transport) # 用服务定义的强类型构造参数(替换urn:***stservice为实际命名空间,从SoapUI请求里找) request_token_type = client2.get_type('urn:***stservice:requestTokenCT') parameters = request_token_type( username='n**l***s', password='ra8*******PeSw', counterpart='***', ipAddress='127.0.0.1' ) # 调用方法时传递强类型参数,确保XML结构符合服务要求 info = client2.service.requestToken(parameters=parameters) json_result = zeep.helpers.serialize_object(info, dict) return json_result print(getToken1())
这里的关键是用client.get_type()获取服务定义的参数类型,而不是用普通字典,这样Zeep会生成符合命名空间要求的XML结构。
3. 手动构造请求XML(最稳妥的方法)
如果上面的方法还是不行,直接复用SoapUI里能成功的请求XML,用requests发送,再用Zeep解析响应:
import requests from zeep import Client, helpers def getToken1(): wsdl2 = 'http://trx.*********ast.co.id/Webservice/b******ervice?wsdl' client2 = zeep.Client(wsdl2) # 完全复制SoapUI里的正确请求XML,替换占位符为你的实际参数 soap_request = """ <soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:SOAPServerWSDL"> <soapenv:Header/> <soapenv:Body> <urn:requestToken soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <parameters xsi:type="urn:requestTokenCT" xmlns:urn="urn:***stservice"> <username xsi:type="xsd:string">n**l***s</username> <password xsi:type="xsd:string">ra8*******PeSw</password> <counterpart xsi:type="xsd:string">***</counterpart> <ipAddress xsi:type="xsd:string">127.0.0.1</ipAddress> </parameters> </urn:requestToken> </soapenv:Body> </soapenv:Envelope> """.strip() # 发送POST请求,完全复刻SoapUI的成功请求格式 response = requests.post( url='http://trx.*********ast.co.id/Webservice/b******ervice', data=soap_request, headers={'Content-Type': 'text/xml; charset=utf-8'} ) # 用Zeep解析响应,确保和服务定义的返回结构匹配 operation = client2.service._binding.get_operation('requestToken') result = client2.service._binding.process_reply(operation, response) json_result = helpers.serialize_object(result, dict) return json_result print(getToken1())
这个方法相当于完全复刻了SoapUI的成功请求,兼容性最好,适合那些对请求格式要求特别严格的NuSOAP服务。
问题根源说明
NuSOAP是比较老旧的SOAP实现,它对SOAP 1.1的格式要求非常苛刻,比如必须指定encodingStyle、参数节点的命名空间必须准确,而Zeep默认的SOAP请求更偏向现代SOAP规范,导致服务无法识别请求,最终返回了错误页面(HTML)而非正常的SOAP响应。
内容的提问来源于stack exchange,提问作者Tushar Hasan

