2012-03-21 21 views
9

Bạn sẽ đọc một tệp XML bằng cách sử dụng sax và chuyển đổi nó thành phần tử lxml etree.iterparse như thế nào?Python sax để lxml cho 80 + GB XML

Để cung cấp tổng quan về vấn đề, tôi đã xây dựng một công cụ nhập XML bằng cách sử dụng lxml cho nguồn cấp dữ liệu XML có kích thước 25 - 500MB cần nhập trên cơ sở hai ngày, nhưng cần thực hiện một lần nhập một tệp có dung lượng từ 60 - 100GB.

Tôi đã chọn sử dụng lxml dựa trên các thông số chi tiết một nút sẽ không vượt quá kích thước 4 -8 GB mà tôi nghĩ sẽ cho phép nút được đọc vào bộ nhớ và xóa khi hoàn tất.

Tổng quan nếu mã dưới

elements = etree.iterparse(
    self._source, events = ('end',) 
) 
for event, element in elements: 
    finished = True 
    if element.tag == 'Artist-Types': 
     self.artist_types(element) 

def artist_types(self, element): 
    """ 
    Imports artist types 

    :param list element: etree.Element 
    :returns boolean: 
    """ 
    self._log.info("Importing Artist types") 
    count = 0 
    for child in element: 
     failed = False 
     fields = self._getElementFields(child, (
      ('id', 'Id'), 
      ('type_code', 'Type-Code'), 
      ('created_date', 'Created-Date') 
     )) 
     if self._type is IMPORT_INC and has_artist_type(fields['id']): 
      if update_artist_type(fields['id'], fields['type_code']): 
       count = count + 1 
      else: 
       failed = True 
     else: 
      if create_artist_type(fields['type_code'], 
       fields['created_date'], fields['id']): 
       count = count + 1 
      else: 
       failed = True 
     if failed: 
      self._log.error("Failed to import artist type %s %s" % 
       (fields['id'], fields['type_code']) 
      ) 
    self._log.info("Imported %d Artist Types Records" % count) 
    self._artist_type_count = count 
    self._cleanup(element) 
    del element 

Hãy cho tôi biết nếu tôi có thể thêm bất kỳ loại làm rõ.

+0

Vậy câu hỏi là gì? Bạn có nhận được thông báo lỗi không? –

+2

Câu hỏi đặt ra trong câu đầu tiên ... tại sao câu trả lời là? – Nick

+0

Câu hỏi của bạn hơi lạ. Tại sao bạn sử dụng SAX? iterparse là * một thay thế cho * SAX. Bạn có thể tạo các sự kiện lặp lại từ các sự kiện SAX, nhưng tại sao mọi người lại làm điều đó? –

Trả lời

19

iterparse là trình phân tích cú pháp lặp lại. Nó sẽ phát ra các đối tượng và sự kiện Element và từng bước xây dựng toàn bộ cây Element khi nó phân tích cú pháp, vì vậy cuối cùng nó sẽ có toàn bộ cây trong bộ nhớ.

Tuy nhiên, thật dễ dàng để có hành vi bộ nhớ bị chặn: xóa các phần tử bạn không cần nữa khi bạn phân tích cú pháp chúng.

Khối lượng công việc "khổng lồ xml" điển hình là một phần tử gốc đơn với một số lượng lớn các phần tử con đại diện cho các bản ghi. Tôi cho rằng đây là loại cấu trúc XML mà bạn đang làm việc?

Thường thì việc sử dụng clear() để xóa phần tử bạn đang xử lý là đủ. Việc sử dụng bộ nhớ của bạn sẽ tăng lên một chút nhưng không nhiều lắm. Nếu bạn có tệp rất lớn, thì ngay cả đối tượng trống Element sẽ tiêu thụ quá nhiều và trong trường hợp này, bạn cũng phải xóa các đối tượng đã xem trước đó Element. Lưu ý rằng bạn không thể xóa phần tử hiện tại một cách an toàn. lxml.etree.iterparse documentation describes this technique.

Trong trường hợp này, bạn sẽ xử lý một bản ghi mỗi khi tìm thấy </record>, sau đó bạn sẽ xóa tất cả các phần tử bản ghi trước đó.

Dưới đây là ví dụ sử dụng tài liệu XML dài vô hạn. Nó sẽ in mức sử dụng bộ nhớ của quá trình khi nó phân tích cú pháp. Lưu ý rằng việc sử dụng bộ nhớ ổn định và không tiếp tục tăng.

from lxml import etree 
import resource 

class InfiniteXML (object): 
    def __init__(self): 
     self._root = True 
    def read(self, len=None): 
     if self._root: 
      self._root=False 
      return "<?xml version='1.0' encoding='US-ASCII'?><records>\n" 
     else: 
      return """<record>\n\t<ancestor attribute="value">text value</ancestor>\n</record>\n""" 

def parse(fp): 
    context = etree.iterparse(fp, events=('end',)) 
    for action, elem in context: 
     if elem.tag=='record': 
      # processing goes here 
      pass 

     #memory usage 
     print resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 

     # cleanup 
     # first empty children from current element 
      # This is not absolutely necessary if you are also deleting siblings, 
      # but it will allow you to free memory earlier. 
     elem.clear() 
     # second, delete previous siblings (records) 
     while elem.getprevious() is not None: 
      del elem.getparent()[0] 
     # make sure you have no references to Element objects outside the loop 

parse(InfiniteXML()) 
+0

Không có đơn nút "root", thay vào đó dữ liệu được chia nhỏ thành hơn 20 nút "gốc", mỗi nút chứa các tập con của riêng chúng. Công cụ hiện tại hoạt động theo kiểu tương tự như mã của bạn liên quan đến việc xóa bất kỳ nút không cần thiết nào sau khi được xử lý và điều này cho phép xử lý một lượng lớn dữ liệu nhưng một khi tôi cố gắng xử lý một trong các nút lớn hơn "Tôi giả sử kích thước lớn hơn 8GB ", quá trình sẽ phân đoạn (tại vòng lặp for)' '' cho hành động, trong ngữ cảnh: '' 'dẫn tôi tin rằng nó đang được đọc vào bộ nhớ. – Nick

+0

Bạn có thể hiển thị một số mẫu XML không? Mã bạn đã đăng chỉ xuất hiện để hiển thị một loại phần tử chính. Iterparse không đọc toàn bộ tệp vào bộ nhớ vì vậy việc phân chia luồng công việc của bạn thành các subtrees nhỏ hơn * làm * phù hợp với bộ nhớ và xóa mọi thứ sau mỗi lần lặp lại. –

+1

Mã được đăng ở trên là khoảng nhiều như tôi có thể cho không may, nhưng với điều đó nói sau khi viết lại một phần tốt của việc nhập khẩu hiện đang hoạt động bằng cách sử dụng phương pháp trên của bạn. Xem đoạn mã sau cho mã https://gist.github.com/2161849. – Nick

3

Tôi đã tìm thấy ví dụ hữu ích này tại http://effbot.org/zone/element-iterparse.htm. Nhấn mạnh đậm là của tôi.

Incremental Parsing #

Lưu ý rằng iterparse vẫn xây dựng một cây, giống như phân tích cú pháp, nhưng bạn có thể an toàn sắp xếp lại hoặc loại bỏ các bộ phận của cây khi phân tách. Ví dụ, để phân tích các file lớn, bạn có thể loại bỏ các yếu tố ngay sau khi bạn đã xử lý chúng:

for event, elem in iterparse(source): 
    if elem.tag == "record": 
     ... process record elements ... 
     elem.clear() 

Các mô hình trên có một nhược điểm; nó không xóa phần tử gốc, vì vậy bạn sẽ kết thúc với một phần tử đơn lẻ với nhiều phần tử con trống. Nếu tệp của bạn lớn, thay vì chỉ lớn, điều này có thể là một vấn đề. Để giải quyết vấn đề này, bạn cần nắm lấy phần tử gốc. Cách dễ nhất để làm điều này là để cho phép sự kiện bắt đầu, và lưu một tham chiếu đến phần tử đầu tiên trong một biến:

# get an iterable 
context = iterparse(source, events=("start", "end")) 

# turn it into an iterator 
context = iter(context) 

# get the root element 
event, root = context.next() 

for event, elem in context: 
    if event == "end" and elem.tag == "record": 
     ... process record elements ... 
     root.clear() 

(phiên bản tương lai sẽ làm cho nó dễ dàng hơn để truy cập vào phần tử gốc từ bên trong vòng lặp)

+0

Cảm ơn câu trả lời nhưng tôi đã khám phá điều này và ít nhất là từ thử nghiệm của tôi, nút vẫn được đọc hoàn toàn vào bộ nhớ và không được phát trực tuyến – Nick

0

Đây là một vài tuổi và tôi không có đủ uy tín để bình luận trực tiếp trên câu trả lời được chấp nhận, nhưng tôi đã cố gắng sử dụng này để phân tích một OSM nơi tôi đang tìm kiếm tất cả các nút giao thông trong một quốc gia. Vấn đề ban đầu của tôi là tôi đã hết RAM, vì vậy tôi nghĩ tôi sẽ phải sử dụng trình phân tích cú pháp SAX nhưng tìm thấy câu trả lời này thay thế. Kỳ lạ là nó không phân tích cú pháp chính xác, và sử dụng dọn dẹp được đề xuất bằng cách nào đó đã xóa nút elem trước khi đọc qua nó (vẫn không chắc chắn điều này xảy ra như thế nào). Đã xóa elem.clear() khỏi mã và giờ nó chạy hoàn toàn tốt!

Các vấn đề liên quan