mirror of
https://github.com/ArvinLovegood/go-stock.git
synced 2025-07-19 00:00:09 +08:00
feat(stock-data):添加屏幕分辨率适配,动态调整应用窗口大小
- 新增 GetRealTimeStockPriceInfo 函数,用于获取指定股票的实时价格和时间 - 优化爬虫配置,提高数据抓取效率 - 添加屏幕分辨率适配,动态调整应用窗口大小 - 修复部分股票代码格式问题,确保数据准确性
This commit is contained in:
parent
1763435aa1
commit
797a35eaa5
11
README.md
11
README.md
@ -10,7 +10,12 @@
|
|||||||
- 本项目仅供娱乐,不喜勿喷,AI分析股票结果仅供学习研究,投资有风险,请谨慎使用。
|
- 本项目仅供娱乐,不喜勿喷,AI分析股票结果仅供学习研究,投资有风险,请谨慎使用。
|
||||||
- 开发环境主要基于Windows10+,其他平台未测试或功能受限。
|
- 开发环境主要基于Windows10+,其他平台未测试或功能受限。
|
||||||
|
|
||||||
### 💬 大模型
|
### 📦 立即体验
|
||||||
|
- 安装版:[go-stock-amd64-installer.exe](https://github.com/ArvinLovegood/go-stock/releases)
|
||||||
|
- 绿色版:[go-stock-windows-amd64.exe](https://github.com/ArvinLovegood/go-stock/releases)
|
||||||
|
|
||||||
|
|
||||||
|
### 💬 支持大模型/平台
|
||||||
| 模型 | 状态 | 备注 |
|
| 模型 | 状态 | 备注 |
|
||||||
| --- | --- |-------------------------------------------|
|
| --- | --- |-------------------------------------------|
|
||||||
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 |
|
| [OpenAI](https://platform.openai.com/) | ✅ | 可接入任何 OpenAI 接口格式模型 |
|
||||||
@ -47,10 +52,6 @@
|
|||||||
### 2025.02.12 可配置的提问模板
|
### 2025.02.12 可配置的提问模板
|
||||||
- [v2025.2.12.7-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.12.7-alpha)
|
- [v2025.2.12.7-alpha](https://github.com/ArvinLovegood/go-stock/releases/tag/v2025.2.12.7-alpha)
|
||||||
|
|
||||||
## 📦 立即体验
|
|
||||||
- 安装版:[go-stock-amd64-installer.exe](https://github.com/ArvinLovegood/go-stock/releases)
|
|
||||||
- 绿色版:[go-stock-windows-amd64.exe](https://github.com/ArvinLovegood/go-stock/releases)
|
|
||||||
|
|
||||||
|
|
||||||
## 🦄 重大更新
|
## 🦄 重大更新
|
||||||
### BIG NEWS !!! 重大更新!!!
|
### BIG NEWS !!! 重大更新!!!
|
||||||
|
10
app.go
10
app.go
@ -21,6 +21,7 @@ import (
|
|||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -638,3 +639,12 @@ func (a *App) ExportConfig() string {
|
|||||||
}
|
}
|
||||||
return "导出成功:" + file
|
return "导出成功:" + file
|
||||||
}
|
}
|
||||||
|
func getScreenResolution() (int, int, error) {
|
||||||
|
user32 := syscall.NewLazyDLL("user32.dll")
|
||||||
|
getSystemMetrics := user32.NewProc("GetSystemMetrics")
|
||||||
|
|
||||||
|
width, _, _ := getSystemMetrics.Call(0)
|
||||||
|
height, _, _ := getSystemMetrics.Call(1)
|
||||||
|
|
||||||
|
return int(width), int(height), nil
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bo
|
|||||||
c.crawlerCtx,
|
c.crawlerCtx,
|
||||||
chromedp.ExecPath(path),
|
chromedp.ExecPath(path),
|
||||||
chromedp.Flag("headless", headless),
|
chromedp.Flag("headless", headless),
|
||||||
|
chromedp.Flag("blink-settings", "imagesEnabled=false"),
|
||||||
chromedp.Flag("disable-javascript", false),
|
chromedp.Flag("disable-javascript", false),
|
||||||
chromedp.Flag("disable-gpu", true),
|
chromedp.Flag("disable-gpu", true),
|
||||||
chromedp.UserAgent(c.crawlerBaseInfo.Headers["User-Agent"]),
|
chromedp.UserAgent(c.crawlerBaseInfo.Headers["User-Agent"]),
|
||||||
@ -88,6 +89,73 @@ func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bo
|
|||||||
return htmlContent, true
|
return htmlContent, true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CrawlerApi) GetHtmlWithNoCancel(url, waitVisible string, headless bool) (html string, ok bool, parent context.CancelFunc, child context.CancelFunc) {
|
||||||
|
htmlContent := ""
|
||||||
|
path, e := checkBrowserOnWindows()
|
||||||
|
logger.SugaredLogger.Infof("GetHtml path:%s", path)
|
||||||
|
var parentCancel context.CancelFunc
|
||||||
|
var childCancel context.CancelFunc
|
||||||
|
var pctx context.Context
|
||||||
|
var cctx context.Context
|
||||||
|
|
||||||
|
if e {
|
||||||
|
pctx, parentCancel = chromedp.NewExecAllocator(
|
||||||
|
c.crawlerCtx,
|
||||||
|
chromedp.ExecPath(path),
|
||||||
|
chromedp.Flag("headless", headless),
|
||||||
|
chromedp.Flag("blink-settings", "imagesEnabled=false"),
|
||||||
|
chromedp.Flag("disable-javascript", false),
|
||||||
|
chromedp.Flag("disable-gpu", true),
|
||||||
|
chromedp.UserAgent(c.crawlerBaseInfo.Headers["User-Agent"]),
|
||||||
|
chromedp.Flag("disable-background-networking", true),
|
||||||
|
chromedp.Flag("enable-features", "NetworkService,NetworkServiceInProcess"),
|
||||||
|
chromedp.Flag("disable-background-timer-throttling", true),
|
||||||
|
chromedp.Flag("disable-backgrounding-occluded-windows", true),
|
||||||
|
chromedp.Flag("disable-breakpad", true),
|
||||||
|
chromedp.Flag("disable-client-side-phishing-detection", true),
|
||||||
|
chromedp.Flag("disable-default-apps", true),
|
||||||
|
chromedp.Flag("disable-dev-shm-usage", true),
|
||||||
|
chromedp.Flag("disable-extensions", true),
|
||||||
|
chromedp.Flag("disable-features", "site-per-process,Translate,BlinkGenPropertyTrees"),
|
||||||
|
chromedp.Flag("disable-hang-monitor", true),
|
||||||
|
chromedp.Flag("disable-ipc-flooding-protection", true),
|
||||||
|
chromedp.Flag("disable-popup-blocking", true),
|
||||||
|
chromedp.Flag("disable-prompt-on-repost", true),
|
||||||
|
chromedp.Flag("disable-renderer-backgrounding", true),
|
||||||
|
chromedp.Flag("disable-sync", true),
|
||||||
|
chromedp.Flag("force-color-profile", "srgb"),
|
||||||
|
chromedp.Flag("metrics-recording-only", true),
|
||||||
|
chromedp.Flag("safebrowsing-disable-auto-update", true),
|
||||||
|
chromedp.Flag("enable-automation", true),
|
||||||
|
chromedp.Flag("password-store", "basic"),
|
||||||
|
chromedp.Flag("use-mock-keychain", true),
|
||||||
|
)
|
||||||
|
//defer pcancel()
|
||||||
|
cctx, childCancel = chromedp.NewContext(pctx, chromedp.WithLogf(logger.SugaredLogger.Infof))
|
||||||
|
//defer cancel()
|
||||||
|
err := chromedp.Run(cctx, chromedp.Navigate(url),
|
||||||
|
chromedp.WaitVisible(waitVisible, chromedp.ByQuery), // 确保 元素可见
|
||||||
|
chromedp.WaitReady(waitVisible, chromedp.ByQuery), // 确保 元素准备好
|
||||||
|
chromedp.InnerHTML("body", &htmlContent),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err.Error())
|
||||||
|
return "", false, parentCancel, childCancel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cctx, childCancel = chromedp.NewContext(c.crawlerCtx, chromedp.WithLogf(logger.SugaredLogger.Infof))
|
||||||
|
//defer cancel()
|
||||||
|
err := chromedp.Run(cctx, chromedp.Navigate(url), chromedp.WaitVisible("body"), chromedp.InnerHTML("body", &htmlContent))
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Error(err.Error())
|
||||||
|
return "", false, parentCancel, childCancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return htmlContent, true, parentCancel, childCancel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CrawlerApi) GetHtmlWithActions(actions *[]chromedp.Action, headless bool) (string, bool) {
|
func (c *CrawlerApi) GetHtmlWithActions(actions *[]chromedp.Action, headless bool) (string, bool) {
|
||||||
htmlContent := ""
|
htmlContent := ""
|
||||||
*actions = append(*actions, chromedp.InnerHTML("body", &htmlContent))
|
*actions = append(*actions, chromedp.InnerHTML("body", &htmlContent))
|
||||||
@ -99,6 +167,7 @@ func (c *CrawlerApi) GetHtmlWithActions(actions *[]chromedp.Action, headless boo
|
|||||||
c.crawlerCtx,
|
c.crawlerCtx,
|
||||||
chromedp.ExecPath(path),
|
chromedp.ExecPath(path),
|
||||||
chromedp.Flag("headless", headless),
|
chromedp.Flag("headless", headless),
|
||||||
|
chromedp.Flag("blink-settings", "imagesEnabled=false"),
|
||||||
chromedp.Flag("disable-javascript", false),
|
chromedp.Flag("disable-javascript", false),
|
||||||
chromedp.Flag("disable-gpu", true),
|
chromedp.Flag("disable-gpu", true),
|
||||||
chromedp.UserAgent(c.crawlerBaseInfo.Headers["User-Agent"]),
|
chromedp.UserAgent(c.crawlerBaseInfo.Headers["User-Agent"]),
|
||||||
|
@ -585,6 +585,45 @@ func (IndexBasic) TableName() string {
|
|||||||
return "tushare_index_basic"
|
return "tushare_index_basic"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RealTimeStockPriceInfo struct {
|
||||||
|
StockCode string
|
||||||
|
Price string `json:"当前价格"`
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRealTimeStockPriceInfo(ctx context.Context, stockCode string) (price, priceTime string) {
|
||||||
|
if strutil.HasPrefixAny(stockCode, []string{"SZ", "SH", "sh", "sz"}) {
|
||||||
|
crawlerAPI := CrawlerApi{}
|
||||||
|
crawlerBaseInfo := CrawlerBaseInfo{
|
||||||
|
Name: "EastmoneyCrawler",
|
||||||
|
Description: "EastmoneyCrawler Description",
|
||||||
|
BaseUrl: "https://quote.eastmoney.com/",
|
||||||
|
Headers: map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0"},
|
||||||
|
}
|
||||||
|
crawlerAPI = crawlerAPI.NewCrawler(ctx, crawlerBaseInfo)
|
||||||
|
htmlContent, ok := crawlerAPI.GetHtml(fmt.Sprintf("https://quote.eastmoney.com/%s.html", stockCode), "div.zxj", true)
|
||||||
|
if ok {
|
||||||
|
price := ""
|
||||||
|
priceTime := ""
|
||||||
|
document, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
|
||||||
|
if err != nil {
|
||||||
|
logger.SugaredLogger.Errorf("GetRealTimeStockPriceInfo error: %v", err)
|
||||||
|
}
|
||||||
|
document.Find("div.zxj").Each(func(i int, selection *goquery.Selection) {
|
||||||
|
price = selection.Text()
|
||||||
|
logger.SugaredLogger.Infof("股票代码: %s, 当前价格: %s", stockCode, price)
|
||||||
|
})
|
||||||
|
|
||||||
|
document.Find("span.quote_title_time").Each(func(i int, selection *goquery.Selection) {
|
||||||
|
priceTime = selection.Text()
|
||||||
|
logger.SugaredLogger.Infof("股票代码: %s, 当前价格时间: %s", stockCode, priceTime)
|
||||||
|
})
|
||||||
|
return price, priceTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price, priceTime
|
||||||
|
}
|
||||||
|
|
||||||
func SearchStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
func SearchStockPriceInfo(stockCode string, crawlTimeOut int64) *[]string {
|
||||||
|
|
||||||
if strutil.HasPrefixAny(stockCode, []string{"HK", "hk"}) {
|
if strutil.HasPrefixAny(stockCode, []string{"HK", "hk"}) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/duke-git/lancet/v2/convertor"
|
"github.com/duke-git/lancet/v2/convertor"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"go-stock/backend/db"
|
"go-stock/backend/db"
|
||||||
"go-stock/backend/logger"
|
"go-stock/backend/logger"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -43,8 +45,36 @@ func TestSearchStockInfoByCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchStockPriceInfo(t *testing.T) {
|
func TestSearchStockPriceInfo(t *testing.T) {
|
||||||
SearchStockPriceInfo("hk06030", 30)
|
//SearchStockPriceInfo("hk06030", 30)
|
||||||
//SearchStockPriceInfo("sh600859", 30)
|
SearchStockPriceInfo("sh600171", 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRealTimeStockPriceInfo(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
text, texttime := GetRealTimeStockPriceInfo(ctx, "sh600171")
|
||||||
|
logger.SugaredLogger.Infof("res:%s,%s", text, texttime)
|
||||||
|
|
||||||
|
text, texttime = GetRealTimeStockPriceInfo(ctx, "sh600438")
|
||||||
|
logger.SugaredLogger.Infof("res:%s,%s", text, texttime)
|
||||||
|
|
||||||
|
texttime = strings.ReplaceAll(texttime, ")", "")
|
||||||
|
texttime = strings.ReplaceAll(texttime, "(", "")
|
||||||
|
parts := strings.Split(texttime, " ")
|
||||||
|
logger.SugaredLogger.Infof("parts:%+v", parts)
|
||||||
|
|
||||||
|
//去除中文字符
|
||||||
|
// 正则表达式匹配中文字符
|
||||||
|
re := regexp.MustCompile(`\p{Han}+`)
|
||||||
|
texttime = re.ReplaceAllString(texttime, "")
|
||||||
|
|
||||||
|
logger.SugaredLogger.Infof("texttime:%s", texttime)
|
||||||
|
location, err := time.ParseInLocation("2006-01-02 15:04:05", texttime, time.Local)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.SugaredLogger.Infof("location:%s", location.Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseFullSingleStockData(t *testing.T) {
|
func TestParseFullSingleStockData(t *testing.T) {
|
||||||
@ -52,7 +82,7 @@ func TestParseFullSingleStockData(t *testing.T) {
|
|||||||
SetHeader("Host", "hq.sinajs.cn").
|
SetHeader("Host", "hq.sinajs.cn").
|
||||||
SetHeader("Referer", "https://finance.sina.com.cn/").
|
SetHeader("Referer", "https://finance.sina.com.cn/").
|
||||||
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
|
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
|
||||||
Get(fmt.Sprintf(sinaStockUrl, time.Now().Unix(), "sh600859,sz000034,hk01810,hk00856"))
|
Get(fmt.Sprintf(sinaStockUrl, time.Now().Unix(), "sh600584,sz000938,hk01810,hk00856"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SugaredLogger.Error(err.Error())
|
logger.SugaredLogger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
21
main.go
21
main.go
@ -107,14 +107,25 @@ func main() {
|
|||||||
logger.NewDefaultLogger().Info("version: " + Version)
|
logger.NewDefaultLogger().Info("version: " + Version)
|
||||||
logger.NewDefaultLogger().Info("commit: " + VersionCommit)
|
logger.NewDefaultLogger().Info("commit: " + VersionCommit)
|
||||||
// Create application with options
|
// Create application with options
|
||||||
err := wails.Run(&options.App{
|
var width, height int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
width, height, err = getScreenResolution()
|
||||||
|
if err != nil {
|
||||||
|
logger.NewDefaultLogger().Error("get screen resolution error")
|
||||||
|
width = 1366
|
||||||
|
height = 768
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create application with options
|
||||||
|
err = wails.Run(&options.App{
|
||||||
Title: "go-stock",
|
Title: "go-stock",
|
||||||
Width: 1366,
|
Width: width * 2 / 3,
|
||||||
Height: 920,
|
Height: height * 2 / 3,
|
||||||
MinWidth: 1024,
|
MinWidth: 1024,
|
||||||
MinHeight: 768,
|
MinHeight: 768,
|
||||||
MaxWidth: 1920,
|
MaxWidth: width,
|
||||||
MaxHeight: 960,
|
MaxHeight: height,
|
||||||
DisableResize: false,
|
DisableResize: false,
|
||||||
Fullscreen: false,
|
Fullscreen: false,
|
||||||
Frameless: true,
|
Frameless: true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user