利用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;保存图片⭐️⭐️⭐️总结 &#…

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

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

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、执行计划中五个重…

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

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

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

在日常工作中&#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…

微信商家转账到零钱

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

筑算网基石 创数智未来|锐捷网络闪耀2024 MWC上海

2024年6月26日至28日&#xff0c;全球科技界瞩目的GSMA世界移动大会&#xff08;MWC 上海&#xff09;在上海新国际博览中心&#xff08;SNIEC&#xff09;盛大召开。作为行业领先的网络解决方案提供商&#xff0c;锐捷网络以“筑算网基石 创数智未来”为主题&#xff0c;带来了…

Java程序员学习Go开发Higress的WASM插件

Java程序员学习Go开发Higress的WASM插件 契机 ⚙ 今年天池大赛有higress相关挑战&#xff0c;研究一下。之前没搞过go&#xff0c;踩了很多坑&#xff0c;最主要的就是tinygo打包&#xff0c;多方寻求解决无果&#xff0c;结论是tinygo0.32go1.19无法在macos arm架构下打包。…

SerDes介绍以及原语使用介绍(3)ISERDESE2原语介绍

文章目录 前言一、ISERDESE21.1、ISERDESE2端口信号1.1、ISERDESE2参数 二、BITSLIP-位滑动2.1、BITSLIP作用2.2、BITSLIP使用2.3、BITSLIP示例 前言 上文对OSERDESE进行了详细介绍并且进行了仿真分析&#xff0c;本文开始对ISERDES进行介绍&#xff0c; 一、ISERDESE2 不难…

云计算【第一阶段(20)】磁盘管理与文件系统 服务器硬件及RAID配置实战(三)

一、服务器硬件详解 cpu 主板 内存 硬盘 网卡 电源 raid卡 风扇 远程管理卡 1.1、硬盘尺寸 目前生产环境中主流的两种类型硬盘 3.5寸 和2.5寸硬盘 2.5寸硬盘可以通过使用硬盘托架后适用于3.5寸硬盘的服务器 但是3.5寸没法转换成2.5寸 二、RAID阵列详解 独立硬盘冗余阵…

【深度学习】图形模型基础(2):概率机器学习模型与人工智能

1.引言 1.1.背景 当机器需要从经验中汲取知识时&#xff0c;概率建模成为了一个至关重要的工具。它不仅为理解学习机制提供了理论框架&#xff0c;而且在实际应用中&#xff0c;特别是在设计能够从数据中学习的机器时&#xff0c;概率建模展现出了其独特的价值。概率框架的核…

不知道自己的优势擅长和兴趣爱好,我该如何填报高考志愿选专业?

天生我才必有用&#xff0c;每个人都是独立的个体&#xff0c;拥有自己的优势和擅长&#xff0c;当然这个优势和擅长&#xff0c;不是和别人对比&#xff0c;而是和自己对比产生的。 如果说你不知道自己的优势擅长&#xff0c;不知道自己的兴趣和爱好&#xff0c;那只不过是你没…

cuda编码入门学习笔记

在日常深度学习和科学计算中,使用图形处理器(GPU)进行加速是一个常见的做法。CUDA (Compute Unified Device Architecture) 是英伟达公司提供的用于GPU编程的平台和编程模型。同时它是一种并行计算模型,允许开发人员使用标准C语言对GPU进行编程。CUDA的核心思想是将任务分解为…

The difference between Manhattan distance and Cosine Distance

题意&#xff1a;为什么即使返回了相同的文本块&#xff0c;曼哈顿距离&#xff08;Manhattan Distance&#xff09;和余弦距离&#xff08;Cosine Distance&#xff09;之间还是存在差异&#xff1f; 问题背景&#xff1a; I am using the qdrant DB and client for embeddin…

排序【插入排序】

排序的概念 排序&#xff1a;所谓排序&#xff0c;就是将一份数据&#xff0c;通过某个或者某些关键字的大小&#xff0c;进行递增或者递减排序的操作。 稳定性&#xff1a;假定在待排序的数据组中&#xff0c;存在多个相同的元素&#xff0c;若经过排序&#xff0c;这些数据…

决定佛蒙特州版图的关键历史事件:

​决定佛蒙特州版图的关键历史事件: 1. 早期探险与命名&#xff1a; - 1609年&#xff0c;法国探险家萨缪尔德尚普兰&#xff08;Samuel de Champlain&#xff09;到达了现在的佛蒙特州区域&#xff0c;并探索了尚普兰湖&#xff08;Lake Champlain&#xff09;。他将周围的山…

TS_类型

目录 1.类型注解 2.类型检查 3.类型推断 4.类型断言 ①尖括号&#xff08;<>&#xff09;语法 ②as语法 5.数据类型 ①boolean ②number ③string ④undefined 和 null ⑤数组和元组 ⑥枚举 ⑦any 和void ⑧symbol ⑨Function ⑩Object 和 object 6.高…