go语言访问新浪股票(hq.sinajs.cn)

config.go:

package main

// 股票代码配置 - 简化版本,只包含主要指数和热门股票
var stockSymbols = []string{
	// 主要指数
	"sh000001", // 上证指数
	"sh000016", // 上证50
	"sh000300", // 沪深300
	"sz399001", // 深证成指
	"sz399006", // 创业板指
	
	// 热门股票
	"sh600519", // 贵州茅台
	"sz000858", // 五粮液
	"sh601318", // 中国平安
	"sz000001", // 平安银行
	"sh600036", // 招商银行
	"sz000002", // 万科A
	"sh601166", // 兴业银行
	"sz000651", // 格力电器
	"sh600887", // 伊利股份
	"sz000333", // 美的集团
	"sh601888", // 中国国旅
	"sz000063", // 中兴通讯
	"sh600276", // 恒瑞医药
	"sz000538", // 云南白药
	"sh600104", // 上汽集团
	"sz000625", // 长安汽车
	"sh601398", // 工商银行
	"sz000776", // 广发证券
	"sh601288", // 农业银行
	"sz000725", // 京东方A
	"sh601328", // 交通银行
	"sz000100", // TCL科技
	"sh601988", // 中国银行
	"sz000977", // 浪潮信息
	"sh601939", // 建设银行
	"sz000066", // 中国长城
	"sh601668", // 中国建筑
	"sh601766", // 中国中车
	"sz000898", // 鞍钢股份
	"sh601800", // 中国交建
	"sh601186", // 中国铁建
	"sh601390", // 中国中铁
	"sh601618", // 中国中冶
	"sh601857", // 中国石油
	"sh601088", // 中国神华
	"sh601899", // 紫金矿业
	"sh601600", // 中国铝业
	"sh601168", // 西部矿业
	"sh601958", // 金钼股份
	"sh601699", // 潞安环能
	"sh601101", // 昊华能源
	"sh601666", // 平煤股份
	"sh601001", // 大同煤业
	"sh601898", // 中煤能源
	"sh601918", // 新集能源
}

main.go:

package main

import (
	"encoding/csv"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

// StockData 股票数据结构
type StockData struct {
	Symbol     string  // 股票代码
	Name       string  // 股票名称
	Open       float64 // 开盘价
	PrevClose  float64 // 昨收价
	Price      float64 // 当前价
	High       float64 // 最高价
	Low        float64 // 最低价
	Volume     int64   // 成交量
	Amount     float64 // 成交额
	Change     float64 // 涨跌额
	ChangeRate float64 // 涨跌幅
	Time       string  // 时间
}

func main() {
	fmt.Println("开始获取股票数据...")

	// 获取所有股票数据
	stocks, err := fetchAllStockData()
	if err != nil {
		fmt.Printf("获取股票数据失败: %v\n", err)
		return
	}

	// 显示股票数据
	displayStockData(stocks)

	// 保存到CSV文件
	err = saveToCSV(stocks, "stock_data.csv")
	if err != nil {
		fmt.Printf("保存CSV文件失败: %v\n", err)
	} else {
		fmt.Println("数据已保存到 stock_data.csv")
	}
}

// fetchAllStockData 获取所有股票数据
func fetchAllStockData() ([]StockData, error) {
	var stocks []StockData

	// 分批获取,避免URL过长
	batchSize := 20 // 减少批次大小,避免URL过长
	for i := 0; i < len(stockSymbols); i += batchSize {
		end := i + batchSize
		if end > len(stockSymbols) {
			end = len(stockSymbols)
		}

		batchSymbols := stockSymbols[i:end]
		batchStocks, err := fetchBatchStockData(batchSymbols)
		if err != nil {
			fmt.Printf("批次 %d 获取失败: %v\n", i/batchSize+1, err)
			continue // 继续处理下一批,而不是直接返回错误
		}

		stocks = append(stocks, batchStocks...)

		// 添加延迟避免请求过快
		time.Sleep(500 * time.Millisecond)
	}

	return stocks, nil
}

// fetchBatchStockData 批量获取股票数据
func fetchBatchStockData(symbols []string) ([]StockData, error) {
	// 构建URL
	url := "https://hq.sinajs.cn/list=" + strings.Join(symbols, ",")

	// 创建HTTP客户端
	client := &http.Client{
		Timeout: 10 * time.Second,
	}

	// 创建请求
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("创建请求失败: %v", err)
	}

	// 设置请求头(模拟浏览器请求)
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
	req.Header.Set("Referer", "https://finance.sina.com.cn/")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("请求失败: %v", err)
	}
	defer resp.Body.Close()

	// 检查响应状态
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP错误: %s", resp.Status)
	}

	// 读取响应内容
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}

	// 解析响应数据
	return parseResponseData(string(body), symbols)
}

// parseResponseData 解析新浪财经返回的数据
func parseResponseData(data string, symbols []string) ([]StockData, error) {
	var stocks []StockData

	// 按行分割数据
	lines := strings.Split(data, "\n")

	for i, line := range lines {
		if line == "" || !strings.Contains(line, "=\"") {
			continue
		}

		// 提取股票数据部分
		parts := strings.SplitN(line, "=\"", 2)
		if len(parts) < 2 {
			continue
		}

		// 获取股票代码
		symbol := ""
		if i < len(symbols) {
			symbol = symbols[i]
		}

		// 解析数据字段
		stock, err := parseStockFields(parts[1], symbol)
		if err != nil {
			fmt.Printf("解析股票数据失败 (代码: %s): %v\n", symbol, err)
			continue
		}

		stocks = append(stocks, stock)
	}

	return stocks, nil
}

// parseStockFields 解析股票数据字段
func parseStockFields(data string, symbol string) (StockData, error) {
	// 去除末尾的引号
	data = strings.TrimSuffix(data, "\";")

	// 分割字段
	fields := strings.Split(data, ",")
	if len(fields) < 30 {
		return StockData{}, fmt.Errorf("数据字段不足")
	}

	// 解码股票名称(GB2312到UTF-8)
	decodedName, decodeErr := decodeGB2312(fields[0])
	if decodeErr != nil {
		// 如果解码失败,使用原始名称
		decodedName = fields[0]
	}

	stock := StockData{
		Symbol: symbol,
		Name:   decodedName,
	}

	// 解析数值字段
	var err error
	stock.Open, err = parseFloat(fields[1])
	if err != nil {
		return StockData{}, err
	}

	stock.PrevClose, err = parseFloat(fields[2])
	if err != nil {
		return StockData{}, err
	}

	stock.Price, err = parseFloat(fields[3])
	if err != nil {
		return StockData{}, err
	}

	stock.High, err = parseFloat(fields[4])
	if err != nil {
		return StockData{}, err
	}

	stock.Low, err = parseFloat(fields[5])
	if err != nil {
		return StockData{}, err
	}

	stock.Volume, err = parseInt(fields[8])
	if err != nil {
		return StockData{}, err
	}

	stock.Amount, err = parseFloat(fields[9])
	if err != nil {
		return StockData{}, err
	}

	// 计算涨跌额和涨跌幅
	if stock.PrevClose > 0 {
		stock.Change = stock.Price - stock.PrevClose
		stock.ChangeRate = (stock.Change / stock.PrevClose) * 100
	}

	// 时间字段
	if len(fields) > 30 {
		stock.Time = fields[30] + " " + fields[31]
	}

	return stock, nil
}

// parseFloat 解析浮点数
func parseFloat(s string) (float64, error) {
	if s == "" {
		return 0, nil
	}
	return parseFloatWithDefault(s, 0)
}

// parseFloatWithDefault 解析浮点数,带默认值
func parseFloatWithDefault(s string, def float64) (float64, error) {
	if s == "" {
		return def, nil
	}
	var result float64
	_, err := fmt.Sscanf(s, "%f", &result)
	if err != nil {
		return def, err
	}
	return result, nil
}

// parseInt 解析整数
func parseInt(s string) (int64, error) {
	if s == "" {
		return 0, nil
	}
	var result int64
	_, err := fmt.Sscanf(s, "%d", &result)
	if err != nil {
		return 0, err
	}
	return result, nil
}

// displayStockData 显示股票数据
func displayStockData(stocks []StockData) {
	fmt.Printf("\n=== 股票数据 (%d只股票) ===\n", len(stocks))
	fmt.Printf("%-10s %-12s %-8s %-8s %-8s %-8s %-8s %-12s\n",
		"代码", "名称", "当前价", "涨跌额", "涨跌幅", "最高", "最低", "时间")
	fmt.Println(strings.Repeat("-", 90))

	for _, stock := range stocks {
		changeSign := ""
		if stock.Change > 0 {
			changeSign = "+"
		}

		// 限制名称长度,避免显示问题
		name := stock.Name
		if len(name) > 10 {
			name = name[:10] + "..."
		}

		fmt.Printf("%-10s %-12s %-8.2f %s%-7.2f %-7.2f%% %-8.2f %-8.2f %-12s\n",
			stock.Symbol, name, stock.Price, changeSign, stock.Change,
			stock.ChangeRate, stock.High, stock.Low, stock.Time)
	}
}

// saveToCSV 保存数据到CSV文件
func saveToCSV(stocks []StockData, filename string) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// 写入表头
	header := []string{"股票代码", "股票名称", "开盘价", "昨收价", "当前价", "最高价",
		"最低价", "成交量", "成交额", "涨跌额", "涨跌幅", "时间"}
	if err := writer.Write(header); err != nil {
		return err
	}

	// 写入数据
	for _, stock := range stocks {
		row := []string{
			stock.Symbol,
			stock.Name,
			fmt.Sprintf("%.2f", stock.Open),
			fmt.Sprintf("%.2f", stock.PrevClose),
			fmt.Sprintf("%.2f", stock.Price),
			fmt.Sprintf("%.2f", stock.High),
			fmt.Sprintf("%.2f", stock.Low),
			fmt.Sprintf("%d", stock.Volume),
			fmt.Sprintf("%.2f", stock.Amount),
			fmt.Sprintf("%.2f", stock.Change),
			fmt.Sprintf("%.2f", stock.ChangeRate),
			stock.Time,
		}
		if err := writer.Write(row); err != nil {
			return err
		}
	}

	return nil
}

// decodeGB2312 将GB2312编码的字符串转换为UTF-8
func decodeGB2312(s string) (string, error) {
	// 创建GB2312到UTF-8的转换器
	transformer := simplifiedchinese.GBK.NewDecoder()
	
	// 将字符串转换为字节切片
	gbkBytes := []byte(s)
	
	// 进行编码转换
	utf8Bytes, _, err := transform.Bytes(transformer, gbkBytes)
	if err != nil {
		return s, fmt.Errorf("GB2312解码失败: %v", err)
	}
	
	return string(utf8Bytes), nil
}
posted @ 2025-11-04 19:46  卓能文  阅读(1)  评论(0)    收藏  举报