NewMCP serverRead the guide

Extract Textual Data from EDGAR 10-K Filings Using Python

Open In Colab  View on GitHub

This Python tutorial demonstrates how to extract specific sections of textual data from SEC EDGAR 10-K filings, without relying on regular expressions or custom BeautifulSoup extractors.

The tutorial covers the extraction of any of the 19 10-K filing sections, from "Item 1 - Business" to "Item 7 - MD&A, Management’s Discussion of Financial Results" to "Item 14 - Principal Accountant Fees and Services".

Our approach is using the Extractor API to easily extract sections in either HTML, which includes iXBRL data, or text format. The text format includes the raw text without HTML, but with table starts and ends marked. The tutorial also touches on using pandas to convert extracted financial statements from HTML tables into dataframes.

Although an alternative extraction approach using BeautifulSoup and regular expressions is possible, it can be error-prone since every filing has a unique structure and only about 30% of the content can be extracted using regular expressions. There are several related stackoverflow questions, but none of them provide a solution that can cover the majority of EDGAR filings.

Getting Started

The first step is to install the sec-api Python package which provides access to the ExtractorApi.

1 API_KEY = 'YOUR_API_KEY'
1 !pip install -q sec-api
1 from sec_api import ExtractorApi
2
3 extractorApi = ExtractorApi(API_KEY)

We define the pprint helper function to convert long, single-line text into a multi-line, easily readable format. This function is used to output the extracted text sections in a more readable format, especially when running the code in a Jupyter notebook.

1 # helper function to pretty print long, single-line text to multi-line text
2 def pprint(text, line_length=100):
3 words = text.split(' ')
4 lines = []
5 current_line = ''
6 for word in words:
7 if len(current_line + ' ' + word) <= line_length:
8 current_line += ' ' + word
9 else:
10 lines.append(current_line.strip())
11 current_line = word
12 if current_line:
13 lines.append(current_line.strip())
14 print('\n'.join(lines))

Extract "Item 1 - Business" from 10-K Filings

We will begin by extracting the business section (Item 1) from a 10-K filing using the .get_section(filing_url, section_id, output_type) function. This function allows us to specify the URL of the 10-K filing, the ID of the item section to be extracted, and the desired output type (HTML or text), and returns the extracted section. Refer to the documentation for a complete list of all 10-K item section IDs.

As an example, let's extract Item 1 as text from Tesla's 10-K filing.

1 # URL of Tesla's 10-K filing
2 filing_10_k_url = 'https://www.sec.gov/Archives/edgar/data/1318605/000156459021004599/tsla-10k_20201231.htm'
3
4 # extract text section "Item 1 - Business" from 10-K
5 item_1_text = extractorApi.get_section(filing_10_k_url, '1', 'text')
6
7 print('Extracted Item 1 (Text)')
8 print('-----------------------')
9 pprint(item_1_text[0:1500])
10 print('\n... cut for brevity')
11 print('-----------------------')
Extracted Item 1 (Text) ----------------------- ITEM 1. BUSINESS ##TABLE_END Overview We design, develop, manufacture, sell and lease high-performance fully electric vehicles and energy generation and storage systems, and offer services related to our sustainable energy products. We generally sell our products directly to customers, including through our website and retail locations. We also continue to grow our customer-facing infrastructure through a global network of vehicle service centers, Mobile Service technicians, body shops, Supercharger stations and Destination Chargers to accelerate the widespread adoption of our products. We emphasize performance, attractive styling and the safety of our users and workforce in the design and manufacture of our products and are continuing to develop full self-driving technology for improved safety. We also strive to lower the cost of ownership for our customers through continuous efforts to reduce manufacturing costs and by offering financial services tailored to our products. Our mission to accelerate the world&#8217;s transition to sustainable energy, engineering expertise, vertically integrated business model and focus on user experience differentiate us from other companies. Segment Information We operate as two reportable segments: (i) automotive and (ii) energy generation and storage. The automotive segment includes the design, development, manufacturing, sales and leasing of electric vehicles as well as sales of automotive regulatory credits. Additionally, the ... cut for brevity -----------------------

Now let's see how we can extract the same section "Item 1 - Business" in HTML format.

1 from IPython.display import display, HTML
1 # extract HTML section "Item 1 - Business" from 10-K
2 item_1_html = extractorApi.get_section(filing_10_k_url, '1', 'html')
3
4 print('Extracted Item 1 (HTML)')
5 print('-----------------------')
6 display(HTML(item_1_html[0:3000]))
7 print('\n... cut for brevity')
8 print('-----------------------')

item-1-html-output

Extract "Item 6 - Financial Data" from 10-K Filings

In this example, we'll show you how to extract the "Selected Financial Data" section (Item 6) of a 10-K filing in HTML format. This section includes financial statements in the form of HTML tables. We can then use BeautifulSoup to convert the tables and access the financial data in a pandas dataframe.

1 # extract the HTML version of section "Item 6 - Selected Financial Data"
2 item_6_html = extractorApi.get_section(filing_10_k_url, '6', 'html')
1 print('Extracted Item 6 (HTML)')
2 print('-----------------------')
3 display(HTML(item_6_html[0:150000]))
4 print('\n... cut for brevity')
5 print('-----------------------')

item-6-html-output

Let's now take a look at the actual content of the extracted section. This will display the raw HTML of the section, including all HTML elements and their corresponding style attributes.

1 print('Extracted Content of Item 6 (HTML)')
2 print('-----------------------')
3 pprint(item_6_html[0:1000])
4 print('\n... cut for brevity')
5 print('-----------------------')
Extracted Content of Item 6 (HTML) ----------------------- <span style="font-weight:bold;font-family:Times New Roman Bold;font-size:10pt;font-style:normal;text-transform:none;font-variant: normal;">ITEM 6.</span></p></td> <td valign="top"> <p style="margin-bottom:0pt;margin-top:0pt;font-weight:bold;font-style:normal;text-transform:none;font-variant: normal;font-family:Times New Roman Bold;font-size:10pt;" id="ITEM_6_SELECTED_CONSOLIDATED_FINANCIAL_D">SELECTED CONSOLIDATED FINANCIAL DATA</p></td></tr></table></div> <p style="margin-top:4pt;margin-bottom:0pt;text-indent:4.54%;font-family:Times New Roman;font-size:10pt;font-weight:normal;font-style:normal;text-transform:none;font-variant: normal;">The following selected consolidated financial data should be read in conjunction with &#8220;Management&#8217;s Discussion and Analysis of Financial Condition and Results of Operations&#8221; and the consolidated financial statements and the related notes included elsewhere in this Annual Report on Form 10-K and from the historical consolidated financia ... cut for brevity -----------------------

Convert Financial Statements in an HTML Table to a DataFrame

Next, you can use pandas to convert the financial statements table from section 6 of the filing, which is a HTML table, into a dataframe. Using pd.read_html(item_6_html), pandas can locate and convert all HTML tables into dataframes. Once the financial statements table is extracted, we clean the dataframe by removing empty columns and unnecessary NaN values. This makes it easy to access all financial data, such as total revenue, gross profit, or net income.

1 import pandas as pd
2
3 # read HTML table from a string and convert to dataframe
4 tables = pd.read_html(item_6_html)
5 # first table includes the financial statements
6 df = tables[0]
1 # drop all columns with NaN values except if the first cell is not NaN
2 mask = (df.iloc[1:, :].isna()).all(axis=0)
3 financial_statements = df.drop(df.columns[mask], axis=1).fillna('')
4 print('Consolidated financial statements as dataframe:')
5 financial_statements
Consolidated financial statements as dataframe:
023678101112141516181920
0Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,Year Ended December 31,
1202020202019 (3)2019 (3)2018 (2)2018 (2)201720172016 (1)2016 (1)
2Consolidated Statements of Operations Data:
3Total revenues$31536$24578$21461$11759$7000
4Gross profit$6630$4069$4042$2223$1599
5Income (loss) from operations$1994$(69)$(388)$(1,632)$(667)
6Net income (loss) attributable to common stock...$721$(862)$(976)$(1,962)$(675)
7Net income (loss) per share of common stock at...
8Basic$0.74$(0.98)$(1.14)$(2.37)$(0.94)
9Diluted$0.64$(0.98)$(1.14)$(2.37)$(0.94)
10Weighted average shares used in computing net ...
11Basic933887853829721
12Diluted1083887853829721

Extract Other Text Sections from 10-K Filings

We conclude the tutorial with an overview of all the available parameters for the 10-K extractor.

1 # # extract text sections
2 item_1_text = extractorApi.get_section(filing_10_k_url, '1', 'text')
3 item_1_a_text = extractorApi.get_section(filing_10_k_url, '1A', 'text')
4 item_1_b_text = extractorApi.get_section(filing_10_k_url, '1B', 'text')
5 item_2_text = extractorApi.get_section(filing_10_k_url, '2', 'text')
6 item_3_text = extractorApi.get_section(filing_10_k_url, '3', 'text')
7 item_4_text = extractorApi.get_section(filing_10_k_url, '4', 'text')
8 item_5_text = extractorApi.get_section(filing_10_k_url, '5', 'text')
9 item_6_text = extractorApi.get_section(filing_10_k_url, '6', 'text')
10 item_7_text = extractorApi.get_section(filing_10_k_url, '7', 'text')
11 item_7_a_text = extractorApi.get_section(filing_10_k_url, '7A', 'text')
12 item_8_text = extractorApi.get_section(filing_10_k_url, '8', 'text')
13 item_9_text = extractorApi.get_section(filing_10_k_url, '9', 'text')
14 item_9_a_text = extractorApi.get_section(filing_10_k_url, '9A', 'text')
15 item_9_b_text = extractorApi.get_section(filing_10_k_url, '9B', 'text')
16 item_10_text = extractorApi.get_section(filing_10_k_url, '10', 'text')
17 item_11_text = extractorApi.get_section(filing_10_k_url, '11', 'text')
18 item_12_text = extractorApi.get_section(filing_10_k_url, '12', 'text')
19 item_13_text = extractorApi.get_section(filing_10_k_url, '13', 'text')
20 item_14_text = extractorApi.get_section(filing_10_k_url, '14', 'text')
21 item_15_text = extractorApi.get_section(filing_10_k_url, '15', 'text')
22
23 # # extract HTML sections
24 item_1_html = extractorApi.get_section(filing_10_k_url, '1', 'html')
25 item_1_a_html = extractorApi.get_section(filing_10_k_url, '1A', 'html')
26 item_1_b_html = extractorApi.get_section(filing_10_k_url, '1B', 'html')
27 item_2_html = extractorApi.get_section(filing_10_k_url, '2', 'html')
28 item_3_html = extractorApi.get_section(filing_10_k_url, '3', 'html')
29 item_4_html = extractorApi.get_section(filing_10_k_url, '4', 'html')
30 item_5_html = extractorApi.get_section(filing_10_k_url, '5', 'html')
31 item_6_html = extractorApi.get_section(filing_10_k_url, '6', 'html')
32 item_7_html = extractorApi.get_section(filing_10_k_url, '7', 'html')
33 item_7_a_html = extractorApi.get_section(filing_10_k_url, '7A', 'html')
34 item_8_html = extractorApi.get_section(filing_10_k_url, '8', 'html')
35 item_9_html = extractorApi.get_section(filing_10_k_url, '9', 'html')
36 item_9_a_html = extractorApi.get_section(filing_10_k_url, '9A', 'html')
37 item_9_b_html = extractorApi.get_section(filing_10_k_url, '9B', 'html')
38 item_10_html = extractorApi.get_section(filing_10_k_url, '10', 'html')
39 item_11_html = extractorApi.get_section(filing_10_k_url, '11', 'html')
40 item_12_html = extractorApi.get_section(filing_10_k_url, '12', 'html')
41 item_13_html = extractorApi.get_section(filing_10_k_url, '13', 'html')
42 item_14_html = extractorApi.get_section(filing_10_k_url, '14', 'html')
43 item_15_html = extractorApi.get_section(filing_10_k_url, '15', 'html')