利用python爬取上证指数股吧评论并保存到mongodb数据库

       大家好,我是带我去滑雪!

       东方财富网是中国领先的金融服务网站之一,以提供全面的金融市场数据、资讯和交易工具而闻名。其受欢迎的“股吧”论坛特别适合爬取股票评论,东方财富网的股吧聚集了大量投资者和金融分析师,他们经常在此分享投资观点、分析报告和市场动态。这些内容对于进行市场情绪分析、投资策略研究或金融模型训练非常有价值。此外,东方财富网的用户基础庞大且活跃,每日都有大量的新帖子和评论产生。这种活跃的讨论环境可以提供实时的市场反馈和投资者情绪的动态变化。相比于其他金融网站,东方财富网的股吧系统更加集中和规范,容易进行数据爬取和分析。每个股票的讨论都有其专属的页面和结构化的评论区,便于自动化工具识别和抽取数据。

       在2022年的时候,我就尝试爬取了东方财富网的股吧评论,链接如下:http://t.csdnimg.cn/F45MZ。但是最近做文本的情感分析时,需要最新的上证指数评论时,再次运行代码,出现了爬取列表为空的问题,后面我查看了东方财富网的网页结构,发现结构已经变化。基于此,本文应运而生,主要解决爬取上证指数股吧评论问题,后续可能会对评论进行数据处理和情感分析。下面开始代码实战。

目录

(1)页面爬取

(2)解析评论信息

(3)保存数据

(4)主函数


(1)页面爬取

from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import random
import pandas as pd
import os
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutExceptionfrom mongodb import MongoAPI
from parser import PostParser
from parser import CommentParserclass PostCrawler(object):def __init__(self, stock_symbol: str):self.browser = Noneself.symbol = stock_symbolself.start = time.time()def create_webdriver(self):options = webdriver.ChromeOptions()options.add_argument('lang=zh_CN.UTF-8')options.add_argument('user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, ''like Gecko) Chrome/111.0.0.0 Safari/537.36"')self.browser = webdriver.Chrome(options=options)current_dir = os.path.dirname(os.path.abspath(__file__))js_file_path = os.path.join(current_dir, 'stealth.min.js')with open(js_file_path) as f:js = f.read()self.browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": js})def get_page_num(self):self.browser.get(f'http://guba.eastmoney.com/list,{self.symbol},f_1.html')page_element = self.browser.find_element(By.CSS_SELECTOR, 'ul.paging > li:nth-child(7) > a > span')return int(page_element.text)def crawl_post_info(self, page1: int, page2: int):self.create_webdriver()max_page = self.get_page_num()current_page = page1stop_page = min(page2, max_page)parser = PostParser()postdb = MongoAPI('post_info', f'post_{self.symbol}')while current_page <= stop_page:time.sleep(abs(random.normalvariate(0, 0.1)))url = f'http://guba.eastmoney.com/list,{self.symbol},f_{current_page}.html'try:self.browser.get(url)dic_list = []list_item = self.browser.find_elements(By.CSS_SELECTOR, '.listitem')for li in list_item:dic = parser.parse_post_info(li)if 'guba.eastmoney.com' in dic['post_url']:dic_list.append(dic)postdb.insert_many(dic_list)print(f'{self.symbol}: 已经成功爬取第 {current_page} 页帖子基本信息,'f'进度 {(current_page - page1 + 1)*100/(stop_page - page1 + 1):.2f}%')current_page += 1except Exception as e:print(f'{self.symbol}: 第 {current_page} 页出现了错误 {e}')time.sleep(0.01)self.browser.refresh()self.browser.delete_all_cookies()self.browser.quit()self.create_webdriver()end = time.time()time_cost = end - self.startstart_date = postdb.find_last()['post_date']end_date = postdb.find_first()['post_date']row_count = postdb.count_documents()self.browser.quit()print(f'成功爬取 {self.symbol}股吧共 {stop_page - page1 + 1} 页帖子,总计 {row_count} 条,花费 {time_cost/60:.2f} 分钟')print(f'帖子的时间范围从 {start_date} 到 {end_date}')class CommentCrawler(object):def __init__(self, stock_symbol: str):self.browser = Noneself.symbol = stock_symbolself.start = time.time()self.post_df = Noneself.current_num = 0def create_webdriver(self):options = webdriver.ChromeOptions()options.add_argument('lang=zh_CN.UTF-8')options.add_argument('user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, ''like Gecko) Chrome/111.0.0.0 Safari/537.36"')self.browser = webdriver.Chrome(options=options)current_dir = os.path.dirname(os.path.abspath(__file__))js_file_path = os.path.join(current_dir, 'stealth.min.js')with open(js_file_path) as f:js = f.read()self.browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": js})def find_by_date(self, start_date, end_date):""":param start_date: '2003-07-21' 字符串格式 ≥:param end_date: '2024-07-21' 字符串格式 ≤"""postdb = MongoAPI('post_info', f'post_{self.symbol}')time_query = {'post_date': {'$gte': start_date, '$lte': end_date},'comment_num': {'$ne': 0}}post_info = postdb.find(time_query, {'_id': 1, 'post_url': 1})  # , 'post_date': 1self.post_df = pd.DataFrame(post_info)def find_by_id(self, start_id: int, end_id: int):""":param start_id: 721 整数 ≥:param end_id: 2003 整数 ≤"""postdb = MongoAPI('post_info', f'post_{self.symbol}')id_query = {'_id': {'$gte': start_id, '$lte': end_id},'comment_num': {'$ne': 0}}post_info = postdb.find(id_query, {'_id': 1, 'post_url': 1})self.post_df = pd.DataFrame(post_info)def crawl_comment_info(self):url_df = self.post_df['post_url']id_df = self.post_df['_id']total_num = self.post_df.shape[0]self.create_webdriver()parser = CommentParser()commentdb = MongoAPI('comment_info', f'comment_{self.symbol}')for url in url_df:try:time.sleep(abs(random.normalvariate(0.03, 0.01)))try:self.browser.get(url)WebDriverWait(self.browser, 0.2, poll_frequency=0.1).until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.reply_item.cl')))except TimeoutException:self.browser.refresh()print('------------ refresh ------------')finally:reply_items = self.browser.find_elements(By.CSS_SELECTOR, 'div.allReplyList > div.replylist_content > div.reply_item.cl')  # some have hot reply list avoid fetching twicedic_list = []for item in reply_items:dic = parser.parse_comment_info(item, id_df.iloc[self.current_num].item())dic_list.append(dic)if parser.judge_sub_comment(item):sub_reply_items = item.find_elements(By.CSS_SELECTOR, 'li.reply_item_l2')for subitem in sub_reply_items:dic = parser.parse_comment_info(subitem, id_df.iloc[self.current_num].item(), True)dic_list.append(dic)commentdb.insert_many(dic_list)self.current_num += 1print(f'{self.symbol}: 已成功爬取 {self.current_num} 页评论信息,进度 {self.current_num*100/total_num:.3f}%')except TypeError as e:self.current_num += 1print(f'{self.symbol}: 第 {self.current_num} 页出现了错误 {e} ({url})')  # maybe the invisible commentsprint(f'应爬取的id范围是 {id_df.iloc[0]} 到 {id_df.iloc[-1]}, id {id_df.iloc[self.current_num - 1]} 出现了错误')self.browser.delete_all_cookies()self.browser.refresh()self.browser.quit()self.create_webdriver()end = time.time()time_cost = end - self.startrow_count = commentdb.count_documents()self.browser.quit()print(f'成功爬取 {self.symbol}股吧 {self.current_num} 页评论,共 {row_count} 条,花费 {time_cost/60:.2f}分钟')

(2)解析评论信息

from selenium.webdriver.common.by import By
from selenium import webdriverclass PostParser(object):def __init__(self):self.year = Noneself.month = 13self.id = 0@staticmethoddef parse_post_title(html):title_element = html.find_element(By.CSS_SELECTOR, 'td:nth-child(3) > div')return title_element.text@staticmethoddef parse_post_view(html):view_element = html.find_element(By.CSS_SELECTOR, 'td > div')return view_element.text@staticmethoddef parse_comment_num(html):num_element = html.find_element(By.CSS_SELECTOR, 'td:nth-child(2) > div')return int(num_element.text)@staticmethoddef parse_post_url(html):url_element = html.find_element(By.CSS_SELECTOR, 'td:nth-child(3) > div > a')return url_element.get_attribute('href')def get_post_year(self, html):driver = webdriver.Chrome()driver.get(self.parse_post_url(html))date_str = driver.find_element(By.CSS_SELECTOR, 'div.newsauthor > div.author-info.cl > div.time').textself.year = int(date_str[:4])driver.quit()@staticmethoddef judge_post_date(html):try:judge_element = html.find_element(By.CSS_SELECTOR, 'td:nth-child(3) > div > span')if judge_element.text == '问董秘':return Falseexcept:return Truedef parse_post_date(self, html):time_element = html.find_element(By.CSS_SELECTOR, 'div.update.pub_time')time_str = time_element.textmonth, day = map(int, time_str.split(' ')[0].split('-'))if self.judge_post_date(html):if self.month < month == 12:self.year -= 1self.month = monthif self.id == 1:self.get_post_year(html)date = f'{self.year}-{month:02d}-{day:02d}'time = time_str.split(' ')[1]return date, timedef parse_post_info(self, html):self.id += 1title = self.parse_post_title(html)view = self.parse_post_view(html)num = self.parse_comment_num(html)url = self.parse_post_url(html)date, time = self.parse_post_date(html)post_info = {'_id': self.id,'post_title': title,'post_view': view,'comment_num': num,'post_url': url,'post_date': date,'post_time': time,}return post_infoclass CommentParser(object):@staticmethoddef judge_sub_comment(html):sub = html.find_elements(By.CSS_SELECTOR, 'ul.replyListL2')  # must use '_elements' instead of '_element'return bool(sub)@staticmethoddef parse_comment_content(html, sub_bool):if sub_bool:content_element = html.find_element(By.CSS_SELECTOR, 'div.reply_title > span')else:content_element = html.find_element(By.CSS_SELECTOR, 'div.recont_right.fl > div.reply_title > span')return content_element.text@staticmethoddef parse_comment_like(html, sub_bool):if sub_bool:like_element = html.find_element(By.CSS_SELECTOR, 'span.likemodule')else:like_element = html.find_element(By.CSS_SELECTOR, 'ul.bottomright > li:nth-child(4) > span')if like_element.text == '点赞':  # website display text instead of '0'return 0else:return int(like_element.text)@staticmethoddef parse_comment_date(html, sub_bool):if sub_bool:  # situation to deal with sub-commentsdate_element = html.find_element(By.CSS_SELECTOR, 'span.pubtime')else:date_element = html.find_element(By.CSS_SELECTOR, 'div.publishtime > span.pubtime')date_str = date_element.textdate = date_str.split(' ')[0]time = date_str.split(' ')[1][:5]return date, timedef parse_comment_info(self, html, post_id, sub_bool: bool = False):  # sub_pool is used to distinguish sub-commentscontent = self.parse_comment_content(html, sub_bool)like = self.parse_comment_like(html, sub_bool)date, time = self.parse_comment_date(html, sub_bool)whether_subcomment = int(sub_bool)  # '1' means it is sub-comment, '0' means it is notcomment_info = {'post_id': post_id,'comment_content': content,'comment_like': like,'comment_date': date,'comment_time': time,'sub_comment': whether_subcomment,}return comment_info

(3)保存数据

from pymongo import MongoClientclass MongoAPI(object):def __init__(self, db_name: str, collection_name: str, host='localhost', port=27017):self.host = hostself.port = portself.db_name = db_nameself.collection = collection_nameself.client = MongoClient(host=self.host, port=self.port)self.database = self.client[self.db_name]self.collection = self.database[self.collection]def insert_one(self, kv_dict):self.collection.insert_one(kv_dict)def insert_many(self, li_dict):  # more efficientself.collection.insert_many(li_dict)def find_one(self, query1, query2):return self.collection.find_one(query1, query2)def find(self, query1, query2):return self.collection.find(query1, query2)def find_first(self):return self.collection.find_one(sort=[('_id', 1)])def find_last(self):return self.collection.find_one(sort=[('_id', -1)])def count_documents(self):return self.collection.count_documents({})def update_one(self, kv_dict):self.collection.update_one(kv_dict, {'$set': kv_dict}, upsert=True)def drop(self):self.collection.drop()

(4)主函数

from crawler import PostCrawler
from crawler import CommentCrawler
import threadingdef post_thread(stock_symbol, start_page, end_page):post_crawler = PostCrawler(stock_symbol)post_crawler.crawl_post_info(start_page, end_page)def comment_thread_date(stock_symbol, start_date, end_date):comment_crawler.find_by_date(start_date, end_date)comment_crawler.crawl_comment_info()def comment_thread_id(stock_symbol, start_id, end_id):comment_crawler = CommentCrawler(stock_symbol)comment_crawler.find_by_id(start_id, end_id)comment_crawler.crawl_comment_info()if __name__ == "__main__":thread1 = threading.Thread(target=post_thread, args=('zssh000001', 5835, 5875))thread1.start()thread1.join()print(f"成功爬取评论数据!")

输出结果展示:

       成功爬取了495775条评论数据,运行了14个小时,实属不易。后续将会对这个数据集,进行深度的分析。

需要数据集的家人们可以去百度网盘(永久有效)获取:

链接:https://pan.baidu.com/s/16Pp57kAbC3xAqPylyfQziA?pwd=2138
提取码:2138 


更多优质内容持续发布中,请移步主页查看。

   点赞+关注,下次不迷路!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/364011.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

夏令营1期-对话分角色要素提取挑战赛-第①次打卡

零基础入门大模型技术竞赛 简介&#xff1a; 本次学习是 Datawhale 2024 年 AI 夏令营第一期&#xff0c;学习活动基于讯飞开放平台“基于星火大模型的群聊对话分角色要素提取挑战赛”开展实践学习。 适合想 入门并实践大模型 API 开发、了解如何微调大模型的学习者参与 快来…

【C++】哈希表

目录 一、unordered系列关联式容器 二、哈希 2.1 概念 2.2 哈希冲突 2.3 哈希函数 &#xff08;1&#xff09;直接定址法 &#xff08;2&#xff09;除留余数法 &#xff08;3&#xff09;平方取中法 &#xff08;4&#xff09;折叠法 &#xff08;5&#xff09;随机…

springboot注解@ComponentScan注解作用

一 ComponentScan作用 1.1 注解作用 项目会默认扫描SpringBootApplication注解所在路径的同级和下级的所有子包&#xff0c;使用ComponentScan后他会取代掉默认扫描。 ComponentScan 是Spring框架的注解&#xff0c;它的作用是扫描指定的包路径下的标有 Component、Service、…

已备案网站变更并且不影响现有业务的方案

已备案网站变更并且不影响现有业务的方案 近日有个工作上的需求&#xff0c;已备案网站变更并且不影响现有业务&#xff0c;记录一下。 需求 域名&#xff1a;XXXXXX.com备案变更前主体&#xff1a; 海南XXXXXX科技有限公司 备案变更后主体&#xff1a; 深圳XXXXXX科技有限…

梦想CAD二次开发

1.mxdraw简介 mxdraw是一个HTML5 Canvas JavaScript框架&#xff0c;它在THREE.js的基础上扩展开发&#xff0c;为用户提供了一套在前端绘图更为方便&#xff0c;快捷&#xff0c;高效率的解决方案&#xff0c;mxdraw的实质为一个前端二维绘图平台。你可以使用mxdraw在画布上绘…

50-2 内网信息收集 - 内网工作环境(域相关知识)

一、工作组 工作组(Work Group)是局域网中最基本的资源管理模式,适用于小规模网络环境。 工作组的定义: 工作组是将不同功能或部门的计算机分组管理的方式。它提供了层次化的网络资源管理,使得组织内的计算机可以按照功能或部门分类。每个工作组有一个自定义的主机名称,…

Java访问修饰符的区别

public&#xff1a;公开的&#xff0c;任何地方都可以访问。 protected&#xff1a;受保护的&#xff0c;同一个包中的类和所有子类(可跨包)可以访问。 private&#xff1a;私有的&#xff0c;只有在同一个类中可以访问。 默认&#xff08;无修饰符&#xff09;&#xff1a;包级…

零基础STM32单片机编程入门(四)ADC详解及实战含源码视频

文章目录 一.概要二.STM32F103C8T6单片机ADC外设特点三.STM32单片机ADC内部结构图1.ADC相关引脚说明2.ADC通道分类3.触发源4.转换周期5.电压转换计算6.更精确电压转换计算 四.规则通道ADC采集信号流向1.单次转换模式2.连续转换模式 五.CubeMX配置一个ADC采集例程六.CubeMX工程源…

AI基础:从线性回归到梯度下降

一个简单的问题&#xff1a; 如果此时你正站在迷路缭绕的山坡上&#xff0c;能见度不高&#xff0c;但是你又想去往最低的山谷的位置&#xff0c;怎么走&#xff1f; 很简单&#xff0c;哪里陡那就往那里走呗——而这就是梯度下降算法的思想。 古话说&#xff1a;“先发制于人…

mindspore打卡第9天 transformer的encoder和decoder部分

mindspore打卡第9天 transformer的encoder和decoder部分 import mindspore from mindspore import nn from mindspore import ops from mindspore import Tensor from mindspore import dtype as mstypeclass ScaledDotProductAttention(nn.Cell):def __init__(self, dropout_…

计算机毕业设计hadoop+spark+hive知识图谱酒店推荐系统 酒店数据分析可视化大屏 酒店爬虫 高德地图API 酒店预测系统 大数据毕业设计

酒店推荐系统开题报告 一、研究背景与意义 随着旅游业的蓬勃发展和人们生活水平的提高&#xff0c;酒店行业迎来了前所未有的发展机遇。然而&#xff0c;面对众多的酒店选择&#xff0c;消费者往往难以在短时间内找到最适合自己需求和预算的酒店。因此&#xff0c;开发一款高…

推荐一款免费的GIF编辑器——【ScreenToGif编辑器】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️木道寻的主页 文章目录 &#x1f525;前言&#x1f680;素材准备&#x1f680;逐帧制作&#x1f680;保存图片⭐️⭐️⭐️总结 &#…

LangGPT:高质量提示词框架

题目&#xff1a;LangGPT: Rethinking Structured Reusable Prompt Design Framework for LLMs from the Programming Language作者: Ming Wang; Yuanzhong Liu; Xiaoming Zhang; Songlian Li; Yijie Huang; Chi Zhang; Daling Wang; Shi Feng; Jigang LiDOI: 10.48550/arXiv.2…

【排序算法】—— 希尔排序

目录 一、希尔排序原理 二、希尔排序的思路 三、希尔排序为什么快 四、如何取增量 五、源码 希尔排序是简单插入排序的一种升级版&#xff0c;它也是用了插入的思想&#xff0c;而插入排序相比冒泡排序和选择排序的效率要高的多&#xff0c;再将它优化为希尔排序后效率跟原…

【C++11(二)】lambda表达式和可变参数模板

一、可变参数模板 C11的新特性可变参数模板 能够让您创建可以接受 可变参数的函数模板和类模板 // Args是一个模板参数包&#xff0c;args是一个函数形参参数包 // 声明一个参数包Args...args&#xff0c;这个参数包中可以包含0到任意个模板参数。 template <class ...Arg…

智慧记账,轻松管理,让借还款明细一目了然,一键导出

在繁忙的生活中&#xff0c;财务记账管理往往成为我们的一大难题。尤其是面对频繁的借还款项&#xff0c;如何快速、准确地记录每一笔收支明细&#xff0c;并确保数据的清晰、完整&#xff0c;成为许多人关注的焦点。现在&#xff0c;我们为您带来一款全新的记账管理工具——晨…

【第三方JSON库】org.json.simple用法初探—Java编程【Eclipse平台】【不使用项目管理工具】【不添加依赖解析】

本文将重点介绍&#xff0c;在不使用项目管理工具&#xff0c;不添加依赖解析情况下&#xff0c;【第三方库】JSON.simple库在Java编程的应用。 JSON.simple是一种由纯java开发的开源JSON库&#xff0c;包含在JSON.simple.jar中。它提供了一种简单的方式来处理JSON数据和以JSO…

有趣的仿神经猫html5圈小猫游戏源码

有趣的仿神经猫html5圈小猫游戏源码,点击小圆点&#xff0c;围住小猫游戏。猫已经跑到地图边缘&#xff0c;你输了。内含json数据&#xff0c;部署到服务器方可运行 微信扫码免费获取源码

Kafka 位移

Consumer位移管理机制 将Consumer的位移数据作为一条条普通的Kafka消息&#xff0c;提交到__consumer_offsets中。可以这么说&#xff0c;__consumer_offsets的主要作用是保存Kafka消费者的位移信息。使用Kafka主题来保存位移。 消息格式 位移主题就是普通的Kafka主题。也是…

Type-C接口OTG转接器的应用与发展

随着科技的飞速发展&#xff0c;智能移动设备已成为我们生活中不可或缺的一部分。而在这些设备的连接与数据传输中&#xff0c;Type-C接口以其高效、便捷的特性逐渐占据了主导地位。OTG&#xff08;On-The-Go&#xff09;技术则进一步扩展了Type-C接口的功能&#xff0c;使得设…