9

我想用 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,这是文件的链接。

4

4 回答 4

6

鉴于这些文件有一个标准,可以编写一个适当的解析器,而不是猜测事情,或者希望 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/whatever 或使用 ctypes 包装。只需要封装顶层函数,不需要构建一套完整的绑定。但是,如果您以前从未做过类似的事情,那么这可能不是学习的最佳时机。

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

或者,当然,您可以用 perl(或 C++)而不是 Python 编写整个事情,或者只编写解析层。

于 2012-09-14T19:51:30.507 回答
4

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

SGML 解析要困难得多。这是一个似乎适用于 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>
于 2012-09-13T21:19:40.730 回答
1

尽管问题定义暗示您想从第一个“<”开始解析,但我认为这不是一个好主意。这些看起来像 PEM 标头(如果不是,它们是从 RFC(2)822 派生的其他东西),并且它们可能包含“<”字符。例如,您可能会发现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 的问题。任何地方都没有内容标题这一事实意味着您在这里可能是安全的,但您可能想要 grep 大量的它们(或者,如果某处有文件格式的定义,则略过它)。

于 2012-09-13T20:48:34.877 回答
1

您可以为此使用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>
于 2012-09-13T20:55:08.123 回答