daShangDao_kfzgw-info/kongfz/kongfz.go
2025-12-22 19:09:56 +08:00

2090 lines
65 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
/*
#include <stdlib.h>
*/
import "C"
import (
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"unsafe"
"github.com/PuerkitoBio/goquery"
_ "github.com/go-sql-driver/mysql"
"github.com/parnurzeal/gorequest"
)
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"`
}
// BookInfo 图书详情结构体
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
}
// APIResponse 响应结构体
type APIResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
// 孔网用户信息响应结构体
type UserInfo struct {
UserID int64 `json:"userId"`
Nickname string `json:"nickname"`
Mobile string `json:"mobile"`
}
// 全局变量
var (
cf Config // 配置信息
)
// ProductInfo 商品信息结构
type ProductInfo struct {
ItemID string `json:"itemId"`
BookName string `json:"bookName"`
Price string `json:"price"`
ShippingFee string `json:"shippingFee"`
}
// 图书详情响应结构体
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 ParamsInfo struct {
Params []Param `json:"params"`
Area string `json:"area"`
}
type Param struct {
UserId string `json:"userId"`
ItemId string `json:"itemId"`
}
// 定义快递费用的响应结构体
type DataItem struct {
Fee []FeeInfo `json:"fee"`
IsSeller bool `json:"isSeller"`
ItemID string `json:"itemId"`
UserID string `json:"userId"`
}
type FeeInfo struct {
ShippingID string `json:"shippingId"`
ShippingName string `json:"shippingName"`
TotalFee string `json:"totalFee"`
ShippingText string `json:"shippingText"`
FilterTotalFee string `json:"filterTotalFee"`
}
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 outLogin(username, password string) (string, error) {
// 判断用户名和密码是否为空
if username == "" || password == "" {
return "", fmt.Errorf("请输入用户名和密码!")
}
// Post请求参数
formData := map[string]string{
"loginName": username,
"loginPass": password,
"returnUrl": "http://user.kongfz.com/",
}
// 孔网登录URL
loginUrl := "https://login.kongfz.com/Pc/Login/account"
//发送请求
resp, body, errs := gorequest.New().
Post(loginUrl).
Set("Content-Type", "application/x-www-form-urlencoded").
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,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"`
}
// 解析json
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("登录失败,未知错误!")
}
// 获取孔网用户信息
func outGetUserMsg(token string) (*UserInfo, error) {
// 用户信息URL
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"`
}
}
// 解析json
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, proxy, itemId string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 商品模板URL
url := fmt.Sprintf("https://seller.kongfz.com/pc/itemInfo/getTplFields?itemId=%s&isClone=1&v=%d",
itemId, time.Now().Unix())
// 创建请求
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)
}
// 解析json
var data map[string]interface{}
if err := json.Unmarshal([]byte(body), &data); err != nil {
return nil, fmt.Errorf("解析JSON失败: %w", err)
}
// 检查status状态
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) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 已登店铺的商品信息URL
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()
// 设置代理如果有提供代理URL
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)
}
// 解析json
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)
}
// 新增商品-已登的店铺
func outAddGoods(token, proxy, formData string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 新增商品的URL
url := "https://seller.kongfz.com/pc/itemInfo/add"
// 创建请求
request := gorequest.New()
// 设置代理如果有提供代理URL
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("代理连接失败: %s", errs)
}
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)
}
// 删除商品-已登的店铺
func outDelGoodsFromSelfShop(token, proxy, itemId string) (map[string]interface{}, error) {
// 判断登录token
if token == "" {
return nil, fmt.Errorf("请先登录获取Token")
}
// 删除商品的URL
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)
}
// 解析json
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)
}
// 获取孔网商品图片(官图和拍图)-带有店铺过滤
func outGetImageFilterShopId(token string, proxy string, isbn string, shopId int, isLiveImage int, isReturnMsg int) (map[string]string, error) {
if isLiveImage == 0 {
// 图书条目URL
gtUrl := fmt.Sprintf("%s?keyword=%s",
"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", 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 {
size := 10
// 实拍图
sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, 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 size >= apiSptResp.Data.ItemResponse.Total {
startIndex = apiSptResp.Data.ItemResponse.Total - 1
} else {
startIndex = 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_s": item.ImgUrl,
"isbn": item.Isbn,
}
return info, nil
}
}
}
return nil, nil
}
// 获取孔网商品图片和信息(官图和拍图)
func outGetImageByIsbn(token string, proxy string, isbn string, isLiveImage int, isReturnMsg int) (*BookInfo, error) {
// isLiveImage 1实拍图 0官图 isReturnMsg 0商品信息
bookInfo := &BookInfo{}
if isLiveImage == 0 {
// 孔网官图请求
gtUrl := fmt.Sprintf("%s?keyword=%s",
"https://search.kongfz.com/pc-gw/search-web/client/pc/bookLib/keyword/list", 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 {
size := 10
// 实拍图
sptUrl := fmt.Sprintf("%s?dataType=0&keyword=%s&page=1&size=%d&sortType=7&actionPath=quality,sortType&quality=85~&quaSelect=2&userArea=13003000000",
"https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list", isbn, 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 size >= apiSptResp.Data.ItemResponse.Total {
startIndex = apiSptResp.Data.ItemResponse.Total - 1
} else {
startIndex = 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
}
// 获取商品信息通过商品详情链接
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 := 3
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) * 1000 // 平方退避
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
}
// =============== 辅助函数 ==============
// 替换所有空白字符为空格
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 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 initializeConfig(config Config) {
// 设置全局配置
cf = config
}
// =================== C 导出函数 =======================
// 孔网登录
//
//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))
}
// 获取孔网用户信息
//
//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))
}
// 获取商品模版--已登的店铺
//
//export OutGetGoodsTplMsg
func OutGetGoodsTplMsg(token, proxy, itemId *C.char) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goItemId := C.GoString(itemId)
info, err := outGetGoodsTplMsg(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))
}
// 获取商品列表-已登的店铺
//
//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 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))
}
// 删除商品-已登的店铺
//
//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))
}
// 获取孔网商品图片和信息(官图和拍图)-带有店铺过滤
//
//export OutGetImageFilterShopId
func OutGetImageFilterShopId(token, proxy, isbn *C.char, shopId C.int, isLiveImage C.int, isReturnMsg C.int) *C.char {
goToken := C.GoString(token)
goProxy := C.GoString(proxy)
goIsbn := C.GoString(isbn)
goShopId := int(shopId)
goIsLiveImage := int(isLiveImage)
goIsReturnMsg := int(isReturnMsg)
info, err := outGetImageFilterShopId(goToken, goProxy, goIsbn, goShopId, 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))
}
// 获取孔网商品图片和信息(官图和拍图)
//
//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
//
//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"`
BookInfo interface{} `json:"book_info,omitempty"`
}{
GoodsNum: num,
PNum: pNum,
BookInfo: 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))
}
// 获取商品信息通过商品详情链接
//
//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))
}
// 获取销量榜商品列表
//
//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() {
}