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
}