feat(stock-data):添加屏幕分辨率适配,动态调整应用窗口大小

- 新增 GetRealTimeStockPriceInfo 函数,用于获取指定股票的实时价格和时间
- 优化爬虫配置,提高数据抓取效率
- 添加屏幕分辨率适配,动态调整应用窗口大小
- 修复部分股票代码格式问题,确保数据准确性
This commit is contained in:
ArvinLovegood 2025-02-25 22:07:33 +08:00
parent 1763435aa1
commit 797a35eaa5
6 changed files with 173 additions and 13 deletions

View File

@ -10,7 +10,12 @@
- 本项目仅供娱乐不喜勿喷AI分析股票结果仅供学习研究投资有风险请谨慎使用。
- 开发环境主要基于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 接口格式模型 |
@ -47,10 +52,6 @@
### 2025.02.12 可配置的提问模板
- [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 !!! 重大更新!!!

10
app.go
View File

@ -21,6 +21,7 @@ import (
"golang.org/x/sys/windows/registry"
"os"
"strings"
"syscall"
"time"
)
@ -638,3 +639,12 @@ func (a *App) ExportConfig() string {
}
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
}

View File

@ -38,6 +38,7 @@ func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bo
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"]),
@ -88,6 +89,73 @@ func (c *CrawlerApi) GetHtml(url, waitVisible string, headless bool) (string, bo
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) {
htmlContent := ""
*actions = append(*actions, chromedp.InnerHTML("body", &htmlContent))
@ -99,6 +167,7 @@ func (c *CrawlerApi) GetHtmlWithActions(actions *[]chromedp.Action, headless boo
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"]),

View File

@ -585,6 +585,45 @@ func (IndexBasic) TableName() string {
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 {
if strutil.HasPrefixAny(stockCode, []string{"HK", "hk"}) {

View File

@ -1,6 +1,7 @@
package data
import (
"context"
"encoding/json"
"fmt"
"github.com/duke-git/lancet/v2/convertor"
@ -9,6 +10,7 @@ import (
"go-stock/backend/db"
"go-stock/backend/logger"
"io/ioutil"
"regexp"
"strings"
"testing"
"time"
@ -43,8 +45,36 @@ func TestSearchStockInfoByCode(t *testing.T) {
}
func TestSearchStockPriceInfo(t *testing.T) {
SearchStockPriceInfo("hk06030", 30)
//SearchStockPriceInfo("sh600859", 30)
//SearchStockPriceInfo("hk06030", 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) {
@ -52,7 +82,7 @@ func TestParseFullSingleStockData(t *testing.T) {
SetHeader("Host", "hq.sinajs.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").
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 {
logger.SugaredLogger.Error(err.Error())
}

21
main.go
View File

@ -107,14 +107,25 @@ func main() {
logger.NewDefaultLogger().Info("version: " + Version)
logger.NewDefaultLogger().Info("commit: " + VersionCommit)
// 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",
Width: 1366,
Height: 920,
Width: width * 2 / 3,
Height: height * 2 / 3,
MinWidth: 1024,
MinHeight: 768,
MaxWidth: 1920,
MaxHeight: 960,
MaxWidth: width,
MaxHeight: height,
DisableResize: false,
Fullscreen: false,
Frameless: true,