尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Selenium实战:动态爬取东方财富股吧评论,构建舆情分析数据源

Selenium实战:动态爬取东方财富股吧评论,构建舆情分析数据源 1. 项目概述与核心价值最近在搞数据分析和市场情绪研究发现东方财富网股吧里的散户评论是个信息金矿。但手动去翻成千上万条帖子效率实在太低而且动态加载、登录验证这些门槛用传统的requestsBeautifulSoup组合去硬啃很容易碰壁。这时候Selenium这类Web自动化工具的价值就凸显出来了。它不仅能模拟真人操作浏览器完美应对JavaScript渲染的动态内容还能处理登录、翻页、点击“加载更多”这些交互动作。这个实战案例就是带你用Selenium这套组合拳把股吧里指定股票下的评论数据结构化地爬取下来。无论你是想分析舆情、做量化投资的情绪因子还是单纯学习Selenium应对复杂场景这个案例都能给你一套可直接复用的“抄作业”模板。2. 环境准备与核心工具选型工欲善其事必先利其器。在开始写代码之前我们需要把环境和工具链搭建好。这里的选择直接关系到后续开发的效率和脚本的稳定性。2.1 浏览器与驱动管理Selenium本身只是一个控制浏览器的API它需要一个“桥梁”来和具体的浏览器对话这个桥梁就是浏览器驱动。最主流的选择依然是Chrome和它的驱动ChromeDriver。为什么选Chrome市场占有率最高开发者工具强大Selenium对其支持也最成熟稳定。相比之下Firefox的geckodriver在某些复杂页面的渲染上可能略有差异Edge虽然同源但生态稍逊。驱动管理是关键痛点。手动下载驱动最头疼的就是版本匹配问题。Chrome浏览器会自动更新但你的ChromeDriver版本必须和Chrome浏览器的大版本号一致。比如Chrome是124.0.6367.91那么ChromeDriver也必须是124.x.x.x版本。手动维护非常麻烦。解决方案使用webdriver-manager。这是一个Python库能自动检测你本地安装的浏览器版本并下载匹配的驱动。彻底解决了手动管理的版本地狱。pip install webdriver-manager安装后在代码中初始化驱动就变得异常简单from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # 自动下载并匹配版本的驱动路径 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)注意webdriver-manager在首次运行时会从网络下载驱动请确保网络通畅。它默认会将驱动缓存到用户目录后续启动无需重复下载。2.2 Python环境与依赖库建议使用Python 3.8及以上版本。除了selenium和webdriver-manager我们还需要一些辅助库来处理数据、等待和可能的异常。pip install selenium webdriver-manager pandaspandas用于将爬取的数据结构化为DataFrame并方便地导出为CSV或Excel文件。这是数据处理和分析的标配。selenium核心自动化库。2.3 IDE与调试工具推荐使用PyCharm或VS Code。它们对Python的支持非常完善调试功能强大。在编写和调试Selenium脚本时浏览器开发者工具F12是你的另一双眼睛。你需要频繁使用“检查”功能来定位页面元素的CSS Selector或XPath。3. 目标网站分析与爬取策略设计在写第一行代码之前我们必须像侦探一样先把目标网站——东方财富网股吧的页面结构、数据加载方式和潜在反爬机制摸清楚。3.1 页面结构与数据定位以贵州茅台SH600519的股吧为例URL通常为http://guba.eastmoney.com/list,600519.html。列表页分析进入页面我们看到的是帖子的标题列表。我们需要爬取的是每个帖子下的评论而不是帖子列表本身。因此策略是先从列表页获取一批帖子的详细页链接。详情页与评论定位点击一个帖子进入详情页。评论数据并不总是直接加载在HTML源码中。你需要滚动到页面下方的评论区域。查看“最新评论”、“全部评论”的选项卡。评论通常是动态加载的可能需要点击“查看更多”或滚动触发。使用开发者工具在“网络”选项卡中筛选XHR或Fetch请求观察点击“加载更多”时浏览器向后台发送了哪些请求返回的数据是否是结构化的JSON。这是最高效的方式。如果找到了返回JSON的API接口那么我们可以直接模拟这个请求绕过Selenium渲染速度会快几个数量级。元素定位策略如果必须从渲染后的页面抓取就需要定位评论的包裹容器。右键点击一条评论选择“检查”。你会发现每条评论可能在一个div里有共同的类名比如.articleh、.l1、.l2等。你需要找到能唯一标识一条评论的最外层元素的选择器。3.2 动态加载与翻页处理东方财富网股吧的评论加载是典型的动态渲染。滚动加载很多现代网站为了性能不会一次性加载所有评论而是当用户滚动到底部时通过JavaScript动态加载更多。对于Selenium我们需要模拟这个滚动动作并等待新内容出现。from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 模拟滚动到页面底部 driver.find_element(By.TAG_NAME, body).send_keys(Keys.END) time.sleep(2) # 等待新内容加载更好的做法是使用显式等待“点击加载更多”按钮另一种常见模式是有一个明确的按钮。我们需要定位这个按钮并循环点击直到没有更多内容。while True: try: load_more_btn driver.find_element(By.CSS_SELECTOR, .load-more-btn) load_more_btn.click() time.sleep(1) # 等待加载 except: # 找不到按钮说明加载完毕或出现异常 print(评论加载完毕或加载按钮定位失败) break分页器有些老式页面仍使用数字分页。我们需要获取“下一页”按钮的链接或点击事件并循环处理。核心策略建议优先尝试在“网络”选项卡中寻找评论数据的API接口。如果找不到或者接口参数复杂、有加密再退而求其次使用Selenium模拟滚动或点击。3.3 反爬机制与应对措施东方财富网这类财经网站对爬虫比较敏感。请求头检测Selenium启动的浏览器默认的User-Agent会包含HeadlessChrome或Chrome自动化测试字样容易被识别。我们需要添加或修改请求头但这在Selenium中比较麻烦。一种方法是使用ChromeOptions添加--user-agent参数。from selenium.webdriver.chrome.options import Options chrome_options Options() 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) driver webdriver.Chrome(serviceservice, optionschrome_options)行为特征检测网站可能会检测鼠标移动轨迹、点击速度等非人类行为。Selenium的默认操作是瞬间完成的过于完美。为了模拟真人可以引入随机延迟和更自然的动作链ActionChains。from selenium.webdriver.common.action_chains import ActionChains import random element driver.find_element(By.CSS_SELECTOR, some-button) # 使用ActionChains模拟更自然的移动和点击 actions ActionChains(driver) actions.move_to_element(element).pause(random.uniform(0.5, 1.5)).click().perform()IP限制与验证码频繁访问会触发IP限制或弹出验证码。这是最棘手的问题。应对措施包括降低请求频率在每个请求或翻页操作间增加随机等待时间time.sleep(random.uniform(2, 5))。使用代理IP池对于大规模爬取这是必备的。可以通过ChromeOptions配置代理。验证码处理一旦弹出目前没有完美的全自动解决方案。可以尝试第三方打码平台或者设计中断脚本、手动处理的机制。Selenium特征隐藏新版Chrome可以通过ChromeOptions添加参数来隐藏一些自动化特征。chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False)4. 核心代码实现与分步解析假设我们经过分析发现股吧评论必须通过Selenium渲染页面来获取且采用“点击加载更多”的模式。下面我们来构建核心爬虫代码。4.1 驱动初始化与页面访问首先我们编写一个健壮的初始化函数集成反爬策略。from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager import time import random def init_driver(headlessFalse): 初始化Chrome WebDriver :param headless: 是否启用无头模式后台运行 :return: 配置好的WebDriver实例 chrome_options Options() # 基础反爬配置 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 设置一个常见的User-Agent user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ] chrome_options.add_argument(f--user-agent{random.choice(user_agents)}) # 其他优化参数 chrome_options.add_argument(--start-maximized) # 启动最大化有些页面响应式布局需要 chrome_options.add_argument(--disable-gpu) chrome_options.add_argument(--no-sandbox) if headless: chrome_options.add_argument(--headless) # 无头模式不显示GUI # 使用webdriver-manager自动管理驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) # 执行CDP命令进一步隐藏自动化特征Selenium 4及以上 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); }) return driver # 使用示例 driver init_driver(headlessFalse) # 调试阶段建议关闭无头模式便于观察 target_url http://guba.eastmoney.com/list,600519.html driver.get(target_url) time.sleep(random.uniform(3, 5)) # 初始页面加载等待4.2 帖子链接抓取与筛选进入股吧列表页后我们需要获取一定数量帖子的链接。这里需要注意广告帖、置顶帖的过滤。from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def fetch_post_links(driver, max_links20): 从股吧列表页抓取帖子链接 :param driver: WebDriver实例 :param max_links: 最大抓取链接数 :return: 帖子链接列表 post_links [] try: # 使用显式等待确保帖子列表加载出来 wait WebDriverWait(driver, 10) # 假设帖子标题所在的a标签在一个具有特定类的div里例如 .articleh .l3 a # 你需要根据实际页面结构调整这个选择器 post_elements wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, .articleh .l3 a)) ) for element in post_elements[:max_links]: href element.get_attribute(href) title element.text.strip() # 简单的过滤链接有效且标题不为空有时可以过滤掉广告广告可能无href或标题为“广告” if href and href.startswith(http) and title: # 东方财富网的帖子链接通常包含‘news’和股票代码 if news in href and 600519 in href: post_links.append({title: title, url: href}) print(f捕获帖子: {title}) except Exception as e: print(f抓取帖子链接时出错: {e}) print(f共捕获到 {len(post_links)} 个有效帖子链接) return post_links # 在初始化并访问页面后调用 links fetch_post_links(driver, max_links10)实操心得列表页的选择器.articleh .l3 a非常关键且易变。网站改版后可能就失效了。务必使用开发者工具仔细检查并考虑使用更宽松但精准的XPath例如//div[contains(class, articleh)]//a[contains(href, news)]。同时不要一次性抓取太多链接先小规模测试。4.3 单帖子评论爬取函数这是最核心的部分。我们将进入一个帖子详情页处理可能的登录弹窗然后定位评论区域并加载所有评论。def scrape_comments_from_post(driver, post_url, max_comments100): 爬取单个帖子下的所有评论 :param driver: WebDriver实例 :param post_url: 帖子详情页URL :param max_comments: 最大爬取评论数防止无限循环 :return: 评论数据列表每条评论是一个字典 print(f\n正在处理帖子: {post_url}) driver.get(post_url) time.sleep(random.uniform(4, 6)) # 等待详情页加载 comments_data [] # 1. 处理可能的登录浮窗如果出现 try: # 观察登录弹窗的关闭按钮例如一个class为‘close’的span或div close_btn driver.find_element(By.CSS_SELECTOR, .login-popup .close, .dialog-close) close_btn.click() print(已关闭登录弹窗) time.sleep(1) except: # 没有找到登录弹窗正常流程 pass # 2. 定位并展开评论区域如果需要 # 假设有个“展开全部评论”的按钮 try: expand_btn driver.find_element(By.CSS_SELECTOR, .expand-all-comments, .js-open-all) expand_btn.click() time.sleep(2) except: print(未找到‘展开全部评论’按钮或已默认展开。) # 3. 循环点击“加载更多”或滚动直到抓取足够评论或没有更多 comments_loaded 0 load_attempts 0 max_attempts 20 # 防止因元素定位失败导致无限循环 while comments_loaded max_comments and load_attempts max_attempts: load_attempts 1 # 首先尝试从当前已加载的DOM中抓取评论 current_comments extract_comments_from_current_page(driver) new_comments [c for c in current_comments if c not in comments_data] if new_comments: comments_data.extend(new_comments) comments_loaded len(comments_data) print(f已加载 {comments_loaded} 条评论...) # 然后尝试加载更多 try: # 寻找“加载更多”按钮选择器需要根据实际情况调整 # 可能是‘加载更多’、‘查看更多回复’等 load_more_btn WebDriverWait(driver, 3).until( EC.element_to_be_clickable((By.CSS_SELECTOR, .load-more, .js-load-more, .more-reply)) ) # 模拟人类点击前稍微移动鼠标到元素上可选 webdriver.ActionChains(driver).move_to_element(load_more_btn).pause(0.5).click().perform() print(点击‘加载更多’...) # 等待新内容加载使用显式等待更佳 time.sleep(random.uniform(2, 4)) except Exception as e: # 没有找到按钮或按钮不可点击可能已加载完毕 print(f未找到‘加载更多’按钮或加载完毕。尝试滚动触发加载。) # 尝试滚动到底部触发滚动加载 old_height driver.execute_script(return document.body.scrollHeight) driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(random.uniform(2, 3)) new_height driver.execute_script(return document.body.scrollHeight) if new_height old_height: print(滚动后页面高度未变化评论可能已全部加载。) break # 退出循环 # 如果高度变化继续循环解析新加载的评论 print(f该帖子爬取结束共获得 {len(comments_data)} 条评论。) return comments_data def extract_comments_from_current_page(driver): 从当前页面DOM中提取评论信息 :param driver: WebDriver实例 :return: 评论字典列表 comments [] # 关键步骤找到每条评论的容器。需要仔细分析页面结构。 # 假设每条评论在一个class为‘comment-item’的div里 comment_elements driver.find_elements(By.CSS_SELECTOR, .comment-item) for elem in comment_elements: try: # 提取用户名、内容、时间、点赞数等 # 这些选择器需要你根据实际页面结构调整 user_elem elem.find_element(By.CSS_SELECTOR, .user-name) content_elem elem.find_element(By.CSS_SELECTOR, .comment-content) time_elem elem.find_element(By.CSS_SELECTOR, .comment-time) like_elem elem.find_element(By.CSS_SELECTOR, .like-count) comment_info { username: user_elem.text.strip() if user_elem else N/A, content: content_elem.text.strip() if content_elem else N/A, post_time: time_elem.text.strip() if time_elem else N/A, like_count: like_elem.text.strip() if like_elem else 0, # 可以添加更多字段如回复数、用户等级等 } comments.append(comment_info) except Exception as e: # 某条评论解析失败跳过记录日志 # print(f解析单条评论时出错: {e}) continue return comments4.4 数据存储与主流程控制最后我们将爬取的数据保存下来并组织主函数循环处理多个帖子。import pandas as pd import json from datetime import datetime def save_data(data, filename_prefixeastmoney_guba_comments): 将数据保存为CSV和JSON文件 :param data: 要保存的数据列表 :param filename_prefix: 文件名前缀 if not data: print(没有数据可保存。) return df pd.DataFrame(data) # 生成带时间戳的文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) csv_filename f{filename_prefix}_{timestamp}.csv json_filename f{filename_prefix}_{timestamp}.json # 保存为CSV df.to_csv(csv_filename, indexFalse, encodingutf-8-sig) # utf-8-sig解决Excel打开中文乱码 print(f数据已保存到CSV文件: {csv_filename}) # 保存为JSON保留结构 with open(json_filename, w, encodingutf-8) as f: # 使用ensure_asciiFalse来正确保存中文 json.dump(data, f, ensure_asciiFalse, indent2) print(f数据已保存到JSON文件: {json_filename}) return df def main(): 主爬虫流程 all_comments [] # 1. 初始化浏览器 print(正在初始化浏览器驱动...) driver init_driver(headlessFalse) # 调试时关闭无头模式 try: # 2. 访问目标股吧列表页 list_url http://guba.eastmoney.com/list,600519.html driver.get(list_url) time.sleep(5) # 3. 抓取帖子链接 post_links fetch_post_links(driver, max_links5) # 先测试5个帖子 # 4. 遍历每个帖子爬取评论 for i, post_info in enumerate(post_links): print(f\n 开始处理第 {i1}/{len(post_links)} 个帖子 ) post_comments scrape_comments_from_post(driver, post_info[url], max_comments50) # 为每条评论添加来源帖子信息 for comment in post_comments: comment[source_post_title] post_info[title] comment[source_post_url] post_info[url] all_comments.extend(post_comments) # 每个帖子爬取后随机休息降低频率 time.sleep(random.uniform(5, 10)) # 5. 保存所有数据 if all_comments: print(f\n所有帖子处理完成共爬取 {len(all_comments)} 条评论。) save_data(all_comments) else: print(未爬取到任何评论数据。) except Exception as e: print(f主流程运行出错: {e}) import traceback traceback.print_exc() finally: # 6. 无论如何最后都要关闭浏览器 print(\n爬虫执行完毕正在关闭浏览器...) driver.quit() if __name__ __main__: main()5. 常见问题排查与进阶优化即使按照上述步骤操作在实际运行中你依然会遇到各种问题。这里我总结了一些常见的坑和解决方案。5.1 元素定位失败问题这是Selenium自动化中最常见的问题错误信息通常是NoSuchElementException。问题现象可能原因解决方案控制台报错找不到元素1. 页面尚未加载完成。2. 元素选择器CSS/XPath写错了。3. 元素在iframe或shadow DOM内。4. 页面结构已更新选择器失效。1.增加等待使用WebDriverWait配合EC如presence_of_element_located,visibility_of_element_located。2.复核选择器在浏览器开发者工具的Console中用$$(‘你的CSS’)或$x(‘你的XPath’)测试。3.切换上下文如果是iframe使用driver.switch_to.frame(frame_element)。shadow DOM需用execute_script穿透。4.使用更健壮的选择器避免使用绝对路径或依赖易变属性的选择器。多用contains,starts-with等函数。有时能找到有时找不到页面是动态渲染的元素出现时机不稳定。使用显式等待这是必须的。不要只用time.sleep。显式等待会在超时时间内不断尝试查找直到成功或超时。元素找到了但点击无效元素被遮挡、不可点击、或需要滚动到可视区域。1.滚动到元素driver.execute_script(“arguments[0].scrollIntoView(true);”, element)。2.使用ActionChains点击ActionChains(driver).move_to_element(element).click().perform()。3.JavaScript直接点击driver.execute_script(“arguments[0].click();”, element)。显式等待最佳实践示例from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import TimeoutException try: # 等待最多10秒直到元素出现并且可见、可点击 element WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, .load-more-btn)) ) element.click() except TimeoutException: print(等待‘加载更多’按钮超时可能已无更多内容。)5.2 反爬升级与应对策略当你的脚本运行一段时间后可能会发现被限制访问如弹出验证码、返回空白页、跳转到异常页面。请求头完善除了User-Agent检查并添加其他常见请求头如Accept-Language,Accept-Encoding,Referer等。这可以通过ChromeOptions的add_argument添加但Selenium对部分头部的支持有限。更彻底的方法是使用selenium-wire或undetected-chromedriver这类增强库。行为模拟将固定的time.sleep替换为随机的、更人性化的等待时间。在关键操作点击、输入前加入小幅移动和暂停。import random from selenium.webdriver.common.action_chains import ActionChains def human_like_click(driver, element): actions ActionChains(driver) # 先移动到元素附近的一个随机点再移动到元素上然后点击 x_offset random.randint(-5, 5) y_offset random.randint(-5, 5) actions.move_by_offset(x_offset, y_offset).pause(random.uniform(0.1, 0.3)) actions.move_to_element(element).pause(random.uniform(0.2, 0.5)) actions.click().perform() time.sleep(random.uniform(1.0, 2.5)) # 操作后等待使用undetected-chromedriver这是一个专门为绕过Cloudflare等反爬系统设计的Selenium补丁。它能更好地隐藏自动化特征。安装pip install undetected-chromedriver。使用方式与selenium类似但初始化更简单。import undetected_chromedriver as uc driver uc.Chrome() driver.get(your_url)注意undetected-chromedriver与webdriver-manager可能不完全兼容可能需要手动指定驱动路径。代理IP轮换对于大规模爬取这是终极方案。可以通过ChromeOptions设置代理。chrome_options.add_argument(f--proxy-serverhttp://{proxy_ip}:{proxy_port})你需要有一个可靠的代理IP池并在每次新建driver实例或定期更换IP。5.3 性能优化与稳定性提升无头模式Headless脚本调试稳定后可以启用无头模式不显示浏览器GUI节省资源。driver init_driver(headlessTrue)踩坑提示有些网站会检测无头模式。如果启用后无法获取数据尝试禁用或添加--disable-blink-featuresAutomationControlled等参数。禁用图片和CSS如果不需要页面渲染可以禁用图片、CSS甚至JavaScript来加速加载。prefs { profile.managed_default_content_settings.images: 2, # 2为禁用 profile.managed_default_content_settings.stylesheets: 2, } chrome_options.add_experimental_option(prefs, prefs)慎用禁用JavaScript会导致大量动态内容无法加载本案例中很可能导致评论区域不出现。异常处理与重试机制网络不稳定、元素短暂丢失是常态。必须用try...except包裹核心操作并加入重试逻辑。def retry_find_element(driver, by, selector, max_retries3): for i in range(max_retries): try: element driver.find_element(by, selector) return element except Exception as e: if i max_retries - 1: raise e print(f第{i1}次查找元素失败重试...) time.sleep(2) return None数据去重在爬取过程中由于翻页或加载可能重复获取相同评论。可以在保存前根据评论内容、时间、用户ID等组合键进行去重。日志记录使用Python的logging模块记录运行日志便于出错时回溯。记录成功爬取的帖子、失败的URL、遇到的异常等。5.4 数据清洗与后续分析建议爬取到的原始数据通常是脏数据需要清洗。处理空白和特殊字符使用pandas的.str.strip()、.str.replace()。时间格式标准化股吧时间可能是“今天 10:30”、“昨天 15:45”、“2023-01-01”等混合格式。需要编写函数统一转换为datetime对象。过滤广告和无效内容根据用户名如包含“推广”、“广告”、评论内容过短、无意义字符进行过滤。情感分析进阶使用snownlp、jieba情感词典或预训练模型对评论内容进行情感打分正面、负面、中性这是舆情分析的核心。关键词提取使用jieba.analyse提取每条评论或每日评论汇总中的高频关键词了解市场讨论热点。这个案例的代码和思路为你提供了一个坚实的起点。Selenium爬虫的挑战主要在于与网站动态变化的对抗。保持代码的灵活性勤于分析页面变化并合理运用等待、重试和反反爬策略你就能稳定地获取所需数据。记住爬虫的伦理和法律边界很重要务必尊重网站的robots.txt控制爬取频率不要对目标网站造成负担。
返回列表