modify scripts
This commit is contained in:
154
src/crawler/zixuan/em_zixuan.py
Normal file
154
src/crawler/zixuan/em_zixuan.py
Normal file
@ -0,0 +1,154 @@
|
||||
import requests
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
import json
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler("eastmoney_stock.log", encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger("eastmoney_stock_fetcher")
|
||||
|
||||
class EastMoneyStockFetcher:
|
||||
def __init__(self,
|
||||
appkey: str,
|
||||
cookies: str,
|
||||
retry_count: int = 3,
|
||||
retry_delay: int = 2):
|
||||
"""
|
||||
初始化东方财富自选股获取器
|
||||
:param appkey: 接口认证密钥(从抓包获取)
|
||||
:param cookies: 完整cookie字符串(从抓包获取)
|
||||
:param retry_count: 重试次数
|
||||
:param retry_delay: 重试间隔(秒)
|
||||
"""
|
||||
self.base_url = "https://myfavor.eastmoney.com/v4/webouter/gstkinfos"
|
||||
self.appkey = appkey
|
||||
self.cookies = cookies
|
||||
self.retry_count = retry_count
|
||||
self.retry_delay = retry_delay
|
||||
self.headers = self._build_headers()
|
||||
|
||||
def _build_headers(self) -> Dict[str, str]:
|
||||
"""构建请求头"""
|
||||
return {
|
||||
"Accept": "*/*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://quote.eastmoney.com/zixuan/?from=home",
|
||||
"Sec-Fetch-Dest": "script",
|
||||
"Sec-Fetch-Mode": "no-cors",
|
||||
"Sec-Fetch-Site": "same-site",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
"sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "macOS",
|
||||
"Cookie": self.cookies
|
||||
}
|
||||
|
||||
def _build_params(self) -> Dict[str, str]:
|
||||
"""构建请求参数"""
|
||||
timestamp = int(time.time() * 1000)
|
||||
# 生成唯一回调函数名(模拟前端行为)
|
||||
callback = f"jQuery3710{int(time.time()*1000000)}"
|
||||
return {
|
||||
"appkey": self.appkey,
|
||||
"cb": callback,
|
||||
"g": "1", # 固定参数(抓包观察值)
|
||||
"_": str(timestamp)
|
||||
}
|
||||
|
||||
def _parse_jsonp(self, jsonp_str: str) -> Optional[Dict]:
|
||||
"""解析JSONP响应为字典"""
|
||||
try:
|
||||
# 匹配JSONP包裹的JSON部分(去除函数调用)
|
||||
match = re.match(r'^jQuery\d+(_\d+)?\((.*)\);$', jsonp_str)
|
||||
if not match:
|
||||
logger.error("JSONP格式解析失败:无法提取JSON内容")
|
||||
return None
|
||||
json_str = match.group(2) # 取第二个分组(括号内的JSON内容)
|
||||
return json.loads(json_str)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"JSON解析失败:{str(e)}", exc_info=True)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"JSONP处理异常:{str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_favorites(self) -> Optional[List[Dict]]:
|
||||
"""获取自选股列表(带重试机制)"""
|
||||
for retry in range(self.retry_count):
|
||||
try:
|
||||
logger.info(f"获取自选股(第{retry+1}/{self.retry_count}次尝试)")
|
||||
|
||||
# 发送请求
|
||||
response = requests.get(
|
||||
url=self.base_url,
|
||||
headers=self.headers,
|
||||
params=self._build_params(),
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status() # 触发HTTP错误(如403、500)
|
||||
|
||||
# 解析响应
|
||||
json_data = self._parse_jsonp(response.text)
|
||||
if not json_data:
|
||||
logger.warning("解析响应失败,将重试")
|
||||
logger.warning(f"response: {response.text[:500]}")
|
||||
continue
|
||||
|
||||
# 验证业务状态
|
||||
if json_data.get("state") != 0:
|
||||
error_msg = json_data.get("message", "未知错误")
|
||||
logger.error(f"接口返回错误:{error_msg}")
|
||||
continue
|
||||
|
||||
# 提取股票列表
|
||||
stock_list = json_data.get("data", {}).get("stkinfolist", [])
|
||||
logger.info(f"成功获取{len(stock_list)}只自选股")
|
||||
return stock_list
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error(f"HTTP错误:{str(e)}", exc_info=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("网络连接失败,正在重试...")
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error("请求超时,正在重试...")
|
||||
except Exception as e:
|
||||
logger.error(f"获取失败:{str(e)}", exc_info=True)
|
||||
|
||||
# 重试前等待(最后一次失败不等待)
|
||||
if retry < self.retry_count - 1:
|
||||
time.sleep(self.retry_delay)
|
||||
|
||||
logger.error(f"达到最大重试次数({self.retry_count}次),获取自选股失败")
|
||||
return None
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 替换为你的实际参数(从抓包获取)
|
||||
USER_CONFIG = {
|
||||
"appkey": "e9166c7e9cdfad3aa3fd7d93b757e9b1", # 抓包得到的appkey
|
||||
"cookies": "qgqp_b_id=5107797c7296e8e7fc529ab2daa8bf8b; AUTH_FUND.EASTMONEY.COM_GSJZ=AUTH*TTJJ*TOKEN; xsb_history=831566%7C%u76DB%u5927%u5728%u7EBF%2C874086%7C%u5C0F%u5510%u79D1%u6280; mtp=1; uidal=5697316400113710%e7%ba%a2%e6%98%8coscar; sid=173318833; vtpst=|; ct=Cg2Q_hxYYLVYDcl8gpqSfxI47pA0EhhzkPjOjakoKXlgBcuGD-UqiREN2uiJtzIYD7tq7QKsj8BXMMyB2qUNG51SBbWCzy6Mvj0S3PxV67qqGiSf25tA9_HkAXbOEC26TggMsSv_G6x_CUkfAP0ElJO0ly0PDqUG6nvRx9xA074; ut=FobyicMgeV6W21ICVIh677eBIvbu__ZyQz5TBZWTQa8trNG3ATJNzZsOuWDUzrzavA0SNRBKoUwSOj-mk-kLXkuGvwj1EVY7aq3Ed-I76o9UqkhrvIjWMkjJTS8zYZ-dMiFJW3j0NtDkDLQ-T7-UvQR_zZPaow6M7iiz6-n9RVIypp03BZxsnjE3f_p3Ns2CRWNHoaJfdyW9LmWCBFOgNNmdBoyvqAVPThz_JYTzDRCuMx1tAe3l2GMuu2k8krYCjhEMmhbp1uqNtStmq8I1g__t3ajwvBQQ; pi=5697316400113710%3Bv5697316400113710%3B%E7%BA%A2%E6%98%8Coscar%3B817EV8jx6Tt0RJBA5N2FGOTBXhXXUP0r%2BYE54iCMCtD%2BXii5B7kf5bRI7srQ3L0TuJa1gpd125NW4ApaR0SL6PoeTdF6ueZi2GPFVX5rkeDCLWILf0rJHdYRL8JDKGySpCFxvzODOozSDz5w3Jb%2BOyJsvAns0Q69ea2VHHPJBRcL4ZhzOix%2FKocZlOQv7W6aawo0YhcY%3BWKiXjWmqqqz9pMgKRJpzeAwI1Be6om8gJWVcunwDO3UQK4FQ%2F8BLLGzJc8DdX%2FBcByq1faXnmy7KZa8dNxDN8J7n3Q2LDqSzswh9D1%2BZHQMxDO4AOfCouvrBk%2B2Gcnq0Pr4XdLmL6%2BzLVcCKDDiRPxEzBCfYFg%3D%3D; st_si=47023831054215; fullscreengg=1; fullscreengg2=1; HAList=ty-0-002555-%u4E09%u4E03%u4E92%u5A31%2Cty-0-159687-%u4E9A%u592A%u7CBE%u9009ETF%2Cty-116-01024-%u5FEB%u624B-W%2Cty-116-00700-%u817E%u8BAF%u63A7%u80A1%2Cty-1-601919-%u4E2D%u8FDC%u6D77%u63A7%2Cty-0-002714-%u7267%u539F%u80A1%u4EFD%2Cty-0-000423-%u4E1C%u963F%u963F%u80F6%2Cty-0-002595-%u8C6A%u8FC8%u79D1%u6280%2Cty-0-836395-%u6717%u9E3F%u79D1%u6280%2Cty-0-832982-%u9526%u6CE2%u751F%u7269; st_asi=delete; rskey=Ah3stV3Q5aVRHVVFmMFMyNTByV0hveTMwZz09IWhHQ; st_pvi=05050221710102; st_sp=2022-01-20%2014%3A22%3A55; st_inirUrl=https%3A%2F%2Fwww.baidu.com%2Flink; st_sn=35; st_psi=20250716151931682-113200301712-2597539270" # 你的完整cookie
|
||||
}
|
||||
|
||||
# 初始化获取器并获取自选股
|
||||
fetcher = EastMoneyStockFetcher(
|
||||
appkey=USER_CONFIG["appkey"],
|
||||
cookies=USER_CONFIG["cookies"]
|
||||
)
|
||||
|
||||
favorites = fetcher.get_favorites()
|
||||
if favorites:
|
||||
print("获取到的自选股列表:")
|
||||
for idx, stock in enumerate(favorites, 1):
|
||||
# 解析股票代码(从security字段提取,格式如"0$300760$36443142646090")
|
||||
code = stock["security"].split("$")[1] if "$" in stock["security"] else stock["security"]
|
||||
print(f"{idx}. 代码: {code}, 当前价: {stock['price']}, 更新时间: {stock['updatetime']}")
|
||||
160
src/crawler/zixuan/qq_zixuan.py
Normal file
160
src/crawler/zixuan/qq_zixuan.py
Normal file
@ -0,0 +1,160 @@
|
||||
import requests
|
||||
import logging
|
||||
import time
|
||||
import re
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler("tencent_stock.log", encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger("tencent_stock_fetcher")
|
||||
|
||||
class TencentStockFetcher:
|
||||
def __init__(self,
|
||||
uin: str,
|
||||
openid: str,
|
||||
fskey: str,
|
||||
cookies: str,
|
||||
retry_count: int = 3,
|
||||
retry_delay: int = 2):
|
||||
"""
|
||||
初始化腾讯自选股获取器
|
||||
:param uin: 用户唯一标识(从抓包获取)
|
||||
:param openid: 开放平台ID(从抓包获取)
|
||||
:param fskey: 会话密钥(从抓包获取)
|
||||
:param cookies: 完整的cookie字符串(从抓包获取)
|
||||
:param retry_count: 重试次数
|
||||
:param retry_delay: 重试间隔(秒)
|
||||
"""
|
||||
self.base_url = "https://webstock.finance.qq.com/stockapp/zixuanguweb/stocklist"
|
||||
self.uin = uin
|
||||
self.openid = openid
|
||||
self.fskey = fskey
|
||||
self.cookies = cookies
|
||||
self.retry_count = retry_count
|
||||
self.retry_delay = retry_delay
|
||||
self.headers = self._build_headers()
|
||||
|
||||
def _build_headers(self) -> Dict[str, str]:
|
||||
"""构建请求头"""
|
||||
return {
|
||||
"accept": "*/*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"referer": "https://gu.qq.com/",
|
||||
"sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Google Chrome";v="138"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "macOS",
|
||||
"sec-fetch-dest": "script",
|
||||
"sec-fetch-mode": "no-cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
"cookie": self.cookies
|
||||
}
|
||||
|
||||
def _build_params(self) -> Dict[str, str]:
|
||||
"""构建请求参数"""
|
||||
timestamp = int(time.time() * 1000) # 生成当前时间戳
|
||||
return {
|
||||
"uin": self.uin,
|
||||
"app": "guanwang",
|
||||
"range": "group",
|
||||
"check": "10",
|
||||
"appid": "101481127",
|
||||
"openid": self.openid,
|
||||
"fskey": self.fskey,
|
||||
"access_token": "undefined",
|
||||
"callback": "GET_2",
|
||||
"_": str(timestamp)
|
||||
}
|
||||
|
||||
def _parse_jsonp(self, jsonp_str: str) -> Optional[Dict]:
|
||||
"""解析JSONP格式响应为JSON"""
|
||||
try:
|
||||
# 提取JSON部分(去掉前后的函数包裹)
|
||||
match = re.match(r'^GET_2\((.*)\)$', jsonp_str)
|
||||
if not match:
|
||||
logger.error("无法解析JSONP响应格式")
|
||||
return None
|
||||
|
||||
json_str = match.group(1)
|
||||
import json
|
||||
return json.loads(json_str)
|
||||
except Exception as e:
|
||||
logger.error(f"解析JSONP失败: {str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_favorites(self) -> Optional[List[Dict]]:
|
||||
"""获取自选股列表"""
|
||||
for retry in range(self.retry_count):
|
||||
try:
|
||||
logger.info(f"获取自选股列表(第{retry+1}/{self.retry_count}次尝试)")
|
||||
|
||||
# 发送请求
|
||||
response = requests.get(
|
||||
url=self.base_url,
|
||||
headers=self.headers,
|
||||
params=self._build_params(),
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status() # 触发HTTP错误状态码的异常
|
||||
|
||||
# 解析响应
|
||||
json_data = self._parse_jsonp(response.text)
|
||||
if not json_data:
|
||||
continue
|
||||
|
||||
# 检查返回状态
|
||||
if json_data.get("code") != 0:
|
||||
logger.error(f"接口返回错误: {json_data.get('msg', '未知错误')}")
|
||||
continue
|
||||
|
||||
# 提取股票列表
|
||||
stock_list = json_data.get("data", {}).get("grouplist", {}).get("stocklist", [])
|
||||
logger.info(f"成功获取{len(stock_list)}只自选股")
|
||||
return stock_list
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error(f"HTTP请求错误: {str(e)}", exc_info=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("网络连接错误,正在重试...")
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error("请求超时,正在重试...")
|
||||
except Exception as e:
|
||||
logger.error(f"获取自选股失败: {str(e)}", exc_info=True)
|
||||
|
||||
# 重试间隔
|
||||
if retry < self.retry_count - 1:
|
||||
time.sleep(self.retry_delay)
|
||||
|
||||
logger.error(f"达到最大重试次数({self.retry_count}次),获取自选股失败")
|
||||
return None
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 以下参数需要替换为您自己抓包获取的值
|
||||
USER_CONFIG = {
|
||||
"uin": "57EE1D146B0F2785875C6064C8A51F06", # 替换为您的uin
|
||||
"openid": "57EE1D146B0F2785875C6064C8A51F06", # 替换为您的openid
|
||||
"fskey": "v0b94c9501068774c3728b5d55017473", # 替换为您的fskey
|
||||
"cookies": "_qimei_q36=; _qimei_h38=cd57f0fb697333e1319643370300000d217b0b; ptcz=f00816984093b2aa10bb5df66bee2bbdbff36f0f08554e195a3f5ee581ec426d; pgv_pvid=8296401627; pac_uid=0_9ZeWrs3t9r911; tvfe_boss_uuid=e9e7fc0254dac269; _qimei_uuid42=18c04121a0610061b7c03e45cb2801033a173a6cc3; RK=pDGU2iWKVW; _qimei_fingerprint=e9c7426ab8cf575a8dcb1e5b06bcec14; suid=user_1_27315909; omgid=1_27315909; pgv_info=ssid=s1753181612165394; _clck=3006684973|1|fxk|0; check=10; appid=101481127; openid=57EE1D146B0F2785875C6064C8A51F06; fskey=v0b94c9501068774c3728b5d55017473; g_openid=oA0GbjrzijlLftzuZ7_e8BrHCaLI; qq_openid=57EE1D146B0F2785875C6064C8A51F06; headimgurl=https%3A%2F%2Fpic.finance.qq.com%2Fuser%2Fheadimg%2Fe0dc7c583728191172a2719481e49ccc; nickname=oscar; zxg_fskey=v0b94c9501068774c3728b5d55017473; zxg_openid=57EE1D146B0F2785875C6064C8A51F06; tgw_l7_route=2068d115e8b0083ed181bb3085b13d57" # 替换为您的完整cookie
|
||||
}
|
||||
|
||||
# 初始化获取器并获取自选股
|
||||
fetcher = TencentStockFetcher(
|
||||
uin=USER_CONFIG["uin"],
|
||||
openid=USER_CONFIG["openid"],
|
||||
fskey=USER_CONFIG["fskey"],
|
||||
cookies=USER_CONFIG["cookies"]
|
||||
)
|
||||
|
||||
favorites = fetcher.get_favorites()
|
||||
if favorites:
|
||||
print("获取到的自选股列表:")
|
||||
for idx, stock in enumerate(favorites, 1):
|
||||
print(f"{idx}. 代码: {stock['symbol']}, 名称: {stock['symbol']}, 市场: {stock['market']}, 类型: {stock['type']}")
|
||||
175
src/crawler/zixuan/xueqiu_zixuan.py
Normal file
175
src/crawler/zixuan/xueqiu_zixuan.py
Normal file
@ -0,0 +1,175 @@
|
||||
import requests
|
||||
import logging
|
||||
import time
|
||||
import json
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
class XueQiuStockFetcher:
|
||||
def __init__(self,
|
||||
cookies: str,
|
||||
size: int = 1000,
|
||||
retry_count: int = 3,
|
||||
retry_delay: int = 2):
|
||||
"""
|
||||
初始化雪球自选股获取器
|
||||
:param cookies: 完整的cookie字符串(从抓包获取)
|
||||
:param size: 最大返回数量
|
||||
:param retry_count: 重试次数
|
||||
:param retry_delay: 重试间隔(秒)
|
||||
"""
|
||||
self.base_url = "https://stock.xueqiu.com/v5/stock/portfolio"
|
||||
self.cookies = cookies
|
||||
self.size = size
|
||||
self.retry_count = retry_count
|
||||
self.retry_delay = retry_delay
|
||||
self.headers = self._build_headers()
|
||||
|
||||
def _build_headers(self) -> Dict[str, str]:
|
||||
"""构建请求头"""
|
||||
return {
|
||||
"accept": "application/json, text/plain, */*",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"origin": "https://xueqiu.com",
|
||||
"priority": "u=1, i",
|
||||
"referer": "https://xueqiu.com/",
|
||||
"sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Google Chrome\";v=\"138\"",
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": "macOS",
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-site",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
||||
"Cookie": self.cookies
|
||||
}
|
||||
|
||||
def _request_with_retry(self, url: str, params: Dict[str, str]) -> Optional[Dict]:
|
||||
"""带重试机制的通用请求方法"""
|
||||
for retry in range(self.retry_count):
|
||||
try:
|
||||
response = requests.get(
|
||||
url=url,
|
||||
headers=self.headers,
|
||||
params=params,
|
||||
timeout=10
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析JSON响应
|
||||
try:
|
||||
return response.json()
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"JSON解析失败: {str(e)}")
|
||||
logger.error(f"响应内容: {response.text[:200]}...")
|
||||
continue
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error(f"HTTP请求错误: {str(e)}", exc_info=True)
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.error("网络连接错误,正在重试...")
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error("请求超时,正在重试...")
|
||||
except Exception as e:
|
||||
logger.error(f"请求失败: {str(e)}", exc_info=True)
|
||||
|
||||
if retry < self.retry_count - 1:
|
||||
time.sleep(self.retry_delay)
|
||||
|
||||
logger.error(f"达到最大重试次数({self.retry_count}次),请求失败")
|
||||
return None
|
||||
|
||||
def get_groups(self) -> Tuple[Optional[List[Dict]], Optional[List[Dict]],
|
||||
Optional[List[Dict]], Optional[List[Dict]]]:
|
||||
"""
|
||||
获取雪球自选股所有分组
|
||||
:return: 四个分组列表元组 (cubes, funds, stocks, mutualFunds)
|
||||
"""
|
||||
url = f"{self.base_url}/list.json"
|
||||
params = {"system": "true"}
|
||||
|
||||
json_data = self._request_with_retry(url, params)
|
||||
if not json_data:
|
||||
return None, None, None, None
|
||||
|
||||
# 检查错误码
|
||||
if json_data.get("error_code") != 0:
|
||||
error_msg = json_data.get("error_description", "未知错误")
|
||||
logger.error(f"获取分组失败: {error_msg}")
|
||||
return None, None, None, None
|
||||
|
||||
data = json_data.get("data", {})
|
||||
return (
|
||||
data.get("cubes"),
|
||||
data.get("funds"),
|
||||
data.get("stocks"),
|
||||
data.get("mutualFunds")
|
||||
)
|
||||
|
||||
def get_stocks_by_group(self, category: int = 1, pid: int = -1) -> Optional[List[Dict]]:
|
||||
"""
|
||||
获取指定分组的自选股列表
|
||||
:param category: 分类ID(从分组信息中获取)
|
||||
:param pid: 组合ID(从分组信息中获取)
|
||||
:return: 股票列表
|
||||
"""
|
||||
url = f"{self.base_url}/stock/list.json"
|
||||
params = {
|
||||
"size": str(self.size),
|
||||
"category": str(category),
|
||||
"pid": str(pid)
|
||||
}
|
||||
|
||||
json_data = self._request_with_retry(url, params)
|
||||
if not json_data:
|
||||
return None
|
||||
|
||||
# 检查错误码
|
||||
if json_data.get("error_code") != 0:
|
||||
error_msg = json_data.get("error_description", "未知错误")
|
||||
logger.error(f"获取股票列表失败: {error_msg}")
|
||||
if "登录" in error_msg or json_data.get("error_code") in [401, 403]:
|
||||
logger.warning("可能是登录状态失效,请更新cookies")
|
||||
return None
|
||||
|
||||
return json_data.get("data", {}).get("stocks", [])
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
# 替换为你的实际cookie
|
||||
USER_COOKIES = "u=5682299253; HMACCOUNT=AA6F9D2598CE96D7; xq_is_login=1; snbim_minify=true; _c_WBKFRo=BuebJX5KAbPh1PGBVFDvQTV7x7VF8W2cvWtaC99v; _nb_ioWEgULi=; cookiesu=661740133906455; device_id=fbe0630e603f726742fec4f9a82eb5fb; s=b312165egu; bid=1f3e6ffcb97fd2d9b4ddda47551d4226_m7fv1brw; Hm_lvt_1db88642e346389874251b5a1eded6e3=1751852390; xq_a_token=a0fd17a76966314ab80c960412f08e3fffb3ec0f; xqat=a0fd17a76966314ab80c960412f08e3fffb3ec0f; xq_id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjU2ODIyOTkyNTMsImlzcyI6InVjIiwiZXhwIjoxNzU0NzAzMjk5LCJjdG0iOjE3NTIxMTEyOTkyODYsImNpZCI6ImQ5ZDBuNEFadXAifQ.Vbs-LDgB4bCJI2N644DwfeptdcamKsAm2hbXxlPnJ_0fnTJhXp6T-2Gc6b6jmhTjXJIsWta8IuS0rQBB1L-9fKpUliNFHkv4lr7FW2x7QhrZ1D4lrvjihgBxKHq8yQl31uO6lmUOJkoRaS4LM1pmkSL_UOVyw8aUeuVjETFcJR1HFDHwWpHCLM8kY55fk6n1gEgDZnYNh1_FACqlm6LU4Vq14wfQgyF9sfrGzF8rxXX0nns_j-Dq2k8vN3mknh8yUHyzCyq6Sfqn6NeVdR0vPOciylyTtNq5kOUBFb8uJe48aV2uLGww3dYV8HbsgqW4k0zam3r3QDErfSRVIg-Usw; xq_r_token=1b73cbfb47fcbd8e2055ca4a6dc7a08905dacd7d; Hm_lpvt_1db88642e346389874251b5a1eded6e3=1752714700; is_overseas=0; ssxmod_itna=QqfxBD2D9DRQPY5i7YYxiwS4GhDYu0D0dGMD3qiQGglDFqAPKDHKm=lerDUhGr5h044VYmkTtDlxWeDZDG9dDqx0orXU7BB411D+iENYYe2GG+=3X0xOguYo7I=xmAkwKhSSIXNG2A+DnmeDQKDoxGkDivoD0IYwDiiTx0rD0eDPxDYDG4mDDvvQ84DjmEmFfoGImAeQIoDbORhz74DROdDS73A+IoGqW3Da1A3z8RGDmKDIhjozmoDFOL3Yq0k54i3Y=Ocaq0OZ+BGR0gvh849m1xkHYRr/oRCYQD4KDx5qAxOx20Z3isrfDxRvt70KGitCH4N4DGbh5gYH7x+GksdC58CNR3sx=1mt2qxkGd+QmoC5ZGYdixKG52q4iiqPj53js4D; ssxmod_itna2=QqfxBD2D9DRQPY5i7YYxiwS4GhDYu0D0dGMD3qiQGglDFqAPKDHKm=lerDUhGr5h044VYmkwYDioSBbrtN4=Htz/DUihxz=w4aD"
|
||||
|
||||
# 初始化获取器
|
||||
fetcher = XueQiuStockFetcher(
|
||||
cookies=USER_COOKIES,
|
||||
size=1000,
|
||||
retry_count=3
|
||||
)
|
||||
|
||||
# 1. 获取所有分组
|
||||
print("===== 获取分组列表 =====")
|
||||
cubes, funds, stocks_groups, mutual_funds = fetcher.get_groups()
|
||||
|
||||
if stocks_groups:
|
||||
print("股票分组:")
|
||||
for group in stocks_groups:
|
||||
print(f"ID: {group['id']}, 名称: {group['name']}, 股票数量: {group['symbol_count']}, 分类: {group['category']}")
|
||||
|
||||
# 2. 获取指定分组的股票(以"全部"分组为例)
|
||||
if stocks_groups and len(stocks_groups) > 0:
|
||||
for target_group in stocks_groups:
|
||||
print(f"\n===== 获取分组 [{target_group['name']}] 的股票 =====")
|
||||
|
||||
stocks = fetcher.get_stocks_by_group(
|
||||
category=target_group['category'],
|
||||
pid=target_group['id']
|
||||
)
|
||||
|
||||
if stocks:
|
||||
print(f"共{len(stocks)}只股票:")
|
||||
for idx, stock in enumerate(stocks[:10], 1): # 只显示前10只
|
||||
print(f"{idx}. 代码: {stock['symbol']}, 名称: {stock['name']}, 市场: {stock['exchange']}")
|
||||
if len(stocks) > 10:
|
||||
print(f"... 还有{len(stocks)-10}只股票未显示")
|
||||
|
||||
time.sleep(1)
|
||||
Reference in New Issue
Block a user