Используйте lxml для разбора текстового файла с плохим заголовком в Python

Я хотел бы проанализировать текстовые файлы (хранящиеся локально) с помощью etree lxml. Но все мои файлы (тысячи) имеют заголовки, такие как:

-----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

и "первый"!--3--> не до строки 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

Edit:

FWIW, вот ссылка на .

4 ответов


учитывая, что для этих файлов есть стандарт, можно написать правильный парсер, а не гадать на вещи или надеяться, что beautifulsoup все сделает правильно. Это не означает, что это лучший ответ для вас, но это, безусловно, работа.

согласно стандарту на http://www.sec.gov/info/edgar/pdsdissemspec910.pdf то, что у вас есть (внутри корпуса PEM), является документом SGML, определенным предоставленным DTD. Итак, сначала перейдите на страницы 48-55, извлеките текст там, и сохраните его как, скажем, " Эдгар.шаблон DTD."

первое, что я бы сделал, это установить SP и используйте его инструменты, чтобы убедиться, что документы действительно действительны и поддаются анализу с помощью этого DTD, чтобы убедиться, что вы не тратите кучу времени на то, что не получится.

Python поставляется с проверяющим синтаксическим анализатором SGML, sgmllib. К сожалению, он никогда не был полностью завершен, и он устарел в 2.6-2.7 (и удален в 3.икс.) Но это не значит, что он не работа. Так что попробуйте и убедитесь, что это работает.

Если нет, я не знаю никаких хороших альтернатив в 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.


Вы можете легко добраться до капсулированные текст Пэм (конфиденциальность-улучшенное сообщение, указанное в RFC 1421) путем удаления границ инкапсуляции и разделения всего между ними на заголовок и инкапсулированный текст в первой пустой строке.

разбор SGML намного сложнее. Вот попытка, которая, кажется, работает с документ от Эдгара:

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>

хотя определение проблемы подразумевает, что вы хотите начать разбор с первого"Originator-Name: "Foo Bar" <foo@bar.edu> один день. Возможно, что конкретные файлы, которые вы смотрите, никогда не будут, но если вы не можете знать это наверняка, лучше не полагаться на него.

если вы хотите на самом деле разобрать это как сообщение RFC822 с телом XML, это довольно просто:

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

но технически это недопустимо для PEM (потому что формат заголовка-тела PEM является фактически вилкой RFC822, а не включает его по ссылке). А может быть, даже и нет!--9-->практически действительно для различных других подобных не совсем RFC822 форматов. И действительно, все, что вас волнует, это то, как разделяются заголовки и тела, что является очень простым правилом:

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

другая альтернатива полагаться на (очевидный) факт, что тело всегда является узлом SEC-документа:

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 большую коллекцию из них (или, если где-то есть определение формата файла, пролистайте его).


можно использовать 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>