648 lines
19 KiB
Go
648 lines
19 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"io/ioutil"
|
||
"log"
|
||
"net/url"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/wechat-crawler/configs"
|
||
"github.com/wechat-crawler/pkg/utils"
|
||
"github.com/wechat-crawler/pkg/wechat"
|
||
)
|
||
|
||
func main() {
|
||
fmt.Println("微信公众号文章爬取工具 v1.0")
|
||
fmt.Println("==============================")
|
||
|
||
// 初始化配置
|
||
cfg := configs.NewConfig()
|
||
|
||
// 检查命令行参数
|
||
if len(os.Args) > 1 && os.Args[1] != "" {
|
||
// 如果提供了文章链接参数,使用新的方法
|
||
articleLink := os.Args[1]
|
||
fmt.Printf("\n从文章链接开始操作: %s\n", articleLink)
|
||
|
||
// 直接获取公众号主页链接,不依赖cookie,不进行后续爬取
|
||
officialAccountLink, err := GetOfficialAccountLinkFromArticleOnly(articleLink)
|
||
if err != nil {
|
||
log.Fatalf("获取公众号主页链接失败: %v", err)
|
||
}
|
||
fmt.Printf("公众号主页链接: %s\n", officialAccountLink)
|
||
return
|
||
}
|
||
|
||
// 显示功能菜单
|
||
showMenu(cfg)
|
||
|
||
fmt.Println("\n操作完成!")
|
||
}
|
||
|
||
func startCrawling(cfg *configs.Config) {
|
||
// 读取cookie信息
|
||
cookiePath := filepath.Join(cfg.RootPath, "cookie.txt")
|
||
cookieContent, err := ioutil.ReadFile(cookiePath)
|
||
if err != nil {
|
||
fmt.Printf("读取cookie失败: %v,请确保cookie.txt文件存在\n", err)
|
||
return
|
||
}
|
||
|
||
// 解析cookie获取必要参数
|
||
cookieStr := string(cookieContent)
|
||
biz, uin, key, passTicket, err := parseCookieInfo(cookieStr)
|
||
if err != nil {
|
||
fmt.Printf("解析cookie信息失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 创建爬虫实例
|
||
crawler, err := wechat.NewWechatCrawler(biz, uin, key, passTicket, cfg)
|
||
if err != nil {
|
||
fmt.Printf("创建爬虫实例失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 获取公众号名称
|
||
nickname, err := crawler.GetOfficialAccountName()
|
||
if err != nil {
|
||
fmt.Printf("获取公众号名称失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
fmt.Printf("\n正在爬取公众号: %s\n", nickname)
|
||
|
||
// 创建公众号目录
|
||
officialPath := filepath.Join(cfg.RootPath, "data", nickname)
|
||
err = utils.CreateDir(officialPath)
|
||
if err != nil {
|
||
fmt.Printf("创建目录失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 获取所有文章列表
|
||
fmt.Println("\n开始获取文章列表...")
|
||
articleList := [][]string{}
|
||
page := 0
|
||
for {
|
||
fmt.Printf("正在获取第 %d 页文章...\n", page+1)
|
||
result, err := crawler.GetNextList(page)
|
||
if err != nil {
|
||
fmt.Printf("获取第 %d 页文章失败: %v\n", page+1, err)
|
||
break
|
||
}
|
||
|
||
if result["m_flag"].(int) == 0 {
|
||
fmt.Printf("已获取全部文章,共 %d 页\n", page)
|
||
break
|
||
}
|
||
|
||
passageList := result["passage_list"].([][]string)
|
||
articleList = append(articleList, passageList...)
|
||
|
||
fmt.Printf("第 %d 页获取成功,新增 %d 篇文章\n", page+1, len(passageList))
|
||
|
||
page++
|
||
// 防止请求过于频繁
|
||
time.Sleep(2 * time.Second)
|
||
}
|
||
|
||
fmt.Printf("\n共获取到 %d 篇文章\n", len(articleList))
|
||
|
||
// 保存文章列表
|
||
err = crawler.SaveArticleListToExcel(officialPath, articleList, nickname)
|
||
if err != nil {
|
||
fmt.Printf("保存文章列表失败: %v\n", err)
|
||
}
|
||
|
||
// 转换文章链接(这个方法不需要单独调用,GetArticleList已经内部调用了)
|
||
// 文章列表已经在GetArticleList方法中转换过了
|
||
|
||
// 读取文章链接
|
||
links, err := crawler.ReadArticleLinksFromExcel(filepath.Join(officialPath, "文章列表(article_list)_原始链接.txt"))
|
||
if err != nil {
|
||
// 如果读取失败,直接使用articleList中的链接
|
||
links = []string{}
|
||
for _, article := range articleList {
|
||
links = append(links, article[3])
|
||
}
|
||
}
|
||
|
||
// 获取文章详情
|
||
fmt.Println("\n开始获取文章详情...")
|
||
for i, link := range links {
|
||
fmt.Printf("正在获取文章 %d/%d: %s\n", i+1, len(links), link)
|
||
|
||
// 转换链接
|
||
transformedLink := utils.TransformLink(link)
|
||
|
||
// 获取文章内容
|
||
content, err := crawler.GetOneArticle(transformedLink)
|
||
if err != nil {
|
||
fmt.Printf("获取文章内容失败: %v\n", err)
|
||
continue
|
||
}
|
||
|
||
// 提取文章标题
|
||
title := ""
|
||
for _, article := range articleList {
|
||
if article[3] == link {
|
||
title = article[2]
|
||
break
|
||
}
|
||
}
|
||
|
||
// 提取创建时间
|
||
createTime := ""
|
||
for _, article := range articleList {
|
||
if article[3] == link {
|
||
createTime = article[1]
|
||
break
|
||
}
|
||
}
|
||
|
||
// 提取评论ID
|
||
commentID, _ := utils.ExtractFromRegex(content, "comment_id = '(.*?)';")
|
||
|
||
// 生成req_id
|
||
reqID := fmt.Sprintf("%d", time.Now().UnixNano())
|
||
|
||
// 获取文章统计信息
|
||
stats, err := crawler.GetArticleStats(link, title, commentID, reqID, createTime)
|
||
if err != nil {
|
||
fmt.Printf("获取文章统计信息失败: %v\n", err)
|
||
// 设置默认值
|
||
stats = map[string]string{
|
||
"read_num": "0",
|
||
"old_like_num": "0",
|
||
"share_num": "0",
|
||
"show_read": "0",
|
||
}
|
||
}
|
||
|
||
// 获取文章评论
|
||
comments, commentLikes, err := crawler.GetArticleComments(commentID)
|
||
if err != nil {
|
||
fmt.Printf("获取文章评论失败: %v\n", err)
|
||
comments = []string{}
|
||
commentLikes = []string{}
|
||
}
|
||
|
||
// 解析文章正文内容(这里简化处理,实际需要使用goquery进行HTML解析)
|
||
articleContent := parseArticleContent(content)
|
||
|
||
// 创建文章详情对象
|
||
detail := &wechat.ArticleDetail{
|
||
LocalTime: utils.GetCurrentTime(),
|
||
CreateTime: createTime,
|
||
Title: title,
|
||
Link: transformedLink,
|
||
Content: articleContent,
|
||
ReadCount: stats["read_num"],
|
||
LikeCount: stats["old_like_num"],
|
||
ShareCount: stats["share_num"],
|
||
Comments: comments,
|
||
CommentLikes: commentLikes,
|
||
CommentID: commentID,
|
||
}
|
||
|
||
// 保存文章详情
|
||
err = crawler.SaveArticleDetailToExcel(detail, officialPath)
|
||
if err != nil {
|
||
fmt.Printf("保存文章详情失败: %v\n", err)
|
||
}
|
||
|
||
// 防止请求过于频繁
|
||
time.Sleep(3 * time.Second)
|
||
}
|
||
|
||
fmt.Printf("\n公众号 %s 爬取完成!\n", nickname)
|
||
}
|
||
|
||
func parseCookieInfo(cookieStr string) (string, string, string, string, error) {
|
||
// 从cookie中提取必要的参数
|
||
biz, err := utils.ExtractFromRegex(cookieStr, "__biz=(.*?);")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到__biz参数")
|
||
}
|
||
|
||
uin, err := utils.ExtractFromRegex(cookieStr, "uin=(.*?);")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到uin参数")
|
||
}
|
||
|
||
key, err := utils.ExtractFromRegex(cookieStr, "key=(.*?);")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到key参数")
|
||
}
|
||
|
||
passTicket, err := utils.ExtractFromRegex(cookieStr, "pass_ticket=(.*?);")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到pass_ticket参数")
|
||
}
|
||
|
||
return biz, uin, key, passTicket, nil
|
||
}
|
||
|
||
func parseArticleContent(content string) []string {
|
||
// 这里简化处理,实际需要使用goquery进行HTML解析
|
||
// 提取文章正文内容
|
||
var result []string
|
||
|
||
// 尝试提取body内容
|
||
bodyContent, err := utils.ExtractFromRegex(content, "<body[^>]*>(.*?)</body>")
|
||
if err != nil {
|
||
result = append(result, "无法解析文章内容")
|
||
return result
|
||
}
|
||
|
||
// 简单去除HTML标签
|
||
txt := strings.ReplaceAll(bodyContent, "<", " <")
|
||
txt = strings.ReplaceAll(txt, ">", "> ")
|
||
|
||
// 按行分割并过滤空白行
|
||
lines := strings.Split(txt, "\n")
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
if line != "" && !strings.HasPrefix(line, "<") {
|
||
result = append(result, line)
|
||
// 限制内容长度
|
||
if len(result) > 100 {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// 仅从文章链接获取公众号主页链接,不依赖cookie,不进行后续爬取
|
||
func GetOfficialAccountLinkFromArticleOnly(articleLink string) (string, error) {
|
||
// 创建一个简单的爬虫实例,不需要cookie信息
|
||
crawler := wechat.NewSimpleCrawler()
|
||
|
||
// 从文章链接获取公众号主页链接
|
||
officialAccountLink, err := crawler.GetOfficialAccountLinkFromArticle(articleLink)
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取文章内容失败: %v", err)
|
||
}
|
||
|
||
return officialAccountLink, nil
|
||
}
|
||
|
||
// 从文章链接开始爬取
|
||
func startCrawlingFromArticleLink(cfg *configs.Config, articleLink string) {
|
||
// 读取cookie信息
|
||
cookiePath := filepath.Join(cfg.RootPath, "cookie.txt")
|
||
cookieContent, err := ioutil.ReadFile(cookiePath)
|
||
if err != nil {
|
||
fmt.Printf("读取cookie失败: %v,请确保cookie.txt文件存在\n", err)
|
||
return
|
||
}
|
||
|
||
// 解析cookie获取必要参数
|
||
cookieStr := string(cookieContent)
|
||
_, uin, key, passTicket, err := parseCookieInfo(cookieStr)
|
||
if err != nil {
|
||
fmt.Printf("解析cookie信息失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 创建爬虫实例(biz将通过文章链接获取)
|
||
crawler, err := wechat.NewWechatCrawler("", uin, key, passTicket, nil)
|
||
if err != nil {
|
||
fmt.Printf("创建爬虫实例失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 1. 从文章链接获取公众号主页链接
|
||
officialAccountLink, err := crawler.GetOfficialAccountLinkFromArticle(articleLink)
|
||
if err != nil {
|
||
log.Fatalf("获取公众号主页链接失败: %v", err)
|
||
}
|
||
fmt.Printf("获取到公众号主页链接: %s\n", officialAccountLink)
|
||
|
||
// 2. 获取公众号名称
|
||
officialAccountName, err := crawler.GetOfficialAccountName()
|
||
if err != nil {
|
||
log.Printf("警告: 获取公众号名称失败: %v,将使用默认名称", err)
|
||
officialAccountName = "公众号_" + time.Now().Format("20060102150405")
|
||
}
|
||
fmt.Printf("获取到公众号名称: %s\n", officialAccountName)
|
||
|
||
// 3. 创建输出目录
|
||
outputDir := filepath.Join(cfg.RootPath, "data", officialAccountName)
|
||
err = utils.CreateDir(outputDir)
|
||
if err != nil {
|
||
log.Fatalf("创建输出目录失败: %v", err)
|
||
}
|
||
|
||
// 4. 获取文章列表(使用我们新实现的GetArticleList方法)
|
||
fmt.Println("\n开始获取文章列表...")
|
||
articleList, err := crawler.GetArticleList()
|
||
if err != nil {
|
||
log.Fatalf("获取文章列表失败: %v", err)
|
||
}
|
||
|
||
// 5. 保存文章列表到Excel
|
||
excelPath := filepath.Join(outputDir, "文章列表.xlsx")
|
||
if err := crawler.SaveArticleListToExcel(outputDir, articleList, officialAccountName); err != nil {
|
||
log.Fatalf("保存文章列表失败: %v", err)
|
||
}
|
||
fmt.Printf("文章列表已保存到: %s\n", excelPath)
|
||
fmt.Printf("共获取到 %d 篇文章\n", len(articleList))
|
||
|
||
// 6. 获取文章详情(使用我们新实现的GetDetailList方法)
|
||
fmt.Println("\n开始获取文章详情...")
|
||
if err := crawler.GetDetailList(articleList, outputDir); err != nil {
|
||
log.Fatalf("获取文章详情失败: %v", err)
|
||
}
|
||
|
||
fmt.Printf("\n公众号 %s 爬取完成!\n", officialAccountName)
|
||
}
|
||
|
||
// 显示功能菜单
|
||
func showMenu(cfg *configs.Config) {
|
||
screenText := `请输入数字键!
|
||
数字键1:仅获取公众号主页链接(输入公众号下任意一篇已发布的文章链接即可,无需cookie)
|
||
数字键2:使用cookie信息爬取公众号文章
|
||
数字键3:通过access_token和pages获取文章链接列表
|
||
数字键4:根据先前生成的文章链接列表下载文件
|
||
数字键5:根据公众号名称或链接,从文件读取文章列表并下载内容
|
||
输入其他任意字符退出!`
|
||
fmt.Println(screenText)
|
||
|
||
for {
|
||
text := ""
|
||
fmt.Print("请输入功能数字:")
|
||
fmt.Scanln(&text)
|
||
|
||
if text == "1" {
|
||
articleLink := ""
|
||
fmt.Print("请输入公众号下任意一篇已发布的文章链接:")
|
||
fmt.Scanln(&articleLink)
|
||
if articleLink == "" {
|
||
fmt.Println("链接不能为空,请重新输入")
|
||
continue
|
||
}
|
||
|
||
officialAccountLink, err := GetOfficialAccountLinkFromArticleOnly(articleLink)
|
||
if err != nil {
|
||
fmt.Printf("获取失败: %v\n", err)
|
||
} else {
|
||
fmt.Printf("\n成功获取公众号主页链接:\n%s\n", officialAccountLink)
|
||
}
|
||
fmt.Println("\n" + screenText)
|
||
|
||
} else if text == "2" {
|
||
// 使用原有的方法
|
||
fmt.Println("\n使用cookie中的信息爬取")
|
||
startCrawling(cfg)
|
||
break
|
||
} else if text == "3" {
|
||
// 通过access_token和pages获取文章链接列表
|
||
fmt.Println("\n通过access_token和pages获取文章链接列表")
|
||
startGettingArticleListByAccessToken(cfg)
|
||
fmt.Println("\n" + screenText)
|
||
} else if text == "4" {
|
||
// 根据先前生成的文章链接列表下载文件
|
||
fmt.Println("\n根据先前生成的文章链接列表下载文件")
|
||
fmt.Println("功能4执行完成")
|
||
fmt.Println("\n" + screenText)
|
||
} else if text == "5" {
|
||
// 根据公众号名称或链接,从文件读取文章列表并下载内容
|
||
fmt.Println("\n开始执行:根据公众号名称或链接,从文件读取文章列表并下载内容")
|
||
crawler, err := wechat.NewWechatCrawler("", "", "", "", cfg)
|
||
if err != nil {
|
||
fmt.Printf("创建爬虫实例失败: %v\n", err)
|
||
continue
|
||
}
|
||
|
||
// 获取用户输入
|
||
fmt.Print("请输入公众号名称或文章链接:")
|
||
var nameLink string
|
||
fmt.Scanln(&nameLink)
|
||
|
||
// 设置是否保存图片和内容
|
||
imgSaveFlag := false
|
||
contentSaveFlag := true
|
||
|
||
fmt.Print("是否保存图片?(y/n,默认n):")
|
||
var imgChoice string
|
||
fmt.Scanln(&imgChoice)
|
||
if imgChoice == "y" || imgChoice == "Y" {
|
||
imgSaveFlag = true
|
||
}
|
||
|
||
// 执行功能
|
||
err = crawler.GetListArticleFromFile(nameLink, imgSaveFlag, contentSaveFlag)
|
||
if err != nil {
|
||
fmt.Printf("功能执行失败: %v\n", err)
|
||
} else {
|
||
fmt.Println("功能5执行完成")
|
||
}
|
||
fmt.Println("\n" + screenText)
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通过access_token和pages获取文章链接列表
|
||
func startGettingArticleListByAccessToken(cfg *configs.Config) {
|
||
// 提示用户输入从fiddler获取的链接
|
||
fmt.Println("【accessToken获取方式提示】")
|
||
fmt.Println("1. 使用Fiddler等抓包工具,设置代理监听浏览器流量")
|
||
fmt.Println("2. 打开微信公众号文章列表页面,向下滚动加载更多文章")
|
||
fmt.Println("3. 在Fiddler中找到包含\"list\"和\"access_token\"参数的请求")
|
||
fmt.Println("4. 复制完整的请求URL作为输入")
|
||
fmt.Println("5. 文章页数为想要获取的历史文章数量,通常每10篇文章为1页")
|
||
fmt.Println()
|
||
accessToken := ""
|
||
fmt.Print("请输入从fiddler获取的链接(包含access_token等参数):")
|
||
fmt.Scanln(&accessToken)
|
||
if accessToken == "" {
|
||
fmt.Println("链接不能为空,请重新输入")
|
||
return
|
||
}
|
||
|
||
// 提示用户输入文章页数
|
||
pagesStr := ""
|
||
fmt.Print("请输入要获取的文章页数(0表示全部):")
|
||
fmt.Scanln(&pagesStr)
|
||
|
||
// 转换页数为整数
|
||
pages := 0
|
||
if pagesStr != "" {
|
||
_, err := fmt.Sscanf(pagesStr, "%d", &pages)
|
||
if err != nil || pages < 0 {
|
||
fmt.Println("页数输入错误,将使用默认值0(全部)")
|
||
pages = 0
|
||
}
|
||
}
|
||
|
||
// 从access_token中提取必要参数
|
||
biz, uin, key, passTicket, err := parseAccessTokenParams(accessToken)
|
||
if err != nil {
|
||
fmt.Printf("解析access_token失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 创建爬虫实例
|
||
crawler, err := wechat.NewWechatCrawler(biz, uin, key, passTicket, cfg)
|
||
if err != nil {
|
||
fmt.Printf("创建爬虫实例失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 获取公众号名称
|
||
nickname, err := crawler.GetOfficialAccountName()
|
||
if err != nil {
|
||
fmt.Printf("获取公众号名称失败: %v,将使用默认名称\n", err)
|
||
nickname = "公众号_" + time.Now().Format("20060102150405")
|
||
}
|
||
|
||
fmt.Printf("\n正在获取公众号 '%s' 的文章列表...\n", nickname)
|
||
|
||
// 创建公众号目录
|
||
officialPath := filepath.Join(cfg.RootPath, "data", nickname)
|
||
err = utils.CreateDir(officialPath)
|
||
if err != nil {
|
||
fmt.Printf("创建目录失败: %v\n", err)
|
||
return
|
||
}
|
||
|
||
// 获取文章列表
|
||
articleList := [][]string{}
|
||
offset := 0
|
||
pageCount := 0
|
||
|
||
for {
|
||
fmt.Printf("正在获取第 %d 页文章...\n", pageCount+1)
|
||
result, err := crawler.GetNextList(offset)
|
||
if err != nil {
|
||
fmt.Printf("获取第 %d 页文章失败: %v\n", pageCount+1, err)
|
||
break
|
||
}
|
||
|
||
mFlag, ok := result["m_flag"].(int)
|
||
if !ok || mFlag == 0 {
|
||
fmt.Printf("已获取全部文章,共 %d 页\n", pageCount)
|
||
break
|
||
}
|
||
|
||
passageList, ok := result["passage_list"].([][]string)
|
||
if !ok {
|
||
fmt.Printf("文章列表格式错误\n")
|
||
break
|
||
}
|
||
|
||
articleList = append(articleList, passageList...)
|
||
|
||
fmt.Printf("第 %d 页获取成功,新增 %d 篇文章\n", pageCount+1, len(passageList))
|
||
|
||
pageCount++
|
||
offset += 10
|
||
|
||
// 检查是否达到指定页数
|
||
if pages > 0 && pageCount >= pages {
|
||
fmt.Printf("已获取指定的 %d 页文章\n", pages)
|
||
break
|
||
}
|
||
|
||
// 防止请求过于频繁
|
||
time.Sleep(2 * time.Second)
|
||
}
|
||
|
||
fmt.Printf("\n共获取到 %d 篇文章\n", len(articleList))
|
||
|
||
// 保存文章列表
|
||
err = crawler.SaveArticleListToExcel(officialPath, articleList, nickname)
|
||
if err != nil {
|
||
fmt.Printf("保存文章列表失败: %v\n", err)
|
||
} else {
|
||
fmt.Printf("文章列表已保存到: %s\n", filepath.Join(officialPath, "文章列表(article_list)_原始链接.xlsx"))
|
||
}
|
||
|
||
// 转换链接并保存直连链接
|
||
transformedList := transformLinks(articleList)
|
||
err = crawler.SaveArticleListToExcel(officialPath, transformedList, nickname)
|
||
if err != nil {
|
||
fmt.Printf("保存直连链接失败: %v\n", err)
|
||
} else {
|
||
fmt.Printf("直连链接已保存到: %s\n", filepath.Join(officialPath, "文章列表(article_list)_直连链接.xlsx"))
|
||
}
|
||
}
|
||
|
||
// 转换链接列表中的链接
|
||
func transformLinks(articleList [][]string) [][]string {
|
||
transformedList := make([][]string, len(articleList))
|
||
for i, article := range articleList {
|
||
transformedArticle := make([]string, len(article))
|
||
copy(transformedArticle, article)
|
||
// 转换链接,移除amp;
|
||
if len(article) > 3 {
|
||
transformedArticle[3] = strings.ReplaceAll(article[3], "amp;", "")
|
||
}
|
||
transformedList[i] = transformedArticle
|
||
}
|
||
return transformedList
|
||
}
|
||
|
||
// 从access_token中提取必要参数
|
||
func parseAccessTokenParams(accessToken string) (string, string, string, string, error) {
|
||
// 从URL中提取必要的参数
|
||
biz, err := utils.ExtractFromRegex(accessToken, "__biz=([^&]*)")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到__biz参数")
|
||
}
|
||
// URL解码biz参数
|
||
biz, err = url.QueryUnescape(biz)
|
||
if err != nil {
|
||
fmt.Printf("警告: URL解码__biz失败: %v,使用原始值\n", err)
|
||
}
|
||
|
||
uin, err := utils.ExtractFromRegex(accessToken, "uin=([^&]*)")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到uin参数")
|
||
}
|
||
// URL解码uin参数
|
||
uin, err = url.QueryUnescape(uin)
|
||
if err != nil {
|
||
fmt.Printf("警告: URL解码uin失败: %v,使用原始值\n", err)
|
||
}
|
||
|
||
key, err := utils.ExtractFromRegex(accessToken, "key=([^&]*)")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到key参数")
|
||
}
|
||
// URL解码key参数
|
||
key, err = url.QueryUnescape(key)
|
||
if err != nil {
|
||
fmt.Printf("警告: URL解码key失败: %v,使用原始值\n", err)
|
||
}
|
||
|
||
passTicket, err := utils.ExtractFromRegex(accessToken, "pass_ticket=([^&]*)")
|
||
if err != nil {
|
||
return "", "", "", "", fmt.Errorf("未找到pass_ticket参数")
|
||
}
|
||
// URL解码pass_ticket参数
|
||
passTicket, err = url.QueryUnescape(passTicket)
|
||
if err != nil {
|
||
fmt.Printf("警告: URL解码pass_ticket失败: %v,使用原始值\n", err)
|
||
}
|
||
|
||
// 打印解码后的参数用于调试
|
||
fmt.Printf("\n提取到的参数(已解码):\n")
|
||
fmt.Printf(" __biz: %s\n", biz)
|
||
fmt.Printf(" uin: %s\n", uin)
|
||
fmt.Printf(" key长度: %d 字符\n", len(key))
|
||
fmt.Printf(" pass_ticket长度: %d 字符\n", len(passTicket))
|
||
|
||
return biz, uin, key, passTicket, nil
|
||
}
|