How to Build a Public Company Director Network Tracker
Board interlocks — where directors sit on multiple company boards — reveal hidden power networks in corporate governance. Let's build a Python tool that maps these connections using public SEC data.
Data Sources
- SEC EDGAR — Official filings with director information
- Proxy statements (DEF 14A) — Most detailed director info
- OpenCorporates — Open corporate data
Setting Up
pip install requests beautifulsoup4 pandas networkx matplotlib
Fetching Director Data from SEC EDGAR
import requests
import time
SEC_HEADERS = {"User-Agent": "DirectorTracker research@example.com"}
EDGAR_BASE = "https://efts.sec.gov/LATEST"
def search_company(company_name):
params = {
"q": f'"{company_name}"',
"forms": "DEF 14A",
"dateRange": "custom",
"startdt": "2025-01-01",
"enddt": "2026-12-31"
}
resp = requests.get(f"{EDGAR_BASE}/search-index",
params=params, headers=SEC_HEADERS)
time.sleep(0.2)
if resp.status_code == 200:
return resp.json().get("hits", {}).get("hits", [])
return []
results = search_company("Apple Inc")
print(f"Found {len(results)} proxy filings")
Parsing Proxy Statements for Directors
from bs4 import BeautifulSoup
import re
def extract_directors_from_proxy(filing_url):
params = {
"api_key": "YOUR_SCRAPERAPI_KEY",
"url": filing_url,
"render": "false"
}
resp = requests.get("https://api.scraperapi.com", params=params)
soup = BeautifulSoup(resp.text, "html.parser")
directors = []
for section in soup.find_all(["h2", "h3", "b", "strong"]):
text = section.get_text()
if any(kw in text.lower() for kw in ["director", "nominee", "board member"]):
parent = section.find_parent()
if parent:
names = re.findall(r"([A-Z][a-z]+ (?:[A-Z]\. )?[A-Z][a-z]+)",
parent.get_text())
directors.extend(names)
return list(set(directors))
ScraperAPI handles SEC filing pages reliably.
Building the Board Network
import networkx as nx
def build_board_network(company_directors):
G = nx.Graph()
for company, directors in company_directors.items():
G.add_node(company, type="company")
for director in directors:
G.add_node(director, type="director")
G.add_edge(director, company)
return G
board_data = {
"Apple": ["Tim Cook", "Al Gore", "James Bell", "Andrea Jung"],
"Alphabet": ["Sundar Pichai", "John Hennessy", "Frances Arnold"],
"Microsoft": ["Satya Nadella", "John Thompson", "Emma Walmsley"],
}
G = build_board_network(board_data)
Finding Board Interlocks
def find_interlocks(G):
interlocks = []
directors = [n for n, d in G.nodes(data=True) if d.get("type") == "director"]
for director in directors:
companies = list(G.neighbors(director))
if len(companies) > 1:
interlocks.append({
"director": director,
"companies": companies,
"board_count": len(companies)
})
return sorted(interlocks, key=lambda x: x["board_count"], reverse=True)
for il in find_interlocks(G):
print(f" {il['director']}: {', '.join(il['companies'])}")
Visualization
import matplotlib.pyplot as plt
def visualize_network(G):
plt.figure(figsize=(16, 12))
pos = nx.spring_layout(G, k=1.5, seed=42)
companies = [n for n, d in G.nodes(data=True) if d.get("type") == "company"]
directors = [n for n, d in G.nodes(data=True) if d.get("type") == "director"]
nx.draw_networkx_nodes(G, pos, nodelist=companies,
node_color="#e74c3c", node_size=800)
nx.draw_networkx_nodes(G, pos, nodelist=directors,
node_color="#3498db", node_size=400)
nx.draw_networkx_edges(G, pos, alpha=0.3)
nx.draw_networkx_labels(G, pos, font_size=8)
plt.title("Corporate Board Network")
plt.savefig("board_network.png", dpi=150)
visualize_network(G)
Scale with ThorData proxies and monitor with ScrapeOps.
Key Takeaways
- SEC proxy statements (DEF 14A) are the authoritative source for director data
- NetworkX models bipartite company-director relationships
- Board interlocks reveal hidden corporate influence networks
- Regular monitoring tracks governance changes over time
SEC filings are public records. Respect EDGAR rate limits (10 requests/second max).
Top comments (0)