feat: 添加 Chrome 浏览器支持和安装说明折叠 (#67)

* feat: 添加 Chrome 浏览器支持和安装说明折叠

- 支持 Chrome 浏览器作为备选方案
- README 安装部分使用 details 标签折叠
- 优化浏览器配置和初始化逻辑

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: 更新 README.md 以包含 Windows 问题和登录部分说明

- 添加 Windows 问题的链接以供参考
- 更新 README 中的登录部分内容

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
zy
2025-09-14 17:49:19 +08:00
committed by GitHub
parent 47aba20735
commit e4745d28d4
8 changed files with 92 additions and 17 deletions

View File

@@ -135,7 +135,35 @@ https://github.com/user-attachments/assets/cc385b6c-422c-489b-a5fc-63e92c695b80
## 1. 使用教程 ## 1. 使用教程
### 1.1. 登录 ### 1.1. 安装
<details>
<summary>安装配置详情</summary>
依赖 Golang 环境,安装方法请参考 [Golang 官方文档](https://go.dev/doc/install)。
设置 Go 国内源的代理,
```bash
# 配置 GOPROXY 环境变量,以下三选一
# 1. 七牛 CDN
go env -w GOPROXY=https://goproxy.cn,direct
# 2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
# 3. 官方
go env -w GOPROXY=https://goproxy.io,direct
```
</details>
Windows 问题:
可以参考这里 https://github.com/xpzouying/xiaohongshu-mcp/issues/56
### 1.2. 登录
第一次需要手动登录,需要保存小红书的登录状态。 第一次需要手动登录,需要保存小红书的登录状态。
@@ -145,7 +173,7 @@ https://github.com/user-attachments/assets/cc385b6c-422c-489b-a5fc-63e92c695b80
go run cmd/login/main.go go run cmd/login/main.go
``` ```
### 1.2. 启动 MCP 服务 ### 1.3. 启动 MCP 服务
启动 xiaohongshu-mcp 服务。 启动 xiaohongshu-mcp 服务。
@@ -158,7 +186,7 @@ go run .
go run . -headless=false go run . -headless=false
``` ```
## 1.3. 验证 MCP ## 1.4. 验证 MCP
```bash ```bash
npx @modelcontextprotocol/inspector npx @modelcontextprotocol/inspector
@@ -172,7 +200,7 @@ npx @modelcontextprotocol/inspector
按照上面配置 MCP inspector 后,点击 `List Tools` 按钮,查看所有的 Tools。 按照上面配置 MCP inspector 后,点击 `List Tools` 按钮,查看所有的 Tools。
## 1.4. 使用 MCP 发布 ## 1.5. 使用 MCP 发布
### 检查登录状态 ### 检查登录状态
@@ -410,7 +438,6 @@ npx @modelcontextprotocol/inspector
<img src="./assets/publish_result.jpeg" alt="xiaohongshu-mcp 发布结果" width="400"> <img src="./assets/publish_result.jpeg" alt="xiaohongshu-mcp 发布结果" width="400">
## 小红书 MCP 互助群 ## 小红书 MCP 互助群
因为项目刚刚启动,会有很多问题,拉一个群大家一起讨论问题,一起为开源项目做贡献。扫我的微信二维码加群讨论技术。 因为项目刚刚启动,会有很多问题,拉一个群大家一起讨论问题,一起为开源项目做贡献。扫我的微信二维码加群讨论技术。
@@ -418,4 +445,3 @@ npx @modelcontextprotocol/inspector
**申请时务必添加备注。** **申请时务必添加备注。**
![WechatIMG111](https://github.com/user-attachments/assets/ae535953-6c8b-4c12-91b6-ac1572ce9032) ![WechatIMG111](https://github.com/user-attachments/assets/ae535953-6c8b-4c12-91b6-ac1572ce9032)

View File

@@ -6,11 +6,30 @@ import (
"github.com/xpzouying/xiaohongshu-mcp/cookies" "github.com/xpzouying/xiaohongshu-mcp/cookies"
) )
func NewBrowser(headless bool) *headless_browser.Browser { type browserConfig struct {
binPath string
}
type Option func(*browserConfig)
func WithBinPath(binPath string) Option {
return func(c *browserConfig) {
c.binPath = binPath
}
}
func NewBrowser(headless bool, options ...Option) *headless_browser.Browser {
cfg := &browserConfig{}
for _, opt := range options {
opt(cfg)
}
opts := []headless_browser.Option{ opts := []headless_browser.Option{
headless_browser.WithHeadless(headless), headless_browser.WithHeadless(headless),
} }
if cfg.binPath != "" {
opts = append(opts, headless_browser.WithChromeBinPath(cfg.binPath))
}
// 加载 cookies // 加载 cookies
cookiePath := cookies.GetCookiesFilePath() cookiePath := cookies.GetCookiesFilePath()

View File

@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"flag"
"github.com/go-rod/rod" "github.com/go-rod/rod"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -12,9 +13,14 @@ import (
) )
func main() { func main() {
var (
binPath string // 浏览器二进制文件路径
)
flag.StringVar(&binPath, "bin", "", "浏览器二进制文件路径")
flag.Parse()
// 登录的时候,需要界面,所以不能无头模式 // 登录的时候,需要界面,所以不能无头模式
b := browser.NewBrowser(false) b := browser.NewBrowser(false, browser.WithBinPath(binPath))
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()

View File

@@ -2,6 +2,8 @@ package configs
var ( var (
useHeadless = true useHeadless = true
binPath = ""
) )
func InitHeadless(h bool) { func InitHeadless(h bool) {
@@ -12,3 +14,11 @@ func InitHeadless(h bool) {
func IsHeadless() bool { func IsHeadless() bool {
return useHeadless return useHeadless
} }
func SetBinPath(b string) {
binPath = b
}
func GetBinPath() string {
return binPath
}

6
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/xpzouying/xiaohongshu-mcp module github.com/xpzouying/xiaohongshu-mcp
go 1.23.5 go 1.24.0
require ( require (
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
@@ -9,7 +9,7 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/xpzouying/headless_browser v0.0.2 github.com/xpzouying/headless_browser v0.1.0
) )
require ( require (
@@ -45,7 +45,7 @@ require (
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

6
go.sum
View File

@@ -81,8 +81,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xpzouying/headless_browser v0.0.2 h1:sLc4gqUT/5IyTruYIOfCW4aZLinq38hIdUHCHem1KYo= github.com/xpzouying/headless_browser v0.0.2 h1:sLc4gqUT/5IyTruYIOfCW4aZLinq38hIdUHCHem1KYo=
github.com/xpzouying/headless_browser v0.0.2/go.mod h1:bQTSzGYHIipa1zwToMlOGHcXWDlvw8y33Cx5zzElekc= github.com/xpzouying/headless_browser v0.0.2/go.mod h1:bQTSzGYHIipa1zwToMlOGHcXWDlvw8y33Cx5zzElekc=
github.com/xpzouying/headless_browser v0.1.0 h1:0FyMIzhe/If/VhEdDrs7T1fqm1gOZSCFrmMXI/1JM58=
github.com/xpzouying/headless_browser v0.1.0/go.mod h1:bQTSzGYHIipa1zwToMlOGHcXWDlvw8y33Cx5zzElekc=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/fetchup v0.5.2 h1:P9w3OIA7RSNEEFvEmOiTq09IOu42C96PMyZ1MWd8TAs=
github.com/ysmood/fetchup v0.5.2/go.mod h1:yCv8s8itjsCul1LGXJ1Q+8EQnZcVjfbZ4+l1zDm4StE=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk= github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
@@ -110,6 +114,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=

View File

@@ -10,11 +10,14 @@ import (
func main() { func main() {
var ( var (
headless bool headless bool
binPath string // 浏览器二进制文件路径
) )
flag.BoolVar(&headless, "headless", true, "是否无头模式") flag.BoolVar(&headless, "headless", true, "是否无头模式")
flag.StringVar(&binPath, "bin", "", "浏览器二进制文件路径")
flag.Parse() flag.Parse()
configs.InitHeadless(headless) configs.InitHeadless(headless)
configs.SetBinPath(binPath)
// 初始化服务 // 初始化服务
xiaohongshuService := NewXiaohongshuService() xiaohongshuService := NewXiaohongshuService()

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/xpzouying/headless_browser"
"github.com/xpzouying/xiaohongshu-mcp/browser" "github.com/xpzouying/xiaohongshu-mcp/browser"
"github.com/xpzouying/xiaohongshu-mcp/configs" "github.com/xpzouying/xiaohongshu-mcp/configs"
"github.com/xpzouying/xiaohongshu-mcp/pkg/downloader" "github.com/xpzouying/xiaohongshu-mcp/pkg/downloader"
@@ -50,7 +51,7 @@ type FeedsListResponse struct {
// CheckLoginStatus 检查登录状态 // CheckLoginStatus 检查登录状态
func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatusResponse, error) { func (s *XiaohongshuService) CheckLoginStatus(ctx context.Context) (*LoginStatusResponse, error) {
b := browser.NewBrowser(configs.IsHeadless()) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -117,7 +118,7 @@ func (s *XiaohongshuService) processImages(images []string) ([]string, error) {
// publishContent 执行内容发布 // publishContent 执行内容发布
func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohongshu.PublishImageContent) error { func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohongshu.PublishImageContent) error {
b := browser.NewBrowser(configs.IsHeadless()) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -134,7 +135,7 @@ func (s *XiaohongshuService) publishContent(ctx context.Context, content xiaohon
// ListFeeds 获取Feeds列表 // ListFeeds 获取Feeds列表
func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) { func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse, error) {
b := browser.NewBrowser(configs.IsHeadless()) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -158,7 +159,7 @@ func (s *XiaohongshuService) ListFeeds(ctx context.Context) (*FeedsListResponse,
} }
func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*FeedsListResponse, error) { func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*FeedsListResponse, error) {
b := browser.NewBrowser(configs.IsHeadless()) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -181,7 +182,7 @@ func (s *XiaohongshuService) SearchFeeds(ctx context.Context, keyword string) (*
// GetFeedDetail 获取Feed详情 // GetFeedDetail 获取Feed详情
func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToken string) (*FeedDetailResponse, error) { func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToken string) (*FeedDetailResponse, error) {
b := browser.NewBrowser(configs.IsHeadless()) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -207,7 +208,7 @@ func (s *XiaohongshuService) GetFeedDetail(ctx context.Context, feedID, xsecToke
// PostCommentToFeed 发表评论到Feed // PostCommentToFeed 发表评论到Feed
func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsecToken, content string) (*PostCommentResponse, error) { func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsecToken, content string) (*PostCommentResponse, error) {
// 使用非无头模式以便查看操作过程 // 使用非无头模式以便查看操作过程
b := browser.NewBrowser(false) b := newBrowser()
defer b.Close() defer b.Close()
page := b.NewPage() page := b.NewPage()
@@ -230,3 +231,7 @@ func (s *XiaohongshuService) PostCommentToFeed(ctx context.Context, feedID, xsec
return response, nil return response, nil
} }
func newBrowser() *headless_browser.Browser {
return browser.NewBrowser(configs.IsHeadless(), browser.WithBinPath(configs.GetBinPath()))
}