# encoding utf-8 import json from pathlib import Path import base64 import time import re import traceback from datetime import datetime from collections import defaultdict import requests from urllib.parse import quote import os from curl_cffi import requests as requests_frp from config import base_dir from amazon.tool import show_notification,get_shop_info,remove_special_characters,split_currency_values from amazon.amazon_base import TaskBase from amazon.chrome_base import ChromeAmzoneBase from config import runing_task, runing_shop,base_dir,DELETE_BRAND_API_BASE try: from config import proxy_url as CONFIG_PROXY_URL, proxy_mode as CONFIG_PROXY_MODE except ImportError: CONFIG_PROXY_URL = None CONFIG_PROXY_MODE = 1 # Forbidden 后 3 分钟内统一使用代理:记录代理生效截止时间与当前代理 _FORBIDDEN_PROXY_UNTIL_Similar = 0.0 _FORBIDDEN_PROXY_DICT_Similar = None _FORBIDDEN_PROXY_MINUTES_Similar = 0.5 class ChromeAmzone(ChromeAmzoneBase): mark_name = "亚马逊相似ASIN" def wait_load_complete(self): try: for _ in range(3): content_area = self.tab.eles('xpath://div[@class="ap-sbi-aside__main"]', timeout=20) if len(content_area) > 0: pass except Exception as e: self.log(f"等待加载完成失败,{e},等待5秒") time.sleep(5) def encode_custom(self,input_): if not input_ or not isinstance(input_, str): return "" transformed = "" # 第一个字符:字符编码 + 字符串长度 transformed += chr((ord(input_[0]) + len(input_)) & 0xFFFF) # 后续字符:当前字符编码 + 前一个字符编码 for i in range(1, len(input_)): current_code = ord(input_[i]) prev_code = ord(input_[i - 1]) transformed += chr((current_code + prev_code) & 0xFFFF) # 对应 JavaScript 的 encodeURIComponent encoded = quote(transformed, safe="-_.!~*'()") # 对应 replace(/[!'()*]/g, ...) for ch in ["!", "'", "(", ")", "*"]: encoded = encoded.replace(ch, "%" + format(ord(ch), "x")) return encoded def image_to_base64(self,image_source: str) -> str: """ 将图片链接(URL 或本地文件路径)转换为 Base64 编码的字符串。 """ if image_source.startswith(('http://', 'https://')): response = requests.get(image_source, timeout=10,headers={ "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0 18444" }) response.raise_for_status() # 非 2xx 状态码将抛出异常 image_data = response.content else: path = Path(image_source) if not path.is_file(): raise ValueError(f"本地文件不存在: {image_source}") with open(path, 'rb') as f: image_data = f.read() base64_str = base64.b64encode(image_data).decode('utf-8') return base64_str def get_aliprice_data(self,page=1,title="",domain="",category="",imageBase64=""): """ "fishkeeper Quick Aquarium Siphon Pump Gravel Cleaner - 256GPH Adjustable Powerful Fish Tank Vacuum Gravel Cleaning Kit for Aquarium Water Changer, Sand Cleaner, Dirt Removal : Amazon.co.uk: Pet Supplies" 标题 : """ global _FORBIDDEN_PROXY_UNTIL_Similar, _FORBIDDEN_PROXY_DICT_Similar headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", "browser": "chrome", "cache-control": "no-cache", "channel": "chrome_offline", "content-type": "application/json;charset=UTF-8", "ext-id": "10100", "ext_id": "10100", "origin": "chrome-extension://ephkdklmdkaakeleplfpahjphaokcllh", "platform": "1688", "pragma": "no-cache", "priority": "u=1, i", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "none", "sec-fetch-storage-access": "active", "version": "3.7.4", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36" } cookie = { "e-info": "[{\"e-name\":\"1688\",\"adid\":\"100\",\"version\":\"3.7.4\",\"ext_id\":\"10100\"}]", "language": "chinese", "province_code": "Guangdong", "_ct": "czg", "_e_ct": "czg", "is_reto": "1", "crossborder": "1", "agent": "0", "first_view": "1", "currency": "USD", "is_coo": "1", "plugin": "chrome", "plugin_ext": "100,129", "m-info": "[{\"platform\":\"1688\",\"version\":\"3.7.4\",\"browser\":\"chrome\",\"m\":\"uni\",\"t\":1777902599578}]" } url = "https://api.aliprice.com/index.php/chrome/items/imageAnalysis" itemTitle = f"{title} : {domain}: {category}" params = {"page": page, "size": 20, "website": "1688_lite2", "language": "zh-CN", "currency": "USD", "from": "", "itemTitle": itemTitle, "domain": domain } params['sign'] = self.encode_custom(json.dumps(params, ensure_ascii=False)) data = { "imageBase64" : imageBase64 } proxies = None # 若 3 分钟内曾出现 Forbidden,则直接使用当时保存的代理 now = time.time() if now < _FORBIDDEN_PROXY_UNTIL_Similar and _FORBIDDEN_PROXY_DICT_Similar: proxies = _FORBIDDEN_PROXY_DICT_Similar print("处于 Forbidden 代理窗口内,直接使用代理", proxies) # 发送第一次请求 try: response = requests_frp.post(url, headers=headers, params=params, json=data, impersonate="chrome101", cookies=cookie, proxies=proxies,verify=False) response.encoding = "utf-8" # 检查响应状态码和数据有效性 need_retry = False if response.status_code != 200: print(f"请求失败,状态码: {response.status_code}") need_retry = True else: try: result_data = response.json() # 检查是否获取到有效数据 if not result_data or not isinstance(result_data, dict): print("返回数据为空或格式不正确") need_retry = True elif "Forbidden" in response.text or "Too Many Requests" in response.text: print("返回Forbidden或Too Many Requests") need_retry = True except Exception as e: print(f"解析响应JSON失败: {e}") need_retry = True # 如果需要重试且配置了代理URL # need_retry = True if need_retry and CONFIG_PROXY_URL and not proxies: try: proxy_resp = requests.get(CONFIG_PROXY_URL, timeout=10) print("代理请求结果->:", proxy_resp.text) # 模式 2:账号密码代理,接口返回 JSON if CONFIG_PROXY_MODE == 2: resp_json = proxy_resp.json() proxy_list = resp_json.get("data", {}).get("list") or [] first_item = proxy_list[0] if proxy_list else None if first_item: ip = first_item.get("ip") port = first_item.get("port") account = first_item.get("account") password = first_item.get("password") if ip and port and account and password: auth_proxy = f"{account}:{password}@{ip}:{port}" print("获取到账号密码代理:", auth_proxy) proxies = { "http": f"http://{auth_proxy}", "https": f"http://{auth_proxy}", } # 默认模式 1:普通 IP:port 文本 if CONFIG_PROXY_MODE != 2: proxy_ip = (proxy_resp.text or "").strip() if proxy_ip: proxies = { "http": f"http://{proxy_ip}", "https": f"https://{proxy_ip}", } if proxies: print("使用代理重试请求") response = requests_frp.post(url, headers=headers, params=params, json=data, impersonate="chrome101", cookies=cookie, proxies=proxies) response.encoding = "utf-8" print("代理重试结果状态码:", response.status_code) # 记录 3 分钟内都使用该代理 _FORBIDDEN_PROXY_UNTIL_Similar = now + _FORBIDDEN_PROXY_MINUTES_Similar * 60 _FORBIDDEN_PROXY_DICT_Similar = proxies except Exception as e: print("获取代理或重试失败:", e) # 返回最终结果 result_data = response.json() return result_data except Exception as e: print(f"get_aliprice_data 请求异常: {e}") return {} def _scrape_data(self): """ 抓取商品数据 Returns: dict: 抓取到的数据 """ data = { 'image_url': "", 'title': "", 'category' : '', 'sku' : '', 'success' : True } try: # 等待页面加载 # time.sleep(3) title_ele = self.tab.ele('xpath://h1[@id="title"]',timeout=30) title = title_ele.text data["title"] = title imge_ele = self.tab.ele('xpath://div[@id="imgTagWrapperId"]//img',timeout=20) image_url = "" data_a_dynamic_image = imge_ele.attr("data-a-dynamic-image") if data_a_dynamic_image: dynamic_image_json = json.loads(data_a_dynamic_image) self.log(f"图片信息:{dynamic_image_json}") max_area = 0 for url, (width, height) in dynamic_image_json.items(): area = width * height if area > max_area: max_area = area image_url = url if not image_url: image_url = imge_ele.attr("src") data["image_url"] = image_url # sku sku_ele_ls = self.tab.eles('xpath://ul[@class="a-unordered-list a-vertical a-spacing-mini"]',timeout=20) if len(sku_ele_ls) > 0: data["sku"] = sku_ele_ls[0].text category = self.tab.eles('xpath://div[@id="wayfinding-breadcrumbs_feature_div"]//span[@class="a-list-item"]//a[@class="a-link-normal a-color-tertiary"]',timeout=20) if len(category) > 0: data["category"] = category[0].text return data except Exception as e: print(f"抓取数据时出错: {traceback.format_exc()}") data["success"] = False return data def run(self, country, asin,total_page=2): """ 运行亚马逊详情采集任务 Args: country: 国家名称(如:英国、德国、法国、西班牙、意大利) asin: 亚马逊商品ASIN码 Returns: dict: 包含采集到的数据 """ return_data = {} try: # 验证国家是否支持 if country not in self.country_info: error_msg = f"不支持的国家: {country},支持的国家有: {list(self.country_info.keys())}" print(error_msg) # show_notification(error_msg, "error") return None # 获取国家配置 country_config = self.country_info[country] zip_code = country_config["zip_code"] mark = country_config.get("mark") # 1. 根据国家和ASIN拼接链接 base_url = country_config["url"] # 提取域名部分 domain = base_url.split("/dp/")[0] # 拼接新的URL product_url = f"{domain}/dp/{asin}" self.log(f"正在访问: {product_url}") # 打开链接 self.tab.get(product_url) time.sleep(3) # 等待页面初步加载 self.tab.wait.doc_loaded(timeout=30, raise_err=False) self.close_init_popup() # 2. 切换国家/设置邮编 self.log(f"正在检查并设置邮编: {zip_code},标识: {mark}") self._set_zip_code(zip_code, mark) # self.tab.wait.doc_loaded(timeout=5, raise_err=False) # 3. 抓取数据 self.log("正在抓取商品数据...") data = self._scrape_data() return_data.update(data) # 判断是否采集标题出错 new_size = "220,220" pattern = r"\._(?:[A-Z]+)?(\d+)_\." replacement = f"._{new_size}_.jpg" # 使用正则替换 image_new_url = re.sub(pattern, lambda m: replacement, data["image_url"]) print(image_new_url) iamge_base64 = self.image_to_base64(image_new_url) similar_data = [] # 4、请求获取插件的数据 if image_new_url: for page_num in range(total_page): resp_data = self.get_aliprice_data( page=page_num+1, title = data["title"], domain=domain, category=data["category"], imageBase64 = iamge_base64 ) self.log(f"Aliprice 扩展数据获取:{str(resp_data)[0:200]}") if len(resp_data.get("data",[])) > 0: for i in resp_data.get("data"): similar_data.append(i) data["similar_data"] = similar_data # 添加基本信息 data['country'] = country data['asin'] = asin data['url'] = product_url data['timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"数据抓取完成: {json.dumps(return_data)}") return_data.update(data) return return_data except Exception as e: error_msg = f"运行出错: {traceback.format_exc()}" print(error_msg) # show_notification(f"采集失败: {str(e)}", "error") raise RuntimeError(f"{traceback.format_exc()}") return return_data class SimilarAsinTask(TaskBase): mark_name = "相似ASIN采集" @staticmethod def group_by_id_prefix(data): """ 根据每条数据的 id 字段进行分组: - 如果 id 为 "2_1",则取 "_" 前面的 "2" 作为分组 key - 如果 id 为 "1",则分组 key 就是 "1" - 相同 key 的数据归为同一组 :param data: 原始列表数据 :return: 二维列表,按 id 前缀分组 """ grouped = defaultdict(list) for item in data: item_id = str(item.get("id", "")) group_key = item_id.split("_")[0] grouped[group_key].append(item) return list(grouped.values()) @staticmethod def normalize_groups(data): groups = data.get("groups") if isinstance(groups, list) and len(groups) > 0: return groups rows = data.get("rows") or data.get("items") or [] if not isinstance(rows, list) or len(rows) == 0: return [] normalized = [] for items in SimilarAsinTask.group_by_id_prefix(rows): first = items[0] if items else {} item_id = str(first.get("id") or first.get("displayId") or "") base_id = item_id.split("_")[0] if item_id else "" normalized.append({ "sourceFileKey": first.get("sourceFileKey", ""), "sourceFilename": first.get("sourceFilename", ""), "groupKey": first.get("groupKey") or base_id, "baseId": first.get("baseId") or base_id, "displayId": first.get("displayId") or item_id, "items": items, }) return normalized def fetch_parsed_payload(self, task_id, user_id=1): url = f"{DELETE_BRAND_API_BASE}/api/similar-asin/tasks/{task_id}/parsed-payload" response = requests.get(url, params={"user_id": user_id}, timeout=300, verify=False) response.raise_for_status() payload = response.json() if response.text else {} if isinstance(payload, dict) and payload.get("success"): return payload.get("data") or {} raise RuntimeError(f"获取货源查询解析载荷失败: {payload}") def _merge_parsed_payload(self, data, parsed_payload): payload_rows = parsed_payload.get("allItems") or parsed_payload.get("items") or [] merged = { **data, "groups": parsed_payload.get("groups") or [], "rows": payload_rows, } if parsed_payload.get("aiPrompt"): merged["prompt"] = parsed_payload.get("aiPrompt") if parsed_payload.get("apiKey") and not merged.get("api_key"): merged["api_key"] = parsed_payload.get("apiKey") return merged def process_task(self, task_data: dict): """处理审批任务主入口 Args: task_data: 任务数据 """ try: data = task_data.get("data", {}) task_id = data.get("taskId") parsed_payload = self.fetch_parsed_payload(task_id, data.get("user_id") or data.get("userId") or 1) data = self._merge_parsed_payload(data, parsed_payload) groups = self.normalize_groups(data) print(groups) if not task_id: self.log("任务ID为空,跳过", "WARNING") return self.log(f"开始处理爬取任务 {task_id},{len(groups)} 个任务") if not groups: self.log("appearance-patent groups/rows is empty, skip", "WARNING") return runing_task[task_id] = { "status": "running", "start_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "total_shops": 1, "processed_shops": 0, "total_countries": len(groups) , "processed_countries": 0, "total_asins": 0, "processed_asins": 0, "success_count": 0, "failed_count": 0, "stop_requested": False } # 检查是否收到暂停请求 if task_id in runing_task and runing_task[task_id].get("stop_requested", False): self.log(f"检测到任务 {task_id} 的暂停请求,停止处理", "WARNING") runing_task[task_id]["status"] = "stopped" return max_retry = 3 show_notification(f"开始爬取数据", "info") chrome = ChromeAmzone() try: # 数据整理 # new_items = self.group_by_id_prefix(items) result = [] for gp_index,gp in enumerate(groups): items = gp.get("items", []) group_item = [] for index,value in enumerate(items): print(value) return_data = { 'image_url': "", 'title': "", 'category' : '', 'sku' : '', 'success' : False, 'similar_data' : [] } asin = value.get("asin") country = value.get("country") for _ in range(max_retry): try: return_data = chrome.run(country, asin,total_page=1) or {} self.log(f"抓取结果->{return_data}") break except Exception as e: # if "与页面的连接已断开" in str(e): chrome = ChromeAmzone() self.log(f"{asin}抓取数据报错,{e}") # if not isinstance(return_data, dict): # return_data = {} # if return_data.get("image_url"): # break group_item.append({ "sourceFileKey": value.get("sourceFileKey",""), "sourceFilename": value.get("sourceFilename",""), "rowToken": value.get("rowToken",""), "groupKey": value.get("groupKey",""), "id": value.get("values",{}).get("id",""), "asin": value.get("asin",""), "sku" : return_data.get("sku",""), "country": value.get("country",""), "url": return_data.get("image_url",""), "title": return_data.get("title",""), "done": False, "urls" : [i.get("ori_picture") for i in return_data.get("similar_data",[])[:16]] }) # task_id: int, chunkIndex:int,chunkTotal: int, country_code: str, asin: str, status: dict,error:str="", # item_data:dict={}, res = { "sourceFileKey": gp.get("sourceFileKey"), "sourceFilename": gp.get("sourceFilename"), "groupKey": gp.get("groupKey"), "baseId": gp.get("baseId"), "displayId": gp.get("displayId"), "items": group_item } # print("================") result.append(res) # print("================") is_done = gp_index == len(groups)-1 if len(result) > 5 or is_done: self.post_result(task_id=task_id,chunkIndex=gp_index+1,chunkTotal=len(groups), asin=asin,item_data=result,is_done=is_done) result = [] if len(result) > 0: is_done = True self.post_result(task_id=task_id, chunkIndex=len(groups), chunkTotal=len(groups), asin=asin, item_data=result, is_done=is_done) # 更新已处理店铺数 if task_id in runing_task: runing_task[task_id]["processed_shops"] += 1 except Exception as e: self.log(f"处理店铺 {task_id} 失败: {str(e)}", "ERROR") self.log(traceback.format_exc(), "ERROR") try: chrome.close() except Exception as e: print("退出浏览器出错", e) # 更新任务状态 if task_id in runing_task: if runing_task[task_id].get("stop_requested", False): runing_task[task_id]["status"] = "stopped" self.log(f"任务 {task_id} 已被暂停!") else: runing_task[task_id]["status"] = "completed" self.log(f"任务 {task_id} 处理完成!") except Exception as e: self.log(f"任务处理失败: {traceback.format_exc()}", "ERROR") if task_id: if task_id in runing_task: runing_task[task_id]["status"] = "failed" runing_task[task_id]["error"] = str(e) def post_result(self, task_id: int, chunkIndex:int,chunkTotal: int, asin: str, error:str="", item_data:dict={}, is_done: bool = False): """回传处理结果到API """ url = f"{DELETE_BRAND_API_BASE}/api/similar-asin/tasks/{task_id}/result" payload ={ "submissionId": f"{task_id}", "chunkIndex": chunkIndex, "chunkTotal": chunkTotal, "error": error, "groups": item_data, "done": is_done } max_retries = 3 for retry in range(max_retries): try: request_timeout = 300 if is_done else 30 print("================【详情采集】=====================") self.log(f"尝试回传结果 (第 {retry + 1}/{max_retries} 次)") self.log(f"回传URL: {url}") self.log(f"回传数据: {payload}") response = requests.post( url, json=payload, headers={"Content-Type": "application/json"}, timeout=request_timeout, verify=False ) self.log(f"回传结果: {response.text}") data = response.json() if response.text else {} if response.status_code == 200 and isinstance(data, dict) and data.get("success"): self.log(f"结果回传成功: {asin} - {item_data}") return else: self.log(f"结果回传失败,状态码: {response.status_code}", "WARNING") print("=====================================") except Exception as e: self.log(f"调用API异常: {str(e)}", "ERROR") print("=====================================") # 如果还有重试机会,等待后继续 if retry < max_retries - 1: time.sleep(2) self.log(f"已达到最大重试次数,结果回传最终失败", "ERROR") # raise RuntimeError("已达到最大重试次数,结果回传最终失败") if __name__ == '__main__': spide = ChromeAmzone() resp = spide.run( country="德国", asin="B0CJ8SNXXV", total_page=2 ) print(resp) # task_data = { # "type": "similar-asin-run", # "ts": 1778038688824, # "data": { # "taskId": 6850, # "api_key": "sk-EMcDFg36zCeWUtCbRzKUbrRZyNeC6M4KBhY6fAVcNP7GG4xI", # "groups": [ # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "groupKey": "similar-asin:6850", # "baseId": "", # "displayId": "1", # "items": [ # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::2", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::1@2", # "id": "1", # "asin": "B0D792ND9V", # "country": "英国", # "price": "12.29", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::3", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_1", # "asin": "B0D14L34S7", # "country": "英国", # "price": "14.88", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::4", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_2", # "asin": "B0D14N8FJY", # "country": "英国", # "price": "15.36", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::5", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_3", # "asin": "B0D14NBL5D", # "country": "英国", # "price": "14.87", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::6", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_4", # "asin": "B0D14M5X4J", # "country": "英国", # "price": "14.41", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::7", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::3@7", # "id": "3_1", # "asin": "B0CWQFZMGY", # "country": "英国", # "price": "17.05", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::8", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::3@7", # "id": "3_2", # "asin": "B0CX8D68L3", # "country": "英国", # "price": "16.46", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::9", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::4@9", # "id": "4_1", # "asin": "B0CX87XXWJ", # "country": "英国", # "price": "17.54", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::10", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::5@10", # "id": "5_1", # "asin": "B0CR3PMR8L", # "country": "英国", # "price": "16.74", # "url": "", # "title": "", # "target_urls": [] # } # ] # } # ], # "rows": [ # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::2", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::1@2", # "id": "1", # "asin": "B0D792ND9V", # "country": "英国", # "price": "12.29", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::3", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_1", # "asin": "B0D14L34S7", # "country": "英国", # "price": "14.88", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::4", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_2", # "asin": "B0D14N8FJY", # "country": "英国", # "price": "15.36", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::5", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_3", # "asin": "B0D14NBL5D", # "country": "英国", # "price": "14.87", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::6", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::2@3", # "id": "2_4", # "asin": "B0D14M5X4J", # "country": "英国", # "price": "14.41", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::7", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::3@7", # "id": "3_1", # "asin": "B0CWQFZMGY", # "country": "英国", # "price": "17.05", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::8", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::3@7", # "id": "3_2", # "asin": "B0CX8D68L3", # "country": "英国", # "price": "16.46", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::9", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::4@9", # "id": "4_1", # "asin": "B0CX87XXWJ", # "country": "英国", # "price": "17.54", # "url": "", # "title": "", # "target_urls": [] # }, # { # "sourceFileKey": "cdfa437df62b40a0bde5682a05c1f6a3", # "sourceFilename": "17(1) - 副本1.xlsx", # "rowToken": "cdfa437df62b40a0bde5682a05c1f6a3::row::10", # "groupKey": "cdfa437df62b40a0bde5682a05c1f6a3::5@10", # "id": "5_1", # "asin": "B0CR3PMR8L", # "country": "英国", # "price": "16.74", # "url": "", # "title": "", # "target_urls": [] # } # ] # } # } # sim_asin = SimilarAsinTask({}) # sim_asin.process_task(task_data)