123 lines
3.5 KiB
Go
123 lines
3.5 KiB
Go
package xiaohongshu
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/go-rod/rod"
|
||
)
|
||
|
||
type UserProfileAction struct {
|
||
page *rod.Page
|
||
}
|
||
|
||
func NewUserProfileAction(page *rod.Page) *UserProfileAction {
|
||
pp := page.Timeout(60 * time.Second)
|
||
return &UserProfileAction{page: pp}
|
||
}
|
||
|
||
// UserProfile 获取用户基本信息及帖子
|
||
func (u *UserProfileAction) UserProfile(ctx context.Context, userID, xsecToken string) (*UserProfileResponse, error) {
|
||
page := u.page.Context(ctx)
|
||
|
||
searchURL := makeUserProfileURL(userID, xsecToken)
|
||
page.MustNavigate(searchURL)
|
||
page.MustWaitStable()
|
||
|
||
return u.extractUserProfileData(page)
|
||
}
|
||
|
||
// extractUserProfileData 从页面中提取用户资料数据的通用方法
|
||
func (u *UserProfileAction) extractUserProfileData(page *rod.Page) (*UserProfileResponse, error) {
|
||
page.MustWait(`() => window.__INITIAL_STATE__ !== undefined`)
|
||
|
||
userDataResult := page.MustEval(`() => {
|
||
if (window.__INITIAL_STATE__ &&
|
||
window.__INITIAL_STATE__.user &&
|
||
window.__INITIAL_STATE__.user.userPageData) {
|
||
const userPageData = window.__INITIAL_STATE__.user.userPageData;
|
||
const data = userPageData.value !== undefined ? userPageData.value : userPageData._value;
|
||
if (data) {
|
||
return JSON.stringify(data);
|
||
}
|
||
}
|
||
return "";
|
||
}`).String()
|
||
|
||
if userDataResult == "" {
|
||
return nil, fmt.Errorf("user.userPageData.value not found in __INITIAL_STATE__")
|
||
}
|
||
|
||
// 2. 获取用户帖子:window.__INITIAL_STATE__.user.notes.value
|
||
notesResult := page.MustEval(`() => {
|
||
if (window.__INITIAL_STATE__ &&
|
||
window.__INITIAL_STATE__.user &&
|
||
window.__INITIAL_STATE__.user.notes) {
|
||
const notes = window.__INITIAL_STATE__.user.notes;
|
||
// 优先使用 value(getter),如果不存在则使用 _value(内部字段)
|
||
const data = notes.value !== undefined ? notes.value : notes._value;
|
||
if (data) {
|
||
return JSON.stringify(data);
|
||
}
|
||
}
|
||
return "";
|
||
}`).String()
|
||
|
||
if notesResult == "" {
|
||
return nil, fmt.Errorf("user.notes.value not found in __INITIAL_STATE__")
|
||
}
|
||
|
||
// 解析用户信息
|
||
var userPageData struct {
|
||
Interactions []UserInteractions `json:"interactions"`
|
||
BasicInfo UserBasicInfo `json:"basicInfo"`
|
||
}
|
||
if err := json.Unmarshal([]byte(userDataResult), &userPageData); err != nil {
|
||
return nil, fmt.Errorf("failed to unmarshal userPageData: %w", err)
|
||
}
|
||
|
||
// 解析帖子数据(帖子为双重数组)
|
||
var notesFeeds [][]Feed
|
||
if err := json.Unmarshal([]byte(notesResult), ¬esFeeds); err != nil {
|
||
return nil, fmt.Errorf("failed to unmarshal notes: %w", err)
|
||
}
|
||
|
||
// 组装响应
|
||
response := &UserProfileResponse{
|
||
UserBasicInfo: userPageData.BasicInfo,
|
||
Interactions: userPageData.Interactions,
|
||
}
|
||
|
||
// 添加用户帖子(展平双重数组)
|
||
for _, feeds := range notesFeeds {
|
||
if len(feeds) != 0 {
|
||
response.Feeds = append(response.Feeds, feeds...)
|
||
}
|
||
}
|
||
|
||
return response, nil
|
||
}
|
||
|
||
func makeUserProfileURL(userID, xsecToken string) string {
|
||
return fmt.Sprintf("https://www.xiaohongshu.com/user/profile/%s?xsec_token=%s&xsec_source=pc_note", userID, xsecToken)
|
||
}
|
||
|
||
func (u *UserProfileAction) GetMyProfileViaSidebar(ctx context.Context) (*UserProfileResponse, error) {
|
||
page := u.page.Context(ctx)
|
||
|
||
// 创建导航动作
|
||
navigate := NewNavigate(page)
|
||
|
||
// 通过侧边栏导航到个人主页
|
||
if err := navigate.ToProfilePage(ctx); err != nil {
|
||
return nil, fmt.Errorf("failed to navigate to profile page via sidebar: %w", err)
|
||
}
|
||
|
||
// 等待页面加载完成并获取 __INITIAL_STATE__
|
||
page.MustWaitStable()
|
||
|
||
return u.extractUserProfileData(page)
|
||
}
|