go语言获取腾讯股票示例

package main

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

// StockInfo 股票信息结构体
type StockInfo struct {
	Symbol    string  `json:"symbol"`    // 股票代码(带市场前缀)
	Code      string  `json:"code"`      // 股票代码
	Name      string  `json:"name"`      // 股票名称
	Price     float64 `json:"price"`     // 当前价格
	Change    float64 `json:"change"`    // 涨跌额
	ChangeP   float64 `json:"changeP"`   // 涨跌幅
	Volume    int64   `json:"volume"`    // 成交量(手)
	Amount    float64 `json:"amount"`    // 成交额(万元)
	High      float64 `json:"high"`      // 最高价
	Low       float64 `json:"low"`       // 最低价
	Open      float64 `json:"open"`      // 开盘价
	PrevClose float64 `json:"prevClose"` // 昨收价
	Market    string  `json:"market"`    // 市场
	Time      string  `json:"time"`      // 更新时间
}

// 腾讯财经实时行情接口
const (
	// 单只股票查询格式:http://qt.gtimg.cn/q=sh600000
	// 批量查询格式:http://qt.gtimg.cn/q=sh600000,sz000001
	TENCENT_API_URL = "http://qt.gtimg.cn/q=%s"
)

// 主要A股指数代码
var majorIndices = []string{
	"sh000001", // 上证指数
	"sz399001", // 深证成指
	"sz399006", // 创业板指
	"bj899050", // 北证50
}

// 示例股票代码(用于演示)
var sampleStocks = []string{
	"sh600000", // 浦发银行
	"sh601318", // 中国平安
	"sz000001", // 平安银行
	"sz000858", // 五粮液
	"sz002415", // 海康威视
	"bj430090", // 同辉信息
}

func main() {
	fmt.Println("=== A股信息获取程序 ===")
	fmt.Println("使用腾讯财经接口获取实时股票数据")
	
	// 1. 获取主要指数
	fmt.Println("\n1. 获取主要指数行情:")
	indices, err := GetStocksData(majorIndices)
	if err != nil {
		fmt.Printf("获取指数失败: %v\n", err)
	} else {
		for _, index := range indices {
			color := ""
			if index.Change > 0 {
				color = "\033[31m" // 红色
			} else if index.Change < 0 {
				color = "\033[32m" // 绿色
			}
			reset := "\033[0m"
			fmt.Printf("%s%s: %.2f %s%.2f %.2f%%%s\n", 
				index.Name, index.Code, index.Price, color, index.Change, index.ChangeP, reset)
		}
	}
	
	// 2. 获取示例股票数据
	fmt.Println("\n2. 获取示例股票行情:")
	stocks, err := GetStocksData(sampleStocks)
	if err != nil {
		fmt.Printf("获取股票数据失败: %v\n", err)
		return
	}
	
	// 显示股票信息
	for _, stock := range stocks {
		color := ""
		if stock.Change > 0 {
			color = "\033[31m" // 红色
		} else if stock.Change < 0 {
			color = "\033[32m" // 绿色
		}
		reset := "\033[0m"
		
		fmt.Printf("%s%s %s: %.2f %s%.2f %.2f%%%s 成交量: %d手\n", 
			stock.Market, stock.Code, stock.Name, stock.Price, 
			color, stock.Change, stock.ChangeP, reset, stock.Volume)
	}
	
	// 3. 保存数据到文件
	fmt.Println("\n3. 保存数据到文件...")
	err = SaveDataToFiles(stocks)
	if err != nil {
		fmt.Printf("保存文件失败: %v\n", err)
	} else {
		fmt.Println("数据已保存到以下文件:")
		fmt.Println("  - stocks.json: JSON格式数据")
		fmt.Println("  - stocks.csv: CSV格式数据")
	}
	
	fmt.Println("\n程序执行完成!")
}

// GetStocksData 获取股票数据
func GetStocksData(symbols []string) ([]StockInfo, error) {
	if len(symbols) == 0 {
		return nil, fmt.Errorf("股票代码列表为空")
	}
	
	// 构建查询参数
	query := strings.Join(symbols, ",")
	url := fmt.Sprintf(TENCENT_API_URL, query)
	
	// 发送HTTP请求
	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Get(url)
	if err != nil {
		return nil, fmt.Errorf("HTTP请求失败: %v", err)
	}
	defer resp.Body.Close()
	
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("读取响应失败: %v", err)
	}
	
	// 将GB2312编码转换为UTF-8
	utf8Body, err := GB2312ToUTF8(body)
	if err != nil {
		return nil, fmt.Errorf("编码转换失败: %v", err)
	}
	
	// 解析数据
	return ParseTencentResponse(string(utf8Body))
}

// GB2312ToUTF8 将GB2312编码转换为UTF-8
func GB2312ToUTF8(gb2312Bytes []byte) ([]byte, error) {
	decoder := simplifiedchinese.GBK.NewDecoder()
	reader := transform.NewReader(strings.NewReader(string(gb2312Bytes)), decoder)
	
	utf8Bytes, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	
	return utf8Bytes, nil
}

// ParseTencentResponse 解析腾讯财经响应数据
func ParseTencentResponse(data string) ([]StockInfo, error) {
	var stocks []StockInfo
	
	lines := strings.Split(data, ";")
	
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		
		// 解析单只股票数据
		stock, err := ParseStockLine(line)
		if err != nil {
			// 跳过解析失败的行
			continue
		}
		
		stocks = append(stocks, stock)
	}
	
	return stocks, nil
}

// ParseStockLine 解析单只股票数据行
func ParseStockLine(line string) (StockInfo, error) {
	// 数据格式: v_sh600000="1~浦发银行~600000~8.12~8.10~8.13~1234567~8901234~..."
	parts := strings.SplitN(line, "=", 2)
	if len(parts) < 2 {
		return StockInfo{}, fmt.Errorf("数据格式错误")
	}
	
	// 提取股票代码
	symbol := strings.TrimPrefix(parts[0], "v_")
	
	// 解析数据字段
	dataStr := strings.Trim(parts[1], "\"")
	fields := strings.Split(dataStr, "~")
	
	if len(fields) < 40 {
		return StockInfo{}, fmt.Errorf("数据字段不足")
	}
	
	// 创建股票信息
	stock := StockInfo{
		Symbol: symbol,
		Code:   fields[2],
		Name:   fields[1],
		Market: getMarketFromSymbol(symbol),
		Time:   time.Now().Format("2006-01-02 15:04:05"),
	}
	
	// 解析数值字段
	stock.Price = parseFloat(fields[3])
	stock.Change = parseFloat(fields[4])
	stock.ChangeP = parseFloat(fields[5])
	stock.Volume = parseInt(fields[6])
	stock.Amount = parseFloat(fields[7]) / 10000 // 转换为万元
	stock.High = parseFloat(fields[8])
	stock.Low = parseFloat(fields[9])
	stock.Open = parseFloat(fields[10])
	stock.PrevClose = parseFloat(fields[11])
	
	return stock, nil
}

// getMarketFromSymbol 从股票代码获取市场
func getMarketFromSymbol(symbol string) string {
	if strings.HasPrefix(symbol, "sh") {
		return "上证"
	} else if strings.HasPrefix(symbol, "sz") {
		return "深证"
	} else if strings.HasPrefix(symbol, "bj") {
		return "北证"
	}
	return "未知"
}

// parseFloat 解析浮点数
func parseFloat(s string) float64 {
	if s == "" {
		return 0
	}
	val, _ := strconv.ParseFloat(s, 64)
	return val
}

// parseInt 解析整数
func parseInt(s string) int64 {
	if s == "" {
		return 0
	}
	val, _ := strconv.ParseInt(s, 10, 64)
	return val
}

// SaveDataToFiles 保存数据到多个文件格式
func SaveDataToFiles(stocks []StockInfo) error {
	// 保存为JSON
	jsonData := map[string]interface{}{
		"timestamp": time.Now().Format("2006-01-02 15:04:05"),
		"count":     len(stocks),
		"stocks":    stocks,
	}
	
	jsonBytes, err := json.MarshalIndent(jsonData, "", "  ")
	if err != nil {
		return err
	}
	
	if err := os.WriteFile("stocks.json", jsonBytes, 0644); err != nil {
		return err
	}
	
	// 保存为CSV
	csvFile, err := os.Create("stocks.csv")
	if err != nil {
		return err
	}
	defer csvFile.Close()
	
	writer := csv.NewWriter(csvFile)
	defer writer.Flush()
	
	// 写入表头
	header := []string{"代码", "名称", "市场", "价格", "涨跌额", "涨跌幅", "成交量(手)", "成交额(万元)", "最高", "最低", "开盘", "昨收", "更新时间"}
	if err := writer.Write(header); err != nil {
		return err
	}
	
	// 写入数据行
	for _, stock := range stocks {
		row := []string{
			stock.Code,
			stock.Name,
			stock.Market,
			fmt.Sprintf("%.2f", stock.Price),
			fmt.Sprintf("%.2f", stock.Change),
			fmt.Sprintf("%.2f%%", stock.ChangeP),
			fmt.Sprintf("%d", stock.Volume),
			fmt.Sprintf("%.2f", stock.Amount),
			fmt.Sprintf("%.2f", stock.High),
			fmt.Sprintf("%.2f", stock.Low),
			fmt.Sprintf("%.2f", stock.Open),
			fmt.Sprintf("%.2f", stock.PrevClose),
			stock.Time,
		}
		if err := writer.Write(row); err != nil {
			return err
		}
	}
	
	return nil
}
posted @ 2025-11-04 15:23  卓能文  阅读(3)  评论(0)    收藏  举报