如何高效将FINRA网站文本数据转换为规整的Pandas DataFrame?
20180801|ZYNE|85036|0|202011|Q N
7676 NaN
由于我需要遍历提取FINRA多年的历史数据,希望能在执行`pd.read_csv()`后直接得到干净可用的DataFrame,避免后续对DataFrame进行修改操作,同时保证数据提取的性能。请问如何实现这一目标?当前的技术方案是否合适?是否需要改用BeautifulSoup等其他网络爬虫库? --- ## 回答 ### 问题根源分析 你的代码出现问题主要有两个核心原因: 1. FINRA提供的文件是**竖线(`|`)分隔**的文本文件,而`pd.read_csv()`默认用逗号作为分隔符,导致列识别完全错误。 2. 文件末尾有一行无效的汇总数据(比如示例里的`7676 NaN`),这行数据破坏了DataFrame的结构,引发索引异常。 ### 一步到位的干净读取代码 直接在`pd.read_csv()`中配置正确参数,就能直接得到可用的DataFrame,无需后续修改: ```python import requests import pandas as pd import io url = "http://regsho.finra.org/CNMSshvol20180801.txt" response_data = requests.get(url).content df = pd.read_csv( io.BytesIO(response_data), sep="|", # 指定正确的竖线分隔符 skipfooter=1, # 跳过最后一行无效的汇总数据 dtype={ # 手动指定列类型,避免自动推断的开销和错误 "Date": str, "Symbol": str, "ShortVolume": int, "ShortExemptVolume": int, "TotalVolume": int, "Market": str }, engine="python" # Python引擎支持skipfooter,C引擎不支持该参数 )
参数详解:
sep="|":告诉Pandas用竖线分割列,彻底解决列识别错误问题。skipfooter=1:直接跳过文件末尾的无效汇总行,避免结构异常。dtype:提前指定每列的数据类型,既避免Pandas自动推断时可能出现的类型错误,还能大幅提升读取性能(尤其是批量处理大量文件时)。engine="python":默认的C引擎不支持skipfooter,所以需要切换到Python引擎;如果后续处理超大规模数据,也可以先读取所有行再过滤最后一行,换回C引擎进一步提升速度。
当前技术方案是否合适?需要用BeautifulSoup吗?
完全不需要改用BeautifulSoup!
你的现有方案(requests + pandas)是处理这类场景的最优选择:
- FINRA直接提供的是原始结构化文本文件,不是HTML页面,BeautifulSoup是用来解析HTML/XML结构的,在这里完全用不上,反而会增加不必要的复杂度和性能开销。
requests负责高效下载文件内容,pandas负责快速解析结构化数据,两者搭配是处理批量结构化数据文件的黄金组合。
批量处理多年数据的性能优化方案
如果要遍历多年历史数据,可以结合多线程下载(文件下载是IO密集型任务,多线程能大幅提升效率),同时保持统一的读取参数,确保所有文件都直接输出干净的DataFrame:
from concurrent.futures import ThreadPoolExecutor import datetime def fetch_finra_data(date): date_str = date.strftime("%Y%m%d") url = f"http://regsho.finra.org/CNMSshvol{date_str}.txt" try: response = requests.get(url, timeout=10) response.raise_for_status() # 捕获HTTP错误(比如文件不存在) df = pd.read_csv( io.BytesIO(response.content), sep="|", skipfooter=1, dtype={ "Date": str, "Symbol": str, "ShortVolume": int, "ShortExemptVolume": int, "TotalVolume": int, "Market": str }, engine="python" ) return df except requests.exceptions.RequestException as e: print(f"Failed to fetch data for {date_str}: {e}") return None # 示例:获取2018年8月1日到8月5日的数据 start_date = datetime.date(2018, 8, 1) end_date = datetime.date(2018, 8, 5) date_range = [start_date + datetime.timedelta(days=i) for i in range((end_date - start_date).days + 1)] # 多线程批量下载解析,最大化处理速度 with ThreadPoolExecutor(max_workers=5) as executor: results = list(executor.map(fetch_finra_data, date_range)) # 合并所有有效数据 clean_dfs = [df for df in results if df is not None] combined_df = pd.concat(clean_dfs, ignore_index=True)
这个方案既能保证每一个文件都直接输出干净的DataFrame,又通过多线程最大化了数据提取的性能。
内容的提问来源于stack exchange,提问作者Bython
相关产品推荐
相关产品推荐

