import pandas as pd from bs4 import BeautifulSoup import os import re import requests from io import BytesIO import hashlib import concurrent.futures import time class Wpdata: def __init__(self, data_path: str, input_excel: str, goods_url_path: str, upload_workers: int = 10, upload_image_domain:str = "https://www.fzcaiji.com/upload.php", column_name:str = "商品图片*"): self.__data_path__ = data_path self.__input_excel__ = input_excel self.__goods_url_path__ = goods_url_path self.__upload_workers__ = upload_workers self.__upload_image_domain__ = upload_image_domain self.__column_name__ = column_name self.__output_csv__ = f"{input_excel}.csv" self.__images_txt__ = f"{data_path}_images.txt" # 原始数据转换 def transform_excel_to_csv(self): def match_variant_count(number, group_df, variant_1, title, text, spu, data_rows, variant_2=None,variant_3=None): # 提取当前spu分组的价格 sale_prices = group_df[(group_df['商品属性*'] == 'P') | (group_df['商品属性*'] == 'S')]['商品售价*'].tolist() origin_prices = group_df[(group_df['商品属性*'] == 'P') | (group_df['商品属性*'] == 'S')]['商品原价'].tolist() # 提取对应属性的图片,并统计 M_images = group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['商品图片*'].tolist() image_list = [url.strip() for url in M_images[0].split(',')] group_df # 获取当前分组的URL if 'url' in group_df: url = group_df['url'].tolist()[0] else: url = group_df['URL'].tolist()[0] match number: case 3: # 提取当前spu分组属性为P的值 all_variants_first = group_df[(group_df['商品属性*'] == 'P')]['款式1'].tolist() all_variants_second = group_df[(group_df['商品属性*'] == 'P')]['款式2'].tolist() all_variants_three = group_df[(group_df['商品属性*'] == 'P')]['款式3'].tolist() all_variants_count = max(len(all_variants_first), len(all_variants_second), len(all_variants_three)) if all_variants_count >= len(image_list): # 计算需要扩大的长度,并进行扩大 expand_length = all_variants_count - len(image_list) expanded_image_list = image_list + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(all_variants_first): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1, "Option1 Value": all_variants_first[k], "Option2 Name": variant_2, "Option2 Value": all_variants_second[k], "Option3 Name": variant_3, "Option3 Value": all_variants_second[k], "Variant SKU": f"{spu}{str(k)}", "Variant Grams": "0", "Variant Inventory Tracker": "shopify", "Variant Inventory Qty": "1000", "Variant Inventory Policy": "continue", "Variant Fulfillment Service": "manual", "Variant Price": sale_prices[k], "Variant Compare At Price": origin_prices[k], "Variant Requires Shipping": "TRUE", "Variant Taxable": "TRUE", "Variant Barcode": "", "Image Src": expanded_image_list[k], "Image Position": (image_counter := image_counter + 1) if expanded_image_list[ k] is not None else "", # 海象运算符 "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if all_variants_first[k] else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) else: # 变体少于图片数量 # 计算扩大长度 expand_length = len(image_list) - all_variants_count expanded_all_variants_first_count = all_variants_first + [None] * expand_length expanded_all_variants_two_count = all_variants_second + [None] * expand_length expanded_all_variants_three_count = all_variants_three + [None] * expand_length # 扩大价格长度(为了统一价格) expanded_sale_prices = sale_prices + [None] * expand_length expanded_origin_prices = origin_prices + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(image_list): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1 if expanded_all_variants_first_count[k] is not None else "", "Option1 Value": expanded_all_variants_first_count[k], "Option2 Name": variant_2 if expanded_all_variants_two_count[k] is not None else "", "Option2 Value": expanded_all_variants_two_count[k], "Option3 Name": variant_3 if expanded_all_variants_three_count[k] is not None else "", "Option3 Value": expanded_all_variants_three_count[k], "Variant SKU": f"{spu}{str(k)}" if expanded_all_variants_first_count[k] is not None else "", "Variant Grams": "0" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Tracker": "shopify" if expanded_all_variants_first_count[ k] is not None else "", "Variant Inventory Qty": "1000" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Policy": "continue" if expanded_all_variants_first_count[ k] is not None else "", "Variant Fulfillment Service": "manual" if expanded_all_variants_first_count[ k] is not None else "", "Variant Price": expanded_sale_prices[k], "Variant Compare At Price": expanded_origin_prices[k], "Variant Requires Shipping": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Taxable": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Barcode": "", "Image Src": image_list[k], "Image Position": (image_counter := image_counter + 1) if image_list[k] is not None else "", "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if k == 0 else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) case 2: # 提取当前spu分组属性为P的值 all_variants_first = group_df[(group_df['商品属性*'] == 'P')]['款式1'].tolist() all_variants_second = group_df[(group_df['商品属性*'] == 'P')]['款式2'].tolist() all_variants_count = max(len(all_variants_first), len(all_variants_second)) if all_variants_count >= len(image_list): # 计算需要扩大的长度,并进行扩大 expand_length = all_variants_count - len(image_list) expanded_image_list = image_list + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(all_variants_first): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1, "Option1 Value": all_variants_first[k], "Option2 Name": variant_2, "Option2 Value": all_variants_second[k], "Option3 Name": "", "Option3 Value": "", "Variant SKU": f"{spu}{str(k)}", "Variant Grams": "0", "Variant Inventory Tracker": "shopify", "Variant Inventory Qty": "1000", "Variant Inventory Policy": "continue", "Variant Fulfillment Service": "manual", "Variant Price": sale_prices[k], "Variant Compare At Price": origin_prices[k], "Variant Requires Shipping": "TRUE", "Variant Taxable": "TRUE", "Variant Barcode": "", "Image Src": expanded_image_list[k], "Image Position": (image_counter := image_counter + 1) if expanded_image_list[ k] is not None else "", # 海象运算符 "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if all_variants_first[k] else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) else: # 变体少于图片数量 # 计算扩大长度 expand_length = len(image_list) - all_variants_count expanded_all_variants_first_count = all_variants_first + [None] * expand_length expanded_all_variants_two_count = all_variants_second + [None] * expand_length # 扩大价格长度(为了统一价格) expanded_sale_prices = sale_prices + [None] * expand_length expanded_origin_prices = origin_prices + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(image_list): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1 if expanded_all_variants_first_count[k] is not None else "", "Option1 Value": expanded_all_variants_first_count[k], "Option2 Name": variant_2 if expanded_all_variants_two_count[k] is not None else "", "Option2 Value": expanded_all_variants_two_count[k], "Option3 Name": "", "Option3 Value": "", "Variant SKU": f"{spu}{str(k)}" if expanded_all_variants_first_count[k] is not None else "", "Variant Grams": "0" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Tracker": "shopify" if expanded_all_variants_first_count[ k] is not None else "", "Variant Inventory Qty": "1000" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Policy": "continue" if expanded_all_variants_first_count[ k] is not None else "", "Variant Fulfillment Service": "manual" if expanded_all_variants_first_count[ k] is not None else "", "Variant Price": expanded_sale_prices[k], "Variant Compare At Price": expanded_origin_prices[k], "Variant Requires Shipping": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Taxable": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Barcode": "", "Image Src": image_list[k], "Image Position": (image_counter := image_counter + 1) if image_list[k] is not None else "", "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if k == 0 else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) case 1: all_variants = group_df[(group_df['商品属性*'] == 'P')]['款式1'].tolist() all_variants_count = len(all_variants) if all_variants_count >= len(image_list): # 计算扩大长度 expand_length = all_variants_count - len(image_list) expanded_list = image_list + [None] * expand_length # 扩大价格 expanded_sale_prices = sale_prices + [None] * expand_length expanded_origin_prices = origin_prices + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(all_variants): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1, "Option1 Value": all_variants[k], "Option2 Name": "", "Option2 Value": "", "Option3 Name": "", "Option3 Value": "", "Variant SKU": f"{spu}{str(k)}", "Variant Grams": "0", "Variant Inventory Tracker": "shopify", "Variant Inventory Qty": "1000", "Variant Inventory Policy": "continue", "Variant Fulfillment Service": "manual", "Variant Price": expanded_sale_prices[k], "Variant Compare At Price": expanded_origin_prices[k], "Variant Requires Shipping": "TRUE", "Variant Taxable": "TRUE", "Variant Barcode": "", "Image Src": expanded_list[k], "Image Position": (image_counter := image_counter + 1) if expanded_list[k] is not None else "", "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if all_variants[k] else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) else: # 变体少于图片数量 # 计算扩大长度 expand_length = len(image_list) - all_variants_count expanded_all_variants_first_count = all_variants + [None] * expand_length # 扩大价格 expanded_sale_prices = sale_prices + [None] * expand_length expanded_origin_prices = origin_prices + [None] * expand_length Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(image_list): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": variant_1 if expanded_all_variants_first_count[k] is not None else "", "Option1 Value": expanded_all_variants_first_count[k], "Option2 Name": "", "Option2 Value": "", "Option3 Name": "", "Option3 Value": "", "Variant SKU": f"{spu}{str(k)}" if expanded_all_variants_first_count[k] is not None else "", "Variant Grams": "0" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Tracker": "shopify" if expanded_all_variants_first_count[ k] is not None else "", "Variant Inventory Qty": "1000" if expanded_all_variants_first_count[k] is not None else "", "Variant Inventory Policy": "continue" if expanded_all_variants_first_count[ k] is not None else "", "Variant Fulfillment Service": "manual" if expanded_all_variants_first_count[ k] is not None else "", "Variant Price": expanded_sale_prices[k], "Variant Compare At Price": expanded_origin_prices[k], "Variant Requires Shipping": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Taxable": "TRUE" if expanded_all_variants_first_count[k] is not None else "", "Variant Barcode": "", "Image Src": image_list[k], "Image Position": (image_counter := image_counter + 1) if image_list[k] is not None else "", "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if k == 0 else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) case 0: Handle = title[0].replace(' ', '-').replace('/', '-').lower() image_counter = 0 for k, variant in enumerate(image_list): rows = { "Handle": Handle, "Title": title[0] if k == 0 else "", "Body (HTML)": text if k == 0 else "", "Vendor": "", "Type": "", "Tags": "", "Published": "TRUE" if k == 0 else "", "Option1 Name": "variant", "Option1 Value": "default", "Option2 Name": "", "Option2 Value": "", "Option3 Name": "", "Option3 Value": "", "Variant SKU": f"{spu}" if k == 0 else "", "Variant Grams": "0" if k == 0 else "", "Variant Inventory Tracker": "shopify" if k == 0 else "", "Variant Inventory Qty": "1000" if k == 0 else "", "Variant Inventory Policy": "continue" if k == 0 else "", "Variant Fulfillment Service": "manual" if k == 0 else "", "Variant Price": sale_prices[0] if k == 0 else "", "Variant Compare At Price": origin_prices[0] if k == 0 else "", "Variant Requires Shipping": "TRUE" if k == 0 else "", "Variant Taxable": "TRUE" if k == 0 else "", "Variant Barcode": "", "Image Src": image_list[k], "Image Position": (image_counter := image_counter + 1) if image_list[k] is not None else "", "Image Alt Text": "", "Gift Card": "FALSE" if k == 0 else "", "SEO Title": title[0] if k == 0 else "", "SEO Description": title[0] if k == 0 else "", "Google Shopping / Google Product Category": "", "Google Shopping / Gender": "", "Google Shopping / Age Group": "", "Google Shopping / MPN": "", "Google Shopping / AdWords Grouping": "", "Google Shopping / AdWords Labels": "", "Google Shopping / Condition": "", "Google Shopping / Custom Product": "", "Google Shopping / Custom Label 0": "", "Google Shopping / Custom Label 测试.txt": "", "Google Shopping / Custom Label 2": "", "Google Shopping / Custom Label 3": "", "Google Shopping / Custom Label 4": "", "Variant Image": "", "IsDraft": "", "Variant Weight Unit": "Kg" if k == 0 else "", "Variant Tax Code": "", "Cost per item": "", "Status": "activate" if k == 0 else "", "Collection": "", "url": url } data_rows.append(rows) data_rows = [] df = pd.read_excel(self.__input_excel__) # 以相同商品spu为一组 grouped = df.groupby('商品spu') data_rows = [] for spu, group_df in grouped: print(spu) group_df = group_df.fillna('') # 获取商品属性为M或S的商品标题 title = group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['商品标题*'].tolist() description = group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['商品描述'].tolist() description.append('') text = BeautifulSoup(description[0], 'html.parser').get_text() variant_name_one = group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['款式1'].tolist()[ 0] variant_name_two = group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['款式2'].tolist()[ 0] variant_name_three = \ group_df[(group_df['商品属性*'] == 'M') | (group_df['商品属性*'] == 'S')]['款式3'].tolist()[0] variant_count = 0 if variant_name_one: variant_count += 1 if variant_name_two: variant_count += 1 if variant_name_three: variant_count += 1 # 统计个数 match_variant_count(variant_count, group_df, variant_name_one, title, text, spu, data_rows, variant_name_two,variant_name_three) new_df = pd.DataFrame(data_rows) new_df.to_csv(self.__output_csv__, index=False, encoding="utf-8") return self # 数据分割 def split_csv(self, csv_path: str = "", goods_url_path: str = ""): if csv_path == "": csv_path = self.__output_csv__ if goods_url_path == "": goods_url_path = self.__goods_url_path__ with open(goods_url_path, 'r', encoding='utf-8') as f: lines = f.readlines() dict_all = {} for i in lines: url = i.strip() if url == '': continue album = url.rsplit('#', 1)[1] third_album = album.split('/')[-1].lstrip() # 最后一个专辑 # 全部存进字典 if third_album not in dict_all: dict_all[third_album] = [] dict_all[third_album].append(url.rsplit('#',1)[0]) # print(dict_all) # 测试.txt. 读取 CSV 文件 df = pd.read_csv(csv_path) # 替换为你的 CSV 文件路径 output_dir = f"{self.__data_path__}/已分类数据" os.makedirs(output_dir, exist_ok=True) # 2. 遍历字典的每个键(分类) for category, urls in dict_all.items(): # 3. 筛选出 url 列中包含在当前分类的 URL 的行 filtered_df = df[df['url'].isin(urls)] # 4. 如果筛选后的 DataFrame 不为空,则删除 'url' 列并保存到 CSV if not filtered_df.empty: filtered_df = filtered_df.drop(columns=['url']) # 删除 url 列 output_filename = fr"{output_dir}/{category}.csv" filtered_df.to_csv(output_filename, index=False) print(f"已保存 {len(filtered_df)} 条数据到 {output_filename}(已删除 url 列)") else: print(f"分类 {category} 无匹配数据,跳过保存") return self # 获取原始图片 def get_excel_images_to_txt(self): def split_image_urls(cell: str): """按 ',http' / ',https' 分割字符串,自动补上 'http'。不校验后缀。""" if not isinstance(cell, str) or not cell.strip(): return [] s = re.sub(r',\s*(https?://)', r'||SEP||\1', cell.strip(), flags=re.IGNORECASE) parts = s.split('||SEP||') urls = [] for part in parts: part = part.strip().lstrip(',').strip() if not part: continue m = re.search(r'https?://', part, re.IGNORECASE) if m: part = part[m.start():] urls.append(part) return urls """从 Excel 中读取指定列,返回每行的图片 URL 列表。""" df = pd.read_excel(self.__input_excel__) urls_list = [] for val in df[self.__column_name__].fillna(''): urls_list.append(split_image_urls(val)) """展开 + 去重 + 保存到 txt 文件""" all_urls = set() # 用 set 去重 for row_urls in urls_list: for url in row_urls: all_urls.add(url.strip()) # 写入文件 with open(self.__images_txt__, "w", encoding="utf-8") as f: for url in sorted(all_urls): f.write(url + "\n") print(f"✅ 共提取 {len(all_urls)} 张图片,已保存到 {self.__images_txt__}") return self # 上传原始图片 def update_images(self): def upload_image(img_url: str): """下载并上传单张图片""" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Referer": "https://www.google.com/" } try: # 通过 requests 下载图片(带代理和请求头) resp_img = requests.get(img_url, timeout=15, headers=HEADERS) resp_img.raise_for_status() img_data = BytesIO(resp_img.content) # 生成哈希文件名 hash_name = hashlib.sha1(img_url.encode("utf-8")).hexdigest() # 获取扩展名(安全处理) ext = img_url.split("?")[0].split(".")[-1].lower() if len(ext) > 5 or "/" in ext: ext = "jpg" # 上传 files = {"file": (f"{hash_name}.{ext}", img_data, f"image/{ext}")} data = {"source_url": img_url} resp = requests.post(self.__upload_image_domain__, files=files, data=data, timeout=30) # 打印结果 try: js = resp.json() print(f"✅ {img_url} -> {js.get('url') or js}") except Exception: print(f"❌ {img_url} -> 上传失败: {resp.text[:100]}") except Exception as e: print(f"⚠️ 下载或上传异常: {img_url} -> {e}") # 读取本地 txt 文件 with open(self.__images_txt__, "r", encoding="utf-8") as f: urls = [x.strip() for x in f if x.strip()] print(f"共读取到 {len(urls)} 张图片。开始上传...") start = time.time() # 使用线程池并发上传 with concurrent.futures.ThreadPoolExecutor(max_workers=self.__upload_workers__) as executor: list(executor.map(upload_image, urls)) print(f"\n全部上传完成,用时 {time.time() - start:.2f} 秒。") return self # 替换原始图片 def replace_excel_image(self): """ 读取 Excel 和 txt,将原图片 URL 替换为新图片 URL,保存到新 Excel """ def get_uploaded_image_url_php_style(img_url: str) -> str: """ 模拟 PHP 上传脚本的路径生成逻辑: - hash = sha1(source_url) - 一级目录 = hash 前 1 字符 - 二级目录 = hash 第 2 字符 - 文件名 = hash + 扩展名 """ # 计算 sha1 hash_name = hashlib.sha1(img_url.encode('utf-8')).hexdigest() # 取扩展名(与 PHP 一致:从文件名里提取,默认 jpg) ext = os.path.splitext(img_url.split('?')[0])[1].lstrip('.').lower() if not ext or len(ext) > 5 or "/" in ext: ext = 'jpg' # 两层目录 dir1 = hash_name[0] dir2 = hash_name[1] # 拼接最终 URL new_url = f"{self.__upload_image_domain__}/{dir1}/{dir2}/{hash_name}.{ext}" return new_url def split_image_urls(cell: str): """按 ',http' / ',https' 分割字符串,自动补上 'http'。不校验后缀。""" if not isinstance(cell, str) or not cell.strip(): return [] s = re.sub(r',\s*(https?://)', r'||SEP||\1', cell.strip(), flags=re.IGNORECASE) parts = s.split('||SEP||') urls = [] for part in parts: part = part.strip().lstrip(',').strip() if not part: continue m = re.search(r'https?://', part, re.IGNORECASE) if m: part = part[m.start():] urls.append(part) # cell = ',' + cell # cell = cell.split(',https://') # urls = [] # for image_url in cell: # if '.svg' in image_url: # continue # if image_url == "": # continue # urls.append(('https://'+image_url).strip()) return urls # 读取 txt 中的原图片链接 with open(self.__images_txt__, "r", encoding="utf-8") as f: original_urls = [line.strip() for line in f if line.strip()] # 生成原图片到新图片的映射 url_mapping = {} for old_url in original_urls: new_url = get_uploaded_image_url_php_style(old_url) url_mapping[old_url] = new_url print(f"📋 共加载 {len(url_mapping)} 个图片映射关系") # 读取 Excel df = pd.read_excel(self.__input_excel__) # 替换图片列 replaced_count = 0 for idx, cell_value in enumerate(df[self.__column_name__].fillna('')): if not cell_value: continue # 分割出该单元格的所有图片 URL old_urls = split_image_urls(cell_value) # 替换为新 URL new_urls = [] for old_url in old_urls: if old_url in url_mapping: new_urls.append(url_mapping[old_url]) replaced_count += 1 else: new_urls.append(old_url) # 如果找不到映射,保持原样 # 拼接回去(用逗号分隔) df.at[idx, self.__column_name__] = ','.join(new_urls) output_excel = f"{self.__input_excel__}(new_image).xlsx" # 保存到新 Excel df.to_excel(output_excel, index=False) print(f"✅ 替换完成!共替换 {replaced_count} 个图片链接") print(f"✅ 已保存到 {output_excel}") self.__input_excel__ = output_excel # 一键运行 def run(self, replace_image: bool = False): if replace_image: self.get_excel_images_to_txt().update_images().replace_excel_image() self.transform_excel_to_csv().split_csv() if __name__ == "__main__": wpdata = Wpdata( data_path="data", # 已分类数据文件夹保存路径 input_excel=r"data\jamesavery\info\goods(sort)_2025-12-24.xlsx", # 原始excel路径 goods_url_path=r"data\jamesavery\urls\goods_2025-12-19.txt_old.txt" # 商品txt路径 ) wpdata.run( # replace_image=True # 是否上传图片 )