mirror of
https://github.com/fish2018/pansou.git
synced 2025-11-25 19:37:43 +08:00
update
This commit is contained in:
@@ -23,19 +23,20 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"pansou/model"
|
"pansou/model"
|
||||||
"pansou/plugin"
|
"pansou/plugin"
|
||||||
"pansou/util/json"
|
"pansou/util/json"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
cloudscraper "github.com/Advik-B/cloudscraper/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 插件配置参数
|
// 插件配置参数
|
||||||
const (
|
const (
|
||||||
MaxConcurrentUsers = 10 // 最多使用的用户数
|
MaxConcurrentUsers = 10 // 最多使用的用户数
|
||||||
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
MaxConcurrentDetails = 50 // 最大并发详情请求数
|
||||||
DebugLog = false // 调试日志开关(排查问题时改为true)
|
DebugLog = true // 调试日志开关(排查问题时改为true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 默认账户配置(可通过Web界面添加更多账户)
|
// 默认账户配置(可通过Web界面添加更多账户)
|
||||||
@@ -419,8 +420,8 @@ type SearchData struct {
|
|||||||
|
|
||||||
// DetailData 详情接口JSON数据结构
|
// DetailData 详情接口JSON数据结构
|
||||||
type DetailData struct {
|
type DetailData struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
WP bool `json:"wp"`
|
WP bool `json:"wp"`
|
||||||
Panlist struct {
|
Panlist struct {
|
||||||
ID []string `json:"id"`
|
ID []string `json:"id"`
|
||||||
Name []string `json:"name"`
|
Name []string `json:"name"`
|
||||||
@@ -566,6 +567,8 @@ func (p *GyingPlugin) loadAllUsers() {
|
|||||||
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
// initDefaultAccounts 初始化所有账户(异步执行,不阻塞启动)
|
||||||
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
// 包括:1. DefaultAccounts(代码配置) 2. 从文件加载的用户(使用加密密码重新登录)
|
||||||
func (p *GyingPlugin) initDefaultAccounts() {
|
func (p *GyingPlugin) initDefaultAccounts() {
|
||||||
|
fmt.Printf("[Gying] ========== 异步初始化所有账户 ==========\n")
|
||||||
|
|
||||||
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
// 步骤1:处理DefaultAccounts(代码中配置的默认账户)
|
||||||
for i, account := range DefaultAccounts {
|
for i, account := range DefaultAccounts {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -604,6 +607,8 @@ func (p *GyingPlugin) initDefaultAccounts() {
|
|||||||
p.initOrRestoreUser(user.Username, password, "restore")
|
p.initOrRestoreUser(user.Username, password, "restore")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[Gying] ========== 所有账户初始化完成 ==========\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
// initOrRestoreUser 初始化或恢复单个用户(登录并保存)
|
||||||
@@ -789,13 +794,13 @@ func (p *GyingPlugin) handleGetStatus(c *gin.Context, hash string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
respondSuccess(c, "获取成功", gin.H{
|
respondSuccess(c, "获取成功", gin.H{
|
||||||
"hash": hash,
|
"hash": hash,
|
||||||
"logged_in": loggedIn,
|
"logged_in": loggedIn,
|
||||||
"status": user.Status,
|
"status": user.Status,
|
||||||
"username_masked": user.UsernameMasked,
|
"username_masked": user.UsernameMasked,
|
||||||
"login_time": user.LoginAt.Format("2006-01-02 15:04:05"),
|
"login_time": user.LoginAt.Format("2006-01-02 15:04:05"),
|
||||||
"expire_time": user.ExpireAt.Format("2006-01-02 15:04:05"),
|
"expire_time": user.ExpireAt.Format("2006-01-02 15:04:05"),
|
||||||
"expires_in_days": expiresInDays,
|
"expires_in_days": expiresInDays,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,9 +1020,9 @@ func (p *GyingPlugin) createScraperWithCookies(cookieStr string) (*cloudscraper.
|
|||||||
// 创建cloudscraper实例,配置以保护cookies不被刷新
|
// 创建cloudscraper实例,配置以保护cookies不被刷新
|
||||||
scraper, err := cloudscraper.New(
|
scraper, err := cloudscraper.New(
|
||||||
cloudscraper.WithSessionConfig(
|
cloudscraper.WithSessionConfig(
|
||||||
false, // refreshOn403 = false,禁用403时自动刷新
|
false, // refreshOn403 = false,禁用403时自动刷新
|
||||||
365*24*time.Hour, // interval = 1年,基本不刷新
|
365*24*time.Hour, // interval = 1年,基本不刷新
|
||||||
0, // maxRetries = 0
|
0, // maxRetries = 0
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1049,8 +1054,8 @@ func (p *GyingPlugin) createScraperWithCookies(cookieStr string) (*cloudscraper.
|
|||||||
|
|
||||||
for name, value := range cookies {
|
for name, value := range cookies {
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: value,
|
Value: value,
|
||||||
// 不设置Domain和Path,让cookiejar根据URL自动推导
|
// 不设置Domain和Path,让cookiejar根据URL自动推导
|
||||||
// cookiejar.SetCookies会根据提供的URL自动设置正确的Domain和Path
|
// cookiejar.SetCookies会根据提供的URL自动设置正确的Domain和Path
|
||||||
}
|
}
|
||||||
@@ -1113,9 +1118,9 @@ func parseCookieString(cookieStr string) map[string]string {
|
|||||||
// doLogin 执行登录,返回scraper实例和cookie字符串
|
// doLogin 执行登录,返回scraper实例和cookie字符串
|
||||||
//
|
//
|
||||||
// 登录流程(3步):
|
// 登录流程(3步):
|
||||||
// 1. GET登录页 (https://www.gying.net/user/login/) → 获取PHPSESSID
|
// 1. GET登录页 (https://www.gying.net/user/login/) → 获取PHPSESSID
|
||||||
// 2. POST登录 (https://www.gying.net/user/login) → 获取BT_auth、BT_cookietime等认证cookies
|
// 2. POST登录 (https://www.gying.net/user/login) → 获取BT_auth、BT_cookietime等认证cookies
|
||||||
// 3. GET详情页 (https://www.gying.net/mv/wkMn) → 触发防爬cookies (vrg_sc、vrg_go等)
|
// 3. GET详情页 (https://www.gying.net/mv/wkMn) → 触发防爬cookies (vrg_sc、vrg_go等)
|
||||||
//
|
//
|
||||||
// 返回: (*cloudscraper.Scraper, cookie字符串, error)
|
// 返回: (*cloudscraper.Scraper, cookie字符串, error)
|
||||||
func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper, string, error) {
|
func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper, string, error) {
|
||||||
@@ -1126,7 +1131,14 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建cloudscraper实例(每个用户独立的实例)
|
// 创建cloudscraper实例(每个用户独立的实例)
|
||||||
scraper, err := cloudscraper.New()
|
// 关键配置:禁用403自动刷新,防止cookie被清空
|
||||||
|
scraper, err := cloudscraper.New(
|
||||||
|
cloudscraper.WithSessionConfig(
|
||||||
|
false, // refreshOn403 = false,禁用403时自动刷新(重要!)
|
||||||
|
365*24*time.Hour, // interval = 1年,基本不刷新
|
||||||
|
0, // maxRetries = 0
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 创建cloudscraper失败: %v\n", err)
|
fmt.Printf("[Gying] 创建cloudscraper失败: %v\n", err)
|
||||||
@@ -1135,7 +1147,7 @@ func (p *GyingPlugin) doLogin(username, password string) (*cloudscraper.Scraper,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] cloudscraper创建成功\n")
|
fmt.Printf("[Gying] cloudscraper创建成功(已禁用403自动刷新)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建cookieMap用于收集所有cookies
|
// 创建cookieMap用于收集所有cookies
|
||||||
@@ -1354,6 +1366,54 @@ func min(a, b int) int {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============ 重新登录逻辑 ============
|
||||||
|
|
||||||
|
// reloginUser 重新登录指定用户
|
||||||
|
func (p *GyingPlugin) reloginUser(user *User) error {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] 🔄 开始重新登录用户: %s\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密密码
|
||||||
|
password, err := p.decryptPassword(user.EncryptedPassword)
|
||||||
|
if err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 解密密码失败: %v\n", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("解密密码失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行登录
|
||||||
|
scraper, cookie, err := p.doLogin(user.Username, password)
|
||||||
|
if err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 重新登录失败: %v\n", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("重新登录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新scraper实例
|
||||||
|
p.scrapers.Store(user.Hash, scraper)
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
user.Cookie = cookie
|
||||||
|
user.LoginAt = time.Now()
|
||||||
|
user.ExpireAt = time.Now().AddDate(0, 4, 0)
|
||||||
|
user.Status = "active"
|
||||||
|
|
||||||
|
if err := p.saveUser(user); err != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ⚠️ 保存用户失败: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ✅ 用户 %s 重新登录成功\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ============ 搜索逻辑 ============
|
// ============ 搜索逻辑 ============
|
||||||
|
|
||||||
// executeSearchTasks 并发执行搜索任务
|
// executeSearchTasks 并发执行搜索任务
|
||||||
@@ -1376,8 +1436,14 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 用户 %s 没有scraper实例,尝试使用已保存的cookie创建\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为用户创建新的cloudscraper实例
|
// 为用户创建新的cloudscraper实例(禁用403自动刷新)
|
||||||
newScraper, err := cloudscraper.New()
|
newScraper, err := cloudscraper.New(
|
||||||
|
cloudscraper.WithSessionConfig(
|
||||||
|
false, // refreshOn403 = false
|
||||||
|
365*24*time.Hour, // interval = 1年
|
||||||
|
0, // maxRetries = 0
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 为用户 %s 创建scraper失败: %v\n", u.UsernameMasked, err)
|
fmt.Printf("[Gying] 为用户 %s 创建scraper失败: %v\n", u.UsernameMasked, err)
|
||||||
@@ -1390,7 +1456,7 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
scraper = newScraper
|
scraper = newScraper
|
||||||
|
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 已为用户 %s 创建新的scraper实例\n", u.UsernameMasked)
|
fmt.Printf("[Gying] 已为用户 %s 创建新的scraper实例(已禁用403刷新)\n", u.UsernameMasked)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var ok bool
|
var ok bool
|
||||||
@@ -1403,10 +1469,10 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := p.searchWithScraper(keyword, scraper)
|
results, err := p.searchWithScraperWithRetry(keyword, scraper, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] 用户 %s 搜索失败: %v\n", u.UsernameMasked, err)
|
fmt.Printf("[Gying] 用户 %s 搜索失败(已重试): %v\n", u.UsernameMasked, err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1423,7 +1489,49 @@ func (p *GyingPlugin) executeSearchTasks(users []*User, keyword string) []model.
|
|||||||
return p.deduplicateResults(allResults)
|
return p.deduplicateResults(allResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchWithCookie 使用scraper搜索
|
// searchWithScraperWithRetry 使用scraper搜索(带403自动重新登录重试)
|
||||||
|
func (p *GyingPlugin) searchWithScraperWithRetry(keyword string, scraper *cloudscraper.Scraper, user *User) ([]model.SearchResult, error) {
|
||||||
|
results, err := p.searchWithScraper(keyword, scraper)
|
||||||
|
|
||||||
|
// 检测是否为403错误
|
||||||
|
if err != nil && strings.Contains(err.Error(), "403") {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ⚠️ 检测到403错误,尝试重新登录用户 %s\n", user.UsernameMasked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试重新登录
|
||||||
|
if reloginErr := p.reloginUser(user); reloginErr != nil {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 重新登录失败: %v\n", reloginErr)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("403错误且重新登录失败: %w", reloginErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取新的scraper实例
|
||||||
|
scraperVal, exists := p.scrapers.Load(user.Hash)
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("重新登录后未找到scraper实例")
|
||||||
|
}
|
||||||
|
|
||||||
|
newScraper, ok := scraperVal.(*cloudscraper.Scraper)
|
||||||
|
if !ok || newScraper == nil {
|
||||||
|
return nil, fmt.Errorf("重新登录后scraper实例无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用新scraper重试搜索
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] 🔄 使用新登录状态重试搜索\n")
|
||||||
|
}
|
||||||
|
results, err = p.searchWithScraper(keyword, newScraper)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("重新登录后搜索仍然失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// searchWithScraper 使用scraper搜索
|
||||||
func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Scraper) ([]model.SearchResult, error) {
|
func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Scraper) ([]model.SearchResult, error) {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
fmt.Printf("[Gying] ---------- searchWithScraper 开始 ----------\n")
|
fmt.Printf("[Gying] ---------- searchWithScraper 开始 ----------\n")
|
||||||
@@ -1451,6 +1559,7 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 搜索响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取响应body
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if DebugLog {
|
if DebugLog {
|
||||||
@@ -1471,6 +1580,21 @@ func (p *GyingPlugin) searchWithScraper(keyword string, scraper *cloudscraper.Sc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查403错误
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 收到403 Forbidden - Cookie可能已过期或被网站拒绝\n")
|
||||||
|
if len(body) > 0 {
|
||||||
|
preview := string(body)
|
||||||
|
if len(preview) > 300 {
|
||||||
|
preview = preview[:300] + "..."
|
||||||
|
}
|
||||||
|
fmt.Printf("[Gying] 403响应内容: %s\n", preview)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("HTTP 403 Forbidden - 可能需要重新登录")
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 提取 _obj.search JSON
|
// 2. 提取 _obj.search JSON
|
||||||
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
re := regexp.MustCompile(`_obj\.search=(\{.*?\});`)
|
||||||
matches := re.FindSubmatch(body)
|
matches := re.FindSubmatch(body)
|
||||||
@@ -1624,7 +1748,18 @@ func (p *GyingPlugin) fetchDetail(resourceID, resourceType string, scraper *clou
|
|||||||
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
fmt.Printf("[Gying] 响应状态码: %d\n", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查403错误
|
||||||
|
if resp.StatusCode == 403 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ 详情接口返回403 - Cookie可能已过期\n")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("HTTP 403 Forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
|
if DebugLog {
|
||||||
|
fmt.Printf("[Gying] ❌ HTTP错误: %d\n", resp.StatusCode)
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1974,4 +2109,3 @@ func (p *GyingPlugin) markInactiveUsers() int {
|
|||||||
|
|
||||||
return markedCount
|
return markedCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user