package main /* #include // proxyConfig.dll 函数声明 extern char* ProxyTypeManager(char* proxyType, char* username, char* password, char* machineCode); extern void FreeCString(char* str); */ import "C" import ( "context" "database/sql" "encoding/json" "fmt" "io" "log" "math/rand" "net/http" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "sync" "syscall" "time" "unsafe" "github.com/PuerkitoBio/goquery" "github.com/chromedp/chromedp" _ "github.com/go-sql-driver/mysql" "github.com/parnurzeal/gorequest" ) const ( UseProxy = "proxy" // 使用代理 NotProxy = "direct" // 不用代理代理 ) type Config struct { App struct { MaxRetryTimes int `ini:"app.max_retry_times" json:"max_retry_times" default:"3"` RateLimitDelay time.Duration `ini:"app.rate_limit_delay" json:"rate_limit_delay" default:"500ms"` Size int `ini:"app.size" json:"size" default:"5"` DefaultUserAgent string `ini:"app.default_user_agent" json:"default_user_agent" default:"Mozilla/5.0"` } `ini:"app" json:"app"` API struct { LoginURL string `ini:"api.login_url" json:"login_url" default:"https://login.kongfz.com/Pc/Login/account"` BookSearchURL string `ini:"api.book_search_url" json:"book_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list"` ProductSearchURL string `ini:"api.product_search_url" json:"product_search_url" default:"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list"` } `ini:"api" json:"api"` Proxy struct { Servers string `ini:"proxy.servers" json:"servers" default:"http-dynamic.xiaoxiangdaili.com,http-dynamic-S02.xiaoxiangdaili.com,http-dynamic-S03.xiaoxiangdaili.com,http-dynamic-S04.xiaoxiangdaili.com"` Username string `ini:"proxy.username" json:"username" default:"1297757178467602432"` Password string `ini:"proxy.password" json:"password" default:"QgQBvP7f"` TailMachineCode string `ini:"proxy.tail_machine_code" json:"tail_machine_code" default:"b7bf22a237ec692f13fcc2c43ee63252"` TailCardKey string `ini:"proxy.tail_card_key" json:"tail_card_key" default:"DL_20_YK_1920acb2129844c2aabade3896560a9b"` ProxyFilePath string `ini:"proxy.proxy_file_path" json:"proxy_file_path" default:"dll/proxyConfig.dll"` } `ini:"proxy" json:"proxy"` Database struct { Username string `ini:"database.username" json:"username" default:"newAdmin"` Password string `ini:"database.password" json:"password" default:"bYPp8SbBe5F7nz2i"` Host string `ini:"database.host" json:"host" default:"146.56.227.42:3306"` Name string `ini:"database.name" json:"name" default:"newadmin"` } `ini:"database" json:"database"` } // 条目详情结构体 type BookInfo struct { BookName string `json:"book_name"` // 书名 Author string `json:"author"` // 作者 Publisher string `json:"publisher"` // 出版社 ISBN string `json:"isbn"` // ISBN PublicationTime int64 `json:"publication_time"` // 出版时间 Edition string `json:"edition"` // 版次 PrintTime string `json:"print_time"` // 印刷时间 FixPrice string `json:"fix_price"` // 定价 BindingLayout string `json:"binding_layout"` // 装帧 Format string `json:"format"` // 开本 Paper string `json:"paper"` // 纸张 Pages string `json:"pages"` // 页数 Wordage string `json:"wordage"` // 字数 Languages string `json:"languages"` // 语种 Era string `json:"era"` // 年代 EngravingMethod string `json:"engraving_method"` // 刻印方式 Dimensions string `json:"dimensions"` // 尺寸 VolumeNumber string `json:"volume_number"` // 册数 BookPic string `json:"book_pic"` // 图书封面图(官图) BookPicS string `json:"book_pic_s"` // 图书封面图(实拍图) SellingPrice string `json:"selling_price"` // 售价 Condition string `json:"condition"` // 品相 ExpressDeliveryFee string `json:"express_delivery_fee"` // 快递费 Editor string `json:"editor"` // 编辑 Category string `json:"category"` // 分类 BuyCount string `json:"buy_count"` // 买过 SellCount string `json:"sell_count"` // 在卖 Content string `json:"content"` // 内容 Mid int64 `json:"mid"` // 商家id ItemId int64 `json:"item_id"` // 商品id ShopId int64 `json:"shop_id"` // 店铺id DetailUrl string `json:"detail_url"` // 商品详情url } // API响应结构 type APIResp struct { Success bool `json:"success"` Message string `json:"message,omitempty"` GoodsNum string `json:"goods_num,omitempty"` PNum string `json:"pnum,omitempty"` Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` } // APIResponse type APIResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` Data interface{} `json:"data,omitempty"` } // 账号凭证结构 type AccountCredential struct { ID int64 Username string Password string Token string } // 全局变量 var ( cf Config // 配置信息 tailProxyMu sync.Mutex // 互斥锁 proxyFailCount int mu sync.RWMutex // 数据库锁 // 全局代理管理器 globalProxyManager *ProxyConfigManager proxyManagerOnce sync.Once proxyManagerInitErr error ) // ProductInfo 商品信息结构 type ProductInfo struct { ItemID string `json:"itemId"` BookName string `json:"bookName"` Price string `json:"price"` ShippingFee string `json:"shippingFee"` } // ProductResponse 响应结构 type ProductResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` Data []ProductInfo `json:"data,omitempty"` } // 并行获取详情的结果结构 type DetailResult struct { URL string Doc *goquery.Document Error error Index int } // 第一阶段:收集基本信息并识别需要详情的项目 type BookItem struct { Book BookInfo Selection *goquery.Selection HasDetail bool DetailURL string Index int } // 分类项结构体 type SalesCategory struct { Key string `json:"key"` Value int `json:"value"` } // 图书详情响应结构体 type BookDetailResponse struct { Status bool `json:"status"` Result BookList `json:"result"` ErrMessage string `json:"errMessage"` ErrCode int `json:"errCode"` } // 图书列表结构体 type BookList struct { Current int `json:"current"` Data []BookInformation `json:"data"` Total int `json:"total"` } // 图书信息结构体 type BookInformation struct { Author string `json:"author"` BookName string `json:"bookName"` ContentIntroduction string `json:"contentIntroduction"` ImgUrl string `json:"imgUrl"` Isbn string `json:"isbn"` ItemUrls ItemUrls `json:"itemUrls"` Mid int `json:"mid"` NewMinPrice string `json:"newMinPrice"` OldMinPrice string `json:"oldMinPrice"` Press string `json:"press"` Price string `json:"price"` PubDate string `json:"pubDate"` RiseTag string `json:"riseTag"` AuthorArr []AuthorInfo `json:"authorArr"` PressUrl string `json:"pressUrl"` } // 作者信息结构体 type AuthorInfo struct { Name string `json:"name"` OriName string `json:"oriName"` Nationality string `json:"nationality"` Role string `json:"role"` Url string `json:"url"` } // 商品链接结构体 type ItemUrls struct { AppUrl string `json:"appUrl"` MUrl string `json:"mUrl"` MiniUrl string `json:"miniUrl"` PcUrl string `json:"pcUrl"` } type UserInfo struct { UserID int64 `json:"userId"` Nickname string `json:"nickname"` Mobile string `json:"mobile"` } // 店铺快递费参数 type ParamsInfo struct { Params []Param `json:"params"` Area string `json:"area"` } type Param struct { UserId string `json:"userId"` ItemId string `json:"itemId"` } // ProxyConfigManager 代理配置DLL管理器 type ProxyConfigManager struct { dll *syscall.DLL } func NewProxyConfigManager(dllPath string) (*ProxyConfigManager, error) { dll, err := syscall.LoadDLL(dllPath) if err != nil { return nil, fmt.Errorf("加载代理配置DLL失败: %v", err) } return &ProxyConfigManager{dll: dll}, nil } func (m *ProxyConfigManager) Close() { if m.dll != nil { m.dll.Release() } } // ProxyTypeManager 调用代理类型管理器 func (m *ProxyConfigManager) ProxyTypeManager(proxyType, username, password, machineCode string) (string, error) { proc, err := m.dll.FindProc("ProxyTypeManager") if err != nil { return "", fmt.Errorf("找不到函数 ProxyTypeManager: %v", err) } // 准备参数 proxyTypePtr, _ := syscall.BytePtrFromString(proxyType) usernamePtr, _ := syscall.BytePtrFromString(username) passwordPtr, _ := syscall.BytePtrFromString(password) machineCodePtr, _ := syscall.BytePtrFromString(machineCode) // 调用函数 r1, _, err := proc.Call( uintptr(unsafe.Pointer(proxyTypePtr)), uintptr(unsafe.Pointer(usernamePtr)), uintptr(unsafe.Pointer(passwordPtr)), uintptr(unsafe.Pointer(machineCodePtr)), ) if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用 ProxyTypeManager 失败: %v", err) } // 转换结果 result := (*byte)(unsafe.Pointer(r1)) var resultBytes []byte for i := 0; ; i++ { bytePtr := (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(result)) + uintptr(i))) if *bytePtr == 0 { break } resultBytes = append(resultBytes, *bytePtr) } // 释放内存 freeProc, _ := m.dll.FindProc("FreeCString") if freeProc != nil { freeProc.Call(r1) } return string(resultBytes), nil } // 登录 func outLogin(username, password string) (string, error) { if username == "" || password == "" { return "", fmt.Errorf("请输入用户名和密码!") } formData := map[string]string{ "loginName": username, "loginPass": password, "returnUrl": "http://user.kongfz.com/", } resp, body, errs := gorequest.New(). Post(cf.API.LoginURL). Set("Content-Type", "application/x-www-form-urlencoded"). Set("User-Agent", cf.App.DefaultUserAgent). Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"). Send(formData). Timeout(15 * time.Second).End() if len(errs) > 0 { return "", fmt.Errorf("登录请求失败: %v", errs) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("登录失败(HTTP状态码: %d)", resp.StatusCode) } // 提取Cookie cookie := resp.Header.Get("Set-Cookie") if strings.Contains(body, "window.location.href='https://login.kongfz.cn/Pc/Session/rsync") { if cookie == "" { return "", fmt.Errorf("登录成功但未获取到Cookie") } // 登录成功 if strings.Contains(cookie, "PHPSESSID=") { token := strings.Split(strings.Split(cookie, "PHPSESSID=")[1], ";")[0] return token, nil } return "", fmt.Errorf("登录失败: 未找到PHPSESSID") } // 错误信息 var res struct { Status bool `json:"status"` ErrCode int `json:"errCode"` ErrInfo string `json:"errInfo"` } if err := json.Unmarshal([]byte(body), &res); err == nil { if res.ErrCode == 1001 || res.ErrCode == 1005 { return "", fmt.Errorf("账号或密码错误!") } if res.ErrInfo != "" { return "", fmt.Errorf("登录失败: %s", res.ErrInfo) } } return "", fmt.Errorf("登录失败,未知错误!") } // 获取用户信息(带有Out的都非官方标准接口) func outGetUserMsg(token string) (*UserInfo, error) { url := "https://user.kongfz.com/User/Index/getUserInfo/" resp, body, errs := gorequest.New(). Get(url). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Timeout(15 * time.Second). End() if len(errs) > 0 { return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var userInfo struct { Status bool `json:"status"` Data struct { UserID int64 `json:"userId"` Nickname string `json:"nickname"` Mobile string `json:"mobile"` } } if err := json.Unmarshal([]byte(body), &userInfo); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } user := &UserInfo{} if !userInfo.Status { return nil, fmt.Errorf("获取用户失败!") } user.UserID = userInfo.Data.UserID user.Nickname = userInfo.Data.Nickname user.Mobile = userInfo.Data.Mobile return user, nil } // 获取商品模版 func outGetGoodsTplMsg(token, itemId, proxy string) (map[string]interface{}, error) { if token == "" { return nil, fmt.Errorf("请先登录获取Token") } url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d", itemId, time.Now().Unix()) // 创建HTTP客户端 request := gorequest.New() // 设置代理(如果有提供代理URL) if proxy != "" { request.Proxy(proxy) } resp, body, errs := request.Get(url). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://seller.kongfz.com/"). Set("Origin", "https://seller.kongfz.com"). Timeout(15 * time.Second). End() if errs != nil { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var data map[string]interface{} if err := json.Unmarshal([]byte(body), &data); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if val, ok := data["status"].(float64); ok && val == 1 { return data, nil } return nil, fmt.Errorf("API返回错误: %+v", data) } // 获取商品列表-已登的店铺 func outGetGoodsListMsgFromSelfShop(token string, proxy string, itemSn string, priceMin string, priceMax string, startCreateTime string, endCreateTime string, requestType string, isItemSnEqual int, page int, size int) (map[string]interface{}, error) { if token == "" { return nil, fmt.Errorf("请先登录获取Token") } url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/unSold/list" formData := struct { ItemSn string `json:"itemSn"` PriceMin string `json:"priceMin"` PriceMax string `json:"priceMax"` StartCreateTime string `json:"startCreateTime"` EndCreateTime string `json:"endCreateTime"` RequestType string `json:"requestType,omitempty"` IsItemSnEqual int `json:"isItemSnEqual,omitempty"` Page int `json:"page,omitempty"` Size int `json:"size,omitempty"` }{ ItemSn: itemSn, PriceMin: priceMin, PriceMax: priceMax, StartCreateTime: startCreateTime, EndCreateTime: endCreateTime, RequestType: requestType, IsItemSnEqual: isItemSnEqual, Page: page, Size: size, } request := gorequest.New() if proxy != "" { request.Proxy(proxy) } resp, body, errs := request.Post(url). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Send(formData). Timeout(15 * time.Second). End() if errs != nil { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var data map[string]interface{} if err := json.Unmarshal([]byte(body), &data); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if _, ok := data["status"]; ok { return data, nil } return nil, fmt.Errorf("API返回错误: %+v", data) } // 删除商品-已登的店铺(带有Out的都非官方标准接口) func outDelGoodsFromSelfShop(token, proxy, itemId string) (map[string]interface{}, error) { if token == "" { return nil, fmt.Errorf("请先登录获取Token") } url := "https://seller.kongfz.com/pc-gw/book-manage-service/client/pc/goods/quickUpdate" formData := map[string]string{ "itemId": itemId, "updateType": "delete", "value": "1", } request := gorequest.New() if proxy != "" { request.Proxy(proxy) } resp, body, errs := request.Post(url). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Send(formData). Timeout(15 * time.Second). End() if errs != nil { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var data map[string]interface{} if err := json.Unmarshal([]byte(body), &data); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if status, ok := data["status"].(bool); ok && status { return data, nil } if status, ok := data["status"].(float64); ok && status == 1 { return data, nil } return nil, fmt.Errorf("API返回错误: %+v", data) } // 新增商品(带有Out的都非官方标准接口) func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) { if token == "" { return nil, fmt.Errorf("请先登录获取Token") } url := "https://seller.kongfz.com/pc/itemInfo/add" request := gorequest.New() if proxy != "" { request.Proxy(proxy) } resp, body, errs := request.Post(url). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Send(formData). Timeout(15 * time.Second). End() if errs != nil { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var data map[string]interface{} if err := json.Unmarshal([]byte(body), &data); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if val, ok := data["status"]; ok && val == 1 { return data, nil } return nil, fmt.Errorf("API返回错误: %+v", data) } // 获取图片URL(官图和拍图)带有店铺过滤 func outGetImageFilterShopId(token string, isbn string, shopId int, proxy string, isLiveImage int, isReturnMsg int) (map[string]string, error) { if isLiveImage == 0 { gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn) request := gorequest.New() if proxy != "" { request.Proxy(proxy) } resp, body, errs := request.Get(gtUrl). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://item.kongfz.com/"). Timeout(30 * time.Second). End() if len(errs) > 0 { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } // 解析响应 var apiGtResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { BookName string `json:"bookName"` Mid int64 `json:"mid"` ImgUrlEntity struct { BigImgUrl string `json:"bigImgUrl"` } `json:"imgUrlEntity"` Isbn string `json:"isbn"` BookShowInfo []string `json:"bookShowInfo"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } if err := json.Unmarshal([]byte(body), &apiGtResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if apiGtResp.ErrType == "102" { return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiGtResp.Message, apiGtResp.ErrType) } var info map[string]string // 如果找到条目,返回图片URL if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 { list := apiGtResp.Data.ItemResponse.List[0] info = map[string]string{ "book_name": list.BookName, "book_pic": list.ImgUrlEntity.BigImgUrl, "isbn": list.Isbn, } } return info, nil } if isLiveImage == 1 { // 实拍图 sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000", cf.API.ProductSearchURL, isbn, cf.App.Size) //创建HTTP客户端 requestSpt := gorequest.New() //设置代理(如果有提供代理URL) if proxy != "" { requestSpt.Proxy(proxy) } // 发送请求 respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl). Proxy(proxy). Set("Cookie", token). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://item.kongfz.com/"). Timeout(30 * time.Second). End() // 错误处理 if len(errsSpt) > 0 { return nil, fmt.Errorf("请求失败: %v", errsSpt) } // 检查HTTP状态码 if respSpt.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status) } // 解析响应 var apiSptResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { ItemId int64 `json:"itemId"` Title string `json:"title"` ImgUrl string `json:"imgUrl"` ImgBigUrl string `json:"imgBigUrl"` Author string `json:"author"` PubDateText string `json:"pubDateText"` Isbn string `json:"isbn"` Press string `json:"press"` ShopId int64 `json:"shopId"` TplRecords []struct { Key string `json:"key"` Value string `json:"value"` } `json:"tplRecords"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } // 解析JSON if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if apiSptResp.ErrType == "102" { return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiSptResp.Message, apiSptResp.ErrType) } var info map[string]string if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 { // 确定起始索引 var startIndex int if cf.App.Size >= apiSptResp.Data.ItemResponse.Total { startIndex = apiSptResp.Data.ItemResponse.Total - 1 } else { startIndex = cf.App.Size - 1 } for attempt := 0; attempt < 3; attempt++ { currentIndex := startIndex - attempt // 检查索引是否有效 if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) { log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex) continue } randomNum := rand.Intn(currentIndex + 1) item := apiSptResp.Data.ItemResponse.List[randomNum] // 检查图片URL是否存在 if item.ImgBigUrl == "" { log.Printf("[DEBUG] 索引 %d 的图片URL为空,跳过", randomNum) continue } // 过滤店铺 if item.ShopId == int64(shopId) { log.Printf("[DEBUG] 索引 %d 的店铺ID需要过滤,跳过", randomNum) continue } info = map[string]string{ "book_name": item.Title, "book_pic": item.ImgUrl, "isbn": item.Isbn, } return info, nil } } } return nil, nil } // 获取图片URL(官图和拍图) func outGetImageByIsbn(token string, isbn string, proxy string, isLiveImage int, isReturnMsg int) (*BookInfo, error) { fmt.Println("[DEBUG] 使用的ISBN: ", isbn) // isLiveImage 1实拍图 0官图 ,isReturnMsg 0商品信息 bookInfo := &BookInfo{} if isLiveImage == 0 { // 孔网官图请求 gtUrl := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn) // 创建HTTP客户端 requestGt := gorequest.New() // 设置代理(如果有提供代理URL) if proxy != "" { requestGt.Proxy(proxy) } // 发送请求 respGt, bodyGt, errsGt := requestGt.Get(gtUrl). Set("Cookie", fmt.Sprintf("PHPSESSID=%s", token)). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://item.kongfz.com/"). Timeout(30 * time.Second). End() if len(errsGt) > 0 { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errsGt { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errsGt) } //检查HTTP状态码 if respGt.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", respGt.Status) } // 解析响应 var apiGtResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { BookName string `json:"bookName"` Mid int64 `json:"mid"` ImgUrlEntity struct { BigImgUrl string `json:"bigImgUrl"` } `json:"imgUrlEntity"` Isbn string `json:"isbn"` BookShowInfo []string `json:"bookShowInfo"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } if err := json.Unmarshal([]byte(bodyGt), &apiGtResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if apiGtResp.ErrType == "102" { return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiGtResp.Message, apiGtResp.ErrType) } // 如果找到条目,返回图片URL if apiGtResp.Data.ItemResponse.Total > 0 && len(apiGtResp.Data.ItemResponse.List) > 0 { list := apiGtResp.Data.ItemResponse.List[0] bookShowInfo := list.BookShowInfo bookInfo.BookName = list.BookName bookInfo.BookPic = list.ImgUrlEntity.BigImgUrl bookInfo.ISBN = list.Isbn // 根据长度安全填充字段 if isReturnMsg == 0 { if len(bookShowInfo) > 0 { bookInfo.Author = bookShowInfo[0] } if len(bookShowInfo) > 1 { bookInfo.Publisher = bookShowInfo[1] } if len(bookShowInfo) > 2 { bookInfo.PublicationTime = validateDateFormat(bookShowInfo[2]) } if len(bookShowInfo) > 3 { bookInfo.BindingLayout = bookShowInfo[3] } if len(bookShowInfo) > 4 { bookInfo.FixPrice = bookShowInfo[4] } else { log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(bookShowInfo), bookShowInfo) } } } return bookInfo, nil } if isLiveImage == 1 { // 实拍图 sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000", cf.API.ProductSearchURL, isbn, cf.App.Size) //创建HTTP客户端 requestSpt := gorequest.New() //设置代理(如果有提供代理URL) if proxy != "" { requestSpt.Proxy(proxy) } // 发送请求 respSpt, bodySpt, errsSpt := requestSpt.Get(sptUrl). Proxy(proxy). Set("Cookie", token). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://item.kongfz.com/"). Timeout(30 * time.Second). End() // 错误处理 if len(errsSpt) > 0 { return nil, fmt.Errorf("请求失败: %v", errsSpt) } // 检查HTTP状态码 if respSpt.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", respSpt.Status) } // 解析响应 var apiSptResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { ItemId int64 `json:"itemId"` Title string `json:"title"` ImgUrl string `json:"imgUrl"` ImgBigUrl string `json:"imgBigUrl"` Author string `json:"author"` PubDateText string `json:"pubDateText"` Isbn string `json:"isbn"` Press string `json:"press"` ShopId int64 `json:"shopId"` TplRecords []struct { Key string `json:"key"` Value string `json:"value"` } `json:"tplRecords"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } // 解析JSON if err := json.Unmarshal([]byte(bodySpt), &apiSptResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } if apiSptResp.ErrType == "102" { return nil, fmt.Errorf("错误信息: %w,状态码: %s", apiSptResp.Message, apiSptResp.ErrType) } if apiSptResp.Data.ItemResponse.Total > 0 && len(apiSptResp.Data.ItemResponse.List) > 0 { // 确定其实索引 var startIndex int if cf.App.Size >= apiSptResp.Data.ItemResponse.Total { startIndex = apiSptResp.Data.ItemResponse.Total - 1 } else { startIndex = cf.App.Size - 1 } for attempt := 0; attempt < 3; attempt++ { currentIndex := startIndex - attempt // 检查索引是否有效 if currentIndex < 0 || currentIndex >= len(apiSptResp.Data.ItemResponse.List) { log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex) continue } randomNum := rand.Intn(currentIndex + 1) item := apiSptResp.Data.ItemResponse.List[randomNum] // 检查图片URL是否存在 if item.ImgBigUrl == "" { log.Printf("[DEBUG] 索引 %d 的图片URL为空,跳过", randomNum) continue } // 响应信息 bookInfo.BookPicS = item.ImgUrl bookInfo.ISBN = item.Isbn if bookInfo.BookName == "" { bookInfo.BookName = item.Title if isReturnMsg == 0 { // 安全地获取TplRecords中的值 if len(item.TplRecords) > 0 { bookInfo.Author = item.TplRecords[0].Value } if len(item.TplRecords) > 1 { bookInfo.Publisher = item.TplRecords[1].Value } if len(item.TplRecords) > 2 { bookInfo.PublicationTime = validateDateFormat(item.TplRecords[2].Value) } if len(item.TplRecords) > 3 { bookInfo.BindingLayout = item.TplRecords[3].Value } } } return bookInfo, nil } } } return nil, fmt.Errorf("查询失败,没有数据!") } // 获取商品列表通过店铺ID func outGetGoodsListMsgByShopId(shopId int, proxy string, retPrice int, isImage int, sortType string, sort string, priceMin float32, priceMax float32, pageNum, returnNum int) (books []BookInfo, goodsNum string, pNum string, err error) { // 判断店铺ID if shopId == 0 { return nil, "", "", fmt.Errorf("店铺编码为空!") } // 判断是否有图片,设置默认值0 var isImageStr string if isImage == 0 { isImageStr = "0" } else { isImageStr = "1" } // 判断一页图书数量,设置默认值100 if returnNum == 0 { returnNum = 100 } // 判断页数,设置默认值1 if pageNum == 0 { pageNum = 1 } // 判断排序类型,设置默认值sort if sortType == "" { sortType = "sort" } else { validSortTypes := map[string]bool{ "sort": true, "putDate": true, "newItem": true, "price": true, } if !validSortTypes[sortType] { return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: sort, putDate, newItem, price", sortType) } } // 判断排序,设置默认值desc if sort == "" { sort = "desc" } else { validSorts := map[string]bool{ "desc": true, "asc": true, } if !validSorts[sort] { return nil, "", "", fmt.Errorf("无效的排序类型: %s,可选值: desc, asc", sort) } } var url string var pMin int pMin = 0 var pMax int pMax = 0 // 判断价格下限,设置默认值0 if priceMin == 0 && priceMax == 0 { // 调用的url url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%d", shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, pMax) } else if priceMin == 0 { url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%d_%.2f", shopId, isImageStr, returnNum, pageNum, sortType, sort, pMin, priceMax) } else if priceMax == 0 { url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%d", shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, pMax) } else { url = fmt.Sprintf("https://shop.kongfz.com/%d/all/%s_%d_0_0_%d_%s_%s_%.2f_%.2f", shopId, isImageStr, returnNum, pageNum, sortType, sort, priceMin, priceMax) } // 发送请求 response, err := fetchResponse(url, proxy) if err != nil { return nil, "", "", err } body, err := io.ReadAll(response.Body) if err != nil { return nil, "", "", err } doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) // 全部商品数量 num := doc.Find("div.crumbs-nav-main.clearfix").Find("span") if match := regexp.MustCompile(`\d+`).FindString(num.Text()); match != "" { goodsNum = match } // 商品页数 pg := doc.Find("li.pull-right.page_num").Find("span") _, split, found := strings.Cut(strings.TrimSpace(pg.Text()), "/") if found { pNum = split } else { log.Printf("未找到页数!") } infoDiv := doc.Find("div.list-content") var params ParamsInfo if infoDiv.Length() > 0 { item := infoDiv.Find("div.item.clearfix") for i := 0; i < item.Length(); i++ { s := item.Eq(i) book := BookInfo{} // 书名 book.BookName = strings.TrimSpace(s.Find("div.title a.link").Text()) // 提取ISBN book.ISBN = strings.TrimSpace(s.AttrOr("isbn", "")) // 店铺ID shopid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("shopid", ""))) book.ShopId = int64(shopid) // 商品ID itemid, _ := strconv.Atoi(strings.TrimSpace(s.AttrOr("itemid", ""))) book.ItemId = int64(itemid) // 详情URL book.DetailUrl = s.Find("div.item-img a.img-box").AttrOr("href", "") // 价格 if retPrice == 0 { book.SellingPrice = s.Find("div.f_right.red.price").Find("span.bold").Text() params.Params = append(params.Params, struct { UserId string `json:"userId"` ItemId string `json:"itemId"` }{UserId: strings.TrimSpace(s.AttrOr("userid", "")), ItemId: strings.TrimSpace(s.AttrOr("itemid", ""))}) } books = append(books, book) } if params.Params != nil { params.Area = "13003000000" dataItem, err := getGoodsListShippingFee(params, proxy) if err != nil { return nil, "", "", err } for i := 0; i < len(books); i++ { itemId := fmt.Sprintf("%d", books[i].ItemId) for _, data := range dataItem { if itemId == data.ItemID { books[i].ExpressDeliveryFee = data.Fee[0].TotalFee } } } } } return books, goodsNum, pNum, nil } // 定义对应的结构体 type FeeInfo struct { ShippingID string `json:"shippingId"` ShippingName string `json:"shippingName"` TotalFee string `json:"totalFee"` ShippingText string `json:"shippingText"` FilterTotalFee string `json:"filterTotalFee"` } type DataItem struct { Fee []FeeInfo `json:"fee"` IsSeller bool `json:"isSeller"` ItemID string `json:"itemId"` UserID string `json:"userId"` } type ResponseStruct struct { Status bool `json:"status"` Data []DataItem `json:"data"` Message string `json:"message"` ErrType string `json:"errType"` } // 获取店铺里商品的快递费(定位到河南) func getGoodsListShippingFee(params ParamsInfo, proxy string) ([]DataItem, error) { if params.Params == nil { return nil, fmt.Errorf("没有商品信息!") } paramsJSON, err := json.Marshal(params) if err != nil { return nil, fmt.Errorf("参数序列化失败: %v", err) } // URL 编码参数 encodedParams := url.QueryEscape(string(paramsJSON)) url := fmt.Sprintf("https://shop.kongfz.com/book/shopsearch/getShippingFee?params=%s", encodedParams) request := gorequest.New() if proxy != "" { request.Proxy(proxy) } // 发送请求 resp, body, errs := request.Get(url). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). End() // 错误处理 if len(errs) > 0 { return nil, fmt.Errorf("请求失败: %v", errs) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } responseStruct := ResponseStruct{} err = json.Unmarshal([]byte(body), &responseStruct) if err != nil { return nil, fmt.Errorf("解析JSON失败: %v", err) } var dataItem []DataItem for i := range responseStruct.Data { dataItem = append(dataItem, responseStruct.Data[i]) } return dataItem, nil } // 获取商品信息通过商品详情链接 func outGetGoodsMsgByDetailUrl(detailUrl, proxy string) (*BookInfo, error) { response, err := fetchResponse(detailUrl, proxy) if err != nil { return nil, err } body, err := io.ReadAll(response.Body) if err != nil { return nil, err } // 解析HTML文档 document, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) if err != nil { return nil, err } // 获取商品信息的快递费 fee, err := getBookDetailShippingFee(detailUrl, proxy) if err != nil { return nil, err } book := BookInfo{} //书名 book.BookName = strings.TrimSpace(document.Find("h1.title").Text()) //作者等信息 topDiv := document.Find("div.keywords-define.keywords-define-1000.clear-fix") if topDiv.Length() > 0 { topDiv.Find("li").Each(func(i int, li *goquery.Selection) { titleSpan := li.Find("span.keywords-define-title") contentSpan := li.Find("span.keywords-define-txt") if contentSpan.Length() == 0 { fmt.Printf("未找到指定的contentSpan信息") } titleText := strings.TrimSpace(titleSpan.Text()) contentText := strings.TrimSpace(contentSpan.Text()) titleText = strings.TrimSpace(titleText) if strings.Contains(titleText, "作者") { book.Author = cleanString(contentText) } if strings.Contains(titleText, "出版社") { book.Publisher = contentText } if strings.Contains(titleText, "出版人") { book.Publisher = contentText } if strings.Contains(titleText, "ISBN") { book.ISBN = contentText } if strings.Contains(titleText, "出版时间") { book.PublicationTime = validateDateFormat(contentText) } if strings.Contains(titleText, "版次") { book.Edition = contentText } if strings.Contains(titleText, "装帧") { book.BindingLayout = contentText } if strings.Contains(titleText, "开本") { book.Format = contentText } if strings.Contains(titleText, "页数") { book.Pages = contentText } if strings.Contains(titleText, "字数") { book.Wordage = contentText } if strings.Contains(titleText, "纸张") { book.Paper = contentText } if strings.Contains(titleText, "年代") { book.Era = contentText } if strings.Contains(titleText, "刻印方式") { book.EngravingMethod = contentText } if strings.Contains(titleText, "尺寸") { book.Dimensions = contentText } if strings.Contains(titleText, "册数") { book.VolumeNumber = contentText } }) } else { botDiv := document.Find("div.detail-lists.clear-fix") botDiv.Find("li").Each(func(i int, li *goquery.Selection) { spanText := strings.TrimSpace(li.Find("span").Text()) spanText = strings.TrimSpace(spanText) if strings.Contains(li.Text(), "作者") { book.Author = cleanString(li.Text()) book.Author = strings.ReplaceAll(book.Author, "作者:", "") book.Author = strings.ReplaceAll(book.Author, "著", "") } if strings.Contains(li.Text(), "出版社") { book.Publisher = spanText } if strings.Contains(li.Text(), "出版时间") { book.PublicationTime = validateDateFormat(spanText) } if strings.Contains(li.Text(), "ISBN") { book.ISBN = spanText } if strings.Contains(li.Text(), "装帧") { book.BindingLayout = spanText } if strings.Contains(li.Text(), "开本") { book.Format = spanText } if strings.Contains(li.Text(), "纸张") { book.Paper = spanText } if strings.Contains(li.Text(), "版次") { book.Edition = spanText } if strings.Contains(li.Text(), "页数") { book.Pages = spanText } if strings.Contains(li.Text(), "字数") { book.Wordage = spanText } }) } //图片 var imgUrls []string tpUl := document.Find("ul.lg-list") tpUl.Find("img").Each(func(i int, s *goquery.Selection) { dataImgUrl, exists := s.Attr("data-imgurl") if exists && dataImgUrl != "" { imgUrls = append(imgUrls, dataImgUrl) } }) book.BookPicS = strings.Join(imgUrls, ",") // 售价 price := document.Find("i.now-price-text").Text() priceN := regexp.MustCompile(`(\d+\.?\d*)`) if match := priceN.FindStringSubmatch(price); len(match) > 0 { book.SellingPrice = match[1] } // 定价价 fixPrice := document.Find("span.origin-price-text.clearfix").Text() fixPriceN := regexp.MustCompile(`(\d+\.?\d*)`) if match := fixPriceN.FindStringSubmatch(fixPrice); len(match) > 0 { book.FixPrice = match[1] } // 品相 text := document.Find("span.quality-text-cot.clearfix i").Text() book.Condition = strings.TrimSpace(text) // 快递费 book.ExpressDeliveryFee = fee return &book, nil } // 获取商品信息的快递费(定位到河南) func getBookDetailShippingFee(url, proxy string) (string, error) { compile := regexp.MustCompile(`kongfz\.com/(\d+)/(\d+)`) match := compile.FindStringSubmatch(url) var shippingFee string var shopId int var itemId int if len(match) == 3 { firstNum, err := strconv.Atoi(match[1]) // 店铺ID if err != nil { return "", fmt.Errorf("无效的店铺编码: %s", match[1]) } shopId = firstNum secondNum, err := strconv.Atoi(match[2]) // 图书ID if err != nil { return "", fmt.Errorf("无效的图书编码: %s", match[2]) } itemId = secondNum } shippingFeeUrl := fmt.Sprintf("https://book.kongfz.com/store-web/pc/v1/mould/calculateFee?area=13003000000&itemId=%d&shopId=%d", itemId, shopId) // 创建HTTP客户端 request := gorequest.New() // 设置代理(如果有提供代理URL) if proxy != "" { request.Proxy(proxy) } // 设置超时和其他配置 request.Timeout(30 * time.Second) // 发送请求 resp, body, errs := request.Get(shippingFeeUrl). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). End() // 错误处理 if len(errs) > 0 { return "", fmt.Errorf("请求失败: %v", errs) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP错误: %s", resp.Status) } calculateFee := struct { ErrCode int `json:"errCode"` ErrMessage string `json:"errMessage"` Result struct { FeeList []struct { FreeCondition string `json:"freeCondition"` ShippingID string `json:"shippingId"` ShippingName string `json:"shippingName"` ShippingValue string `json:"shippingValue"` } `json:"feeList"` FeeText string `json:"feeText"` } `json:"result"` Status bool `json:"status"` }{} err := json.Unmarshal([]byte(body), &calculateFee) if err != nil { return "", fmt.Errorf("解析JSON失败: %v", err) } for _, fee := range calculateFee.Result.FeeList { shippingFee = fee.ShippingValue } return shippingFee, nil } // 公用发送请求方法 func fetchResponse(url, proxy string) (*http.Response, error) { log.Printf("调用的URL: %s", url) maxRetries := cf.App.MaxRetryTimes var detailsResp *http.Response var errors []error for attempt := 0; attempt < maxRetries; attempt++ { if attempt > 0 { log.Printf("第 %d 次重试请求...", attempt) // 重试前等待,使用指数退避策略 waitTime := time.Duration(attempt*attempt) * cf.App.RateLimitDelay // 平方退避 log.Printf("等待 %v 后重试", waitTime) time.Sleep(waitTime) } if proxy != "" { detailsResp, _, errors = gorequest.New(). Get(url). Proxy(proxy). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Timeout(120 * time.Second). End() } if proxy == "" { detailsResp, _, errors = gorequest.New(). Get(url). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Timeout(120 * time.Second). End() } // 检查是否需要重试 shouldRetry := false if len(errors) > 0 { shouldRetry = true log.Printf("请求失败 (尝试 %d/%d): %v", attempt+1, maxRetries+1, errors) } else if detailsResp == nil { shouldRetry = true log.Printf("响应为空 (尝试 %d/%d)", attempt+1, maxRetries+1) } else if detailsResp.StatusCode != http.StatusOK { // 只对服务器错误进行重试,不对客户端错误重试 if detailsResp.StatusCode >= 500 { shouldRetry = true } log.Printf("HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d)", detailsResp.StatusCode, detailsResp.Body, attempt+1, maxRetries+1) } // 如果不需要重试,跳出循环 if !shouldRetry { break } // 如果是最后一次尝试,不继续重试 if attempt == maxRetries { break } // 关闭响应体(如果存在) if detailsResp != nil && detailsResp.Body != nil { detailsResp.Body.Close() } } // 检测请求是否错误 if len(errors) > 0 { var proxyAuthFailed bool var timeoutError bool var connectionError bool for _, e := range errors { errStr := e.Error() if strings.Contains(errStr, "Proxy Authentication Required") { proxyAuthFailed = true } if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "i/o timeout") { timeoutError = true } if strings.Contains(errStr, "connection") || strings.Contains(errStr, "connect") { connectionError = true } } if proxyAuthFailed { return nil, fmt.Errorf("代理认证失败") } if timeoutError { return nil, fmt.Errorf("请求超时,经过 %d 次尝试,超时网址:%s", maxRetries+1, url) } if connectionError { return nil, fmt.Errorf("网络连接错误,经过 %d 次尝试,错误网址:%s", maxRetries+1, url) } return nil, fmt.Errorf("查询请求失败,经过 %d 次尝试: %v,失败网址:%s", maxRetries+1, errors, url) } if detailsResp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", detailsResp.Status) } return detailsResp, nil } // 获取销量榜商品列表(带有Out的都非官放标准接口) func outGetTopGoodsListMsg(catId int, proxy string) ([]string, error) { // 构建请求URL url := fmt.Sprintf("https://item.kongfz.com/api/pc/getSellWellListDetail?page=1&pageSize=100&timeRank=2&catId=%d", catId) // 创建HTTP客户端 request := gorequest.New() // 设置代理(如果有提供代理URL) if proxy != "" { request.Proxy(proxy) } // 设置超时和其他配置 request.Timeout(30 * time.Second) // 发送请求 resp, body, errs := request.Get(url). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"). Set("Accept", "application/json, text/plain, */*"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Referer", "https://item.kongfz.com/"). End() // 错误处理 if len(errs) > 0 { return nil, fmt.Errorf("请求失败: %v", errs) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", resp.Status) } var bookDetailResponse BookDetailResponse err := json.Unmarshal([]byte(body), &bookDetailResponse) if err != nil { return nil, fmt.Errorf("解析JSON失败: %v", err) } if !bookDetailResponse.Status { return nil, fmt.Errorf("API返回错误: %s (代码: %d)", bookDetailResponse.ErrMessage, bookDetailResponse.ErrCode) } var isbnList []string for _, item := range bookDetailResponse.Result.Data { if item.Isbn != "" { isbnList = append(isbnList, item.Isbn) } } isbnList = removeDuplicateISBNs(isbnList) return isbnList, nil } // 去除重复的ISBN func removeDuplicateISBNs(isbns []string) []string { seen := make(map[string]bool) var result []string for _, isbn := range isbns { if !seen[isbn] { seen[isbn] = true result = append(result, isbn) } } return result } // 获取Document文档(带重试机制) func fetchDocument(fetchMode, proxyType, username, password, machineCode, url string) (*goquery.Document, error) { log.Printf("调用的URL: %s", url) maxRetries := cf.App.MaxRetryTimes var detailsResp *http.Response var errors []error var detailsBody []byte for attempt := 0; attempt < maxRetries; attempt++ { if attempt > 0 { log.Printf("第 %d 次重试请求...", attempt) // 重试前等待,使用指数退避策略 waitTime := time.Duration(attempt*attempt) * cf.App.RateLimitDelay // 平方退避 log.Printf("等待 %v 后重试", waitTime) time.Sleep(waitTime) } if fetchMode == NotProxy { detailsResp, _, errors = gorequest.New(). Get(url). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Timeout(120 * time.Second). End() } else if fetchMode == UseProxy { detailsReq := XxProxyRequest(proxyType, username, password, machineCode). Get(url). Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"). Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"). Set("Connection", "close") // 每次请求关闭连接 detailsResp, _, errors = detailsReq.End() } else { errors = append(errors, fmt.Errorf("请求失败,选择模式错误!")) } // 检查是否需要重试 shouldRetry := false if len(errors) > 0 { shouldRetry = true log.Printf("请求失败 (尝试 %d/%d): %v", attempt+1, maxRetries+1, errors) } else if detailsResp == nil { shouldRetry = true log.Printf("响应为空 (尝试 %d/%d)", attempt+1, maxRetries+1) } else if detailsResp.StatusCode != http.StatusOK { // 只对服务器错误进行重试,不对客户端错误重试 if detailsResp.StatusCode >= 500 { shouldRetry = true } log.Printf("HTTP状态码: %d,HTTP请求失败: %s (尝试 %d/%d)", detailsResp.StatusCode, detailsResp.Body, attempt+1, maxRetries+1) } // 如果不需要重试,跳出循环 if !shouldRetry { break } // 如果是最后一次尝试,不继续重试 if attempt == maxRetries { break } // 关闭响应体(如果存在) if detailsResp != nil && detailsResp.Body != nil { detailsResp.Body.Close() } } // 检测请求是否错误 if len(errors) > 0 { var proxyAuthFailed bool var timeoutError bool var connectionError bool for _, e := range errors { errStr := e.Error() if strings.Contains(errStr, "Proxy Authentication Required") { proxyAuthFailed = true } if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "i/o timeout") { timeoutError = true } if strings.Contains(errStr, "connection") || strings.Contains(errStr, "connect") { connectionError = true } } if proxyAuthFailed { return nil, fmt.Errorf("代理认证失败") } if timeoutError { return nil, fmt.Errorf("请求超时,经过 %d 次尝试,超时网址:%s", maxRetries+1, url) } if connectionError { return nil, fmt.Errorf("网络连接错误,经过 %d 次尝试,错误网址:%s", maxRetries+1, url) } return nil, fmt.Errorf("查询请求失败,经过 %d 次尝试: %v,失败网址:%s", maxRetries+1, errors, url) } if detailsResp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP错误: %s", detailsResp.Status) } detailsBody, err := io.ReadAll(detailsResp.Body) if err != nil { return nil, err } return goquery.NewDocumentFromReader(strings.NewReader(string(detailsBody))) } // 合并基本信息和详情 func mergeBookDetails(bookItems []BookItem, detailResults map[string]*DetailResult) []BookInfo { var books []BookInfo for _, item := range bookItems { book := item.Book // 如果有详情且获取成功,补充详细信息 if item.HasDetail && item.DetailURL != "" { if result, exists := detailResults[item.DetailURL]; exists && result.Error == nil && result.Doc != nil { detailsDiv := result.Doc.Find("div.detail-lists.clear-fix") if detailsDiv.Length() > 0 { selection := detailsDiv.Find("li") book.Author = extractNormalDetails(selection, "作者") book.Publisher = extractNormalDetails(selection, "出版社") detailsTime := extractNormalDetails(selection, "出版时间") if detailsTime != "" { formats := []string{"2006-01", "2006-01-02", "2006"} for _, format := range formats { if t, err := time.Parse(format, detailsTime); err == nil { book.PublicationTime = t.Unix() break } } } book.ISBN = extractNormalDetails(selection, "ISBN") book.Edition = extractNormalDetails(selection, "版次") book.FixPrice = extractNormalDetails(selection, "定价") book.BindingLayout = extractNormalDetails(selection, "装帧") book.Format = extractNormalDetails(selection, "开本") book.Pages = extractNormalDetails(selection, "页数") book.Wordage = extractNormalDetails(selection, "字数") } } } books = append(books, book) } return books } // 根据url获取单个图书详情信息 func getUrlBookDetails(fetchMode, proxyType, username, password, machineCode, url string) (books []BookInfo, err error) { document, err := fetchDocument(fetchMode, proxyType, username, password, machineCode, url) if err != nil { return nil, err } fee, err := FetchBookDetailsShippingFee(url) if err != nil { return nil, err } book := BookInfo{} //书名 book.BookName = strings.TrimSpace(document.Find("h1.title").Text()) //作者等信息 topDiv := document.Find("div.keywords-define.keywords-define-1000.clear-fix") if topDiv.Length() > 0 { topDiv.Find("li").Each(func(i int, li *goquery.Selection) { titleSpan := li.Find("span.keywords-define-title") contentSpan := li.Find("span.keywords-define-txt") if contentSpan.Length() == 0 { fmt.Printf("未找到指定的contentSpan信息") } titleText := strings.TrimSpace(titleSpan.Text()) contentText := strings.TrimSpace(contentSpan.Text()) titleText = strings.TrimSpace(titleText) if strings.Contains(titleText, "作者") { book.Author = cleanString(contentText) } if strings.Contains(titleText, "出版社") { book.Publisher = contentText } if strings.Contains(titleText, "出版人") { book.Publisher = contentText } if strings.Contains(titleText, "ISBN") { book.ISBN = contentText } if strings.Contains(titleText, "出版时间") { book.PublicationTime = validateDateFormat(contentText) } if strings.Contains(titleText, "版次") { book.Edition = contentText } if strings.Contains(titleText, "装帧") { book.BindingLayout = contentText } if strings.Contains(titleText, "开本") { book.Format = contentText } if strings.Contains(titleText, "页数") { book.Pages = contentText } if strings.Contains(titleText, "字数") { book.Wordage = contentText } if strings.Contains(titleText, "纸张") { book.Paper = contentText } if strings.Contains(titleText, "年代") { book.Era = contentText } if strings.Contains(titleText, "刻印方式") { book.EngravingMethod = contentText } if strings.Contains(titleText, "尺寸") { book.Dimensions = contentText } if strings.Contains(titleText, "册数") { book.VolumeNumber = contentText } }) } else { botDiv := document.Find("div.detail-lists.clear-fix") botDiv.Find("li").Each(func(i int, li *goquery.Selection) { spanText := strings.TrimSpace(li.Find("span").Text()) spanText = strings.TrimSpace(spanText) if strings.Contains(li.Text(), "作者") { book.Author = cleanString(li.Text()) book.Author = strings.ReplaceAll(book.Author, "作者:", "") book.Author = strings.ReplaceAll(book.Author, "著", "") } if strings.Contains(li.Text(), "出版社") { book.Publisher = spanText } if strings.Contains(li.Text(), "出版时间") { book.PublicationTime = validateDateFormat(spanText) } if strings.Contains(li.Text(), "ISBN") { book.ISBN = spanText } if strings.Contains(li.Text(), "装帧") { book.BindingLayout = spanText } if strings.Contains(li.Text(), "开本") { book.Format = spanText } if strings.Contains(li.Text(), "纸张") { book.Paper = spanText } if strings.Contains(li.Text(), "版次") { book.Edition = spanText } if strings.Contains(li.Text(), "页数") { book.Pages = spanText } if strings.Contains(li.Text(), "字数") { book.Wordage = spanText } }) } //图片 var imgUrls []string tpUl := document.Find("ul.lg-list") tpUl.Find("img").Each(func(i int, s *goquery.Selection) { dataImgUrl, exists := s.Attr("data-imgurl") if exists && dataImgUrl != "" { imgUrls = append(imgUrls, dataImgUrl) } }) book.BookPicS = strings.Join(imgUrls, ",") // 售价 price := document.Find("i.now-price-text").Text() priceN := regexp.MustCompile(`(\d+\.?\d*)`) if match := priceN.FindStringSubmatch(price); len(match) > 0 { book.SellingPrice = match[1] } // 定价价 fixPrice := document.Find("span.origin-price-text.clearfix").Text() fixPriceN := regexp.MustCompile(`(\d+\.?\d*)`) if match := fixPriceN.FindStringSubmatch(fixPrice); len(match) > 0 { book.FixPrice = match[1] } // 品相 text := document.Find("span.quality-text-cot.clearfix i").Text() book.Condition = strings.TrimSpace(text) // 快递费 if fee != "" { if fee == "包邮" { book.ExpressDeliveryFee = "0" } else { book.ExpressDeliveryFee = fee } } books = append(books, book) return books, nil } // 获取店铺页面的所有快递费用 func FetchProductInfoWithChromedp(url string) (*ProductResponse, error) { // 创建上下文 opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", true), chromedp.Flag("no-sandbox", true), chromedp.Flag("disable-dev-shm-usage", true), chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"), // 禁用图片加载,加快速度 chromedp.Flag("blink-settings", "imagesEnabled=false"), ) // 启动浏览器 allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() // 创建浏览器上下文 ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() // 设置超时 ctx, cancel = context.WithTimeout(ctx, 60*time.Second) defer cancel() log.Printf("[CHROMEDP] 开始导航到URL: %s", url) // 第一步:先导航到页面并等待完全加载 err := chromedp.Run(ctx, // 导航到目标页面 chromedp.Navigate(url), // 等待页面初步加载 chromedp.WaitReady("body", chromedp.ByQuery), chromedp.Sleep(3*time.Second), // 第二步:刷新页面,重新加载动态内容 chromedp.Reload(), // 等待刷新后页面加载 chromedp.WaitReady("body", chromedp.ByQuery), chromedp.Sleep(5*time.Second), // 等待关键元素出现 chromedp.WaitVisible(".item.clearfix", chromedp.ByQuery), // 滚动页面以触发动态加载 chromedp.Evaluate(`window.scrollTo(0, document.body.scrollHeight / 3)`, nil), chromedp.Sleep(2*time.Second), chromedp.Evaluate(`window.scrollTo(0, document.body.scrollHeight / 2)`, nil), chromedp.Sleep(2*time.Second), chromedp.Evaluate(`window.scrollTo(0, document.body.scrollHeight)`, nil), chromedp.Sleep(3*time.Second), chromedp.Evaluate(`window.scrollTo(0, 0)`, nil), chromedp.Sleep(1*time.Second), ) if err != nil { log.Printf("[CHROMEDP] 页面导航失败: %v", err) return nil, fmt.Errorf("页面导航失败: %v", err) } log.Printf("[CHROMEDP] 页面导航完成,开始提取数据") var evalResult map[string]interface{} // 执行任务 err = chromedp.Run(ctx, // 导航到目标页面 chromedp.Navigate(url), // 执行 JavaScript 来提取商品信息 chromedp.Evaluate(`(function() { console.log("开始提取商品信息..."); try { let productData = []; // 查找所有商品项 - 针对孔夫子旧书网的特定选择器 const itemSelectors = [ '.item.clearfix', '.list-item', '.product-item', '.goods-item', '[class*="item"]' ]; let items = []; for (let selector of itemSelectors) { const found = document.querySelectorAll(selector); if (found.length > 0) { items = found; console.log("使用选择器:", selector, "找到项目数:", items.length); break; } } if (items.length === 0) { // 如果没有找到特定选择器,尝试查找任何看起来像商品的项目 items = document.querySelectorAll('div[class*="item"], li[class*="item"]'); console.log("使用通用选择器找到项目数:", items.length); } console.log("总共找到商品项:", items.length); // 遍历每个商品项 items.forEach((item, index) => { // 提取 itemid let itemId = ''; // 方法1: 从元素属性获取 if (item.hasAttribute('itemid')) { itemId = item.getAttribute('itemid'); } // 方法2: 从数据属性获取 if (!itemId && item.hasAttribute('data-itemid')) { itemId = item.getAttribute('data-itemid'); } // 方法3: 从ID属性获取 if (!itemId && item.hasAttribute('id')) { const id = item.getAttribute('id'); if (id.includes('item') || id.includes('product')) { itemId = id; } } // 方法4: 从链接中提取itemid if (!itemId) { const links = item.querySelectorAll('a[href*="item"], a[href*="product"]'); for (let link of links) { const href = link.getAttribute('href'); if (href) { const itemMatch = href.match(/(?:item|product)[_-]?(\d+)/i); if (itemMatch) { itemId = itemMatch[1]; break; } } } } // 方法5: 从子元素中查找itemid if (!itemId) { const childWithItemId = item.querySelector('[itemid], [data-itemid]'); if (childWithItemId) { if (childWithItemId.hasAttribute('itemid')) { itemId = childWithItemId.getAttribute('itemid'); } else if (childWithItemId.hasAttribute('data-itemid')) { itemId = childWithItemId.getAttribute('data-itemid'); } } } // 提取书名 let bookName = ''; const titleSelectors = [ '.title a', '.book-title', '.item-title', '.name a', 'h3 a', 'h4 a', '.link', 'a[title]' ]; for (let selector of titleSelectors) { const titleEl = item.querySelector(selector); if (titleEl) { bookName = titleEl.textContent.trim(); if (bookName && bookName.length > 1) { break; } } } // 如果没找到,尝试在item内查找任何文本作为书名 if (!bookName) { const textContent = item.textContent; // 尝试提取看起来像书名的文本(较长的文本块) const lines = textContent.split('\n').map(line => line.trim()).filter(line => line.length > 5); if (lines.length > 0) { bookName = lines[0]; } } // 提取价格 let price = ''; const priceSelectors = [ '.price span', '.price .bold', '.current-price', '.sell-price', '.cost', '.money', '[class*="price"]', 'strong' ]; for (let selector of priceSelectors) { const priceEl = item.querySelector(selector); if (priceEl) { let priceText = priceEl.textContent.trim(); // 清理价格文本,保留数字和小数点 priceText = priceText.replace(/[^\d\.]/g, ''); if (priceText && !isNaN(parseFloat(priceText))) { price = '¥' + priceText; break; } } } // 如果没找到价格,尝试在文本中查找价格模式 if (!price) { const itemText = item.textContent; const priceMatch = itemText.match(/[¥¥]?\s*(\d+\.?\d*)/); if (priceMatch) { price = '¥' + priceMatch[1]; } } // 提取快递费用 let shippingFee = ''; const shippingSelectors = [ '.ship-fee', '.shipping-fee', '.express-fee', '.freight', '.postage', '[class*="fee"]', '[class*="快递"]', '[class*="运费"]' ]; for (let selector of shippingSelectors) { const shippingEl = item.querySelector(selector); if (shippingEl) { shippingFee = shippingEl.textContent.trim(); if (shippingFee) break; } } // 如果没找到运费元素,尝试在文本中查找运费关键词 if (!shippingFee) { const itemText = item.textContent; const feeMatches = itemText.match(/(运费[::]\s*[^\s\n]+)|(快递[::]\s*[^\s\n]+)|(邮费[::]\s*[^\s\n]+)/); if (feeMatches) { shippingFee = feeMatches[0]; } else if (itemText.includes('包邮') || itemText.includes('免运费')) { shippingFee = '包邮'; } else { shippingFee = '运费待确认'; } } // 清理书名(移除过长的文本) if (bookName && bookName.length > 100) { bookName = bookName.substring(0, 100) + '...'; } // 如果还没有itemid,生成一个基于索引的ID if (!itemId) { itemId = 'item_' + (index + 1); } // 添加到数据中 productData.push({ itemId: itemId, bookName: bookName || '商品${index + 1}', price: price || '价格待确认', shippingFee: shippingFee }); }); // 过滤掉明显无效的数据 productData = productData.filter(item => (item.bookName !== '商品1' || item.price !== '价格待确认') && item.bookName && item.bookName.length > 0 ); console.log("处理后商品数量:", productData.length); if (productData.length > 0) { console.log("前3个商品示例:", productData.slice(0, 3)); } return { success: true, message: "成功提取商品信息", data: productData }; } catch(error) { console.error("提取商品信息时出错:", error); return { success: false, message: "提取商品信息时出错: " + error.message, data: [] }; } })()`, &evalResult), ) if err != nil { return nil, fmt.Errorf("chromedp执行失败: %v", err) } // 处理 JavaScript 执行结果 response := &ProductResponse{} // 转换结果数据 if success, ok := evalResult["success"].(bool); ok { response.Success = success } if message, ok := evalResult["message"].(string); ok { response.Message = message } if data, ok := evalResult["data"].([]interface{}); ok { for _, item := range data { if productMap, ok := item.(map[string]interface{}); ok { product := ProductInfo{} if itemId, ok := productMap["itemId"].(string); ok { product.ItemID = itemId } if bookName, ok := productMap["bookName"].(string); ok { product.BookName = bookName } if price, ok := productMap["price"].(string); ok { product.Price = price } if shippingFee, ok := productMap["shippingFee"].(string); ok { info := extractShippingInfo(shippingFee) product.ShippingFee = info } response.Data = append(response.Data, product) } } } return response, nil } // 获取详情页的快递费用 func FetchBookDetailsShippingFee(url string) (string, error) { // 创建上下文 opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", true), chromedp.Flag("disable-gpu", true), chromedp.Flag("no-sandbox", true), chromedp.Flag("disable-dev-shm-usage", true), chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"), // 禁用图片加载,加快速度 chromedp.Flag("blink-settings", "imagesEnabled=false"), ) // 启动浏览器 allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() // 创建浏览器上下文 ctx, cancel := chromedp.NewContext(allocCtx) defer cancel() // 设置超时 ctx, cancel = context.WithTimeout(ctx, 60*time.Second) defer cancel() log.Printf("[CHROMEDP] 开始导航到URL: %s", url) var result string // 增强的 JavaScript 脚本 enhancedJSScript := ` (function() { // 查找包含快递费用的元素 const elements = document.querySelectorAll('*'); let shippingFee = null; elements.forEach(element => { if (element.textContent.includes('快递¥')) { const match = element.textContent.match(/快递¥(\d+\.\d{2})/); if (match) { shippingFee = match[1]; console.log('找到快递费用: ¥' + shippingFee); } } // 匹配格式2: 快递:包邮 else if (element.textContent.includes('快递:包邮') || element.textContent.includes('快递:包邮')) { shippingFee = '包邮'; console.log('找到快递费用: 包邮'); } // 匹配格式3: 快递 包邮 (包含空格变体) else if (element.textContent.match(/快递[::\s]包邮/)) { shippingFee = '包邮'; console.log('找到快递费用: 包邮'); } }); if (!shippingFee) { // 如果没找到,尝试搜索整个页面 const pageText = document.body.innerText; const match = pageText.match(/快递¥(\d+\.\d{2})/); if (match) { shippingFee = match[1]; console.log('找到快递费用: ¥' + shippingFee); } // 尝试匹配包邮 else if (pageText.match(/快递[::\s]包邮/)) { shippingFee = '包邮'; console.log('找到快递费用: 包邮'); } // 尝试其他包邮表述 else if (pageText.includes('包邮') && pageText.includes('快递')) { shippingFee = '包邮'; console.log('找到快递费用: 包邮'); } else { console.log('未找到快递费用信息'); } } return shippingFee; })();` // 先导航到页面并等待完全加载 err := chromedp.Run(ctx, // 导航到目标页面 chromedp.Navigate(url), // 刷新页面,重新加载动态内容 chromedp.Reload(), // 等待刷新后页面加载 chromedp.WaitReady("body", chromedp.ByQuery), chromedp.Sleep(8*time.Second), // 执行 JavaScript 来提取商品信息 chromedp.Evaluate(enhancedJSScript, &result), ) if err != nil { log.Printf("[CHROMEDP] 页面导航失败: %v", err) return "", fmt.Errorf("页面导航失败: %v", err) } log.Printf("[CHROMEDP] 页面导航完成,开始提取数据") if err != nil { return "", fmt.Errorf("chromedp执行失败: %v", err) } return result, nil } // 获取孔网实拍图(查询商品列表) func getKFZSPTImageURL(proxyType, username, password, machineCode, isbn string) (*BookInfo, error) { log.Printf("[DEBUG] ", isbn, " 获取图书ISBN: %s", isbn) url := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000", cf.API.ProductSearchURL, isbn, cf.App.Size) req := XxProxyRequest(proxyType, username, password, machineCode). Get(url). Set("User-Agent", cf.App.DefaultUserAgent). Set("Accept", "*/*") resp, body, errs := req.End() log.Printf("[DEBUG] 获取图书列表 URL: %s", url) // 只在有响应内容时才打印,避免日志过长 if len(body) > 0 && len(body) < 500 { log.Printf("[DEBUG] 获取图书列表响应: %s", body) } else if len(body) > 0 { log.Printf("[DEBUG] 获取图书列表响应长度: %d 字符", len(body)) } else { log.Printf("[DEBUG] 获取图书列表响应为空") } // 处理请求错误 if len(errs) > 0 { // 检查是否是代理认证失败 var proxyAuthFailed bool for _, e := range errs { if strings.Contains(e.Error(), "Proxy Authentication Required") { proxyAuthFailed = true break } } if proxyAuthFailed { return nil, fmt.Errorf("代理认证失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { space := strings.TrimSpace(resp.Status) return nil, fmt.Errorf("HTTP错误: %s", space) } // 解析响应 var apiResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { Title string `json:"title"` ImgUrl string `json:"imgUrl"` ImgBigUrl string `json:"imgBigUrl"` ItemId int64 `json:"itemId"` ShopId int64 `json:"shopId"` TplRecords []struct { Key string `json:"key"` Value string `json:"value"` } `json:"tplRecords"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } if err := json.Unmarshal([]byte(body), &apiResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } // 如果找到商品,返回图片URL if apiResp.Data.ItemResponse.Total > 0 && len(apiResp.Data.ItemResponse.List) > 0 { // 确定起始索引 var startIndex int if cf.App.Size >= apiResp.Data.ItemResponse.Total { startIndex = apiResp.Data.ItemResponse.Total - 1 } else { startIndex = cf.App.Size - 1 } // 从指定索引开始,向前查找有效图片,最多重试3次 for attempt := 0; attempt < 3; attempt++ { currentIndex := startIndex - attempt // 检查索引是否有效 if currentIndex < 0 || currentIndex >= len(apiResp.Data.ItemResponse.List) { log.Printf("[DEBUG] 索引 %d 超出范围,跳过", currentIndex) continue } item := apiResp.Data.ItemResponse.List[currentIndex] // 检查图片URL是否存在 if item.ImgBigUrl == "" { log.Printf("[DEBUG] 索引 %d 的图片URL为空,跳过", currentIndex) continue } info := &BookInfo{} info.BookName = item.Title info.BookPicS = item.ImgUrl info.ItemId = item.ItemId info.ShopId = item.ShopId // 安全地获取TplRecords中的值 if len(item.TplRecords) > 0 { info.Author = item.TplRecords[0].Value } if len(item.TplRecords) > 1 { info.Publisher = item.TplRecords[1].Value } if len(item.TplRecords) > 2 { info.PublicationTime = validateDateFormat(item.TplRecords[2].Value) } if len(item.TplRecords) > 3 { info.BindingLayout = item.TplRecords[3].Value } // 成功时重置代理失败计数器 resetProxyFailCount() return info, nil } // 如果所有尝试都失败了,返回错误 log.Printf("[WARN] 经过3次尝试,未找到有效图片") return nil, fmt.Errorf("未找到有效图片,已尝试3次") } return nil, nil } // 获取孔网官图(查询图书条目) func getKFZGTImageURL(proxyType, username, password, machineCode, isbn string) (*BookInfo, error) { url := fmt.Sprintf("%s?keyword=%s", cf.API.BookSearchURL, isbn) info := &BookInfo{} req := XxProxyRequest(proxyType, username, password, machineCode). Get(url). Set("User-Agent", cf.App.DefaultUserAgent). Set("Accept", "*/*") resp, body, errs := req.End() log.Printf("[DEBUG] 获取图书条目 URL: %s", url) // 只在有响应内容时才打印,避免日志过长 if len(body) > 0 && len(body) < 500 { log.Printf("[DEBUG] 获取图书条目响应: %s", body) } else if len(body) > 0 { log.Printf("[DEBUG] 获取图书条目响应长度: %d 字符", len(body)) } else { log.Printf("[DEBUG] 获取图书条目响应为空") } // 处理请求错误 if len(errs) > 0 { // 检查是否是代理相关错误 var isProxyError bool var errorDetails []string for _, e := range errs { errorStr := e.Error() errorDetails = append(errorDetails, errorStr) if strings.Contains(errorStr, "Proxy Authentication Required") || strings.Contains(errorStr, "connectex: A connection attempt failed") || strings.Contains(errorStr, "connectex: No connection could be made") || strings.Contains(errorStr, "proxyconnect tcp") || strings.Contains(errorStr, "timeout") || strings.Contains(errorStr, "connection refused") { isProxyError = true } } log.Printf("[ERROR] 请求错误详情: %v", errorDetails) if isProxyError { // 处理代理失败 return nil, fmt.Errorf("代理连接失败") } return nil, fmt.Errorf("查询请求失败: %v", errs) } //检查HTTP状态码 if resp.StatusCode != http.StatusOK { space := strings.TrimSpace(resp.Status) return nil, fmt.Errorf("HTTP错误: %s", space) } // 解析响应 var apiResp struct { Status int `json:"status"` ErrType string `json:"errType"` Message string `json:"message"` SystemTime int64 `json:"systemTime"` Data struct { ItemResponse struct { Total int `json:"total"` List []struct { BookName string `json:"bookName"` Mid int64 `json:"mid"` ImgUrlEntity struct { BigImgUrl string `json:"bigImgUrl"` } `json:"imgUrlEntity"` BookShowInfo []string `json:"bookShowInfo"` } `json:"list"` } `json:"itemResponse"` } `json:"data"` } if err := json.Unmarshal([]byte(body), &apiResp); err != nil { return nil, fmt.Errorf("解析JSON失败: %w", err) } // 如果找到条目,返回图片URL if apiResp.Data.ItemResponse.Total > 0 && len(apiResp.Data.ItemResponse.List) > 0 { list := apiResp.Data.ItemResponse.List[0] info := list.BookShowInfo bookItem := &BookInfo{ BookName: list.BookName, BookPic: list.ImgUrlEntity.BigImgUrl, Mid: list.Mid, } // 根据长度安全填充字段 if len(info) > 0 { bookItem.Author = info[0] } if len(info) > 1 { bookItem.Publisher = info[1] } if len(info) > 2 { bookItem.PublicationTime = validateDateFormat(info[2]) } if len(info) > 3 { bookItem.BindingLayout = info[3] } if len(info) > 4 { bookItem.FixPrice = info[4] } else { log.Printf("[WARN] BookShowInfo 长度不足 (仅 %d 项): %v", len(info), info) } fmt.Println("bookItem: ", bookItem) return bookItem, nil } return info, nil } // 替换所有空白字符为空格 func cleanString(s string) string { s = strings.ReplaceAll(s, "\n", "") s = strings.ReplaceAll(s, "\r", "") s = strings.ReplaceAll(s, "\t", "") s = strings.ReplaceAll(s, " ", "") return removeDuplicates(s) } // 字符串去重 func removeDuplicates(s string) string { seen := make(map[rune]bool) var result strings.Builder for _, r := range s { if !seen[r] { seen[r] = true result.WriteRune(r) } } return result.String() } // 检测快递费用格式 func extractShippingInfo(shippingFee string) string { // 匹配 "快递¥数字" 格式 re1 := regexp.MustCompile(`快递¥(\d+\.?\d*)`) // 匹配 "包邮" 格式 re2 := regexp.MustCompile(`(包邮)`) if matches := re1.FindStringSubmatch(shippingFee); len(matches) > 1 { return matches[1] // 返回数字部分 } if matches := re2.FindStringSubmatch(shippingFee); len(matches) > 1 { return "0" // 返回"包邮" } return "" } // 改进字段 func extractNormalDetails(s *goquery.Selection, fieldName string) string { var info string s.Each(func(i int, selection *goquery.Selection) { if strings.Contains(selection.Text(), fieldName) { all := strings.ReplaceAll(selection.Text(), " ", "") all = strings.ReplaceAll(all, "\n", "") all = strings.ReplaceAll(all, " ", "") split := strings.SplitN(all, ":", 2) if len(split) > 1 { info = split[1] } else { return } } }) return info } // 改进的字段提取函数 func extractNormalText(s *goquery.Selection, fieldName string) string { // 在左右两个分区中查找 selectors := []string{"div.f_left", "div.f_right"} for _, selector := range selectors { fieldItem := s.Find(selector + " div.normal-item").FilterFunction(func(i int, sel *goquery.Selection) bool { title := sel.Find("span.normal-title").Text() return strings.Contains(title, fieldName) }) if fieldItem.Length() > 0 { text := fieldItem.Find("span.normal-text").Text() return strings.TrimSpace(text) } } return "" } // 重置代理失败计数器(成功时调用) func resetProxyFailCount() { tailProxyMu.Lock() defer tailProxyMu.Unlock() proxyFailCount = 0 } // 验证日期格式 func validateDateFormat(dateStr string) int64 { // 去除前后空格 dateStr = strings.TrimSpace(dateStr) // 替换各种分隔符为统一的分隔符"-" dateStr = regexp.MustCompile(`[/_\\.,\s]+`).ReplaceAllString(dateStr, "-") // 处理纯年份格式 (4位数字) if regexp.MustCompile(`^\d{4}$`).MatchString(dateStr) { dateStr += "-01-01" } // 处理年月格式 (4位数字-1或2位数字) if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})$`).FindStringSubmatch(dateStr); len(matches) == 3 { year := matches[1] month := matches[2] if len(month) == 1 { month = "0" + month } if monthNum, _ := strconv.Atoi(month); monthNum >= 1 && monthNum <= 12 { dateStr = year + "-" + month + "-01" } else { return 0 } } // 处理年月日格式 (4位数字-1或2位数字-1或2位数字) if matches := regexp.MustCompile(`^(\d{4})-(\d{1,2})-(\d{1,2})`).FindStringSubmatch(dateStr); len(matches) >= 4 { year := matches[1] month := matches[2] day := matches[3] // 标准化月份和日期为两位数 if len(month) == 1 { month = "0" + month } if len(day) == 1 { day = "0" + day } dateStr = year + "-" + month + "-" + day } // 加载上海时区 loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { // 如果加载时区失败,使用UTC+8作为后备方案 loc = time.FixedZone("CST", 8*60*60) } // 尝试解析为标准日期格式,并指定上海时区 parsedTime, err := time.ParseInLocation("2006-01-02", dateStr, loc) if err != nil { return 0 } // 返回秒级时间戳(UTC时间,但解析时已经考虑了时区偏移) return parsedTime.Unix() } // 代理请求(小象代理) func XxProxyRequest(proxyType, username, password, machineCode string) *gorequest.SuperAgent { proxyManagerOnce.Do(func() { log.Printf("[INFO] 初始化代理管理器,DLL路径: %s", cf.Proxy.ProxyFilePath) // 检查DLL文件是否存在 if _, err := os.Stat(cf.Proxy.ProxyFilePath); os.IsNotExist(err) { // 尝试在可执行文件目录查找 exePath, _ := os.Executable() exeDir := filepath.Dir(exePath) dllPath := filepath.Join(exeDir, cf.Proxy.ProxyFilePath) if _, err := os.Stat(dllPath); err == nil { cf.Proxy.ProxyFilePath = dllPath } else { proxyManagerInitErr = fmt.Errorf("代理DLL文件不存在: %s (也尝试了: %s)", cf.Proxy.ProxyFilePath, dllPath) return } } globalProxyManager, proxyManagerInitErr = NewProxyConfigManager(cf.Proxy.ProxyFilePath) if proxyManagerInitErr != nil { log.Printf("[ERROR] 代理管理器初始化失败: %v", proxyManagerInitErr) } else { log.Printf("[INFO] 代理管理器初始化成功") } }) typeManager, err := globalProxyManager.ProxyTypeManager( proxyType, username, password, machineCode, ) if err != nil { fmt.Printf("获取代理服务器信息失败: %v", err) } return gorequest.New().Proxy(typeManager).Timeout(120*time.Second).Retry(2, 3*time.Second) } // 初始化 func initializeConfig(config Config) { // 设置全局配置 cf = config } // 连接数据库(选品) func connectDBXP() (*sql.DB, error) { db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", cf.Database.Username, cf.Database.Password, cf.Database.Host, cf.Database.Name)) if err != nil { return nil, fmt.Errorf("打开数据库连接失败: %v", err) } // 设置连接池参数 db.SetMaxOpenConns(20) // 最大打开连接数 db.SetMaxIdleConns(10) // 最大空闲连接数 err = db.Ping() if err != nil { return nil, fmt.Errorf("数据库连接测试失败: %v", err) } return db, nil } // 从数据库随机获取一个可用账号 func getRandomAccount() (*AccountCredential, error) { mu.RLock() defer mu.RUnlock() // sql语句 query := ` SELECT id, credential_account, credential_password, token FROM t_credential WHERE credential_type = 'KWCredential' AND status = 0 AND is_del = 0 ORDER BY RAND() LIMIT 1` db, err := connectDBXP() if err != nil { return nil, err } var ac AccountCredential err = db.QueryRow(query).Scan(&ac.ID, &ac.Username, &ac.Password, &ac.Token) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("没有可用的账号") } return nil, fmt.Errorf("查询账号失败: %v", err) } return &ac, nil } // 修改账号为不可用 func updateAccountInvalid(id int64) error { mu.RLock() defer mu.RUnlock() db, err := connectDBXP() if err != nil { return err } query := `UPDATE t_credential SET is_del = 1 WHERE id = ?` _, err = db.Exec(query, id) return fmt.Errorf("更新失败: %v", err) } // 修改账号为需要验证 func updateAccountNeedVerify(id int64) error { mu.RLock() defer mu.RUnlock() db, err := connectDBXP() if err != nil { return err } query := `UPDATE t_credential SET status = 1 WHERE id = ?` _, err = db.Exec(query, id) return err } // 更新账号token func updateAccountToken(id int64, token string) error { mu.RLock() defer mu.RUnlock() db, err := connectDBXP() if err != nil { return err } query := `UPDATE t_credential SET token = ? WHERE id = ?` _, err = db.Exec(query, token, id) return err } // =================== C 导出函数 ======================= // 登录(带有Out的都非官方标准接口) // //export OutLogin func OutLogin(username, password *C.char) *C.char { goUsername := C.GoString(username) goPassword := C.GoString(password) respToken, err := outLogin(goUsername, goPassword) resp := struct { Token string `json:"token"` }{ Token: respToken, } var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: resp, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取用户信息(带有Out的都非官方标准接口) // //export OutGetUserMsg func OutGetUserMsg(token *C.char) *C.char { goToken := C.GoString(token) userInfo, err := outGetUserMsg(goToken) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: userInfo, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取商品模版(带有Out的都非官方标准接口) // //export OutGetGoodsTplMsg func OutGetGoodsTplMsg(token, itemId, proxy *C.char) *C.char { goToken := C.GoString(token) goItemId := C.GoString(itemId) goProxy := C.GoString(proxy) info, err := outGetGoodsTplMsg(goToken, goItemId, goProxy) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: info, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取商品列表-已登的店铺(带有Out的都非官方标准接口) // //export OutGetGoodsListMsgFromSelfShop func OutGetGoodsListMsgFromSelfShop(token, proxy, itemSn, priceMin, priceMax *C.char, startCreateTime *C.char, endCreateTime *C.char, requestType *C.char, isItemSnEqual C.int, page C.int, size C.int) *C.char { goToken := C.GoString(token) goProxy := C.GoString(proxy) goItemSn := C.GoString(itemSn) goPriceMin := C.GoString(priceMin) goPriceMax := C.GoString(priceMax) goStartCreateTime := C.GoString(startCreateTime) goEndCreateTime := C.GoString(endCreateTime) goRequestType := C.GoString(requestType) goIsItemSnEqual := int(isItemSnEqual) goPage := int(page) goSize := int(size) info, err := outGetGoodsListMsgFromSelfShop(goToken, goProxy, goItemSn, goPriceMin, goPriceMax, goStartCreateTime, goEndCreateTime, goRequestType, goIsItemSnEqual, goPage, goSize) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: info, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 删除商品-已登的店铺(带有Out的都非官方标准接口) // //export OutDelGoodsFromSelfShop func OutDelGoodsFromSelfShop(token, proxy, itemId *C.char) *C.char { goToken := C.GoString(token) goProxy := C.GoString(proxy) goItemId := C.GoString(itemId) info, err := outDelGoodsFromSelfShop(goToken, goProxy, goItemId) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: info, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 新增商品-已登的店铺(带有Out的都非官方标准接口) // //export OutAddGoods func OutAddGoods(token, proxy, formData *C.char) *C.char { goToken := C.GoString(token) goProxy := C.GoString(proxy) goFormData := C.GoString(formData) info, err := outAddGoods(goToken, goProxy, goFormData) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: info, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取图片URL(官图和拍图)带有店铺过滤 // //export OutGetImageFilterShopId func OutGetImageFilterShopId(token, isbn *C.char, shopId C.int, proxy *C.char, isLiveImage C.int, isReturnMsg C.int) *C.char { goToken := C.GoString(token) goIsbn := C.GoString(isbn) goShopId := int(shopId) goProxy := C.GoString(proxy) goIsLiveImage := int(isLiveImage) goIsReturnMsg := int(isReturnMsg) info, err := outGetImageFilterShopId(goToken, goIsbn, goShopId, goProxy, goIsLiveImage, goIsReturnMsg) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: info, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取商品图片(带有Out的都非官方标准接口) // //export OutGetImageByIsbn func OutGetImageByIsbn(token, isbn, proxy *C.char, isLiveImage C.int, isReturnMsg C.int) *C.char { goToken := C.GoString(token) goIsbn := C.GoString(isbn) goProxy := C.GoString(proxy) goIsLiveImage := int(isLiveImage) goIsReturnMsg := int(isReturnMsg) bookInfo, err := outGetImageByIsbn(goToken, goIsbn, goProxy, goIsLiveImage, goIsReturnMsg) var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: bookInfo, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取商品列表通过店铺ID(带有Out的都非官方标准接口) // //export OutGetGoodsListMsgByShopId func OutGetGoodsListMsgByShopId(shopId C.int, proxy *C.char, retPrice C.int, isImage C.int, sortType *C.char, sort *C.char, priceMin C.float, priceMax C.float, pageNum, returnNum C.int) *C.char { goShopId := int(shopId) goProxy := C.GoString(proxy) goRetPrice := int(retPrice) goIsImage := int(isImage) goSortType := C.GoString(sortType) goSort := C.GoString(sort) goPriceMin := float32(priceMin) goPriceMax := float32(priceMax) goPageNum := int(pageNum) goReturnNum := int(returnNum) books, num, pNum, err := outGetGoodsListMsgByShopId(goShopId, goProxy, goRetPrice, goIsImage, goSortType, goSort, goPriceMin, goPriceMax, goPageNum, goReturnNum) // 构建统一格式的响应 bookInfo := struct { GoodsNum string `json:"goods_num,omitempty"` PNum string `json:"pnum,omitempty"` Data interface{} `json:"data,omitempty"` }{ GoodsNum: num, PNum: pNum, Data: books, } var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: bookInfo, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取商品信息通过商品详情链接(带有0ut的都非官方标准接口) // //export OutGetGoodsMsgByDetailUrl func OutGetGoodsMsgByDetailUrl(detailUrl, proxy *C.char) *C.char { goDetailUrl := C.GoString(detailUrl) goProxy := C.GoString(proxy) response, err := outGetGoodsMsgByDetailUrl(goDetailUrl, goProxy) // 构建统一格式的响应 var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: response, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 获取销量榜商品列表(带有Out的都非官放标准接口) // //export OutGetTopGoodsListMsg func OutGetTopGoodsListMsg(catId C.int, proxy *C.char) *C.char { goCatId := int(catId) goProxy := C.GoString(proxy) response, err := outGetTopGoodsListMsg(goCatId, goProxy) // 构建统一格式的响应 var apiResponse APIResponse if err != nil { apiResponse = APIResponse{ Success: false, Message: err.Error(), } } else { apiResponse = APIResponse{ Success: true, Data: response, } } // 转换为JSON字符串 jsonData, marshalErr := json.Marshal(apiResponse) if marshalErr != nil { // 如果JSON序列化失败,返回错误信息 apiResponse = APIResponse{ Success: false, Message: fmt.Sprintf("JSON序列化失败: %v", marshalErr), } errorJson, _ := json.Marshal(apiResponse) return C.CString(string(errorJson)) } return C.CString(string(jsonData)) } // 初始化配置 // //export Initialize func Initialize(configJSON *C.char) *C.char { configStr := C.GoString(configJSON) log.Printf("[DEBUG] 接收到的配置JSON: %s", configStr) var config Config if err := json.Unmarshal([]byte(configStr), &config); err != nil { return C.CString(fmt.Sprintf(`{"success":false,"message":"配置解析失败: %v"}`, err)) } initializeConfig(config) return C.CString(`{"success":true,"message":"初始化成功"}`) } // 导出函数:释放C字符串内存 // //export FreeCString func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) } //// 空main函数,编译DLL时需要 //func main() { //}