使用 BeautifulSoup 解析带有子节点的 SEC EDGAR XML 表单数据

问题描述 投票:0回答:2

我正在尝试使用 beautiful soup 和 xml 从 SEC 的 N-PORT-P/A 表格中抓取个人基金持有量。典型的提交如下所示,[链接在此][1],如下所示:

<edgarSubmission xmlns="http://www.sec.gov/edgar/nport" xmlns:com="http://www.sec.gov/edgar/common" xmlns:ncom="http://www.sec.gov/edgar/nportcommon" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<headerData>
<submissionType>NPORT-P/A</submissionType>
<isConfidential>false</isConfidential>
<accessionNumber>0001145549-23-004025</accessionNumber>
<filerInfo>
<filer>
<issuerCredentials>
<cik>0001618627</cik>
<ccc>XXXXXXXX</ccc>
</issuerCredentials>
</filer>
<seriesClassInfo>
<seriesId>S000048029</seriesId>
<classId>C000151492</classId>
</seriesClassInfo>
</filerInfo>
</headerData>
    <formData>
        <genInfo>
        ...
        </genInfo>
        <fundInfo>
        ...
        </fundInfo>
        <invstOrSecs>
            <invstOrSec>
                <name>ARROW BIDCO LLC</name>
                <lei>549300YHZN08M0H3O128</lei>
                <title>Arrow Bidco LLC</title>
                <cusip>042728AA3</cusip>
                <identifiers>
                    <isin value="US042728AA35"/>
                </identifiers>
                <balance>115000.000000000000</balance>
                <units>PA</units>
                <curCd>USD</curCd>
                <valUSD>114754.170000000000</valUSD>
                <pctVal>0.3967552449</pctVal>
                <payoffProfile>Long</payoffProfile>
                <assetCat>DBT</assetCat>
                <issuerCat>CORP</issuerCat>
                <invCountry>US</invCountry>
                <isRestrictedSec>N</isRestrictedSec>
                <fairValLevel>2</fairValLevel>
                <debtSec>
                    <maturityDt>2024-03-15</maturityDt>
                    <couponKind>Fixed</couponKind>
                    <annualizedRt>9.500000000000</annualizedRt>
                    <isDefault>N</isDefault>
                    <areIntrstPmntsInArrs>N</areIntrstPmntsInArrs>
                    <isPaidKind>N</isPaidKind>
                </debtSec>
                <securityLending>
                    <isCashCollateral>N</isCashCollateral>
                    <isNonCashCollateral>N</isNonCashCollateral>
                    <isLoanByFund>N</isLoanByFund>
                </securityLending>
            </invstOrSec>

Arrow Bidco LLC 是投资组合中的债券,其一些特征包含在文件中(CUSIP、CIK、余额、到期日等)。我正在寻找迭代每个单独的证券 (investOrSec) 并收集数据框中每个证券的特征的最佳方法。 我当前使用的代码是:

import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup

header = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36", "X-Requested-With": "XMLHttpRequest"}

n_port_file = requests.get("https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml", headers=header, verify=False)
n_port_file_xml = n_port_file.content
soup = BeautifulSoup(n_port_file_xml,'xml')

names = soup.find_all('name')
lei = soup.find_all('lei')
title = soup.find_all('title')
cusip = soup.find_all('cusip')
....
maturityDt = soup.find_all('maturityDt')
couponKind = soup.find_all('couponKind')
annualizedRt = soup.find_all('annualizedRt')

然后迭代每个列表,根据每行中的值创建一个数据框。

fixed_income_data = []
for i in range(0,len(names)):
    rows = [names[i].get_text(),lei[i].get_text(),
        title[i].get_text(),cusip[i].get_text(),
        balance[i].get_text(),units[i].get_text(),
        pctVal[i].get_text(),payoffProfile[i].get_text(),
        assetCat[i].get_text(),issuerCat[i].get_text(),
        invCountry[i].get_text(),couponKind[i].get_text()
        ]
    fixed_income_data.append(rows)

fixed_income_df = pd.DataFrame(equity_data,columns = ['name',
                         'lei',
                         'title',
                         'cusip',
                         'balance',
                         'units',
                         'pctVal',
                         'payoffProfile',
                         'assetCat',
                         'issuerCat',
                         'invCountry'
                         'maturityDt',
                         'couponKind',
                         'annualizedRt'
                         ], dtype = float)

当包含所有信息时,这种方法效果很好,但通常有一个变量未被考虑在内。表单的一部分可能是空白的,或者发行人类别可能未正确填写,从而导致 IndexError。该投资组合包含我能够解析的 127 种证券,但可能缺少单一证券的年化回报率,从而失去了整齐创建数据框的能力。

此外,对于同时持有固定收益和股票证券的投资组合,股票证券不会返回 DebtSecs 子项的信息。有没有一种方法可以迭代这些数据,同时以最简单的方式清理它?即使为权益证券未引用的 DebtSec 子项添加“NaN”也是有效的响应。任何帮助将不胜感激! [1]:https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml

python xml beautifulsoup portfolio edgar
2个回答
1
投票

在我看来,这是处理问题的最佳方法。一般来说,EDGAR 文件非常难以解析,因此以下内容可能适用于其他文件,也可能不适用于其他文件,即使来自同一文件管理器也是如此。

为了让自己更轻松,因为这是一个 XML 文件,所以您应该使用 xml 解析器和 xpath。鉴于您要创建一个数据框,最合适的工具是 pandas read_xml() 方法。

因为 XML 是嵌套的,所以您需要创建两个不同的数据帧并将它们连接起来(也许其他人对如何处理它有更好的想法)。最后,虽然

read_xml()
可以直接从 url 读取,但在这种情况下,EDGAR 需要使用用户代理,这意味着您还需要使用
requests
库。

所以,大家一起:

#import required libraries
import pandas as pd
import requests

url = 'https://www.sec.gov/Archives/edgar/data/1618627/000114554923004968/primary_doc.xml'
#set headers with a user-agent
headers = {"User-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"}    
req =  requests.get(url, headers=headers)

#define the columns you want to drop (based on the data in your question)
to_drop = ['identifiers', 'curCd','valUSD','isRestrictedSec','fairValLevel','debtSec','securityLending']

#the filing uses namespaces (too complicated to get into here), so you need to define that as well
namespaces = {"nport": "http://www.sec.gov/edgar/nport"}

#create the first df, for the securities which are debt instruments
invest = pd.read_xml(req.text,xpath="//nport:invstOrSec[.//nport:debtSec]",namespaces=namespaces).drop(to_drop, axis=1)

#crete the 2nd df, for the debt details:
debt = pd.read_xml(req.text,xpath="//nport:debtSec",namespaces=namespaces).iloc[:,0:3]

#finally, concatenate the two into one df:
pd.concat([invest, debt], axis=1)

这应该输出您的 126 种债务证券(请原谅格式):

lei     title   cusip   balance     units   pctVal  payoffProfile   assetCat    issuerCat   invCountry  maturityDt  couponKind  annualizedRt
0   ARROW BIDCO LLC     549300YHZN08M0H3O128    Arrow Bidco LLC     042728AA3   115000.00   PA  0.396755    Long    DBT     CORP    US  2024-03-15  Fixed   9.50000
1   CD&R SMOKEY BUYER INC   NaN     CD&R Smokey Buyer Inc   12510CAA9   165000.00   PA  0.505585    Long    DBT     CORP    US  2025-07-15  Fixed   6.75000

然后您可以使用最终的 df、添加或删除列等


0
投票

您可以使用 MIT 许可的 datamule 包来完成此操作,该包可以处理下载和解析。免责声明:我是开发商。

from datamule import Filing, Downloader
from pathlib import Path
import os
downloader = Downloader()
downloader.download(form='NPORT-P',output_dir='NPORT',date=('2001-01-01','2024-11-01'))

os.makedirs('NPORT_json', exist_ok=True)
for file in Path('NPORT').iterdir():
   filing = Filing(str(file), 'NPORT-P')
   filing.parse_filing()
   filing.write_json(f'NPORT_json/{file.name}.json')

您还可以直接访问馆藏数据,因为 Filing() 是一个可迭代对象。

pd.DataFrame(filing)
© www.soinside.com 2019 - 2024. All rights reserved.