XMLing with Python
ملفات ال xml من اهم الملفات اللى بنتعامل معاها بصورة شبه يومية وبايثون من انسب الحلول للتعامل معاها..
فى اكتر من باكيج للتعامل مع ال Markups
http://docs.python.org/lib/markup.htmlعلى فرض عندنا ملف كالتالى
<?xml version="1.0"?>
<!DOCTYPE books SYSTEM "books.dtd">
<?xml-stylesheet type="text/xsl" href="books.xsl"?>
<books>
<book id="1">
<name>Introduction to Python</name>
<author>Ahmed Youssef</author>
<price>80</price>
</book>
<book id="2">
<name>Introduction to Java</name>
<author>Wael Muhammed</author>
<price>130</price>
</book>
<book id="3">
<name>Introduction to Ruby</name>
<author>Ahmed Youssef</author>
<price>70</price>
</book>
<book id="4">
<name>Introduction to Linux Programming</name>
<author>Ahmed Mostafa</author>
<price>90</price>
</book>
</books>
فى root وهى ال books tag
وليها ابناء كل واحد بإسم book
كل book tag ليه attributes ؟ ايوة كل book لية id معين
داخل كل book بيشمل name, author, price tags لإسم الكتاب والكاتب والسعر
من الملف دا عايزين نجيب اسم كل كتاب ومجموع السعر بتاعهم
1- minidom
فى implementation خفيفة لDOM بإسم minidom هنستدعيها كالتالى
import xml.dom.minidom as md #(parse, parseString..)
فى عندنا دالتين مهمين وهم parse, parseString
parse للتعامل مع file
parseString للتعامل مع string
والإتنين هيدولك ريترن ب document object
ال node object
هو يعتبر الأب لكل العناصر ملف ال xml وليه ميثودز/صفات مهمة
بتعبر عن النوع هل هى text node, element, comment,document,.. etc
رفرنس للأب (ماعدا ال document root) ولل attrs هتكون ديما None
ال node السابقة للnode الحالية إلا اذا كانت هى الأولى
ال node التالية إلا اذا كانت ال node الحالية هى الأخيرة
جميع ال nodes اللى داخل ال node الحالية
هل فى nodes داخلها ؟
اول ابن
اخر ابن
هل فيها attributes ؟
إضافة ابن جديد
insertBefore(child, befored)
بتضيف child قبل ال befored وفى حال عدم وجوده بيتم إضافته فى النهاية
حذف ابن child
ربط ال text nodes المتقاربة
ال document object بيعبر عن الملف وليه ميثودز/صفات مهمة زى
documentElement # used as a property
ودى بتعبر عن ال root element وفى مثالنا هنا هى books
getElementsByTagName(tagName) #tagName
بتدور على tagName معين فى كل الأبناء وابنائهم وهكذا وتديلك ريترن ب element object
لإنشاء tag جديد
لإنشاء تعليق داخلى
لإنشاء صفة attribute
ملحوظة فى بعض الميثودز بنفس الإسم ولكن اخرها NS ودى لربطها مع namespace ما وبتاخد nsURI كمعامل ليها.
ال Element Object بيعبر عن عنصر معين فى الملف وليه ميثودز مهمة زى
tagName #used as a property
بتعيد الإسم المجرد لل element
مشابهه للموجودة بال document object
هل بيحتوى على attribute ؟
بيعيدلك قيمة attribute معينة بإسم attrName
setAttribute(attrName, val)
بيربط attribute معينة attrName ليها قيمة val بالعنصر
removeAttribute(attrName)
لحذف attribute معينة attrName (مش بيرفع اى exception !)
ملحوظة فى بعض الميثودز بنفس الإسم ولكن اخرها NS ودى لربطها مع namespace ما وبتاخد nsURI كمعامل ليها.
مجموعة ال exceptions
http://docs.python.org/lib/dom-exceptions.htmlطيب تمام
1- استدعى ال minidom
import xml.dom.minidom as md #(parse, parseString..)
2- انشئ ال document object سواء بإستخدام parse او parseString حسب تخزينك لملف ال xml
doc=md.parse("books.xml")
3- احصل على ال document root و اعرضه واحصل على كل tag قيمته book واطبعه
def inspectBooks():
global doc
print "Root Element: ", doc.documentElement.tagName
books=doc.getElementsByTagName("book")
for book in books:
if book.hasAttribute("id"): #id and it should have one!
print "ID: ",book.getAttribute("id")
for child in book.childNodes:
if child.nodeType==child.ELEMENT_NODE:
if child.tagName=="name":
child.normalize()
print "Book: ",child.firstChild.data
تمام ال doc هنا global variable
الحصول على ال document root هنا جالنا ريترن ب Element object واحنا عايزين ال tagName
doc.documentElement.tagName
نحصل على كل العناصر اللى tagName بتاعها book
books=doc.getElementsByTagName("book")
نعمل loop على كل عنصر فيها
اذا كان فيه id attribute (لمجرد عرض المثال)
if book.hasAttribute("id"): #id and it should have one!
print "ID: ",book.getAttribute("id")
طيب ولطباعة اسم الكتاب؟ لاحظ انه متخزن فى ال name tag
بسيطة جدا نعمل loop على كل الأبناء فى لا book element ونشوف النوع اذا كان ELEMENT NODE و ال tagName بتاعه هو name نطبعه
if child.tagName=="name":
child.normalize()
print "Book: ",child.firstChild.data
ملحوظة لل nodes انواع كتير element, comment, text, .. etc
ال firstChild دا بيعبر عن ال text node اللى فى ال name tag وال data بتدى ريترن بالstring اللى جواها
<name> text node ... </name
الحصول على الثمن الكلى
def getTotalSum():
global doc
thesum=0
prices=doc.getElementsByTagName("price")
for price in prices:
price.normalize()
thesum += int(price.firstChild.data) #TO int.
return thesum
نحصل على كل ال price elements
<price>numeric_value</price>
ونحول القيمة ل int وبس ونضيفها على ال thesum وبعد مانخلص نعمل الريترن بيها
ناتج التنفيذ ل
inspectBooks()
print "Total Sum: ", getTotalSum()
Root Element: books
ID: 1
Book: Introduction to Python
ID: 2
Book: Introduction to Java
ID: 3
Book: Introduction to Ruby
ID: 4
Book: Introduction to Linux Programming
Total Sum: 370
2- SAX
بيعتمد على ال events بمعنى انه بيديلك خبر كل مايبدأ عنصر او يبدأ ال content اللى داخله وهكذا يمكن تشوفه اعقد شوية لكن انا عن نفسى من محبى استخدامه
1- استدعى اللى هنستخدمه
from xml.sax import make_parser, parseString
from xml.sax.handler import ContentHandler
ال ContentHandler دا هو المفتاح السحرى بتاعنا فيه ميثودز event handlers بيتعمل ليها استدعاء عند حدوث حدث معين
بيتم استدعائها مرة واحدة عند بداية الملف
بيتم استدعائها مرة واحدة عند نهاية الملف
startElement(name, attrs)
بيتم استدعائها عند بداية قراءة كل عنصر el
<el [attr1=val1, attr2=val2, ... attrN=valN]>CONTENT</el>
بيتم استدعائها عن بداية قراءة محتوى العنصر
<el [attr1=val1, attr2=val2, ... attrN=valN]>
CONTENT</el>
بيتم استدعائها عند نهاية قراءة عنصر el
<el [attr1=val1, attr2=val2, ... attrN=valN]>CONTENT</el>
فى بعض الميثودز بتنتهى ب NS ودى فى حالة التعامل مع namespace
الل Attributes ماهى الا mapping او dictionary مضاف ليها بعض الميثودز مثل
للحصول على عددهم
الحصول على اسم كل attribute
للحصول على النوع وهى عادة CDATA
الحصول على القيمة المرافقة للattribute المسماة attrName
نرجع للمثال
1- هنستدعى الميثودز/الكلاسز المستخدمة
from xml.sax import make_parser, parseString
from xml.sax.handler import ContentHandler
ال make_parser هتعيد لينا XML reader
parseString لقراءة ال xml من string
parse هى ميثود تبع الXML reader object بتاخد مسار ملف (نفس parse,parseString اللى تحدثنا عنهم فى minidom)
2- ملف ال xml ك string مخزن
xmldoc="""<?xml version="1.0"?>
<books>
<book id="1">
<name>Introduction to Python</name>
<author>Ahmed Youssef</author>
<price>80</price>
</book>
<book id="2">
<name>Introduction to Java</name>
<author>Wael Muhammed</author>
<price>130</price>
</book>
<book id="3">
<name>Introduction to Ruby</name>
<author>Ahmed Youssef</author>
<price>70</price>
</book>
<book id="4">
<name>Introduction to Linux Programming</name>
<author>Ahmed Mostafa</author>
<price>90</price>
</book>
</books>
"""
3- انشئ كلاس جديد مشتق من ال ContentHandler
class BooksHandler(ContentHandler):
ملحوظة اى ميثود مش هتعملها override مش تكتبها فى الهاندلر..
def __init__(self):
self._total=0 #Sum of prices.
self._curel=None
self._curid=None
self._booksInfo=[]
self._authors=[]
ايه المتغيرات دى كلها ؟
self._total للتخزين المجموع الكلى للأسعار
self._curel لتخزين اسم العنصر اللى بيتم معالجته
self._curid لتخزين اخر id تم قرائته
self._booksInfo تخزين معلومات عن الكتاب مكونة من ال name, id
self._authors تخزين اسماء الكتاب
def getTotal(self):
return self._total
def getBooksInfo(self):
return self._booksInfo
def getAuthors(self):
return self._authors
عرفنا getters للوصول للمتغيرات الداخلية
ملحوظة يفضل تستخدم properties مع lambda
booksinfo=property(fget=lambda self: self._booksInfo)
authors=property(fget=lambda self: self._authors)
total=property(fget=lambda self: self._total)
def startDocument(self):
#print "Starting Document."
pa*s
لو حبيت تضيف اى رسالة او اى حاجة على هواك يتم تنفيذها عند بداية قراءة الملف
def endDocument(self):
#print "Ending Document."
pa*s
نفس السابقة ولكن عند انتهاء القراءة
def startElement(self, el, attrs):
#print "Starting ", el
self._curel=el
if el=="book":
#get the id..
self._curid=attrs.getValue("id") #attrs["id"]
هنا هيتم الإستدعاء عند بداية قراءة كل عنصر el والصفات الخاصة بيه attrs
1- نخزن العنصر الحالى فى ال self._curel
2- نختبر اذا كان العنصر الحالى هو book فليه attribute بإسم id فنحصل عليها ونخزنها كآخر id لآخر كتاب تم قرائته فى ال reader
if el=="book":
#get the id..
self._curid=attrs.getValue("id") #attrs["id"]
طبعا تقدر تحصل عليها كأنك بتتعامل مع dict مش بإستخدام .getValue ميثود
def characters(self, content):
if content.strip():
if self._curel=="price":
#print "In Price.."
try:
self._total += int(content)
except:
pa*s
elif self._curel=="name":
self._booksInfo +=[(content, self._curid)]
elif self._curel=="author":
self._authors +=[content]
else:
pa*s
هنا هيتم استدعائها عن قراءة المحتوى للعنصر الحالى
وبناءا على العنصر الحالى هنتعامل سواء اذا كان ثمن او اسم الكتاب او الكاتب
4- ننشئ اوبجكت من الهاندلر الجديد
ننشئ XML reader ونباصى ليه ال handler الجديد والفايل اللى هيتعالج او نستخدم parseString ونباصى ليها الهاندلر(bh)
p = make_parser( )
p.setContentHandler(bh)
p.parse(open("books2.xml"))
او نستخدم parseString نحدد ال string اللى هيتعالج وال هاندلر (bh)
للمزيد عن
http://docs.python.org/lib/module-xml.sax.htmlومش تنسى
http://www.saxproject.org/استخدم مين ؟
همم DOM بيتعمد على انشاء tree للملف ودا شاق جدا للملفات اللى حجمها كبير!
من ناحية اخرى SAX بيعتمد على ال events ودا اسلوب فعال جدا
3- Expat undercover
Expat مكتبة سى سريعة لمعالجة ملفات ال XML وتم عمل wrapper ليها فى بايثون
1- استدعى الموديلز اللازمة
import xml.parsers.expat as exp
2- انشئ كلاس جديد بنفس فكرة ال ContentHandler
cla*s ParsingHandler(object):
def __init__(self, xml):
self._curel=None
self._curattrs=None
self._inbook=False
self._books=[]
self._thesum=0
self._p=exp.ParserCreate()
self._p.StartElementHandler=self.__startElement
self._p.EndElementHandler=self.__endElement
self._p.CharacterDataHandler=self.__charsDataHandler
self._p.Parse(xml)
لاحظ عندنا متغيرات لمتابعة العنصر الحالى والصفات الحالية ليه وهل احنا داخل ال book tag او لأ واسماء الكتاب والمجموع الكلى
القسم التانى متعلق بال parser
1- انشئ XMLParserType object بإستخدام ParserCreate
2- اربط الhandlers المختصين ببداية كل عنصر ونهايته والمحتوى بhandlers انت هتجهزهم لاحقا
3- عالج ال xml بإستخدام ال Parse method
انشئ getters
def getTotalSum(self):
return self._thesum
def getBooksInfo(self):
return self._books
def printBooksInfo(self):
for book in self._books:
print book
عرف الhandlers بتوعنا اللى اسندناهم للهاندلرز الأساسين لل self._p parser
def __startElement(self, el, attrs):
print "Starting: ",el, attrs
if el=="book":
self._inbook=True
self._curel=el
self._curattrs=attrs
def __charsDataHandler(self, data):
if data.strip():
if self._inbook and self._curel=="name" :
self._books += [data]
elif self._curel=="price" :
self._thesum += int(data)
else:
pa*s
def __endElement(self, el):
if el=="book":
self._inbook=False
self._curel, self._curattrs=None,None
الإستخدام
if __name__=="__main__":
p=ParsingHandler(xmldoc)
print "Total sum: ", p.getTotalSum()
p.printBooksInfo()
لاحظ ان ال xmldoc هو string بيعبر عن ملف ال xml اللى هيتم معالجته
ناتج التنفيذ
Total sum: 370
Introduction to Python
Introduction to Java
Introduction to Ruby
Introduction to Linux Programming
مثال حقيقى لإنشاء Weather.com APIs wrapperHTMLing with Python
على فرض انك قرأت الفصل السابق قم بتنفيذ البرنامج التالى
--سكربت يقوم بقراءة صفحة من الإنترنت ويقوم بالحصول على جميع اللينكات فيها اعتمد على النموذج التالى
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from HTMLParser import HTMLParser as HP
import urllib2 as ulib
import sys
def fetchdatafrom(url):
return ulib.urlopen(url).read()
def as_unicode(data):
return data.decode("cp1256").encode("utf-8")
class PageParser(HP):
def __init__(self):
self._ina=False
self._links=[]
links=lambda self: self._links
def handle_start_tag(self, tag, attrs):
pass
def handle_data(self, data):
pass
def handle_endtag(self, tag):
pass
def getlinks(url):
htmlsrc=fetchdatafrom(url)
p=PageParser()
p.feed(htmlsrc)
return p.links()
طريقة الإستخدام مشابهه لتلك مع SAX
حيث تعيد تعريف الطرق handle_starttag و handle_data و handle_endtag للتعامل مع وسوم ال HTML
الدالة fetchdatafrom تقوم بإعادة كود الصفحة اليك على صورة string
الدالة as_unciode تقوم بتحويل الcp1256 الى unicode (ربما اذا اردت ان تعالج الdata j تستطيع الإستفادة منها )
ال PageParser هو صف يشتق ال HTMLParser ويتم التعامل داخله مثلما تعاملنا مع الصفوف المشتقة ContentHandler
ولإطعامه السورس نستخدم الطريقة feed
الدالة getlinks تقوم بالحصول على الروابط من الطريقة links اللتى تعيد لنا الروابط اللتى تم قرائتها
Beautiful Soup
هى HTML/XML parser بايثونية وتعالج ايضا الملفات المكتوبة بطريقة سيئة ولاتجعلك تقلق من الإنكودينج
لمعالجة ملفات ال HTML استخدم الصف BeautifulSoup واذا اردت معالجة ملفات XML استخدم BeautifulStoneSoup
حل المطلوب السابق بإستخدام BeautifulSoup
#!bin/python
import BeautifulSoup as bs
import urllib2 as ulib
def fetchdatafrom(url):
return ulib.urlopen(url).read() or "\n"
def getzetcodemain():
return fetchdatafrom('http://zetcode.com')
soup=bs.BeautifulSoup(getzetcodemain())
for el in soup.findAll('a'):
# [0][0] is href.
print "[url=%s]%s[/url]"%(el.attrs[0][1], el.contents)
تنتظرك رحلة رائعة مع الوثائق الخاصة بيها
http://www.crummy.com/software/BeautifulSoup/documentation.htmlاستفيد من السكربتات السابق فى تنفيذ التالى
انشاء مفهرس للمنتديات يأخذ قسم معين كبداية وتقوم بتحديد عدد الصفحات المطلوبة ويقوم بفتحها واستخلاص اللينكات والعناوين لها