利用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.mzph.cn/news/863078.shtml

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

相关文章

vue开发网站--关于window.print()调取打印

1.vue点击按钮调取打印 点击按钮&#xff1a; 调取打印该页面&#xff1a; <div click"clickDown()">下载</div>methods: {//下载-调取打印clickDown() {window.print()}, }<style>/* 点击打印的样式 */media print {.clickDown {display: no…

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

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

Flask蓝图

Flask蓝图 蓝图&#xff08;Blueprint&#xff09;在 Flask 中是一个用于组织多个模块化子应用的强大工具。它允许开发者将不同的功能模块划分到不同的包或目录中&#xff0c;使得大型项目更加易于管理和维护。 一、不使用蓝图 在不使用蓝图的情况下&#xff0c;可能会将所有…

被淘汰的.NET技术概览

最近看到一篇文章&#xff0c;讲的时.NET被淘汰的技术&#xff0c;文章大体内容如下&#xff1a; 被淘汰的.NET技术概览 1.NET Framework 4.8之前的版本 微软已于2019年11月发布了.NET Framework的最后一个版本4.8&#xff0c;并宣布在2023年11月停止对之前版本提供支持。 …

java基础学习:Class类的isAssignableFrom方法

文章目录 一、介绍2、示例 一、介绍 在Java中&#xff0c;Class类有一个名为isAssignableFrom()的方法。这个方法用于判断一个类对象是否表示指定的类的类对象、接口、超类或超接口。换句话说&#xff0c;它用于检查一个类是否是另一个类的子类、接口实现或它们本身就是同一个…

【吊打面试官系列-MyBatis面试题】MyBatis 框架适用场合?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 框架适用场合 &#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 框架适用场合&#xff1f; 1、MyBatis 专注于 SQL 本身&#xff0c;是一个足够灵活的 DAO 层解决方案。 2、对性能的要求很高&#…

信息检索(58):Learning to Reweight Terms with Distributed Representations

Learning to Reweight Terms with Distributed Representations 摘要1 引言2 相关工作3 准备工作3.1 分布式词向量3.2 目标术语权重3.3 术语权重和检索模型3.3.1 概率语言模型3.3.2 BM25 4 使用分布式词向量进行词项权重学习5 实验方法6 实验结果6.1 语言模型的检索结果6.2 BM2…

Java | Leetcode Java题解之第191题位1的个数

题目&#xff1a; 题解&#xff1a; public class Solution {public int hammingWeight(int n) {int ret 0;while (n ! 0) {n & n - 1;ret;}return ret;} }

MySQL之如何分析慢查询

1、一个SQL语句执行很慢&#xff0c;如何分析&#xff1f; 可使用“explain”或者“desc”命令获取MySQL如何执行select语句的信息。 语法&#xff1a;直接在select语句前加关键字 explain或desc explain select job_desc from xxl_job_info where id 1; 2、执行计划中五个重…

基础位运算

基础知识点&#xff1a; 1.判断2的幂 n&&#xff08;n-1&#xff09;0 2.每次减一处理 n&(n-1) 3.判断出现1次次数的数 x^0x&#xff0c;x^x0&#xff0c;a^bc则ab^c&#xff0c;ba^c 力扣练习题&#xff1a; 136.只出现一次的数字 class Solution { public:int si…

【涵子来信】——社交宝典:克服你心中的内向,世界总有缺陷

内向&#xff0c;你是内向的吗&#xff1f;想必每个人不同&#xff0c;面对的情形也是不同的。 暑假是一个很好的机会&#xff0c;我是可以去多社交社交。但是&#xff0c;面对着CSDN上这么多技术人er&#xff0c;那么&#xff0c;我的宝典&#xff0c;对于大家&#xff0c;有…

操作系统期末复习(选择题(一))

单选题 1. 操作系统是一种。 A.应用软件 B.系统软件 C.通用软件 D.工具软件 2. 操作系统是对进行管理的软件。 A.软件 B.硬件 C.计算机资源 D.程序 3. 下列系统中&#xff0c;是实时系统。 A.激光照排系统 B.计算机辅助设计系统 C.办公自动化系统 D.航空定票…

HTTP3抛弃了经典的TCP,拥抱QUIC了!

核心内容概述 HTTP3与QUIC的关联TCP协议的局限性QUIC协议的设计优势QUIC与UDP的结合QUIC协议的特点未来互联网传输技术的展望 笔记 HTTP3与QUIC的关系 HTTP3是HTTP协议的3.0版本&#xff0c;使用QUIC作为其传输协议。QUIC最初被称为HTTP over QUIC&#xff0c;后更名为HTTP/3…

电脑文件夹里的表格删除了怎样恢复?别急,可这样做

在日常工作中&#xff0c;我们经常会使用到各种电子表格来记录、整理和分析数据。然而&#xff0c;有时由于操作失误或其他原因&#xff0c;我们可能会不小心将电脑文件夹中的重要表格删除。面对这种情况&#xff0c;许多人可能会感到惊慌失措&#xff0c;担心数据丢失会给工作…

db2主键自增、IDENTITY自增

db2设置主键自增 一、方式一&#xff1a;IDENTITY设置主键自增1.1、语法一&#xff1a;GENERATED BY DEFAULT AS IDENTITY1.1.1、使用1.1.2、注意事项或坑 1.2、语法二&#xff1a;GENERATED ALWAYS AS IDENTITY 二、方式二&#xff1a;Sequence(了解)2.1、insert时使用Sequenc…

网管平台配置步骤详解

网管平台是现代运维工作中不可或缺的工具&#xff0c;它能够帮助运维人员实时监控网络状态、管理网络设备、排查网络故障等。本文将参考运维行业的经验&#xff0c;详细介绍网管平台的配置步骤&#xff0c;以期为运维人员提供实用的操作指南。 一、明确需求和目标 在进行网管平…

计算机视觉全系列实战教程 (十二):图像分割(阈值分割threshold、分水岭算法watershed的使用步骤、洪水填充floodFill算法的使用)

1.图像分割概述 (1)What(什么是图像分割) 将图像划分为不同的子区域&#xff0c;使得同一子区域具有较高的相似性&#xff0c;不同的子区域具有明显的差异性 (2)Why(对图像进行分割有什么作用) 医学领域&#xff1a;将不同组织分割成不同区域帮助分析病情军事领域&#xff…

微信商家转账到零钱

1.发起商家转账 发起商家转账接口。商户可以通过该接口同时向多个用户微信零钱进行转账操作。请求消息中应包含商家批次单号、转账名称、appid、转账总金额、转账总笔数、转账openid、收款用户姓名等信息。注意受理成功将返回批次单号&#xff0c;此时并不代表转账成功&#x…

JVM性能监控工具:JMX与VisualVM高级用法

在Java应用的开发和维护过程中&#xff0c;性能监控是一个不可或缺的环节。Java Management Extensions&#xff08;JMX&#xff09;和VisualVM是两个强大的工具&#xff0c;它们可以帮助开发者监控和管理Java应用程序的性能。本文将详细介绍如何使用JMX和VisualVM进行高级性能…

第二十站:Java未来光谱——量子计算与新兴技术的展望(第二篇)

在探讨Java与量子计算的未来融合时&#xff0c;我们首先要理解量子计算的基本概念及其与传统计算的区别。量子计算利用量子力学原理&#xff0c;如量子比特&#xff08;qubit&#xff09;的叠加态和纠缠效应&#xff0c;能在理论上实现远超经典计算机的计算速度&#xff0c;尤其…