Python自动化抓取与解析巨潮资讯网年报PDF,构建金融文本分析数据源
简介本资源是一款面向金融数据分析人员、量化研究员及财经专业学生的Python自动化工具专为解决巨潮资讯网上市公司年报PDF难以批量处理的问题而设计支持年报抓取、下载及PDF→TXT格式转换便于后续词频统计与文本挖掘。压缩包共13个文件2.14MB含4个核心Python脚本年报链接抓取、PDF转码、文本分析等、4个XML配置文件IDEA项目结构与检查规则、1个覆盖2004–2023年年报链接的Excel数据源、1个requirements.txt依赖清单及README说明文档结构清晰、模块解耦便于二次开发与参数调优。目前已有201人学习下载。用户可直接运行脚本完成从网页定位、PDF下载到纯文本提取的全流程获得标准化TXT文本并基于此开展关键词提取、情感分析或行业对比研究配套xlsx链接库与分步脚本设计显著降低NLP入门门槛兼顾实用性与教学参考价值。1. 项目概述从数据源到分析起点的自动化桥梁做金融数据分析或者文本挖掘的朋友经常会遇到一个基础但繁琐的痛点如何高效、批量地获取上市公司的官方年报PDF格式并将其转换为纯净的、可供程序直接分析的TXT文本手动一个个去网站下载再用工具转换效率低下且容易出错。这个项目就是为了解决这个“最后一公里”的问题而诞生的。它瞄准的是国内权威的上市公司信息披露平台——巨潮资讯网通过Python脚本实现年报的自动抓取、下载并完成PDF到TXT的格式转换最终输出结构化的文本文件为后续的词频分析、情感分析、主题建模等NLP任务铺平道路。简单来说这就是一个专为金融文本分析研究者、量化投资爱好者或者学生党打造的“数据清洗流水线”入口工具。2. 核心需求与设计思路拆解2.1 需求场景深度剖析这个工具的核心用户画像非常清晰需要进行上市公司年报文本分析的研究人员。他们的需求链条可以拆解为以下几步数据获取需要获取特定公司、特定年份或多个年份的年报。手动在巨潮资讯网搜索、筛选、下载极其耗时。格式转换下载的年报多为PDF格式而主流的文本分析库如Jieba, SnowNLP, Sklearn无法直接处理PDF需要先转换为纯文本。文本预处理基础转换后的文本往往包含大量噪音如页眉、页脚、页码、表格乱码、无关广告信息等需要初步清洗得到相对干净的正文内容。批量化与自动化上述过程需要对成百上千份年报进行操作因此自动化脚本是刚性需求。本工具精准地覆盖了前三个核心环节并将它们串联成一个自动化流程。它不试图做一个大而全的金融数据平台而是聚焦于解决“获取可分析文本”这一具体、高频的痛点。2.2 技术方案选型与考量为什么用Python这是由整个任务链条的技术生态决定的。爬虫与网络请求Python的requests库简单强大BeautifulSoup或lxml用于解析网页结构是处理巨潮资讯网这类动态加载实际多为后端渲染相对友好网站的成熟方案。相比其他语言Python在这方面的库丰富且易上手。PDF解析这是关键且最容易出问题的环节。我们选择了pdfplumber库而不是常见的PyPDF2或pdfminer。原因在于pdfplumber在提取文本时能更好地保持原始布局和顺序对于包含简单表格的年报提取效果更准确乱码和顺序错乱的概率更低。虽然速度可能稍慢但对于准确性要求高的文本分析这是值得的。流程自动化与健壮性需要处理网络超时、PDF损坏、页面结构变动等异常。Python的try-except机制和日志记录模块logging可以很好地构建一个健壮的脚本确保长时间批量运行时单次失败不影响整体任务。后续分析衔接产出TXT文件后可以直接用Python的pandas进行数据管理用jieba进行分词无缝对接后续分析流程形成闭环。整个设计思路遵循“单一职责”和“管道模式”每个模块搜索、下载、转换只做一件事通过参数将它们连接起来使得代码结构清晰也便于后期维护和扩展例如增加其他资讯网站的数据源。3. 核心模块解析与实操要点3.1 巨潮资讯网年报搜索与链接抓取巨潮资讯网的搜索接口通常是基于查询参数的。我们需要模拟浏览器行为构建合法的请求来获取年报列表。关键步骤与代码解析构建查询请求分析巨潮网搜索页面的网络请求。通常需要构造包含以下关键参数的URL或表单数据stock股票代码如000001searchkey公司简称或全称plate指定公告类型如年报、半年报seDate公告日期范围 通过浏览器的开发者工具F12的“网络(Network)”标签查看搜索时产生的XHR或Fetch请求可以找到真实的API接口。解析响应数据巨潮网返回的数据通常是JSON格式里面包含了公告列表。我们需要从中提取出每个公告的标题、链接通常是相对路径或包含唯一ID的链接、以及公告日期。import requests from bs4 import BeautifulSoup import json import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s: %(message)s) def search_annual_reports(stock_code, year): 根据股票代码和年份搜索年报 base_url http://www.cninfo.com.cn/new/hisAnnouncement/query headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: http://www.cninfo.com.cn/new/commonUrl/pageOfSearch?urldisclosure/list/search, } # 注意参数需要根据实际接口调整这里是一个示例 params { stock: f{stock_code},, tabName: fulltext, pageSize: 30, pageNum: 1, seDate: f{year}-01-01~{year}-12-31, # 年份范围 searchkey: 年报, # 搜索关键词 } try: resp requests.post(base_url, headersheaders, paramsparams, timeout10) resp.raise_for_status() # 检查请求是否成功 data resp.json() announcements data.get(announcements, []) report_list [] for ann in announcements: # 筛选出标题中包含“年报”且不是“摘要”的PDF公告 title ann.get(announcementTitle) if 年报 in title and 摘要 not in title and PDF in title: report_list.append({ title: title, url: http://static.cninfo.com.cn/ ann.get(adjunctUrl), # 拼接完整下载链接 date: ann.get(announcementTime) }) logging.info(f找到{len(report_list)}份{year}年年报。) return report_list except requests.exceptions.RequestException as e: logging.error(f搜索请求失败: {e}) return [] except json.JSONDecodeError as e: logging.error(f解析JSON响应失败: {e}) return []注意巨潮资讯网的接口和参数可能会发生变化上述代码中的URL和参数结构仅为示例。在实际操作前必须使用开发者工具对当前网站进行抓包分析以获取最新的接口信息。这是爬虫项目最需要维护的部分。3.2 年报PDF文件下载获取到PDF链接后下载环节相对直接但需要考虑文件命名和避免重复下载。实操要点命名规范建议使用{股票代码}_{年份}_{报告标题}.pdf的格式命名便于管理。注意剔除标题中的非法文件名字符如\/:*?|。断点续传与去重在下载前检查本地是否已存在同名文件可以通过比较文件大小或MD5值来判断是否需重新下载。对于大批量任务这是一个节省时间和流量的好习惯。设置请求头模拟浏览器特别是User-Agent有些网站会对没有User-Agent或类似爬虫的请求进行限制。流式下载使用requests.get(url, streamTrue)来下载大文件可以避免一次性加载到内存更安全可靠。import os from urllib.parse import unquote def download_pdf(report_info, save_dir./reports_pdf): 下载PDF文件 if not os.path.exists(save_dir): os.makedirs(save_dir) # 清理文件名 safe_title .join([c for c in report_info[title] if c not in r\/:*?|]).replace( , _) filename f{report_info.get(stock_code, unknown)}_{report_info[date][:4]}_{safe_title}.pdf filepath os.path.join(save_dir, filename) # 检查文件是否已存在且完整简单通过存在性判断可扩展为检查大小 if os.path.exists(filepath): logging.info(f文件已存在跳过下载: {filename}) return filepath pdf_url report_info[url] try: headers {User-Agent: Mozilla/5.0} resp requests.get(pdf_url, headersheaders, streamTrue, timeout30) resp.raise_for_status() with open(filepath, wb) as f: for chunk in resp.iter_content(chunk_size8192): if chunk: f.write(chunk) logging.info(f下载成功: {filename}) return filepath except Exception as e: logging.error(f下载失败 {pdf_url}: {e}) return None3.3 PDF转TXT核心难点与解决方案这是工具最核心也最容易踩坑的部分。上市公司的PDF年报通常是扫描版图片PDF或文本版。我们的工具主要针对文本版PDF但也要考虑对扫描版的兼容性提示。使用pdfplumber进行文本提取import pdfplumber def pdf_to_txt(pdf_path, txt_save_dir./reports_txt): 将PDF文件转换为TXT文本 if not os.path.exists(txt_save_dir): os.makedirs(txt_save_dir) txt_filename os.path.splitext(os.path.basename(pdf_path))[0] .txt txt_path os.path.join(txt_save_dir, txt_filename) all_text [] try: with pdfplumber.open(pdf_path) as pdf: for page_num, page in enumerate(pdf.pages): # 提取页面文本 text page.extract_text() if text: # 可在此处添加简单的每页标记便于定位 all_text.append(f--- Page {page_num1} ---\n{text}\n) else: # 如果extract_text()返回空可能是扫描件记录日志 logging.warning(f{pdf_path} 第{page_num1}页可能为扫描图像未提取到文本。) all_text.append(f--- Page {page_num1} [扫描图像文本未提取] ---\n) # 将所有页面文本写入文件 with open(txt_path, w, encodingutf-8) as f: f.writelines(all_text) logging.info(f转换完成: {txt_path}) return txt_path except Exception as e: logging.error(f转换PDF失败 {pdf_path}: {e}) return None关键注意事项与心得编码问题确保读写文件时使用utf-8编码避免中文乱码。布局保留pdfplumber的extract_text()方法会尝试保持文本的阅读顺序。但对于复杂多栏排版顺序可能错乱。可以尝试page.extract_text(layoutTrue)或使用page.extract_words()获取单词和坐标后自行排序但这会复杂很多。对于年报大多数正文部分顺序提取是可接受的。非文本内容处理表格、图表中的文字提取效果不佳。pdfplumber提供了page.extract_tables()来提取表格数据但对于分析而言通常选择忽略或仅做简单记录。我们的首要目标是获取连续的叙述性文本。扫描件处理如果PDF是扫描图片上述方法将失效。解决方案是引入OCR光学字符识别如pytesseract库配合PIL或pdf2image先将PDF页面转为图片再进行识别。但这会极大增加处理时间和复杂度且准确性依赖图片质量。在工具设计上建议先尝试文本提取如果发现大量页面无文本则记录日志并提示用户该文件可能需要OCR处理。性能考量处理上百页的PDF时pdfplumber可能较慢。可以考虑只提取前N页如管理层讨论与分析部分以加快速度这需要根据具体分析需求调整。4. 工具集成与完整工作流实现将上述模块整合并添加一些提升用户体验的功能形成一个完整的命令行工具。4.1 主程序流程设计主程序逻辑应该清晰解析用户输入如股票代码列表、年份范围。遍历股票代码调用搜索函数获取年报列表。遍历年报列表下载PDF文件。遍历下载的PDF文件转换为TXT文本。在整个过程中记录详细的日志包括成功、失败、跳过等信息。# main.py 示例框架 import argparse import time from your_module import search_annual_reports, download_pdf, pdf_to_txt # 假设功能在单独模块 def main(): parser argparse.ArgumentParser(description巨潮资讯网年报抓取与转换工具) parser.add_argument(-s, --stock, requiredTrue, help股票代码多个用逗号分隔如 000001,000002) parser.add_argument(-y, --year, requiredTrue, help年份如 2022 或 2020-2023) parser.add_argument(--pdf-dir, default./reports_pdf, helpPDF保存目录) parser.add_argument(--txt-dir, default./reports_txt, helpTXT保存目录) parser.add_argument(--skip-downloaded, actionstore_true, help跳过已下载的PDF) args parser.parse_args() stock_codes [code.strip() for code in args.stock.split(,)] # 解析年份范围 if - in args.year: start_year, end_year map(int, args.year.split(-)) years range(start_year, end_year 1) else: years [int(args.year)] for stock_code in stock_codes: for year in years: logging.info(f开始处理 {stock_code} {year}...) # 1. 搜索 reports search_annual_reports(stock_code, year) if not reports: logging.warning(f未找到{stock_code} {year}年的年报。) continue for report in reports: report[stock_code] stock_code # 补充信息 # 2. 下载 pdf_path download_pdf(report, args.pdf_dir) if not pdf_path: continue # 下载失败跳过此报告 # 3. 转换 txt_path pdf_to_txt(pdf_path, args.txt_dir) # 可选添加短暂延时避免请求过快 time.sleep(1) logging.info(所有任务处理完毕。) if __name__ __main__: main()4.2 配置与依赖管理为了便于他人使用需要提供清晰的requirements.txt文件。# requirements.txt requests2.28.0 beautifulsoup44.11.0 pdfplumber0.9.0 lxml4.9.0用户可以通过pip install -r requirements.txt一键安装所有依赖。4.3 输出结果与后续分析接口工具运行后文件目录结构如下project/ ├── main.py ├── requirements.txt ├── reports_pdf/ │ ├── 000001_2022_年度报告.pdf │ └── 000002_2022_年度报告.pdf └── reports_txt/ ├── 000001_2022_年度报告.txt └── 000002_2022_年度报告.txt生成的TXT文件已经去除了PDF的复杂格式是包含换行符和分页标记的纯文本。接下来用户就可以使用任何文本分析工具进行处理了。例如一个简单的词频分析入门import jieba from collections import Counter import re def simple_word_freq(txt_path, top_n50): with open(txt_path, r, encodingutf-8) as f: text f.read() # 移除分页标记等无关内容 text_clean re.sub(r--- Page \d ---, , text) # 使用jieba分词 words jieba.lcut(text_clean) # 过滤掉纯符号、单个字和停用词这里简单过滤长度 filtered_words [w for w in words if len(w) 1 and not re.match(r^\W$, w)] word_counts Counter(filtered_words) return word_counts.most_common(top_n) # 对单个文件进行分析 freq simple_word_freq(./reports_txt/000001_2022_年度报告.txt) print(freq[:20]) # 打印前20个高频词5. 常见问题、排查技巧与优化建议实录在实际运行过程中你肯定会遇到各种各样的问题。下面是我在开发和多次使用类似工具中积累的一些“坑”和解决方案。5.1 网络请求与反爬虫问题问题请求失败返回403、404或获取不到数据。排查检查User-Agent确保请求头中包含了常见的浏览器User-Agent字符串。检查Referer有些网站会验证Referer需要设置为目标网站的合法来源页。使用Session对于需要保持会话的网站使用requests.Session()对象。分析接口变化巨潮网的API接口地址和参数名可能更新。务必定期使用浏览器开发者工具重新抓包分析这是维护爬虫的必修课。频率限制在循环请求中添加time.sleep(random.uniform(1, 3))来模拟人工操作间隔避免IP被临时封锁。5.2 PDF解析与文本提取乱码问题提取出的TXT文本乱码、顺序错乱、或大量空白。排查与解决确认PDF类型用Adobe Reader等软件打开PDF看能否正常选中文字。如果不能则是扫描件需要OCR方案。尝试不同库如果pdfplumber效果不好可以尝试pymupdf又名fitz它的文本提取速度极快有时对某些PDF格式兼容更好。import fitz # PyMuPDF doc fitz.open(pdf_path) text for page in doc: text page.get_text()调整提取参数pdfplumber的extract_text()有layout,x_tolerance,y_tolerance等参数微调它们可能改善对复杂版式的提取效果。处理加密PDF极少情况下年报PDF可能有简单密码保护如000000pdfplumber打开时需要password参数。5.3 文件管理与错误处理问题脚本中途崩溃或重复下载了相同文件。建议完善的日志使用Python的logging模块记录INFO、WARNING、ERROR等级别的日志并输出到文件便于事后排查。持久化记录可以将已成功处理的股票代码、年份、报告标题记录在一个JSON文件或轻量级数据库如sqlite3中。每次运行时先查询记录避免重复工作。异常捕获与继续在遍历股票和报告的循环内部做好异常捕获确保一个报告的失败不会导致整个程序停止。try: # 下载和转换操作 except Exception as e: logging.error(f处理报告{report_title}时发生未知错误: {e}, exc_infoTrue) # exc_info打印堆栈 continue # 继续下一个报告5.4 性能优化建议并发下载对于大量文件可以使用concurrent.futures.ThreadPoolExecutor实现多线程下载显著提升I/O密集型任务的效率。但要注意对目标网站的压力控制并发数如3-5个线程。增量更新设计脚本时考虑支持“增量模式”只下载和处理新的或之前失败的报告。缓存页面对于搜索请求的结果可以考虑在一定时间内如1小时缓存到本地避免频繁请求相同内容。5.5 扩展性思考这个工具是一个很好的起点你可以根据需求轻松扩展多数据源将搜索模块抽象成接口可以适配其他信息披露网站如上海证券交易所、深圳证券交易所官网。结构化信息提取在PDF转TXT后可以使用正则表达式或更高级的NLP模型尝试提取特定章节如“主要财务指标”、“董事、监事、高级管理人员情况”形成结构化数据。集成OCR增加一个分支流程当检测到PDF为扫描件时自动调用Tesseract-OCR进行识别实现全类型PDF覆盖。图形界面使用PyQt或tkinter为不熟悉命令行的用户制作一个简单的GUI方便输入参数和查看进度。最后我想分享一点个人体会这类数据获取和预处理工具的价值在于将研究者从重复、机械的劳动中解放出来把精力集中在更有创造性的分析工作上。它的稳定性比华丽的功能更重要。因此在开发时务必把日志、错误处理和健壮性放在首位。一开始可能只需要处理几十份报告但当你的分析扩展到全市场、十年维度时一个能稳定运行数小时甚至数天的脚本才是真正可靠的生产力工具。在运行大规模任务前先用小样本如2-3家公司1-2个年份充分测试所有环节确保流程畅通无阻。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →