使用lxml解析Python中带有错误标题的文本文件

时间:2012-09-13 18:59:41

标签: python lxml

我想用lxml的etree解析文本文件(本地存储)。但是我的所有文件(数千个)都有标题,例如:

-----BEGIN PRIVACY-ENHANCED MESSAGE-----
Proc-Type: 2001,MIC-CLEAR
Originator-Name: webmaster@www.sec.gov
Originator-Key-Asymmetric:
 MFgwCgYEVQgBAQICAf8DSgAwRwJAW2sNKK9AVtBzYZmr6aGjlWyK3XmZv3dTINen
 TWSM7vrzLADbmYQaionwg5sDW3P6oaM5D3tdezXMm7z1T+B+twIDAQAB
MIC-Info: RSA-MD5,RSA,
 AHxm/u6lqdt8X6gebNqy9afC2kLXg+GVIOlG/Vrrw/dTCPGwM15+hT6AZMfDSvFZ
 YVPEaPjyiqB4rV/GS2lj6A==

<SEC-DOCUMENT>0001193125-07-200376.txt : 20070913
<SEC-HEADER>0001193125-07-200376.hdr.sgml : 20070913
<ACCEPTANCE-DATETIME>20070913115715
ACCESSION NUMBER:       0001193125-07-200376
CONFORMED SUBMISSION TYPE:  10-K
PUBLIC DOCUMENT COUNT:      7
CONFORMED PERIOD OF REPORT: 20070630
FILED AS OF DATE:       20070913
DATE AS OF CHANGE:      20070913

并且在这种情况下第一个<直到第51行(并且在所有情况下都不是51)。 xml部分如下所示:

</SEC-HEADER>
<DOCUMENT>
<TYPE>10-K
<SEQUENCE>1
<FILENAME>d10k.htm
<DESCRIPTION>FORM 10-K
<TEXT>
<HTML><HEAD>
<TITLE>Form 10-K</TITLE>
</HEAD>
 <BODY BGCOLOR="WHITE">
<h5 align="left"><a href="#toc">Table of Contents</a></h5>

我可以使用lxml动态处理吗?或者我应该使用流编辑器来省略每个文件的标题?谢谢!

这是我当前的代码和错误。

from lxml import etree
f = etree.parse('temp.txt')

XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1

编辑:

FWIW,这是指向file的链接。

4 个答案:

答案 0 :(得分:5)

鉴于这些文件有一个标准,可以编写一个合适的解析器而不是猜测事物,或希望beautifulsoup能够把事情弄清楚。这并不意味着它对你来说是最好的答案,但它肯定会起作用。

根据http://www.sec.gov/info/edgar/pdsdissemspec910.pdf的标准,您所获得的内容(在PEM机箱内)是由提供的DTD定义的SGML文档。所以,首先转到第48-55页,在那里提取文本,并将其保存为“edgar.dtd”。

我要做的第一件事就是安装SP并使用它的工具来确保文档确实有效并且可以被DTD解析,以确保你不会浪费大量时间在某些东西上这不会成功。

Python附带了一个验证的SGML解析器sgmllib。不幸的是,它从未完全完成,并且在2.6-2.7中被弃用(并在3.x中删除)。但这并不意味着它不起作用。所以,试一试,看看它是否有效。

如果没有,我不知道Python有什么好的选择;大多数SGML代码都是C,C ++或Perl。但是你可以很容易地包装任何C或C ++库(我从SP开始),只要你习惯用C / Cython / boost-python /无论使用ctypes编写自己的包装。您只需要包装顶级函数,而不是构建一组完整的绑定。但如果你以前从未做过这样的事情,那可能不是最好的学习时间。

或者,您可以包装命令行工具。 SP附带nsgmls。还有另一个用perl写的同名的好工具(我认为是http://savannah.nongnu.org/projects/perlsgml/的一部分,但我并不积极。)还有其他几十种工具。

或者,当然,您可以用perl(或C ++)而不是Python来编写整个内容,或仅编写解析层。

答案 1 :(得分:2)

您可以通过剥离封装边界并将其间的所有内容分隔为标头和封装文本,轻松访问 PEM (隐私增强消息,在RFC 1421中指定)的封装文本在第一个空白行。

SGML解析要困难得多。这是一个似乎与document from EDGAR

一起使用的尝试
from lxml import html

PRE_EB = "-----BEGIN PRIVACY-ENHANCED MESSAGE-----"
POST_EB = "-----END PRIVACY-ENHANCED MESSAGE-----"

def unpack_pem(pem_string):
    """Takes a PEM encapsulated message and returns a tuple
    consisting of the header and encapsulated text.
    """

    if not pem_string.startswith(PRE_EB):
        raise ValueError("Invalid PEM encoding; must start with %s"
                         % PRE_EB)
    if not pem_string.strip().endswith(POST_EB):
        raise ValueError("Invalid PEM encoding; must end with %s"
                         % POST_EB)
    msg = pem_string.strip()[len(PRE_EB):-len(POST_EB)]
    header, encapsulated_text = msg.split('\n\n', 1)
    return (header, encapsulated_text)


filename = 'secdoc_htm.txt'
data = open(filename, 'r').read()

header, encapsulated_text = unpack_pem(data)

# Now parse the SGML
root = html.fromstring(encapsulated_text)
document = root.xpath('//document')[0]

metadata = {}
metadata['type'] = document.xpath('//type')[0].text.strip()
metadata['sequence'] = document.xpath('//sequence')[0].text.strip()
metadata['filename'] = document.xpath('//filename')[0].text.strip()

inner_html = document.xpath('//text')[0]

print metadata
print inner_html

结果:

{'filename': 'd371464d10q.htm', 'type': '10-Q', 'sequence': '1'}
<Element text at 80d250c>

答案 2 :(得分:1)

虽然问题定义意味着你想要在第一个'&lt;'开始解析,但我认为这不是一个好主意。那些看起来像PEM标题(如果没有,它们是源自RFC(2)822的其他东西),它们可能有'&lt;'他们中的人物。例如,您可能会在某一天找到Originator-Name: "Foo Bar" <foo@bar.edu>。您正在查看的特定文件可能永远不会,但除非您确定知道这一点,否则最好不要依赖它。

如果你想将它实际解析为带有XML主体的RFC822消息,那很容易:

with file('temp.txt') as f:
  rfc822.Message(f).rewindbody()
  x = etree.parse(f)

但从技术上讲,这对PEM无效(因为PEM的header-body格式实际上是RFC822的一个分支,而不是通过引用合并它)。对于各种其他类似的非常相当的RFC822格式,它甚至可能几乎无效。实际上,你关心的是标题和正文是如何分开的,这是一个非常简单的规则:

with file('temp.txt') as f:
  while f.readline():
    pass
  x = etree.parse(f)

另一种选择是依赖于(明显的)身体始终是SEC-DOCUMENT节点的事实:

with file('temp.txt') as f:
  text = f.read()
body = '<SEC-DOCUMENT>' + text.split('<SEC-DOCUMENT>, 1)[1]
x = etree.fromstring(body)

最后一点说明:通常,一旦看到RFC822标题,就会引发格式是否实际上是RFC2822 +可选MIME的问题。事实上,任何地方都没有内容标题,这意味着你可能在这里安全,但是你可能想要大量收集它们(或者,如果在某处有文件格式的定义,请浏览它)。

答案 3 :(得分:0)

您可以使用BeautifulSoup

>>> from BeautifulSoup import BeautifulStoneSoup
>>> soup = BeautifulStoneSoup(xmldata)
>>> print soup.prettify()
-----BEGIN PRIVACY-ENHANCED MESSAGE-----
Proc-Type: 2001,MIC-CLEAR
Originator-Name: webmaster@www.sec.gov
Originator-Key-Asymmetric:
 MFgwCgYEVQgBAQICAf8DSgAwRwJAW2sNKK9AVtBzYZmr6aGjlWyK3XmZv3dTINen
 TWSM7vrzLADbmYQaionwg5sDW3P6oaM5D3tdezXMm7z1T+B+twIDAQAB
MIC-Info: RSA-MD5,RSA,
 AHxm/u6lqdt8X6gebNqy9afC2kLXg+GVIOlG/Vrrw/dTCPGwM15+hT6AZMfDSvFZ
 YVPEaPjyiqB4rV/GS2lj6A==
<sec-document>
 0001193125-07-200376.txt : 20070913
 <sec-header>
  0001193125-07-200376.hdr.sgml : 20070913
  <acceptance-datetime>
   20070913115715
ACCESSION NUMBER:       0001193125-07-200376
CONFORMED SUBMISSION TYPE:  10-K
PUBLIC DOCUMENT COUNT:      7
CONFORMED PERIOD OF REPORT: 20070630
FILED AS OF DATE:       20070913
DATE AS OF CHANGE:      20070913
  </acceptance-datetime>
 </sec-header>
 <document>
  <type>
   10-K
   <sequence>
    1
    <filename>
     d10k.htm
     <description>
      FORM 10-K
      <text>
       <html>
        <head>
         <title>
          Form 10-K
         </title>
        </head>
        <body bgcolor="WHITE">
         <h5 align="left">
          <a href="#toc">
           Table of Contents
          </a>
         </h5>
        </body>
       </html>
      </text>
     </description>
    </filename>
   </sequence>
  </type>
 </document>
</sec-document>