การใช้งาน Claude ผ่าน API นั้นมีประสิทธิภาพสูง แต่หลายคนเจอปัญหาในการ parse ผลลัพธ์ที่อยู่ในรูปแบบ XML วันนี้ผมจะมาแชร์ประสบการณ์ตรงในการแก้ไขปัญหา Claude XML output parsing ที่พบบ่อยที่สุด
สถานการณ์ข้อผิดพลาดจริง: เมื่อ Response มาเป็น XML แต่โค้ดพัง
ผมเคยเจอสถานการณ์ที่ทำให้หงุดหงิดมาก: ส่ง request ไปที่ Claude API แล้วได้ response กลับมาเป็น XML ที่มีโครงสร้างซับซ้อน พอลอง parse ด้วยโค้ดเดิมที่ใช้กับ JSON อยู่ กลับเจอ error json.decoder.JSONDecodeError: Expecting value หรือ worse คือ parse ผ่านแต่ได้ข้อมูลผิดเพี้ยน
Claude XML Output คืออะไร
Claude สามารถ output ในรูปแบบ XML tags ได้โดยการกำหนดใน system prompt เช่น:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status>success</status>
<data>
<user>
<id>12345</id>
<name>สมชาย ใจดี</name>
<email>[email protected]</email>
</user>
<metadata>
<created_at>2024-01-15T10:30:00Z</created_at>
<role>admin</role>
</metadata>
</data>
</response>
การ parse XML ที่ถูกต้องจะช่วยให้คุณดึงข้อมูลได้แม่นยำและรวดเร็ว
การตั้งค่า Claude API ด้วย HolySheep AI
ก่อนจะ parse XML ได้ ต้องเรียก API ให้ได้ response ก่อน สมัครที่นี่ เพื่อรับ API key ฟรี จากนั้นใช้โค้ดต่อไปนี้:
import anthropic
import xml.etree.ElementTree as ET
กำหนดค่า API key และ base URL
client = anthropic.Anthropic(
api_key="YOUR_HOLYSHEEP_API_KEY",
base_url="https://api.holysheep.ai/v1" # URL สำหรับ HolySheep
)
สร้าง system prompt ให้ Claude output เป็น XML
SYSTEM_PROMPT = """คุณจะต้องตอบกลับในรูปแบบ XML เท่านั้น พร้อมใช้ tags ดังนี้:
<result>สำหรับคำตอบหลัก</result>
<details>สำหรับรายละเอียดเพิ่มเติม</details>
<confidence>ค่าความมั่นใจ 0-100</confidence>
ห้ามใช้ JSON หรือ markdown code blocks"""
def call_claude_with_xml_output(prompt_text):
"""เรียก Claude และรับ response เป็น XML"""
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[
{"role": "user", "content": prompt_text}
]
)
# ดึงข้อความ response
response_text = message.content[0].text
return response_text
ทดสอบเรียกใช้
xml_response = call_claude_with_xml_output("อธิบายเรื่อง Machine Learning แบบสั้น")
print(xml_response)
การ Parse XML ด้วย ElementTree
หลังจากได้ response เป็น XML string แล้ว ต่อไปคือการ parse ให้ถูกต้อง:
def parse_claude_xml_response(xml_string):
"""Parse XML response จาก Claude อย่างปลอดภัย"""
# วิธีที่ 1: ใช้ ElementTree (มาตรฐาน library)
try:
# ลบ XML declaration ถ้ามี
if xml_string.strip().startswith('<?xml'):
xml_string = xml_string[xml_string.index('?>')+2:].strip()
root = ET.fromstring(xml_string)
# ดึงข้อมูลจากแต่ละ tag
result = {
'result': root.find('result').text if root.find('result') is not None else None,
'details': root.find('details').text if root.find('details') is not None else None,
'confidence': root.find('confidence').text if root.find('confidence') is not None else None
}
return result
except ET.ParseError as e:
print(f"XML Parse Error: {e}")
return None
def parse_xml_with_namespaces(xml_string, namespace=None):
"""Parse XML ที่มี namespace ซับซ้อน"""
if namespace:
ET.register_namespace('', namespace)
try:
root = ET.fromstring(xml_string)
# ถ้ามี namespace ต้องใช้ ns ในการค้นหา
if namespace:
ns = {'ns': namespace}
result = root.find('.//ns:result', ns)
details = root.find('.//ns:details', ns)
else:
result = root.find('.//result')
details = root.find('.//details')
return {
'result': result.text if result is not None else None,
'details': details.text if details is not None else None
}
except Exception as e:
print(f"Error parsing XML: {e}")
return None
ทดสอบ parse
xml_sample = """
<response>
<result>Machine Learning คือการสอนคอมพิวเตอร์ให้เรียนรู้จากข้อมูล</result>
<details>ML ใช้ algorithms หลายประเภท เช่น supervised, unsupervised learning</details>
<confidence>95</confidence>
</response>
"""
parsed = parse_claude_xml_response(xml_sample)
print(f"Result: {parsed['result']}")
print(f"Confidence: {parsed['confidence']}%")
การ Parse XML ด้วย BeautifulSoup (ทางเลือก)
from bs4 import BeautifulSoup
def parse_xml_with_beautifulsoup(xml_string):
"""ใช้ BeautifulSoup สำหรับ XML ที่ซับซ้อน"""
soup = BeautifulSoup(xml_string, 'xml') # ใช้ 'xml' parser
# ดึงข้อมูลจาก nested tags
response = soup.find('response')
if response:
return {
'status': response.get('status', 'unknown'),
'result': response.result.get_text(strip=True) if response.result else None,
'details': response.details.get_text(strip=True) if response.details else None,
'confidence': response.confidence.get_text(strip=True) if response.confidence else None,
'all_tags': [tag.name for tag in response.find_all()]
}
return None
def extract_nested_data(xml_string):
"""ดึงข้อมูลจาก XML ที่ซ้อนกันหลายชั้น"""
soup = BeautifulSoup(xml_string, 'xml')
results = []
# หาทุก items ใน list
for item in soup.find_all('item'):
item_data = {
'id': item.get('id'),
'name': item.name.text if item.name else None,
'properties': {}
}
# ดึง properties ที่ซ้อนกัน
for prop in item.find_all('property'):
item_data['properties'][prop.get('key')] = prop.text
results.append(item_data)
return results
ตัวอย่าง XML ที่ซ้อนกัน
nested_xml = """
<data>
<items>
<item id="1">
<name>Product A</name>
<property key="price">299</property>
<property key="stock">50</property>
</item>
<item id="2">
<name>Product B</name>
<property key="price">499</property>
<property key="stock">25</property>
</item>
</items>
</data>
"""
items = extract_nested_data(nested_xml)
for item in items:
print(f"ID: {item['id']}, Name: {item['name']}, Price: {item['properties']['price']}")
การ Validate XML ก่อน Parse
import re
from typing import Tuple, Optional
def validate_xml_structure(xml_string) -> Tuple[bool, Optional[str]]:
"""
Validate XML structure ก่อน parse
ป้องกัน error จาก malformed XML
"""
if not xml_string or not xml_string.strip():
return False, "Empty XML string"
# ตรวจสอบ basic structure
xml_string = xml_string.strip()
# ตรวจสอบว่ามี root element หรือไม่
root_pattern = r'<(\w+)(?:\s|>)'
roots = re.findall(root_pattern, xml_string)
if not roots:
return False, "No root element found"
# ตรวจสอบ closing tags
open_tags = re.findall(r'<(\w+)(?:\s[^>]*)?(?<!/)>', xml_string)
close_tags = re.findall(r'</(\w+)>', xml_string)
# ตรวจสอบ tag balance
for tag in open_tags:
if tag not in close_tags:
return False, f"Unclosed tag: <{tag}>"
# ตรวจสอบ CDATA sections
cdata_count = xml_string.count('<![CDATA[')
cdata_close_count = xml_string.count(']]>')
if cdata_count != cdata_close_count:
return False, "Mismatched CDATA sections"
return True, None
def sanitize_xml_for_parsing(xml_string) -> str:
"""ทำความสะอาด XML ก่อน parse"""
# ลบ XML declaration
xml_string = re.sub(r'<\?xml[^?]*\?>', '', xml_string)
# ลบ HTML comments
xml_string = re.sub(r'<!--.*?-->', '', xml_string, flags=re.DOTALL)
# แก้ไข ampersands ที่ไม่ได้ escape
xml_string = re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;)', '&', xml_string)
# ลบ whitespace ที่ไม่จำเป็น
xml_string = re.sub(r'\s+', ' ', xml_string)
return xml_string.strip()
ทดสอบ validation
test_xml = "<response><result>Test</result></response>"
is_valid, error = validate_xml_structure(test_xml)
print(f"Valid: {is_valid}, Error: {error}")
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
ข้อผิดพลาดที่ 1: xml.etree.ElementTree.ParseError: unclosed token
# ❌ วิธีที่ผิด: Parse XML ที่มี special characters โดยตรง
import xml.etree.ElementTree as ET
xml_with_amp = "<data><item>Tom & Jerry</item></data>"
try:
root = ET.fromstring(xml_with_amp) # Error!
except ET.ParseError as e:
print(f"Error: {e}")
✅ วิธีที่ถูก: Escape special characters ก่อน
import xml.sax.saxutils as saxutils
xml_with_amp = "<data><item>Tom & Jerry</item></data>"
ถ้าได้รับ unescaped string มา
unescaped = "Tom & Jerry"
escaped = saxutils.escape(unescaped)
xml_safe = f"<data><item>{escaped}</item></data>"
ถ้าได้รับ double-escaped string มา
double_escaped = "Tom & Jerry"
unescaped_fixed = double_escaped.replace("&", "&")
xml_fixed = f"<data><item>{unescaped_fixed}</item></data>"
หรือใช้ CDATA
def wrap_in_cdata(text):
return f"<![CDATA[{text}]]>"
xml_cdata = f"<data><item>{wrap_in_cdata('Tom & Jerry & Friends')}</item></data>"
root = ET.fromstring(xml_cdata)
print(f"Parsed: {root.find('item').text}")
ข้อผิดพลาดที่ 2: AttributeError: 'NoneType' object has no attribute 'text'
# ❌ วิธีที่ผิด: ดึง text จาก tag ที่อาจไม่มีอยู่
xml_data = "<response><result>OK</result></response>"
root = ET.fromstring(xml_data)
confidence = root.find('confidence').text # AttributeError! ไม่มี confidence tag
✅ วิธีที่ถูก: ตรวจสอบก่อนดึงข้อมูล
def safe_get_text(element, tag_name, default=None):
"""ดึง text จาก tag อย่างปลอดภัย"""
target = element.find(tag_name)
if target is not None:
return target.text
return default
root = ET.fromstring(xml_data)
confidence = safe_get_text(root, 'confidence', 0)
result = safe_get_text(root, 'result', 'No result')
details = safe_get_text(root, 'details', '')
print(f"Confidence: {confidence}, Result: {result}")
✅ หรือใช้ walrus operator (Python 3.8+)
if (tag := root.find('result')) is not None:
print(f"Result: {tag.text}")
else:
print("Result tag not found")
✅ ใช้ try-except เพื่อ graceful handling
def extract_data_safe(xml_string):
try:
root = ET.fromstring(xml_string)
return {
'result': root.find('result').text if root.find('result') is not None else None,
'confidence': int(root.find('confidence').text) if root.find('confidence') is not None else 0,
'metadata': root.find('metadata/details').text if root.find('metadata/details') is not None else None
}
except (ET.ParseError, ValueError, AttributeError) as e:
print(f"Parse error: {e}")
return None
ข้อผิดพลาดที่ 3: ปัญหา Namespace ใน XML Response
# ❌ วิธีที่ผิด: ค้นหา tag โดยไม่สนใจ namespace
xml_ns = """<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:Response xmlns:ns2="http://example.com/api">
<ns2:result>Success</ns2:result>
</ns2:Response>
</soap:Body>
</soap:Envelope>
"""
root = ET.fromstring(xml_ns)
result = root.find('result') # ไม่พบ! เพราะมี namespace
✅ วิธีที่ถูก: กำหนด namespace และใช้ในการค้นหา
namespaces = {
'soap': '