下载股票数据,帮修改

问题遇到的现象和发生背景

我有一个下载个股日线数据的python程序,单线程运行没问题但是耗时太久要40分钟。后来我改为多线程,虽然速度大幅提高,但偶然会出现假死的现象,就是程序运行到某个进度就卡住完全不动了,这个现象有时候出现有时候则正常。
我查阅资料后认为有如下3种可能性:
1、出现了死锁的问题
2、请求网络数据的时候出了问题,迟迟没有获取返回值
3、程序写入时出错,或者是程序本身有缺陷

以下为程序代码
import os
import time
import datetime
import requests
import threading
import pandas as pd
from concurrent.futures import ThreadPoolExecutor,as_completed

class UpdateStockData:
    lock = threading.Lock() # 全局定义一个 Lock()
    def __init__(self,stock_path,user_agent,threading_num):
        self.stock_path = stock_path
        self.user_agent = user_agent
        self.threading_num = threading_num
        self.start_date = '20220701' # start_date用于构建网易url
        self.end_date = ''.join(str(datetime.date.today()).split('-')) # 一般就是今天 20220921
        self.stock_code_list = []
        self.empty_list = []
        self.total_stock_num = 0
        self.finished_num = 0

    # 获取所有的股票数据    这里面已验证完全没问题,不用修改。
    def getStockDataFromSina(self):
        page = 1
        df = pd.DataFrame()
        headers = {'Referer': 'http://finance.sina.com.cn',
                   'User-Agent': self.user_agent}
        while True:
            url = 'http://vip.stock.finance.sina.com.cn/quotes_service/api/json_v2.php/Market_Center.getHQNodeData?page=' \
                  + str(page) + '&num=80&sort=symbol&asc=1&node=hs_a&symbol=&_s_r_a=sort'

            for i in range(10): # 向新浪请求数据的次数,限制在10次
                response = requests.get(url, headers=headers, timeout=30)
                if response.status_code == 200:
                    content = response.json()
                    break
                elif i < 9:
                    print("链接失败,接收到如下信息\n", response)
                    time.sleep(5)
                elif i == 9:
                    exit(f'连续10次向新浪请求第{page}数据失败,程序已终止')

            if not content:
                print("\n新浪股票信息已获取完毕。")
                break

            print('\r' + f"已读取新浪第{page}页数据", end='', flush=True)
            df = df.append(pd.DataFrame(content, dtype='float'), ignore_index=True)
            page += 1
            time.sleep(0.6)

        self.stock_code_list = list(df.symbol)
        self.total_stock_num = len(self.stock_code_list)
        return

    # ==== 下载个股数据_单进程
    def updateStock_pool_per(self,code_url):
        stock_code = code_url[0]
        download_url = code_url[1]
        # 读取网易数据,期间可能会出错,以下为异常处理
        sleep_time = 1  # 出错后停顿的时间
        try_count = 0
        while True:
            try:
                df = pd.read_csv(download_url, encoding='gbk')  # 直接将网易上的文件数据读取下来
            except Exception as e:
                try_count += 1
                print(f'\n在下载{stock_code}时报错,错误信息为:\n{e}\n已尝试第{try_count}次,暂停{sleep_time}秒后再尝试')
                time.sleep(sleep_time)
                if try_count == 5:
                    exit(f'\n在下载{stock_code}时失败,程序终止')
                else:
                    sleep_time += 1
            else:
                break

        if df.empty: #如果下载的数据为空,则可能被反爬管控,也有可能这段时间没有开盘
            self.lock.acquire()
            self.empty_list.append(stock_code)
            self.stock_code_list.remove(stock_code)
            self.lock.release()
            return

        # 数据写入文件
        path = os.path.join(stock_path, stock_code + '.csv') # 构建存储文件路径
        df.to_csv(path, index=False, encoding='gbk') # 直接生成文件

        self.lock.acquire()
        self.stock_code_list.remove(stock_code)
        self.finished_num += 1
        self.lock.release()

    # ==== 下载个股数据_并行多进程
    def updateStock(self):
        # ==== 从网易财经上获取股票数据,构造url
        url = r'http://quotes.money.163.com/service/chddata.html?'
        title = 'TCLOSE;HIGH;LOW;TOPEN;LCLOSE;VOTURNOVER;VATURNOVER;TCAP;MCAP'  # 要请求什么字段,见下面注释
        task_list = self.stock_code_list
        code_urls = []  # 将所有股票的下载链接存入
        for stock_code in task_list:  # 构造url 并存入code_urls
            if stock_code.startswith('sh'):
                code = '0' + stock_code[2:]
            else:
                code = '1' + stock_code[2:]
            download_url = url + 'code=' + code + '&start=' + self.start_date + '&end=' + self.end_date + '&fields=' + title
            code_urls.append([stock_code, download_url])

        pool = ThreadPoolExecutor(self.threading_num) # 构造线程池
        all_task = [pool.submit(self.updateStock_pool_per,code_url) for code_url in code_urls] # 将所有线程添加到任务列表
        for future in as_completed(all_task): # 每完成一个线程就输出一次进度。future.result可以用于接收函数的返回值。
            print('\r' + f'共{self.total_stock_num}支股票,已完成{self.finished_num}支的下载', end='', flush=True) # 打印进度

        # ==== 以下为结果报告
        print('\n已完成个股数据的下载')
        if self.empty_list:
            print(f'以下股票没有获取数据,请确认自{self.start_date}以来是否停牌')
            print(self.empty_list)

        if self.stock_code_list:
            print(f'由于未知原因,以下股票未能完成下载')

    # ==== 以下为主程序
    def main(self):
        t1 = datetime.datetime.now()  # 记录程序开始时间,用于计算总体用时
        self.getStockDataFromSina()
        self.updateStock()
        print(f'程序已运行完毕,总用时{str(datetime.datetime.now()-t1)[:-7]}')

if __name__ == '__main__':
    # 以下3个参数自己更改
    stock_path = r'E:\stock'  # 本地个股数据的储存文件夹地址
    user_agent = ''
    threading_num = 12 # 设定线程数

    a = UpdateStockData(stock_path,user_agent,threading_num)
    a.main()
运行结果及报错内容

在pycharm上运行偶尔会出现这种现象,就是一直卡住不动,两个小时后依然如此。
这种现象大多数出现在获取最后一个数据的时候。如果是被反爬的话,它不会只反我一个线程啊

img

我想要达到的结果

帮我看看是哪里写得不对导致这种问题?如果是网站的问题,是否有其他网址可以爬取这种数据?很多财经网站都有,但都是复权后的数据,或者没有“前收盘价”这个关键数据。我要的是[日期,开盘价,最高价,最低价,收盘价,前收盘价]这些价格都是不复权的。不胜感激!

你这情况可能是被反爬了,建议如下:

  1. 加上代理以后请求
  2. 另外加上timeout参数,不要让他一直等待响应,超时了就抛出错误
  3. 增加重试机制。

img


这是我这边实际运行结果,和代码有一定的关系,报错是FutureWarning关于将来语义会有改变的警告

他们说的对你被反扒了 使用代理或者是模拟点击 使用selenium js 结合的方式 https://dongfangyou.blog.csdn.net/article/details/127156948