Przeglądaj źródła

Merge branch 'main' of https://git.port.run/puge/pythonTest

PUGE 1 miesiąc temu
rodzic
commit
8219652892
6 zmienionych plików z 831 dodań i 358 usunięć
  1. 1 0
      .gitignore
  2. 175 103
      test2.py
  3. 132 117
      天龙八部拍卖行.py
  4. 13 136
      查询手机号.py
  5. 25 0
      注册.py
  6. 485 2
      自动匹配上架图片.py

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@ build
 dist
 *.spec
 user_scripts_backup.json
+*.txt

+ 175 - 103
test2.py

@@ -3,24 +3,28 @@ import json
 import datetime
 import hashlib
 import hmac
-import time
-from urllib.parse import quote
+import uuid
+import base64
+from urllib.parse import quote, urlparse, parse_qsl
+from io import BytesIO
 
-# ==================== 配置区域 ====================
+# ==================== 配置 ====================
 CLIENT_ID = "amzn1.application-oa2-client.9f775d35077a4c7598116e5a89b60222"
 CLIENT_SECRET = "amzn1.oa2-cs.v1.6909777baa82b796555a431cd23709342e8e01434abcad1f8b59cadf048b51c3"
 REFRESH_TOKEN = "Atzr|IwEBICh_fojh7qAfrePzSjOZhQxTnHRZCYOuWNvSAHGaBs1WfkjR4qjC1Dmv0uK3oyq_L-R9SRKK4cYmPwF1MQXQdqKlpzOAGItWeXnNzh-SyhHHISv0_vCt3D6hluVmIEmF2kA7ro_XF-PslD41g3Eq6bv0Qq4y4pQbkDj-9dudDuz7nku8UL-nc4EWgJCrHCJTAs8TK48Yvrfi3E35uv97tYhs6qb8D9d4dR9C7daaC49eoDqP88qyUHThQV1Kqon8xukx4AT2_wLqWNcabpADzuK-5w7D-vvFFtvzfkbmtfzoPeEdHPulG6scc59BPFTyBlGU7a3d1JSFg2ROHIlrljRm"
 
-AWS_ACCESS_KEY = "AKIA24CLQSVTW2K4S6RP"       # ← 填你的
-AWS_SECRET_KEY = "CwBJqlsYATNC920A+U6iv80WnP6Eso+ECfIalQ1W"             # ← 填你的
+AWS_ACCESS_KEY = "AKIA24CLQSVTW2K4S6RP"
+AWS_SECRET_KEY = "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"
 AWS_REGION = "us-east-1"
 
-SELLER_ID = "A1H50Z0WYYLEGQ"
-SKU = "bar table pink"
 MARKETPLACE_ID = "ATVPDKIKX0DER"
-NEW_IMAGE_URL = "https://cunchu.site/temp/8c7a90e1-a5a1-41af-a119-41b1ca74b40d.png"
+ASIN = "B0FR1WRK4M"
+IMAGE_URL = "https://cunchu.site/temp/8c7a90e1-a5a1-41af-a119-41b1ca74b40d.png"
 
-# ==================== 工具函数 ====================
+TARGET_WIDTH = 970
+TARGET_HEIGHT = 600
+
+# ==================== 签名工具 ====================
 def sha256_hash(data): 
     return hashlib.sha256(data.encode("utf-8")).hexdigest()
 
@@ -34,8 +38,7 @@ def get_signature_key(secret_key, date_stamp, region, service):
     return hmac_sha256(k_service, "aws4_request")
 
 def get_access_token():
-    url = "https://api.amazon.com/auth/o2/token"
-    resp = requests.post(url, data={
+    resp = requests.post("https://api.amazon.com/auth/o2/token", data={
         "grant_type": "refresh_token",
         "refresh_token": REFRESH_TOKEN,
         "client_id": CLIENT_ID,
@@ -93,102 +96,171 @@ def spapi_request(method, path, query, body=None, access_token=None):
     url = f"https://{host}{path}?{query}"
     return requests.request(method, url, headers=headers, data=payload or None)
 
-# ==================== 通用图片修改函数 ====================
-def patch_image(access_token, image_type, image_url):
-    """
-    image_type 可选值:
-      "main"              → 主图
-      "other_1" ~ "other_8" → 副图1~8
-      "swatch"            → 变体图
-    """
-    encoded_sku = quote(SKU, safe="")
-    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
-    query = f"marketplaceIds={MARKETPLACE_ID}"
-    
-    # 根据类型生成 path
-    if image_type == "main":
-        attr_path = "/attributes/main_product_image_locator"
-    elif image_type.startswith("other_") and 1 <= int(image_type.split("_")[1]) <= 8:
-        n = image_type.split("_")[1]
-        attr_path = f"/attributes/other_product_image_locator_{n}"
-    elif image_type == "swatch":
-        attr_path = "/attributes/swatch_product_image_locator"
-    else:
-        raise ValueError(f"不支持的 image_type: {image_type}")
-    
-    body = {
-        "productType": "TABLE",
-        "patches": [
+# ==================== 主程序 ====================
+if __name__ == "__main__":
+    print("=" * 60)
+    print("A+ Content 完整流程 - 响应解析修正版")
+    print("=" * 60)
+    
+    # Step 0
+    print("\n🔑 Step 0: 获取 Access Token...")
+    token = get_access_token()
+    print("   ✅ Token 获取成功")
+    
+    # Step 1
+    print("\n📥 Step 1: 下载图片并调整尺寸...")
+    img_resp = requests.get(IMAGE_URL, timeout=30)
+    img_resp.raise_for_status()
+    
+    try:
+        from PIL import Image
+        img = Image.open(BytesIO(img_resp.content))
+        print(f"   原图尺寸: {img.size}")
+        img_resized = img.resize((TARGET_WIDTH, TARGET_HEIGHT), Image.LANCZOS)
+        buffer = BytesIO()
+        img_resized.save(buffer, format="PNG")
+        image_binary = buffer.getvalue()
+        print(f"   ✅ 调整后尺寸: {TARGET_WIDTH}x{TARGET_HEIGHT}")
+    except ImportError:
+        print("   ❌ 需要安装 Pillow: pip install Pillow")
+        exit(1)
+    
+    md5_hash = hashlib.md5(image_binary).digest()
+    content_md5 = base64.b64encode(md5_hash).decode('utf-8')
+    print(f"   图片大小: {len(image_binary)} bytes")
+    
+    # Step 2
+    print("\n📝 Step 2: 创建上传目标...")
+    resource = "aplus/2020-11-01/contentDocuments"
+    path = f"/uploads/2020-11-01/uploadDestinations/{quote(resource, safe='/')}"
+    query = f"marketplaceIds={MARKETPLACE_ID}&contentMD5={quote(content_md5, safe='')}&contentType=image%2Fpng"
+    
+    resp = spapi_request("POST", path, query, access_token=token)
+    print(f"   状态码: {resp.status_code}")
+    
+    if resp.status_code not in [200, 201]:
+        print(f"   ❌ 失败: {resp.text}")
+        exit(1)
+    
+    payload = resp.json().get("payload", {})
+    upload_id = payload.get("uploadDestinationId")
+    upload_url = payload.get("url")
+    print(f"   ✅ uploadDestinationId: {upload_id}")
+    
+    # Step 3
+    print("\n⬆️ Step 3: 上传图片到 S3...")
+    parsed = urlparse(upload_url)
+    base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
+    query_params = dict(parse_qsl(parsed.query))
+    files = {"File": ("image.png", image_binary, "image/png")}
+    s3_resp = requests.post(base_url, data=query_params, files=files, timeout=60)
+    print(f"   S3 状态码: {s3_resp.status_code}")
+    
+    if s3_resp.status_code not in [200, 201, 204]:
+        print("   ❌ S3 上传失败")
+        exit(1)
+    print("   ✅ S3 上传成功")
+    
+    # 构建 contentDocument
+    content_doc = {
+        "name": "Product Image",
+        "contentType": "EBC",
+        "locale": "en-US",
+        "contentModuleList": [
             {
-                "op": "replace",
-                "path": attr_path,
-                "value": [
-                    {
-                        "marketplace_id": MARKETPLACE_ID,
-                        "media_location": image_url
+                "contentModuleType": "STANDARD_HEADER_IMAGE_TEXT",
+                "standardHeaderImageText": {
+                    "headline": {
+                        "value": "Premium Quality Bar Table",
+                        "decoratorSet": []
+                    },
+                    "block": {
+                        "image": {
+                            "uploadDestinationId": upload_id,
+                            "imageCropSpecification": {
+                                "size": {
+                                    "width": {"value": TARGET_WIDTH, "units": "pixels"},
+                                    "height": {"value": TARGET_HEIGHT, "units": "pixels"}
+                                },
+                                "offset": {
+                                    "x": {"value": 0, "units": "pixels"},
+                                    "y": {"value": 0, "units": "pixels"}
+                                }
+                            },
+                            "altText": "Product detail image"
+                        },
+                        "headline": {
+                            "value": "Elegant Design for Modern Living",
+                            "decoratorSet": []
+                        },
+                        "body": {
+                            "textList": [
+                                {
+                                    "value": "Space-saving bar-height table comfortably seats 2-6 people. Adjustable height, durable engineered wood construction, and smart storage shelves make it perfect for any home.",
+                                    "decoratorSet": []
+                                }
+                            ]
+                        }
                     }
-                ]
+                }
             }
         ]
     }
-    return spapi_request("PATCH", path, query, body=body, access_token=access_token)
-
-def check_submission_result(access_token):
-    encoded_sku = quote(SKU, safe="")
-    path = f"/listings/2021-08-01/items/{SELLER_ID}/{encoded_sku}"
-    # 关键:只带 issues,减少返回数据量
-    query = f"marketplaceIds={MARKETPLACE_ID}&includedData=issues"
-    
-    resp = spapi_request("GET", path, query, access_token=access_token)
-    data = resp.json()
-    
-    issues = data.get("issues", [])
-    if not issues:
-        print("✅ 没有 issues,图片应该已生效")
-        return True
-    
-    print(f"⚠️ 发现 {len(issues)} 个问题:\n")
-    for issue in issues:
-        print(f"  错误码: {issue.get('code')}")
-        print(f"  级别: {issue.get('severity')}")
-        print(f"  信息: {issue.get('message')}")
-        
-        enforcements = issue.get("enforcements", {})
-        actions = enforcements.get("actions", [])
-        for action in actions:
-            print(f"  执行动作: {action.get('action')}")  # 如 ATTRIBUTE_SUPPRESSED
-        
-        # 看是否跟图片相关
-        attrs = issue.get("attributeNames", [])
-        if any("image" in a or "media" in a for a in attrs):
-            print(f"  涉及属性: {attrs} → 图片审核未通过")
-        print()
-    
-    return False
-
-# ==================== 主程序 ====================
-if __name__ == "__main__":
-    print("🔑 获取 Access Token...")
-    token = get_access_token()
-    print("✅ 令牌获取成功")
     
-    # 示例:增加/修改副图第3张
-    new_images = {
-        "other_1": NEW_IMAGE_URL
-    }
+    # Step 4: 验证
+    print("\n📝 Step 4: 验证内容文档...")
+    validate_body = {"contentDocument": content_doc}
+    resp = spapi_request("POST", "/aplus/2020-11-01/contentAsinValidations", f"marketplaceId={MARKETPLACE_ID}", body=validate_body, access_token=token)
+    print(f"   验证状态码: {resp.status_code}")
+    print(f"   验证响应: {resp.text[:800]}")
+    
+    # Step 5: 创建文档
+    print("\n📝 Step 5: 创建 Content Document...")
+    resp = spapi_request("POST", "/aplus/2020-11-01/contentDocuments", f"marketplaceId={MARKETPLACE_ID}", body={"contentDocument": content_doc}, access_token=token)
+    print(f"   状态码: {resp.status_code}")
+    print(f"   响应: {resp.text}")
+    
+    if resp.status_code not in [200, 201]:
+        print("\n   ❌ 创建失败")
+        exit(1)
+    
+    # 关键修正:实际响应是 {"contentReferenceKey":"..."},不是 {"contentReference":{"key":"..."}}
+    doc_data = resp.json()
+    content_key = doc_data.get("contentReferenceKey")  # ← 修正点
+    
+    if not content_key:
+        print("   ❌ 响应中缺少 contentReferenceKey")
+        exit(1)
+    
+    print(f"   ✅ 创建成功,Content Key: {content_key}")
+    
+    # Step 6: 绑定 ASIN
+    print("\n🔗 Step 6: 绑定 ASIN...")
+    body = {"asinSet": [ASIN]}
+    resp = spapi_request("POST", f"/aplus/2020-11-01/contentDocuments/{content_key}/asins", f"marketplaceId={MARKETPLACE_ID}", body=body, access_token=token)
+    print(f"   状态码: {resp.status_code}")
+    print(f"   响应: {resp.text[:500]}")
+    
+    if resp.status_code not in [200, 201]:
+        print("   ❌ 绑定失败")
+        exit(1)
+    print("   ✅ ASIN 绑定成功")
+    
+    # Step 7: 提交审核
+    print("\n📋 Step 7: 提交审核...")
+    resp = spapi_request("POST", f"/aplus/2020-11-01/contentDocuments/{content_key}/approvalSubmissions", f"marketplaceId={MARKETPLACE_ID}", access_token=token)
+    print(f"   状态码: {resp.status_code}")
+    print(f"   响应: {resp.text[:500]}")
+    
+    if resp.status_code not in [200, 201]:
+        print("   ❌ 提交失败")
+        exit(1)
+    print("   ✅ 审核提交成功")
     
-    for img_type, img_url in new_images.items():
-        print(f"\n🖼️ 正在设置 {img_type}: {img_url}")
-        resp = patch_image(token, img_type, img_url)
-        print(f"📤 状态码: {resp.status_code}")
-        try:
-            timestamp = int(time.time())
-            print(timestamp)
-            print(json.dumps(resp.json(), indent=2, ensure_ascii=False))
-        except Exception:
-            print(resp.text)
-        
-        if resp.status_code == 200:
-            print(f"✅ {img_type} 提交成功")
-        else:
-            print(f"⚠️ {img_type} 提交失败")
+    print("\n" + "=" * 60)
+    print("🎉 全部步骤执行成功!")
+    print(f"   Content Key: {content_key}")
+    print(f"   ASIN: {ASIN}")
+    print("=" * 60)
+    print("\n⚠️ 请保存 Content Key,用于后续查询状态")
+    print("   审核通常需要 1-7 个工作日")

Plik diff jest za duży
+ 132 - 117
天龙八部拍卖行.py


Plik diff jest za duży
+ 13 - 136
查询手机号.py


+ 25 - 0
注册.py

@@ -0,0 +1,25 @@
+import requests
+
+url = "https://www.greenmangaming.com/api/v2/register"
+
+payload = "{\"username\":\"adasdcc\",\"email\":\"asddsd@qq.com\",\"password\":\"asdasdasd\",\"confirmPassword\":\"asdasdasd\",\"isEmailFromExternal\":false,\"languagePreferenceCode\":\"zh\",\"isThirdPartyRegister\":false,\"currentLocationUrl\":\"/zh/\",\"radioCommunityEmailsOptIn\":null,\"radioMarketingEmailsOptIn\":null,\"hideBirthdayForm\":false,\"isExternalSignUp\":false,\"isPlayfireSignUp\":false,\"ReturnUrl\":\"/zh/\",\"newsletterOptIn\":false,\"termsAndConditions\":true,\"day\":\"01\",\"month\":\"03\",\"year\":\"2021\",\"Fingerprint\":\"DQ1MOkxlSt5BmkfA0f4w\"}"
+headers = {
+    "accept": "application/json, text/plain, */*",
+    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
+    "cache-control": "no-cache",
+    "content-type": "application/json;charset=UTF-8",
+    "current-url": "https://www.greenmangaming.com/zh/",
+    "dnt": "1",
+    "gmg-accept-language": "zh",
+    "origin": "https://www.greenmangaming.com",
+    "pragma": "no-cache",
+    "priority": "u=1, i",
+    "referer": "https://www.greenmangaming.com/zh/",
+    "sec-fetch-mode": "cors",
+    "sec-fetch-site": "same-origin",
+    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36"
+}
+
+response = requests.request("POST", url, data=payload, headers=headers)
+
+print(response.text)

+ 485 - 2
自动匹配上架图片.py

@@ -11,8 +11,10 @@ import json
 import datetime
 import hashlib
 import hmac
-from urllib.parse import quote
+from urllib.parse import quote, urlparse, parse_qsl
 import time
+import base64
+from io import BytesIO
 
 
 class AmazonImageUploader:
@@ -138,13 +140,75 @@ class AmazonImageUploader:
             ]
         }
         return self.spapi_request("PATCH", path, query, body=body, access_token=access_token)
+    
+    # ==================== A+ Content API 方法 ====================
+    def create_upload_destination(self, access_token, content_md5, content_type="image/png"):
+        """创建上传目标"""
+        resource = "aplus/2020-11-01/contentDocuments"
+        path = f"/uploads/2020-11-01/uploadDestinations/{quote(resource, safe='/')}"
+        query = f"marketplaceIds={self.marketplace_id}&contentMD5={quote(content_md5, safe='')}&contentType={quote(content_type, safe='')}"
+        
+        resp = self.spapi_request("POST", path, query, access_token=access_token)
+        if resp.status_code not in [200, 201]:
+            raise Exception(f"创建上传目标失败: {resp.text}")
+        return resp.json().get("payload", {})
+    
+    def upload_to_s3(self, upload_url, image_binary, content_type="image/png"):
+        """上传图片到S3"""
+        parsed = urlparse(upload_url)
+        base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
+        query_params = dict(parse_qsl(parsed.query))
+        files = {"File": ("image.png", image_binary, content_type)}
+        resp = requests.post(base_url, data=query_params, files=files, timeout=60)
+        if resp.status_code not in [200, 201, 204]:
+            raise Exception(f"S3上传失败: {resp.status_code}")
+        return True
+    
+    def create_content_document(self, access_token, content_doc):
+        """创建内容文档"""
+        resp = self.spapi_request(
+            "POST", 
+            "/aplus/2020-11-01/contentDocuments", 
+            f"marketplaceId={self.marketplace_id}", 
+            body={"contentDocument": content_doc}, 
+            access_token=access_token
+        )
+        if resp.status_code not in [200, 201]:
+            raise Exception(f"创建内容文档失败: {resp.text}")
+        return resp.json().get("contentReferenceKey")
+    
+    def bind_asin_to_content(self, access_token, content_key, asin):
+        """绑定ASIN到内容"""
+        body = {"asinSet": [asin]}
+        resp = self.spapi_request(
+            "POST", 
+            f"/aplus/2020-11-01/contentDocuments/{content_key}/asins", 
+            f"marketplaceId={self.marketplace_id}", 
+            body=body, 
+            access_token=access_token
+        )
+        if resp.status_code not in [200, 201]:
+            raise Exception(f"绑定ASIN失败: {resp.text}")
+        return True
+    
+    def submit_for_approval(self, access_token, content_key):
+        """提交审核"""
+        resp = self.spapi_request(
+            "POST", 
+            f"/aplus/2020-11-01/contentDocuments/{content_key}/approvalSubmissions", 
+            f"marketplaceId={self.marketplace_id}", 
+            access_token=access_token
+        )
+        if resp.status_code not in [200, 201]:
+            raise Exception(f"提交审核失败: {resp.text}")
+        return True
 
 
 class ImageMatcherApp:
     def __init__(self, root):
         self.root = root
         self.root.title("图片匹配与上传工具 - 亚马逊版")
-        self.root.geometry("950x750")
+        self.root.geometry("950x800")
         
         # 存储文件路径
         self.template_path = tk.StringVar()
@@ -189,6 +253,11 @@ class ImageMatcherApp:
         self.notebook.add(self.tab_amazon, text="亚马逊主图更换")
         self.create_amazon_tab()
         
+        # 选项卡3: 亚马逊详情图更换 (A+ Content)
+        self.tab_detail = ttk.Frame(self.notebook)
+        self.notebook.add(self.tab_detail, text="亚马逊详情图更换")
+        self.create_detail_tab()
+        
         # 状态栏
         self.status_var = tk.StringVar()
         self.status_var.set("就绪")
@@ -287,6 +356,73 @@ class ImageMatcherApp:
         self.amazon_progress_var = tk.StringVar(value="就绪")
         progress_label = ttk.Label(tab, textvariable=self.amazon_progress_var, relief=tk.SUNKEN, anchor=tk.W)
         progress_label.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
+
+    def create_detail_tab(self):
+        """创建亚马逊详情图更换选项卡 (A+ Content)"""
+        tab = self.tab_detail
+        tab.columnconfigure(1, weight=1)
+        tab.rowconfigure(8, weight=1)
+        
+        # 标题
+        title_label = ttk.Label(tab, text="亚马逊详情图更换 (A+ Content)", font=("Arial", 14, "bold"))
+        title_label.grid(row=0, column=0, columnspan=3, pady=10)
+        
+        # 模板文件选择
+        ttk.Label(tab, text="模板文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
+        self.detail_template_path = tk.StringVar()
+        ttk.Entry(tab, textvariable=self.detail_template_path, width=60).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
+        ttk.Button(tab, text="选择模板", command=self.select_detail_template).grid(row=1, column=2, pady=5, padx=5)
+        
+        # ASIN输入
+        ttk.Label(tab, text="ASIN (手动输入):").grid(row=2, column=0, sticky=tk.W, pady=5)
+        self.asin_input = tk.StringVar(value="B0FR1WRK4M")
+        ttk.Entry(tab, textvariable=self.asin_input, width=40).grid(row=2, column=1, sticky=tk.W, pady=5, padx=5)
+        
+        # 图片URL列选择(详情图)
+        ttk.Label(tab, text="详情图列名(逗号分隔):").grid(row=3, column=0, sticky=tk.W, pady=5)
+        self.detail_image_columns = tk.StringVar(value="详情图URL1,详情图URL2,详情图URL3")
+        ttk.Entry(tab, textvariable=self.detail_image_columns, width=60).grid(row=3, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
+        
+        # 图片尺寸设置
+        ttk.Label(tab, text="图片宽度(像素):").grid(row=4, column=0, sticky=tk.W, pady=5)
+        self.image_width = tk.StringVar(value="970")
+        ttk.Entry(tab, textvariable=self.image_width, width=15).grid(row=4, column=1, sticky=tk.W, pady=5, padx=5)
+        
+        ttk.Label(tab, text="图片高度(像素):").grid(row=5, column=0, sticky=tk.W, pady=5)
+        self.image_height = tk.StringVar(value="600")
+        ttk.Entry(tab, textvariable=self.image_height, width=15).grid(row=5, column=1, sticky=tk.W, pady=5, padx=5)
+        
+        # 标题和描述设置
+        ttk.Label(tab, text="模块标题:").grid(row=6, column=0, sticky=tk.W, pady=5)
+        self.module_headline = tk.StringVar(value="Premium Quality Product")
+        ttk.Entry(tab, textvariable=self.module_headline, width=50).grid(row=6, column=1, sticky=tk.W, pady=5, padx=5)
+        
+        ttk.Label(tab, text="模块描述:").grid(row=7, column=0, sticky=tk.W, pady=5)
+        self.module_body = tk.StringVar(value="High-quality product designed for your needs.")
+        ttk.Entry(tab, textvariable=self.module_body, width=50).grid(row=7, column=1, sticky=tk.W, pady=5, padx=5)
+        
+        # 按钮框架
+        button_frame = ttk.Frame(tab)
+        button_frame.grid(row=8, column=0, columnspan=3, pady=10)
+        
+        ttk.Button(button_frame, text="保存配置", command=self.save_detail_config, width=12).pack(side=tk.LEFT, padx=5)
+        ttk.Button(button_frame, text="加载配置", command=self.load_detail_config, width=12).pack(side=tk.LEFT, padx=5)
+        ttk.Button(button_frame, text="开始上传", command=self.start_detail_upload, width=15).pack(side=tk.LEFT, padx=5)
+        ttk.Button(button_frame, text="清空日志", command=self.clear_detail_log, width=15).pack(side=tk.LEFT, padx=5)
+        
+        # 日志框
+        log_frame = ttk.LabelFrame(tab, text="上传日志", padding="5")
+        log_frame.grid(row=9, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
+        log_frame.columnconfigure(0, weight=1)
+        log_frame.rowconfigure(0, weight=1)
+        
+        self.detail_log_text = scrolledtext.ScrolledText(log_frame, height=15, width=80, wrap=tk.WORD)
+        self.detail_log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        
+        # 进度标签
+        self.detail_progress_var = tk.StringVar(value="就绪")
+        progress_label = ttk.Label(tab, textvariable=self.detail_progress_var, relief=tk.SUNKEN, anchor=tk.W)
+        progress_label.grid(row=10, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
         
     def select_template(self):
         filename = filedialog.askopenfilename(
@@ -311,6 +447,15 @@ class ImageMatcherApp:
         if filename:
             self.amazon_template_path.set(filename)
             self.amazon_log(f"已选择模板文件: {filename}")
+    
+    def select_detail_template(self):
+        filename = filedialog.askopenfilename(
+            title="选择模板文件",
+            filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
+        )
+        if filename:
+            self.detail_template_path.set(filename)
+            self.detail_log(f"已选择模板文件: {filename}")
             
     def log(self, message):
         """在匹配选项卡日志框中添加消息"""
@@ -331,10 +476,24 @@ class ImageMatcherApp:
     def clear_amazon_log(self):
         """清空亚马逊选项卡日志"""
         self.amazon_log_text.delete(1.0, tk.END)
+    
+    def detail_log(self, message):
+        """在详情图选项卡日志框中添加消息"""
+        self.detail_log_text.insert(tk.END, f"{message}\n")
+        self.detail_log_text.see(tk.END)
+        self.root.update_idletasks()
+        
+    def clear_detail_log(self):
+        """清空详情图选项卡日志"""
+        self.detail_log_text.delete(1.0, tk.END)
         
     def get_config_path(self):
         """获取配置文件路径"""
         return os.path.join(os.path.dirname(os.path.abspath(__file__)), "amazon_config.json")
+    
+    def get_detail_config_path(self):
+        """获取详情图配置文件路径"""
+        return os.path.join(os.path.dirname(os.path.abspath(__file__)), "amazon_detail_config.json")
         
     def save_amazon_config(self):
         """保存亚马逊配置"""
@@ -368,6 +527,45 @@ class ImageMatcherApp:
                 self.amazon_log("⚠️ 未找到配置文件,使用默认配置")
         except Exception as e:
             self.amazon_log(f"❌ 加载配置失败: {str(e)}")
+    
+    def save_detail_config(self):
+        """保存详情图配置"""
+        config = {
+            "asin": self.asin_input.get(),
+            "detail_image_columns": self.detail_image_columns.get(),
+            "template_path": self.detail_template_path.get(),
+            "image_width": self.image_width.get(),
+            "image_height": self.image_height.get(),
+            "module_headline": self.module_headline.get(),
+            "module_body": self.module_body.get()
+        }
+        try:
+            with open(self.get_detail_config_path(), 'w', encoding='utf-8') as f:
+                json.dump(config, f, ensure_ascii=False, indent=2)
+            self.detail_log("✅ 配置已保存")
+        except Exception as e:
+            self.detail_log(f"❌ 保存配置失败: {str(e)}")
+            
+    def load_detail_config(self):
+        """加载详情图配置"""
+        try:
+            config_path = self.get_detail_config_path()
+            if os.path.exists(config_path):
+                with open(config_path, 'r', encoding='utf-8') as f:
+                    config = json.load(f)
+                self.asin_input.set(config.get("asin", "B0FR1WRK4M"))
+                self.detail_image_columns.set(config.get("detail_image_columns", "详情图URL1,详情图URL2,详情图URL3"))
+                self.image_width.set(config.get("image_width", "970"))
+                self.image_height.set(config.get("image_height", "600"))
+                self.module_headline.set(config.get("module_headline", "Premium Quality Product"))
+                self.module_body.set(config.get("module_body", "High-quality product designed for your needs."))
+                if config.get("template_path"):
+                    self.detail_template_path.set(config.get("template_path"))
+                self.detail_log("✅ 配置已加载")
+            else:
+                self.detail_log("⚠️ 未找到配置文件,使用默认配置")
+        except Exception as e:
+            self.detail_log(f"❌ 加载配置失败: {str(e)}")
             
     def load_config(self):
         """启动时自动加载亚马逊配置"""
@@ -383,6 +581,22 @@ class ImageMatcherApp:
                     self.amazon_template_path.set(config.get("template_path"))
         except Exception:
             pass
+        # 加载详情图配置
+        try:
+            config_path = self.get_detail_config_path()
+            if os.path.exists(config_path):
+                with open(config_path, 'r', encoding='utf-8') as f:
+                    config = json.load(f)
+                self.asin_input.set(config.get("asin", "B0FR1WRK4M"))
+                self.detail_image_columns.set(config.get("detail_image_columns", "详情图URL1,详情图URL2,详情图URL3"))
+                self.image_width.set(config.get("image_width", "970"))
+                self.image_height.set(config.get("image_height", "600"))
+                self.module_headline.set(config.get("module_headline", "Premium Quality Product"))
+                self.module_body.set(config.get("module_body", "High-quality product designed for your needs."))
+                if config.get("template_path"):
+                    self.detail_template_path.set(config.get("template_path"))
+        except Exception:
+            pass
 
     def start_matching(self):
         """开始匹配处理"""
@@ -668,6 +882,275 @@ class ImageMatcherApp:
             import traceback
             self.amazon_log(traceback.format_exc())
 
+    def start_detail_upload(self):
+        """开始亚马逊详情图上传 (A+ Content)"""
+        if not self.detail_template_path.get():
+            messagebox.showerror("错误", "请先选择模板文件!")
+            return
+        
+        if not self.asin_input.get().strip():
+            messagebox.showerror("错误", "请先输入ASIN!")
+            return
+            
+        thread = threading.Thread(target=self.process_detail_upload, daemon=True)
+        thread.start()
+    
+    def process_detail_upload(self):
+        """执行亚马逊详情图上传 (A+ Content)"""
+        try:
+            self.detail_progress_var.set("处理中...")
+            self.detail_log("=" * 50)
+            self.detail_log("开始亚马逊详情图上传 (A+ Content)...")
+            
+            template_path = self.detail_template_path.get()
+            asin = self.asin_input.get().strip()
+            detail_cols_str = self.detail_image_columns.get()
+            detail_col_names = [col.strip() for col in detail_cols_str.split(',') if col.strip()]
+            
+            # 获取图片尺寸
+            try:
+                target_width = int(self.image_width.get())
+                target_height = int(self.image_height.get())
+            except ValueError:
+                self.detail_log("❌ 图片尺寸必须为数字")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            module_headline = self.module_headline.get().strip()
+            module_body = self.module_body.get().strip()
+            
+            self.detail_log(f"读取模板文件: {template_path}")
+            self.detail_log(f"使用的ASIN: {asin}")
+            self.detail_log(f"图片尺寸: {target_width}x{target_height}")
+            
+            # 读取Excel
+            df = pd.read_excel(template_path)
+            self.detail_log(f"找到 {len(df)} 行数据")
+            
+            # 检查详情图列
+            existing_detail_cols = [col for col in detail_col_names if col in df.columns]
+            if not existing_detail_cols:
+                self.detail_log("❌ 未找到任何详情图列")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            self.detail_log(f"详情图列: {existing_detail_cols}")
+            
+            # 尝试导入PIL
+            try:
+                from PIL import Image
+                from io import BytesIO
+                HAS_PIL = True
+            except ImportError:
+                HAS_PIL = False
+                self.detail_log("⚠️ 未安装Pillow,将使用原始图片尺寸")
+            
+            # 获取Access Token
+            self.detail_log("🔑 获取Access Token...")
+            try:
+                token = self.amazon_uploader.get_access_token()
+                self.detail_log("✅ Token获取成功")
+            except Exception as e:
+                self.detail_log(f"❌ 获取Token失败: {str(e)}")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            # 处理每一行
+            success_count = 0
+            fail_count = 0
+            total_rows = min(len(df), 1)  # 每次只处理一行(A+ Content每次只能绑定一个ASIN)
+            
+            # 只处理第一行
+            row = df.iloc[0]
+            
+            # 收集所有图片URL
+            image_urls = []
+            for col_name in existing_detail_cols:
+                img_url = str(row[col_name]).strip()
+                if img_url and img_url != 'nan' and img_url != 'None' and img_url.startswith('http'):
+                    image_urls.append(img_url)
+            
+            if not image_urls:
+                self.detail_log("❌ 未找到有效的图片URL")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            self.detail_log(f"找到 {len(image_urls)} 张详情图")
+            
+            # 处理每张图片
+            upload_ids = []
+            for i, img_url in enumerate(image_urls, start=1):
+                self.detail_log(f"\n处理第 {i} 张图片: {img_url[:60]}...")
+                
+                try:
+                    # 下载图片
+                    img_resp = requests.get(img_url, timeout=30)
+                    img_resp.raise_for_status()
+                    
+                    image_binary = img_resp.content
+                    
+                    # 调整尺寸
+                    if HAS_PIL:
+                        try:
+                            img = Image.open(BytesIO(image_binary))
+                            original_size = img.size
+                            # 调整尺寸
+                            img_resized = img.resize((target_width, target_height), Image.LANCZOS)
+                            buffer = BytesIO()
+                            # 判断原图格式
+                            if img_url.lower().endswith('.png'):
+                                img_resized.save(buffer, format="PNG")
+                            else:
+                                img_resized.save(buffer, format="JPEG")
+                            image_binary = buffer.getvalue()
+                            self.detail_log(f"  图片尺寸调整: {original_size} -> {target_width}x{target_height}")
+                        except Exception as e:
+                            self.detail_log(f"  图片调整失败: {str(e)},使用原图")
+                    
+                    # 计算MD5
+                    md5_hash = hashlib.md5(image_binary).digest()
+                    content_md5 = base64.b64encode(md5_hash).decode('utf-8')
+                    
+                    # 确定Content-Type
+                    content_type = "image/jpeg"
+                    if img_url.lower().endswith('.png'):
+                        content_type = "image/png"
+                    
+                    self.detail_log(f"  图片大小: {len(image_binary)} bytes")
+                    
+                    # 创建上传目标
+                    upload_data = self.amazon_uploader.create_upload_destination(token, content_md5, content_type)
+                    upload_id = upload_data.get("uploadDestinationId")
+                    upload_url = upload_data.get("url")
+                    
+                    if not upload_id or not upload_url:
+                        self.detail_log(f"  ❌ 创建上传目标失败")
+                        fail_count += 1
+                        continue
+                    
+                    self.detail_log(f"  ✅ 上传目标创建成功")
+                    
+                    # 上传到S3
+                    self.amazon_uploader.upload_to_s3(upload_url, image_binary, content_type)
+                    self.detail_log(f"  ✅ S3上传成功")
+                    
+                    upload_ids.append({
+                        "upload_id": upload_id,
+                        "width": target_width,
+                        "height": target_height,
+                        "alt_text": f"Product detail image {i}"
+                    })
+                    
+                    success_count += 1
+                    time.sleep(0.5)
+                    
+                except Exception as e:
+                    self.detail_log(f"  ❌ 处理失败: {str(e)}")
+                    fail_count += 1
+            
+            if not upload_ids:
+                self.detail_log("❌ 没有成功上传的图片")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            # 构建A+ Content内容文档
+            self.detail_log("\n📝 构建A+ Content文档...")
+            
+            # 构建模块列表 - 每张图片一个模块
+            content_module_list = []
+            for i, upload_info in enumerate(upload_ids):
+                module = {
+                    "contentModuleType": "STANDARD_HEADER_IMAGE_TEXT",
+                    "standardHeaderImageText": {
+                        "headline": {
+                            "value": module_headline if i == 0 else f"{module_headline} - {i+1}",
+                            "decoratorSet": []
+                        },
+                        "block": {
+                            "image": {
+                                "uploadDestinationId": upload_info["upload_id"],
+                                "imageCropSpecification": {
+                                    "size": {
+                                        "width": {"value": upload_info["width"], "units": "pixels"},
+                                        "height": {"value": upload_info["height"], "units": "pixels"}
+                                    },
+                                    "offset": {
+                                        "x": {"value": 0, "units": "pixels"},
+                                        "y": {"value": 0, "units": "pixels"}
+                                    }
+                                },
+                                "altText": upload_info["alt_text"]
+                            },
+                            "headline": {
+                                "value": f"Image {i+1}",
+                                "decoratorSet": []
+                            },
+                            "body": {
+                                "textList": [
+                                    {
+                                        "value": module_body if i == 0 else f"Product detail view {i+1}",
+                                        "decoratorSet": []
+                                    }
+                                ]
+                            }
+                        }
+                    }
+                }
+                content_module_list.append(module)
+            
+            # 构建content文档
+            content_doc = {
+                "name": f"Product Images for {asin}",
+                "contentType": "EBC",
+                "locale": "en-US",
+                "contentModuleList": content_module_list
+            }
+            
+            # 创建内容文档
+            self.detail_log("📤 创建A+ Content文档...")
+            try:
+                content_key = self.amazon_uploader.create_content_document(token, content_doc)
+                self.detail_log(f"✅ Content文档创建成功,Content Key: {content_key}")
+            except Exception as e:
+                self.detail_log(f"❌ 创建Content文档失败: {str(e)}")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            # 绑定ASIN
+            self.detail_log(f"🔗 绑定ASIN: {asin}...")
+            try:
+                self.amazon_uploader.bind_asin_to_content(token, content_key, asin)
+                self.detail_log(f"✅ ASIN绑定成功")
+            except Exception as e:
+                self.detail_log(f"❌ 绑定ASIN失败: {str(e)}")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            # 提交审核
+            self.detail_log("📋 提交审核...")
+            try:
+                self.amazon_uploader.submit_for_approval(token, content_key)
+                self.detail_log(f"✅ 审核提交成功")
+                self.detail_log(f"⚠️ 审核通常需要 1-7 个工作日")
+            except Exception as e:
+                self.detail_log(f"❌ 提交审核失败: {str(e)}")
+                self.detail_progress_var.set("上传失败")
+                return
+            
+            self.detail_log(f"\n✅ 处理完成!成功: {success_count}, 失败: {fail_count}")
+            self.detail_log(f"   Content Key: {content_key}")
+            self.detail_log(f"   ASIN: {asin}")
+            self.detail_progress_var.set(f"上传完成!成功: {success_count}, 失败: {fail_count}")
+            messagebox.showinfo("完成", f"亚马逊详情图上传完成!\n成功: {success_count}\n失败: {fail_count}\n\nContent Key: {content_key}\nASIN: {asin}\n\n审核通常需要 1-7 个工作日")
+            
+        except Exception as e:
+            error_msg = f"处理出错: {str(e)}"
+            self.detail_log(f"❌ {error_msg}")
+            self.detail_progress_var.set("处理出错")
+            messagebox.showerror("错误", error_msg)
+            import traceback
+            self.detail_log(traceback.format_exc())
+
 
 def main():
     root = tk.Tk()

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików