资讯动态

Python Selenium实战:金融数据爬虫构建与反爬策略详解

发布时间:2026/9/3 1:11:16 来源:尧图企业网站定制
简介本资源是一套面向金融数据分析从业者与NLP工程师的实战型数据采集工具包聚焦于上市公司财务报表结构化数据获取这一高频刚需场景专为data2text任务如自动生成财报摘要、分析报告提供高质量原始数据支撑。压缩包共9个文件375KB含5个文本配置与日志文件如stocks.txt、failed_stocks.txt、1个核心Python爬虫脚本main.py、1个Markdown说明文档README.md、1个示例可视化PNGdata-example.png及1个Word附赠资料含扩展应用提示。已有55人学习下载内容覆盖Selenium驱动浏览器自动化控制、同花顺网站动态渲染页面解析、Cookie自动轮换机制与UA/行为指纹级反爬伪装策略完整实现从目标URL调度、登录态维持到财务指标表格精准提取的全流程闭环。用户可直接部署运行快速获得清洗后的结构化财报数据并基于附赠文档延伸至自然语言生成环节。1. 项目缘起从“数据饥渴”到“精准投喂”的实战需求在金融科技和量化分析领域数据是绝对的血液。无论是构建预测模型、进行风险分析还是完成像“data2text”数据到文本生成这样的前沿任务第一步永远是获取高质量、结构化的原始数据。我最近在做一个关于上市公司财报文本自动生成的项目核心就是需要海量的、标准化的财务报表数据作为“饲料”。理想很丰满但现实是骨感的——公开的金融数据API要么收费昂贵要么数据维度不全而像同花顺、东方财富这类财经门户网站虽然数据丰富直观但并没有提供友好的批量下载接口。这就是典型的“数据饥渴”场景。手动复制粘贴面对成百上千家上市公司、动辄十几年的财报数据这无异于天方夜谭。于是自动化数据采集或者说网络爬虫就成了唯一可行的技术路径。但金融网站的反爬虫机制通常比较严密简单的requests库请求很容易被识别和封锁。这时Selenium这类浏览器自动化工具的优势就凸显出来了——它能模拟真实用户的操作从打开浏览器、登录、翻页到点击下载几乎可以复刻人类的所有交互行为从而有效绕过许多基于行为识别的反爬策略。本项目正是基于这样的实战需求诞生的利用Python的Selenium库自动化抓取同花顺网站上的上市公司财务报表数据并完成初步的清洗与格式化为后续的data2text模型训练提供可直接使用的结构化数据集。整个方案不仅实现了核心的抓取功能更重点解决了金融数据抓取中的两大痛点Cookie会话维持与更新以及对抗反爬虫的浏览器伪装策略。下面我就把这套从零搭建、踩过无数坑的完整方案拆解给你。2. 技术选型深析为什么是Selenium而不是Requests或Playwright面对一个数据采集任务工具选型是第一步也是决定后续开发效率和成功率的基石。市面上主流的Python爬虫方案大致有三类Requests/BeautifulSoup静态爬虫、Selenium浏览器自动化、以及新兴的Playwright/Pyppeteer。为什么在这个项目里我坚定地选择了Selenium2.1 静态爬虫Requests的局限性Requests库效率极高直接发起HTTP请求获取HTML再配合BeautifulSoup或lxml进行解析是爬虫的经典组合。对于同花顺这类网站它行不通的核心原因在于数据渲染方式。现代网站大量使用JavaScript动态加载内容财报数据表格很可能是在页面加载完成后通过AJAX请求获取JSON数据再由前端JS渲染到页面上的。使用Requests抓取到的初始HTML源码只是一个“空壳”里面并没有我们需要的表格数据。虽然可以通过分析网络请求找到那个返回数据的API接口进行直接请求即“抓包”但这存在几个问题接口参数复杂金融数据接口的查询参数往往包含加密令牌token、时间戳、复杂编码的股票代码等逆向分析成本高。接口稳定性差这类内部API变动频繁且没有公开文档一旦网站前端升级接口可能失效或变更。反爬升级针对API接口的反爬如请求头校验、频率限制、参数签名可能比网页端更严格。因此对于JavaScript重度渲染的网站静态爬虫要么无能为力要么需要投入大量精力进行逆向工程维护成本很高。2.2 Selenium的不可替代性Selenium的核心思想是“所见即所得”。它通过WebDriver控制一个真实的浏览器如Chrome, Firefox实例等待页面完全加载、所有JS执行完毕后再获取完整的DOM内容。这就完美解决了动态渲染的问题。对于同花顺财报页面Selenium可以模拟点击点击“财务报表”选项卡选择“资产负债表”、“利润表”等。模拟选择选择报告期如“年报”、“季报”、时间范围。获取完整数据在页面所有交互完成后直接获取渲染好的表格HTML简单解析即可。更重要的是Selenium模拟的是真实用户行为。它会产生正常的浏览器指纹、加载所有资源图片、CSS、JS、管理Cookie和Session。这使得它能够相对自然地绕过那些基于“访问者是否使用浏览器”的初级反爬检测。2.3 与Playwright的对比Playwright是微软开源的新一代浏览器自动化工具它更快、API更现代并且原生支持多浏览器和无头模式。它确实是Selenium的有力竞争者。但在本项目语境下我选择Selenium基于以下几点考量生态与资料成熟度Selenium历史悠久社区庞大遇到任何稀奇古怪的问题几乎都能找到解决方案或讨论。这对于需要快速稳定上线的生产级数据采集项目至关重要。伪装成熟度围绕Selenium的“反反爬虫”技巧如隐藏WebDriver特征、使用特定浏览器配置已经形成了非常成熟的实践方案有大量现成的代码片段和扩展库如undetected-chromedriver可供参考或直接使用。项目依赖与可控性Selenium的核心驱动是浏览器本身只要浏览器稳定它就很稳定。Playwright虽然打包了浏览器但作为一个较新的项目在复杂网络环境或特定系统上可能遇到未知兼容性问题。注意这并不意味着Playwright不好。对于全新的项目如果你追求极致的性能和现代APIPlaywright值得尝试。但本项目的目标是稳定、可靠地长期采集金融数据Selenium经过时间检验的稳定性和丰富的“反反爬”生态是更优选择。3. 环境搭建与核心配置打造一个“隐形”的浏览器工欲善其事必先利其器。使用Selenium的第一步不是写代码而是配置一个足够“像人”的浏览器环境。一个裸奔的Selenium驱动浏览器会有很多特征被网站检测到从而被识别为自动化脚本。3.1 基础环境准备首先安装必要的Python库pip install selenium pandas openpyxl webdriver-managerselenium: 核心自动化库。pandas: 数据处理和分析神器用于将抓取的表格数据转换为DataFrame并保存。openpyxl: 支持pandas将数据写入Excel的.xlsx格式。webdriver-manager:强烈推荐它可以自动下载和管理与本地浏览器版本匹配的ChromeDriver或GeckoDriver省去手动查找和配置驱动路径的麻烦。3.2 Chrome浏览器的高级配置反检测核心直接使用webdriver.Chrome()会启动一个带有明显自动化特征的浏览器。我们需要通过ChromeOptions添加一系列参数来消除这些特征。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options def create_stealth_driver(): chrome_options Options() # 1. 基础隐身不显示“Chrome正受到自动测试软件控制” chrome_options.add_argument(--disable-blink-featuresAutomationControlled) # 2. 禁用自动化标志至关重要 chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 3. 修改navigator.webdriver属性通过CDP # 这部分需要在driver创建后执行见后续代码 # 4. 使用常规用户数据目录非默认临时目录 # 这样可以保存登录Cookie避免每次都需要登录 # chrome_options.add_argument(fuser-data-dir{os.path.expanduser(~)}/ChromeProfileForCrawler) # 注意首次使用需手动登录一次后续即可自动保持登录状态 # 5. 其他实用参数 chrome_options.add_argument(--no-sandbox) # 在Linux服务器或无GUI环境下常用 chrome_options.add_argument(--disable-dev-shm-usage) # 解决共享内存问题 chrome_options.add_argument(--start-maximized) # 启动时最大化更像真人操作 # chrome_options.add_argument(--headless) # 无头模式不显示GUI适合服务器。但有些反爬能检测无头模式慎用。 # 6. 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 7. 执行CDP命令覆盖navigator.webdriver属性 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) # 8. 也可以移除其他自动化特征如“window.chrome” driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(window, chrome, { get: () undefined }); }) return driver3.3 关于无头模式Headless的取舍无头模式不显示浏览器界面节省资源适合服务器环境。但越来越多的反爬系统能够检测无头浏览器。对于同花顺这类重要网站我建议在开发调试阶段使用有头模式确保所有流程跑通。在部署到生产环境时可以尝试无头但如果频繁被屏蔽则需要回退到有头模式并可能借助Xvfb虚拟显示缓冲区在无GUI的服务器上运行。3.4 Cookie的持久化与智能更新Cookie是维持登录状态的关键。上述代码中注释掉的user-data-dir参数就是方法之一指定一个固定的用户数据目录Selenium会使用该目录下的Chrome配置文件其中包含了Cookie、本地存储等。你只需要用这个配置的浏览器手动登录一次同花顺以后启动就会自动保持登录。但这种方法有个缺点Cookie会过期。金融网站出于安全考虑登录会话有效期可能只有几小时或几天。因此我们的爬虫必须具备Cookie失效检测与自动更新能力。一个更健壮的策略是尝试使用现有Cookie访问用保存的Cookie可以从user-data-dir读取或序列化到文件初始化请求访问一个需要登录的页面如“个人中心”。检测登录状态检查页面元素或URL判断是否跳转到了登录页。自动登录如果未登录则调用自动登录函数。这需要你事先准备好账号密码并使用Selenium模拟输入和点击登录按钮。务必注意账号安全不要将密码硬编码在代码中应使用环境变量或加密配置文件。保存新Cookie登录成功后获取新的Cookie并持久化保存。这套逻辑增加了程序的复杂性但保证了长时间运行如爬取全市场股票多年数据的稳定性。4. 同花顺财报页面结构与抓取策略解析在开始编码前我们必须像侦探一样仔细分析目标网站的结构和行为逻辑。同花顺的上市公司财报页面例如http://basic.10jqka.com.cn/600519/finance.html是典型的单页应用SPA风格数据通过异步加载。4.1 页面交互流程分析导航到财报页输入股票代码进入公司主页再点击“财务分析”或类似标签。选择报表类型页面内通常有选项卡如“资产负债表”、“利润表”、“现金流量表”。点击后页面主体区域会通过AJAX加载对应的表格。选择时间范围有下拉菜单或按钮可以选择报告期类型年报、季报和具体年份、季度。数据渲染选择完成后表格数据会动态填充。表格通常是标准的HTMLtable但可能包含复杂的表头合并单元格和大量数据行。4.2 数据定位策略Selenium提供了多种元素定位方式ID, Name, Class Name, XPath, CSS Selector。对于结构稳定的生产级爬虫CSS Selector和XPath是主力。CSS Selector速度快语法简洁适合基于class、id、属性的简单定位。例如定位所有报表选项卡driver.find_elements(By.CSS_SELECTOR, .finance-nav li)。XPath功能强大可以基于层级关系、文本内容进行定位在复杂结构中更灵活。例如定位包含“资产负债表”文本的按钮driver.find_element(By.XPATH, //button[contains(text(), 资产负债表)])。一个关键技巧等待。动态加载意味着元素不是立即可用的。必须使用“显式等待”Explicit Wait让Selenium等待特定条件成立后再进行操作这是编写稳定爬虫的基石。from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 错误做法直接查找可能因元素未加载而抛出异常 # table driver.find_element(By.ID, myTable) # 正确做法显式等待 try: # 等待最多10秒直到ID为ProfitStatementNewTable0的表格出现 wait WebDriverWait(driver, 10) table_element wait.until(EC.presence_of_element_located((By.ID, ProfitStatementNewTable0))) print(利润表表格加载完成) except TimeoutException: print(等待表格超时可能页面结构已变化或加载失败) # 这里可以加入重试或错误处理逻辑4.3 应对页面变化的策略网站前端可能会改版。我们不能让爬虫因为一个按钮的class名变了就彻底崩溃。策略如下多层定位不要依赖单一的、可能变化的属性如一个特定的class名。尝试使用更稳定的属性组合或者通过父级容器相对定位。文本内容定位像“资产负债表”、“年报”这类文本内容是相对稳定的使用XPath的text()函数定位是不错的选择。定期维护与监控将关键元素的定位器Selector集中放在配置文件中。爬虫运行时可以加入健康检查尝试访问几个已知页面检查关键元素是否存在如果失败则发出警报。容错与重试对于关键操作如点击选项卡如果失败元素未找到、点击无效可以加入重试机制并记录日志。5. 核心抓取流程代码实现与详解理论分析完毕现在进入实战编码环节。我们将把整个抓取过程模块化构建一个健壮、可维护的爬虫。5.1 主控流程设计整个抓取任务可以抽象为以下步骤初始化创建伪装好的浏览器驱动加载Cookie或尝试自动登录。股票代码遍历读取一个股票代码列表如从文件或数据库。单股票处理 a. 访问该股票的财报主页。 b. 循环处理每一种需要的报表类型资产负债表、利润表、现金流量表。 c. 对于每种报表循环选择需要的报告期如近5年的年报。 d. 抓取当前报表和报告期下的数据表格。 e. 解析表格HTML转换为结构化数据如pandas DataFrame。 f. 保存数据到文件CSV或Excel最好按股票、报表类型、年份组织。异常处理与日志任何步骤失败都应记录错误股票代码、错误信息并尽可能继续处理下一个股票或报表而不是让整个程序崩溃。资源清理所有任务完成后关闭浏览器驱动。5.2 代码模块拆解模块一驱动管理与登录import pickle import os import time from selenium.common.exceptions import TimeoutException, NoSuchElementException class FinanceCrawler: def __init__(self, headlessFalse): self.driver create_stealth_driver() # 使用前面定义的函数 self.cookie_file session_cookies.pkl self.login_url https://stockpage.10jqka.com.cn/ self.is_logged_in False def load_cookies(self): 从文件加载Cookie并添加到驱动 if os.path.exists(self.cookie_file): with open(self.cookie_file, rb) as f: cookies pickle.load(f) for cookie in cookies: # 添加前可以删除expiry字段避免格式错误 if expiry in cookie: # 检查cookie是否过期 if cookie[expiry] time.time(): print(Cookie已过期需要重新登录) return False self.driver.add_cookie(cookie) print(Cookie加载成功) return True return False def save_cookies(self): 将当前驱动的Cookie保存到文件 cookies self.driver.get_cookies() with open(self.cookie_file, wb) as f: pickle.dump(cookies, f) print(Cookie保存成功) def check_login_status(self): 检查当前是否已登录访问一个需要登录的页面验证 self.driver.get(https://my.10jqka.com.cn/) # 假设这是个人中心页 time.sleep(2) if login in self.driver.current_url or 登录 in self.driver.page_source: return False return True def login(self, username, password): 执行自动登录需谨慎确保符合网站条款 print(开始执行登录...) self.driver.get(self.login_url) # 注意这里需要根据同花顺实际登录页面的HTML结构来定位元素 # 以下为示例实际元素ID/Class需要你自行查看页面源码确定 try: wait WebDriverWait(self.driver, 10) # 假设有账号密码输入框和登录按钮 user_input wait.until(EC.presence_of_element_located((By.ID, username))) pass_input self.driver.find_element(By.ID, password) login_btn self.driver.find_element(By.ID, loginBtn) user_input.send_keys(username) pass_input.send_keys(password) login_btn.click() # 等待登录成功跳转 time.sleep(5) if self.check_login_status(): self.save_cookies() self.is_logged_in True print(登录成功) return True else: print(登录可能失败) return False except Exception as e: print(f登录过程发生异常: {e}) return False模块二报表数据抓取器这是最核心的部分负责与页面交互并提取数据。def fetch_financial_report(self, stock_code, report_typebalance, year2023, report_periodyear): 抓取指定股票、报表类型、年份和报告期的数据 :param stock_code: 股票代码如 600519 :param report_type: 报表类型balance(资产负债表), income(利润表), cashflow(现金流量表) :param year: 年份 :param report_period: 报告期year(年报), quarter1(一季报)等 :return: pandas DataFrame 或 None # 1. 构建并访问该股票的财报页面 base_url fhttp://basic.10jqka.com.cn/{stock_code}/finance.html self.driver.get(base_url) time.sleep(3) # 初始页面加载等待 # 2. 映射报表类型到页面操作 report_map { balance: {tab_text: 资产负债表, table_id_prefix: BalanceSheetNewTable}, income: {tab_text: 利润表, table_id_prefix: ProfitStatementNewTable}, cashflow: {tab_text: 现金流量表, table_id_prefix: CashFlowNewTable} } if report_type not in report_map: print(f不支持的报表类型: {report_type}) return None tab_info report_map[report_type] # 3. 点击对应的报表选项卡 try: # 使用XPath通过文本内容定位选项卡 tab_xpath f//div[contains(class, finance-nav)]//a[contains(text(), {tab_info[tab_text]})] tab_element WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, tab_xpath)) ) # 有时元素被遮挡用JavaScript直接点击更可靠 self.driver.execute_script(arguments[0].click();, tab_element) print(f已切换到{tab_info[tab_text]}) time.sleep(2) # 等待选项卡内容加载 except TimeoutException: print(f找不到或无法点击{tab_info[tab_text]}选项卡) return None # 4. 选择报告期例如选择‘2023年年报’ # 这里逻辑较复杂因为同花顺的日期选择器可能是下拉菜单或一组按钮 # 需要你具体分析页面。以下是一个假设性的示例 try: # 假设有一个下拉框选择年份 year_select Select(self.driver.find_element(By.ID, year_select)) year_select.select_by_visible_text(str(year)) time.sleep(1) # 假设有按钮选择报告期类型 period_btn_xpath f//div[classperiod-selector]//button[contains(text(), {年报 if report_periodyear else 季报})] period_btn self.driver.find_element(By.XPATH, period_btn_xpath) period_btn.click() time.sleep(2) # 等待数据重新加载 except Exception as e: print(f设置报告期时出错: {e}) # 有时网站默认就是最新年报可以不进行操作 # 5. 定位并等待数据表格出现 # 表格的ID可能是动态的如BalanceSheetNewTable0需要观察规律 # 这里我们尝试用前缀匹配 try: wait WebDriverWait(self.driver, 15) # 使用XPath的starts-with函数匹配ID前缀 table_element wait.until( EC.presence_of_element_located((By.XPATH, f//table[starts-with(id, {tab_info[table_id_prefix]})])) ) except TimeoutException: print(f等待{table_info[tab_text]}数据表格超时) # 可以尝试截图保存现场便于调试 self.driver.save_screenshot(ferror_{stock_code}_{report_type}.png) return None # 6. 获取表格HTML并解析 table_html table_element.get_attribute(outerHTML) # 使用pandas的read_html直接解析HTML表格非常方便 try: df_list pd.read_html(table_html, flavorhtml5lib) # html5lib解析器容错性好 if df_list: df df_list[0] # 通常第一个表格就是我们需要的数据 # 添加元数据列便于后续识别 df[股票代码] stock_code df[报表类型] report_type df[年份] year df[报告期] report_period df[抓取时间] pd.Timestamp.now() print(f成功抓取 {stock_code} {year}年{report_type}数据形状: {df.shape}) return df else: print(f解析表格HTML失败未找到表格) return None except Exception as e: print(f使用pandas解析表格时出错: {e}) return None模块三主循环与数据存储def crawl_batch_stocks(self, stock_list, report_types, years): 批量爬取多只股票的数据 all_data [] for stock_code in stock_list: print(f\n开始处理股票: {stock_code}) for rpt_type in report_types: for year in years: print(f 正在抓取 {rpt_type} {year}...) df self.fetch_financial_report(stock_code, rpt_type, year) if df is not None: all_data.append(df) else: print(f 抓取失败跳过) # 礼貌性延迟避免请求过快 time.sleep(random.uniform(1, 3)) # 合并所有DataFrame并保存 if all_data: final_df pd.concat(all_data, ignore_indexTrue) # 保存到Excel每个报表类型一个sheet with pd.ExcelWriter(ffinancial_reports_{pd.Timestamp.now().strftime(%Y%m%d_%H%M%S)}.xlsx) as writer: for rpt_type in report_types: type_df final_df[final_df[报表类型] rpt_type] if not type_df.empty: # 简单清理列名原始表格可能有合并表头带来的多层索引 type_df.columns [str(col).strip() for col in type_df.columns] type_df.to_excel(writer, sheet_namerpt_type[:31], indexFalse) # Excel sheet名最多31字符 print(f所有数据已保存至Excel文件) return final_df else: print(未抓取到任何有效数据) return None def close(self): 关闭浏览器驱动 if self.driver: self.driver.quit() print(浏览器驱动已关闭)6. 反爬虫对抗进阶与稳定性优化即使使用了伪装浏览器面对专业的金融数据网站我们仍可能遇到封锁。以下是一些进阶的对抗和优化策略。6.1 请求频率与行为随机化即使模拟了浏览器过于规律和频繁的请求仍然会被识别为机器人。随机延迟在每个关键操作如点击、翻页、切换股票之间加入随机等待时间模拟人类阅读和思考的间隔。time.sleep(random.uniform(2, 5))。随机操作序列不一定严格按照“A-B-C”的顺序。例如在抓取不同报表时可以随机排序。限制并发与总量避免短时间内抓取过多股票。可以设置每日抓取上限或将任务分散到不同时间段执行。6.2 使用代理IP池这是应对IP封锁最有效的手段之一。当网站检测到某个IP请求异常并封锁后我们可以切换另一个IP继续工作。集成代理Selenium可以通过ChromeOptions配置代理。chrome_options.add_argument(f--proxy-serverhttp://{proxy_ip}:{proxy_port})代理池管理你需要一个可靠的代理IP来源付费服务或自建。在爬虫中集成代理池逻辑在请求失败特别是返回403、429状态码时自动更换代理。注意很多公开免费代理速度慢、不稳定不适合用于需要稳定登录状态的金融数据抓取。6.3 应对验证码这是终极挑战。同花顺可能在频繁操作后弹出验证码滑块、点选、文字等。降低触发概率通过上述行为随机化和频率控制尽量减少触发验证码的可能。人工介入对于长期运行的爬虫可以设计一个“报警-人工处理”机制。当检测到验证码页面时程序暂停发送通知如邮件、短信等待人工处理并输入验证码后程序再继续。这需要将浏览器实例保持在无头模式或可远程访问的状态。第三方打码平台对于简单的图形验证码可以接入打码API自动识别。但对于复杂的交互式验证码成本高且识别率有限。6.4 日志、监控与断点续爬一个健壮的工业级爬虫必须有完善的日志和故障恢复机制。详细日志记录每个步骤的开始、结束、成功与否以及任何异常信息。使用Python的logging模块将日志输出到文件和控制台便于事后排查。进度保存将已成功抓取的股票代码、报表类型、年份实时保存到一个进度文件如JSON或数据库中。当程序因网络中断、崩溃或其他原因重启时可以先读取进度文件跳过已抓取的内容实现“断点续爬”。健康检查定期如每抓取20只股票访问一个简单的测试页面检查网络连通性和登录状态是否正常。7. 数据清洗与预处理为data2text任务做准备抓取到的原始表格数据往往是“脏”的不能直接用于模型训练。data2text任务要求输入是干净、结构化的数据。以下是一些常见的清洗步骤7.1 处理表头同花顺的表格为了显示美观表头可能是多层合并单元格。pd.read_html解析后可能会产生多级索引MultiIndex的列名或者将部分表头误读为第一行数据。检查列名打印df.columns查看结构。如果是MultiIndex需要将其扁平化例如取最后一级作为列名或手动拼接。重命名列将中文列名转换为有意义的英文缩写或标准字段名便于后续处理。例如“流动资产合计” -total_current_assets。7.2 处理缺失值与异常格式识别缺失值财报表格中可能用“--”、“-”或空白表示无数据。pd.read_html通常会将它们转换为NaN。需要检查并统一。处理数字格式数字可能带有千位分隔符逗号、百分号%或单位如“亿元”。需要使用字符串操作去除这些符号并转换为数值类型float。def clean_numeric_column(series): # 去除逗号、空格、百分号、中文单位 series series.astype(str).str.replace(,, ).str.replace( , ) series series.str.replace(%, ).str.replace(亿元, e8).str.replace(万元, e4) # 尝试转换为数值无法转换的设为NaN return pd.to_numeric(series, errorscoerce) for col in df.select_dtypes(include[object]).columns: df[col] clean_numeric_column(df[col])7.3 数据重塑与对齐不同年份、不同报表的数据需要对齐到统一的结构。统一时间索引将“年份”和“报告期”合并成一个标准的时间戳索引如2023-12-31代表2023年年报。纵向堆叠我们抓取的是多个时间点的截面数据。对于模型训练通常需要时间序列数据。可以将同一股票同一报表的不同年份数据按时间顺序排列。横向合并最终可能需要将资产负债表、利润表、现金流量表的数据根据股票代码和时间合并成一张宽表每一行包含一家公司在某个时间点的全部财务指标。7.4 输出为模型友好格式清洗完成后根据下游data2text模型的要求输出数据。CSV/JSON Lines最常见的序列化格式每行一个样本如一家公司一年的数据。结构化字段每个样本可以是一个字典包含stock_code、date、report_type以及各个财务指标字段。文本化data2text的输入虽然是结构化数据但模型可能需要文本形式的描述。我们可以将关键指标如营业收入、净利润及其同比变化用自然语言模板简单描述作为辅助输入。例如“{公司}在{年份}年实现营业收入{营收}亿元同比增长{增长率}%。”8. 实战中的坑与经验总结在开发和运行这个爬虫的几个月里我踩过了几乎所有能踩的坑。这里分享几条血泪教训希望能帮你节省大量时间。8.1 元素定位失效网站改版了怎么办这是最常见的问题。我的策略是“防御性编码”加“监控告警”。不要写死定位器将所有XPath/CSS Selector字符串作为配置项集中管理在一个config.py或JSON文件中。使用相对定位和模糊匹配优先使用包含文本内容的XPathcontains(text(),...)或模糊属性匹配starts-with(id,...)这比依赖一个特定的class名更稳定。建立元素健康检查在爬虫主循环开始前用一个测试股票代码跑一遍所有关键步骤点击各个选项卡、选择日期如果任何一步失败立即发出警报邮件、Slack等而不是等到爬了几百个股票后才发现。8.2 浏览器崩溃或驱动失联Selenium WebDriver与浏览器之间的连接有时会不稳定特别是在长时间运行且网络波动的情况下。使用try...except包裹核心操作并设计重试逻辑。如果发生WebDriverException可以尝试重新初始化驱动和浏览器当然这会丢失当前页面的状态。实现会话恢复如前所述通过Cookie持久化即使浏览器崩溃重启也能快速恢复到登录状态。使用driver.service.process监控可以检查浏览器进程是否存活如果死了就重新启动。8.3 数据解析错误表格结构不统一即使在同一网站不同公司的财报表格细微结构也可能有差异比如少数栏目不同。宽容的解析器pd.read_html(flavorhtml5lib)比默认的lxml容错性更好。数据验证解析后检查DataFrame的列数是否在预期范围内是否存在大量NaN列。可以建立一个“预期列名”的白名单过滤掉无关的列。保存原始HTML在解析失败时除了截图最好将出问题的表格HTML原始代码也保存下来供后续离线分析和调试。8.4 法律与伦理边界最后也是最重要的一点。遵守robots.txt检查同花顺的robots.txt文件尊重网站关于爬虫抓取频率和目录的限制。限制抓取速度这是最基本的礼貌也是对自身IP的保护。不要对网站服务器造成压力。数据用途抓取的数据应用于个人学习、研究或内部分析。切勿用于商业售卖、发布或从事任何可能侵犯网站权益的活动。公开的数据并不等于可以无限制地商业化利用。用户代理标识在ChromeOptions中设置一个合理的User-Agent并在请求头中可能包含一个联系方式以示友好。chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 MyResearchBot/1.0 (contact: your-emailexample.com))这个项目从零到一构建了一个针对复杂金融网站的数据采集系统它远不止几行抓取代码那么简单涉及浏览器伪装、会话管理、反爬对抗、异常处理、数据清洗等一系列工程化问题。实际部署时你可能还需要考虑任务队列如Celery、分布式抓取等更复杂的架构。但无论如何本文提供的核心思路和代码模块已经为你打下了一个坚实可靠的基础。记住爬虫是与网站运维人员的一场“动态博弈”保持代码的灵活性、可维护性和友好的抓取习惯才能让你的数据管道行稳致远。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价