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:
38
README.md
38
README.md
@@ -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
|
|||||||
**申请时务必添加备注。**
|
**申请时务必添加备注。**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
6
go.mod
@@ -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
6
go.sum
@@ -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=
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -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()
|
||||||
|
|||||||
17
service.go
17
service.go
@@ -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()))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user