feat: 1688 图搜优先筛选铺货素材包 (#40)
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Architecture-and-Code-Map
|
||||
wiki_url: https://git.ilapage.cn/chengma/cmsp/wiki/Architecture-and-Code-Map.-
|
||||
wiki_revision: 928afb2d53fd93391b384fe3af843180a0bfc728
|
||||
synchronized_at: 2026-10-07T08:26:24Z
|
||||
wiki_revision: 3097e923b60ef404eef7b58cbff42f4ed1bc787a
|
||||
synchronized_at: 2026-10-07T09:03:51Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
@@ -183,4 +183,4 @@ cmsp/
|
||||
|
||||
## 1688 视频下载来源(2026-10-07,#37)
|
||||
|
||||
商品列表“下载视频”旁选择淘宝或 1688;`download.source` 写入本机 `config.yaml`,批次启动时固定来源。1688 与淘宝共用现有专属 Chrome Profile、进程和运行状态文件;默认目录由 `internal/config.DefaultChromeUserDataDir` 定位到 `config.yaml` 旁的 `运行数据/浏览器/淘宝1688`,两个站点均由操作者手动登录。`app_1688.go` 调用 `internal/alibaba1688/search.go`:上传商品主图到网页以图搜,按结果顺序进入候选详情,读取页面视频地址。`internal/downloader` 将候选 MP4 下载到临时文件;`internal/alibaba1688/quality.go` 用 ffprobe 校验、ffmpeg 抽帧检测黑底文字广告和静止拼图。只有判为 `usable` 的视频才晋升为商品文件,`internal/store.CompleteVideoDownload` 在同一 SQLite 事务中写视频记录与商品已下载状态。拒绝、无法判定、候选下载失败时保留原商品状态;上传仍沿用原有 erpgo 流程。
|
||||
商品列表“下载视频”旁选择淘宝或 1688;`download.source` 写入本机 `config.yaml`,批次启动时固定来源。1688 与淘宝共用现有专属 Chrome Profile、进程和运行状态文件;默认目录由 `internal/config.DefaultChromeUserDataDir` 定位到 `config.yaml` 旁的 `运行数据/浏览器/淘宝1688`,两个站点均由操作者手动登录。`app_1688.go` 调用 `internal/alibaba1688/search.go`:上传商品主图到网页以图搜,先在结果页选择“铺货素材包”并确认筛选状态和 `offerTags=4358146` 生效,再按结果顺序读取候选 ID、进入详情并读取视频地址。筛选控件缺失或未生效时停止该商品,不使用默认候选列表。结果卡片目前没有可直接读取的详情 href,详情仍按 ID 打开。`internal/downloader` 将候选 MP4 下载到临时文件;`internal/alibaba1688/quality.go` 用 ffprobe 校验、ffmpeg 抽帧检测黑底文字广告和静止拼图。只有判为 `usable` 的视频才晋升为商品文件,`internal/store.CompleteVideoDownload` 在同一 SQLite 事务中写视频记录与商品已下载状态。拒绝、无法判定、候选下载失败时保留原商品状态;上传仍沿用原有 erpgo 流程。
|
||||
|
||||
@@ -196,6 +196,12 @@ func SearchByImage(ctx context.Context, page *taobao.CDP, port int, imageURL str
|
||||
if err := checkPage(page, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := selectDistributionMaterial(ctx, page); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkPage(page, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if limit < 10 {
|
||||
limit = 10
|
||||
}
|
||||
@@ -225,6 +231,79 @@ func SearchByImage(ctx context.Context, page *taobao.CDP, port int, imageURL str
|
||||
return valid, nil
|
||||
}
|
||||
|
||||
type materialFilterPage interface {
|
||||
Evaluate(string) (string, error)
|
||||
Send(string, any) (json.RawMessage, error)
|
||||
}
|
||||
|
||||
// selectDistributionMaterial 在读取候选之前通过页面控件选择“铺货素材包”。
|
||||
// 未确认筛选生效时不使用默认结果,避免将低质量候选作为已筛选结果。
|
||||
func selectDistributionMaterial(ctx context.Context, page materialFilterPage) error {
|
||||
const stateExpr = `(()=>{const e=[...document.querySelectorAll('span')].find(x=>x.textContent.trim()==='铺货素材包');if(!e)return JSON.stringify({found:false});const r=e.getBoundingClientRect();return JSON.stringify({found:true,selected:e.className.includes('selected'),x:r.x+r.width/2,y:r.y+r.height/2,tags:new URL(location.href).searchParams.get('offerTags')})})()`
|
||||
type filterState struct {
|
||||
Found bool `json:"found"`
|
||||
Selected bool `json:"selected"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
Tags string `json:"tags"`
|
||||
}
|
||||
readState := func() (filterState, error) {
|
||||
value, err := page.Evaluate(stateExpr)
|
||||
if err != nil {
|
||||
return filterState{}, err
|
||||
}
|
||||
var state filterState
|
||||
if err := json.Unmarshal([]byte(value), &state); err != nil {
|
||||
return filterState{}, fmt.Errorf("解析 1688 图搜筛选状态失败:%w", err)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
var state filterState
|
||||
for i := 0; i < 10; i++ {
|
||||
var err error
|
||||
state, err = readState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.Found {
|
||||
break
|
||||
}
|
||||
if err := wait(ctx, time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !state.Found {
|
||||
return fmt.Errorf("1688 图搜未找到“铺货素材包”筛选项")
|
||||
}
|
||||
if state.Selected && state.Tags == "4358146" {
|
||||
return nil
|
||||
}
|
||||
if !state.Selected {
|
||||
if _, err := page.Send("Page.bringToFront", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, typ := range []string{"mouseMoved", "mousePressed", "mouseReleased"} {
|
||||
if _, err := page.Send("Input.dispatchMouseEvent", map[string]any{"type": typ, "x": state.X, "y": state.Y, "button": "left", "clickCount": 1}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := wait(ctx, time.Second); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
state, err = readState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.Selected && state.Tags == "4358146" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("1688 图搜“铺货素材包”筛选未生效,未读取默认候选")
|
||||
}
|
||||
|
||||
func OfferVideo(page *taobao.CDP, id string, delay time.Duration) (string, error) {
|
||||
if !offerIDPattern.MatchString(id) {
|
||||
return "", fmt.Errorf("1688 商品 ID 无效")
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package alibaba1688
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type materialPageStub struct {
|
||||
selected bool
|
||||
tags string
|
||||
clicks int
|
||||
}
|
||||
|
||||
func (p *materialPageStub) Evaluate(string) (string, error) {
|
||||
value, _ := json.Marshal(map[string]any{"found": true, "selected": p.selected, "tags": p.tags, "x": 100, "y": 100})
|
||||
return string(value), nil
|
||||
}
|
||||
|
||||
func (p *materialPageStub) Send(method string, params any) (json.RawMessage, error) {
|
||||
if method == "Input.dispatchMouseEvent" && params.(map[string]any)["type"] == "mouseReleased" {
|
||||
p.clicks++
|
||||
p.selected = true
|
||||
p.tags = "4358146"
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func Test铺货素材包已选中时不重复点击(t *testing.T) {
|
||||
page := &materialPageStub{selected: true, tags: "4358146"}
|
||||
if err := selectDistributionMaterial(context.Background(), page); err != nil || page.clicks != 0 {
|
||||
t.Fatalf("已筛选页面不应重复点击:err=%v clicks=%d", err, page.clicks)
|
||||
}
|
||||
}
|
||||
|
||||
func Test铺货素材包未选中时点击并确认(t *testing.T) {
|
||||
page := &materialPageStub{}
|
||||
if err := selectDistributionMaterial(context.Background(), page); err != nil || page.clicks != 1 {
|
||||
t.Fatalf("应点击并确认筛选:err=%v clicks=%d", err, page.clicks)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user