#!/usr/bin/env python3 from bs4 import BeautifulSoup from multiprocessing import Pool import ntpath import os import csv from filing import Filing # Set directories root_dir = os.path.dirname(os.path.abspath(__file__)) filings_dir = os.path.join(root_dir, "filings") clean_filings_dir = os.path.join(root_dir, "cleanedfilings") print("root_dir:", root_dir) print("filings_dir:", filings_dir) print("clean_filings_dir:", clean_filings_dir) def make_batches(lst, batch_size): res = [] batch = [] for i in range(len(lst)): batch.append(lst[i]) if (i + 1) % batch_size == 0: res.append(batch) batch = [] if len(batch) > 0: res.append(batch) return res # Read CSV data into a list of dicts csv_filename = os.path.join(root_dir, "data", "progress.csv") print("Reading CSV data from ", csv_filename) csv_data_rows = [] with open(csv_filename, newline="") as csvfile: try: companyReader = csv.reader(csvfile, delimiter=";") for row in companyReader: csv_data_rows.append({ "ticker": row[0], "cik": row[1], }) except Exception as ex: print("Reading CSV failed!") raise ex # Construct a list of data_objects (dicts) that contain all necessary info # for processing. data_object_lst = [] print("Writing filings to disk and building data objects") for row in csv_data_rows: ticker = row["ticker"] cik = row["cik"] filings = Filing(cik=cik, filing_type="10-k", count=45) input_file_dir = os.path.join(filings_dir, cik, "10-k") input_filenames = [] for r, d, f in os.walk(input_file_dir): for file_name in f: input_filenames.append(os.path.join(r, file_name)) for input_filename in input_filenames: output_filename = os.path.join( clean_filings_dir, ticker, ntpath.basename(input_filename)) data_object = { "ticker": ticker, "cik": cik, "filings_obj": filings, "input_filename": input_filename, "output_filename": output_filename, "input_string": None, "output_string": None, } data_object_lst.append(data_object) # Multiprocessing worker function # Stores result in data objects def process(data_object): input_string = data_object["input_string"] output_string = BeautifulSoup(input_string, "lxml").text data_object["output_string"] = output_string return data_object def process_data_object_batch(batch): # Fetch filings over HTTP for data_object in batch: filings = data_object["filings_obj"] filings.save(filings_dir) del data_object["filings_obj"] # Read files and store content in data objects print("Reading filing data into memory") for data_object in batch: input_filename = data_object["input_filename"] with open(input_filename, "r+") as rawFile: input_file_string = rawFile.read() data_object["input_string"] = input_file_string print("Parsing filing data with lxml") # Multiprocessing, 4 cores with Pool(4) as pool: result_batch = pool.map(process, batch) # No multiprocessing, does the same thing # result_batch = map(process, batch) # Write output to disk from data objects print("Writing filing data to disk") for data_object in result_batch: output_filename = data_object["output_filename"] output_dir = os.path.dirname(output_filename) os.makedirs(output_dir, exist_ok=True) with open(output_filename, "w") as newFile: output_string = data_object["output_string"] newFile.write(output_string) for data_object in result_batch: data_object.clear() # Clean up memory batches = make_batches(data_object_lst, 5) for i, batch in enumerate(batches): print("=== Processing batch #{}".format(i)) process_data_object_batch(batch) print("Done.")