2014-04-27 28 views
0

如何使用python re提取中間部分(第一行,第二行和第三行)?如何從文檔中間提取第1行到第3行?

--089e013d100acf582104f809fd8d 
Content-Type: text/plain; charset=UTF-8 

first line 
second line 

third line 

--089e013d100acf582104f809fd8d 

第一部分閱讀郵箱:

#!/usr/bin/env python 
import poplib 
from email import parser 

pop_conn = poplib.POP3_SSL('mail.company.com') 
pop_conn.user('user') 
pop_conn.pass_('') 

#newest email has the highest message number 
numMessages = len(pop_conn.list()[1]) 

(server_msg,body,octets) = pop_conn.retr(numMessages) 
+0

請問您的文檔你總是看起來像這樣嗎? – keyser

+0

不是真的。 --089e013d100acf582104f809fd8d是定義不同內容類型塊之間邊界的邊界線。非常感謝您的關注! – john

+0

沒問題:p您需要定義一些規則。計算機如何找到這些線?它們總是在邊界之後的第3,4和6行嗎?在這種情況下,我建議你不要使用正則表達式,而只是循環並計數。 – keyser

回答

2

正則表達式只有真正可用於簡單的(常規)模式。理論上,正則表達式是表示有限狀態機的一種方式。通常,它們用於詞法分析/詞法分析器(將程序字符串拆分爲一系列令牌)或匹配常規字符串(例如羅馬數字)。

它看起來就像你正在試圖解析多部分MIME文件,例如:

MIME-Version: 1.0 
Content-Type: multipart/alternative; boundary="mimetest" 

This part is ignored. 
--mimetest 
Content-Type: text/plain 

Part 1 
--mimetest 
Content-Type: text/rtf 

\rtf{\par Part 2} 
--mimetest-- 

在這裏,你有兩個內MIME文件外MIME文件。每個mime文檔都有一個標題部分,後面跟着一個空行,後面跟着內容。

最好的方法是編寫一個解析器,將該頭與內容一起讀入字典中。然後,您可以使用正則表達式來定位邊界並提取邊界之間,例如文本:

MIME_STATE_HEADER = 1 
MIME_STATE_BODY = 2 

def read_lines(text): 
    if isinstance(text, list): 
     return text 
    return re.split(r'\r?\n', text) 

def parse_mime(text): 
    header_line = re.compile(r'^([A-Za-z\-]+): (.*)$') 
    state = MIME_STATE_HEADER 
    header = {} 
    body = [] 
    for line in read_lines(text): 
     if state == MIME_STATE_HEADER: 
      if line == '': 
       state = MIME_STATE_BODY 
       continue 
      m = header_line.match(line) 
      if not m: 
       raise Exception('Invalid header section: %s' % line) 
      header[ m.group(1).lower() ] = m.group(2) 
     elif state == MIME_STATE_BODY: 
      body.append(line) 
    return header, body 

def mime(text): 
    header, body = parse_mime(text) 
    content_type = re.compile(r'multipart/.*; boundary="(.*)"') 
    m = content_type.match(header['content-type']) 
    if m: 
     boundary = re.escape(m.group(1)) 
     matcher = re.compile(r'\r?\n--%s(--)?\r?\n' % boundary) 
     parts = [ mime(part) for part in matcher.split('\n'.join(body))[1:-2] if part ] 
     return header, parts 
    return header, '\n'.join(body) 

此代碼將處理各種基於MIME的文件,但有幾個限制/錯誤:

  1. 這將不支持標頭包起來,如:

    Content-Type: multipart/related; 
        boundary="text" 
    
  2. 它不支持不帶引號的界限,如Content-Type: multipart/related; boundary=text

  3. 它不支持像Debian bugmail(例如以From email date time開頭)或HTTP/SMTP標識符/狀態行這樣的郵件存檔。

有了這些Python的正則表達式:

  • ^
  • $的開始相匹配的線的端部
  • [abc]匹配所有的字符的匹配abc (之一)
  • [a-z]比賽通過對z
  • \-逸出,因此可以在一個[]表達式中使用的字符-任何字符a
  • a+匹配一個或表達的多個實例a
  • a*匹配零或表達的多個實例a
  • .任何字符
  • a?任選匹配a匹配(即。匹配的a零個或一個實例)
  • \r匹配回車符
  • (a)捕獲下一個組中的匹配a表達的含量 - 通過m.group(n)
  • 任何在上面的表達式匹配作爲別的訪問-is

^([A-Za-z\-]+): (.*)$在MIME頭中的一個頭的條目,使得m.group(1)是標題名稱(例如,「內容類型」)和m.group(2)的是,報頭的內容相匹配。

\r?\n匹配Windows或Linux風格的行結束符(MIME文檔應該使用'\ r \ n',但在本地保存文件時可以轉換爲\n)。

multipart/.*; boundary="?(.*)"?在Content-Type標題條目中查找用於邊界的文本。

\r?\n--%s(--)?\r?\n找到一個單獨的邊界(其中%s是動態添加的邊界 )。注意:我已通過re.escape傳遞邊界字符串以防止它被利用(即它包含諸如boundary="[a-z]"之類的正則表達式)。

實際上,您應該在Python中使用email模塊,該模塊支持解析RFC822(email/mime)文檔。 documentation表示:「對於簡單的非MIME消息,這個根對象的負載可能是一個包含消息文本的字符串。對於MIME消息,根對象將從它的is_multipart()方法返回True,並且子部分可以通過get_payload()和walk()方法訪問。「

更新:我創建了一個read_lines幫手,支持字符串列表(例如C poplib)和一個字符串(例如Cf.read())。

UPDATE:--%s\r?\n(.*)\r?\n--%s匹配邊界檢測在:

matcher = re.compile(r'--%s\r?\n(.*)\r?\n--%s' % (boundary, boundary)) 
parts = [ mime(part) for part in matcher.findall(body) ] 

有兩個問題:

  1. 它不換行(這可以通過使用re.compile(..., re.DOTALL)來解決匹配結束;
  2. 太貪婪(它匹配多個部分)

後者不容易用正則表達式解決。解決的辦法是分割的邊界線,從而導致:

[part0, None, part1, None, part2, ..., partN, '--', ''] 

其中part0是第一邊界之前的部分。因此[1:-2]用於刪除part0和兩個結束匹配,並且if part用於避免None匹配。

+0

非常感謝您爲這樣詳細的輔導!我可以問爲什麼我得到了以下錯誤:換行在newline.split(文本): TypeError:預期的字符串或緩衝區 – john

+0

我不確定。儘管我沒有測試代碼,所以它可能包含錯誤。你是否將文本傳遞給函數 - 「open(filename)作爲f:header,body = mime(f.read())'?注意:newline.split('hello \ r \ nworld \ ntest')'在python控制檯中適用於我。 – reece

+0

我剛剛使用poplin從我的郵箱中讀取了一些郵件。然後我得到郵件的正文部分,然後將這部分傳遞給您的函數。我沒有閱讀外部文件。 – john

0

下面是簡單的代碼,我會用捕捉到三條線:

reobj = re.compile("Content-Type:.*?\n+([^\n]+)\n+([^\n]+)\n+([^\n]+)\n*") 
match = reobj.search(subject) 
if match: 
    line1 = match.group(1) 
    line2 = match.group(2) 
    line3 = match.group(3) 
else: 
    result = "" 

這是如何工作的?

  1. 我們將在一組捕獲括號之間捕獲每一行。它們將是第1組,第2組,第3組。
  2. 要獲得我們想要的內容,我們匹配Content-Type(不捕獲它),然後匹配下一系列新行的所有內容, \n+
  3. 每行捕獲與([^\n]+)這意味着「匹配任何數目的字符是什麼都沒有了新的生產線。
  4. 捕捉每行之後,我們吃的任何數量的新線,\n+。第3組後的最終換行符是可選的:\n*(事實上,正則表達式在沒有它的情況下工作。)
+0

這只是在Content-Type頭+空白行後捕獲3行。如果有三條以上的線路呢?如果Content-Type標題後面有標題項,該怎麼辦?如果在那之後還有其他部分呢?如果嵌套的多部分文檔會怎麼樣? ... – reece

+0

@reece你是對的,這是我的解決方案。我只回答了OP提出的問題,而沒有試圖猜測他可能擁有的其他規格。 – zx81

+0

正在問的問題是如何在兩個邊界之間提取多部分MIME文檔的內容,如示例中所示。這就是我的答案中的' - %s \ r?\ n(。*) - %s'部分。爲了獲得邊界,你需要解析文檔的MIME頭文件(在OP的例子中沒有顯示),以在'content-type'頭文件中尋找邊界:'multipart /.*;邊界= 「(。*)」'。 – reece

相關問題