Files
spider/utils/wpdata.py
2026-04-10 10:57:44 +08:00

829 lines
45 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 # 是否上传图片
)