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 mainimport ("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 + batchSizeif 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) {// 构建URLurl := "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 errorstock.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.PrevClosestock.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.Nameif 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
}