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分析股票结果仅供学习研究,投资有风险,请谨慎使用。
|
||||
- 开发环境主要基于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
10
app.go
@ -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
|
||||
}
|
||||
|
@ -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"]),
|
||||
|
@ -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"}) {
|
||||
|
@ -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
21
main.go
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user