Initial commit: 商品售卖网站
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('backend/sale.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
print("Users table columns:")
|
||||
for col in cursor.fetchall():
|
||||
print(col)
|
||||
@@ -0,0 +1,12 @@
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('backend/sale.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, name FROM categories")
|
||||
print("Categories in database:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" ID: {row[0]}, Name: {row[1]}")
|
||||
|
||||
cursor.execute("SELECT product_id, category_id FROM product_categories")
|
||||
print("\nProduct-Category relations:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" Product ID: {row[0]}, Category ID: {row[1]}")
|
||||
@@ -0,0 +1,7 @@
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('backend/sale.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, name, price FROM products ORDER BY id DESC LIMIT 25")
|
||||
print("Products in database:")
|
||||
for row in cursor.fetchall():
|
||||
print(f" ID: {row[0]}, Name: {row[1]}, Price: {row[2]}")
|
||||
@@ -0,0 +1,156 @@
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
API_BASE = "http://localhost:8080/api"
|
||||
|
||||
ADMIN_TOKEN = None
|
||||
|
||||
def login():
|
||||
global ADMIN_TOKEN
|
||||
response = requests.post(f"{API_BASE}/auth/login", json={
|
||||
"email": "admin@viaeon.com",
|
||||
"password": "admin123"
|
||||
})
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
ADMIN_TOKEN = data.get("token")
|
||||
print(f"登录成功")
|
||||
return True
|
||||
else:
|
||||
print(f"登录失败: {response.text}")
|
||||
return False
|
||||
|
||||
def get_headers():
|
||||
return {
|
||||
"Authorization": f"Bearer {ADMIN_TOKEN}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
def create_category(name):
|
||||
response = requests.post(f"{API_BASE}/admin/categories",
|
||||
headers=get_headers(),
|
||||
json={"name": name}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get("id")
|
||||
else:
|
||||
existing = requests.get(f"{API_BASE}/categories")
|
||||
if existing.status_code == 200:
|
||||
cats = existing.json()
|
||||
if isinstance(cats, list):
|
||||
for cat in cats:
|
||||
if isinstance(cat, dict) and cat.get("name") == name:
|
||||
return cat.get("id")
|
||||
elif isinstance(cat, str) and cat == name:
|
||||
return None
|
||||
return None
|
||||
|
||||
def download_image(url, filepath):
|
||||
try:
|
||||
response = requests.get(url, timeout=30)
|
||||
if response.status_code == 200:
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(response.content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"下载图片失败: {e}")
|
||||
return False
|
||||
|
||||
def upload_image(filepath):
|
||||
try:
|
||||
with open(filepath, 'rb') as f:
|
||||
response = requests.post(f"{API_BASE}/upload",
|
||||
headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},
|
||||
files={"file": f}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get("url")
|
||||
except Exception as e:
|
||||
print(f"上传图片失败: {e}")
|
||||
return None
|
||||
|
||||
def create_product(product, category_id, image_url):
|
||||
price_cny = product.get("price_jpy", 0) * 0.048
|
||||
price_cny = round(price_cny, 2)
|
||||
|
||||
data = {
|
||||
"name": product.get("name_cn", product.get("name_jp", "未知商品")),
|
||||
"description": f"{product.get('name_jp', '')}\n{product.get('description', '')}".strip(),
|
||||
"price": price_cny,
|
||||
"stock": 100,
|
||||
"images": image_url,
|
||||
"category_ids": [category_id] if category_id else [],
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
response = requests.post(f"{API_BASE}/admin/products",
|
||||
headers=get_headers(),
|
||||
json=data
|
||||
)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
return response.json()
|
||||
else:
|
||||
result = response.json()
|
||||
if result.get("data") and result["data"].get("id"):
|
||||
return result["data"]
|
||||
print(f"创建商品失败: {response.text}")
|
||||
return None
|
||||
|
||||
def main():
|
||||
print("开始导入商品数据...")
|
||||
|
||||
if not login():
|
||||
print("请先创建管理员账户")
|
||||
return
|
||||
|
||||
with open('ribenyan_products.json', 'r', encoding='utf-8') as f:
|
||||
products = json.load(f)
|
||||
|
||||
print(f"读取到 {len(products)} 个商品")
|
||||
|
||||
categories = {}
|
||||
for product in products:
|
||||
cat_name = product.get("category")
|
||||
if cat_name not in categories:
|
||||
cat_id = create_category(cat_name)
|
||||
if cat_id:
|
||||
categories[cat_name] = cat_id
|
||||
print(f"创建分类: {cat_name} (ID: {cat_id})")
|
||||
|
||||
os.makedirs("temp_images", exist_ok=True)
|
||||
|
||||
success_count = 0
|
||||
for i, product in enumerate(products):
|
||||
print(f"\n处理商品 {i+1}/{len(products)}: {product.get('name_cn', 'N/A')}")
|
||||
|
||||
image_url = ""
|
||||
if product.get("image_url"):
|
||||
temp_path = f"temp_images/{product.get('external_id', i)}.webp"
|
||||
if download_image(product["image_url"], temp_path):
|
||||
uploaded_url = upload_image(temp_path)
|
||||
if uploaded_url:
|
||||
image_url = uploaded_url
|
||||
print(f" 图片上传成功: {uploaded_url}")
|
||||
|
||||
cat_name = product.get("category")
|
||||
cat_id = categories.get(cat_name)
|
||||
|
||||
result = create_product(product, cat_id, image_url)
|
||||
if result:
|
||||
success_count += 1
|
||||
print(f" 商品创建成功")
|
||||
else:
|
||||
print(f" 商品创建失败")
|
||||
|
||||
import shutil
|
||||
shutil.rmtree("temp_images", ignore_errors=True)
|
||||
|
||||
print(f"\n导入完成: 成功 {success_count}/{len(products)} 个商品")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,72 @@
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('backend/sale.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT id, name FROM categories")
|
||||
categories = cursor.fetchall()
|
||||
|
||||
print(f"Found {len(categories)} categories")
|
||||
|
||||
brand_keywords = [
|
||||
'MEVIUS', 'Camel', 'Marlboro', 'Lucky', 'Kent', 'Seven Stars', 'Winston',
|
||||
'Lark', 'Pianissimo', 'Caster', 'Hope', 'Peace', 'Cabin', 'Golden Bat',
|
||||
'Black Spider', 'Che', 'Ark Royal', 'Cigaronne', 'TEREA', 'IQOS', 'HEETS',
|
||||
'Fiit', 'neo', 'sentia', 'virto', 'BON', 'LEAF', 'glow', 'Ploom',
|
||||
'American Spirit', 'Pall Mall', 'Davidoff', 'Dunhill', 'Parliament',
|
||||
'Virginia', 'Mild Seven', 'Salem', 'Kool', 'Newport', 'Carlton',
|
||||
'Merit', 'Vantage', 'Now', 'Tareyton', 'Belair', 'L&M',
|
||||
'Bond', 'Vogue', 'Rothmans', 'Gauloises', 'Gitanes', 'Prince',
|
||||
'メビウス', 'キャメル', 'マールボロ', 'ラッキー', 'ケント', 'セブンスター', 'ウィンストン',
|
||||
'ピアニッシモ', 'キャスター', 'ホープ', 'ピース', 'キャビン', 'ゴールデンバット',
|
||||
'ブラック', 'チェ', 'アーク', 'シガローネ', 'テリア', 'アイコス', 'ヒーツ',
|
||||
'フィット', 'ネオ', 'センシア', 'ヴェルト', 'ボン', 'リーフ', 'グロー', 'プルーム',
|
||||
'アメリカンスピリット', 'パーラメント', 'バージニア', 'マイルドセブン', 'セーラム', 'クール',
|
||||
'ラーク', 'ホープ', 'ピース', 'キャビン', 'キャメル', 'マールボロ'
|
||||
]
|
||||
|
||||
migrated_count = 0
|
||||
for cat_id, cat_name in categories:
|
||||
is_brand = False
|
||||
for keyword in brand_keywords:
|
||||
if keyword.lower() in cat_name.lower():
|
||||
is_brand = True
|
||||
break
|
||||
|
||||
if is_brand:
|
||||
cursor.execute("SELECT id FROM brands WHERE name = ?", (cat_name,))
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
brand_id = existing[0]
|
||||
else:
|
||||
cursor.execute("INSERT INTO brands (name, created_at, updated_at) VALUES (?, datetime('now'), datetime('now'))", (cat_name,))
|
||||
brand_id = cursor.lastrowid
|
||||
|
||||
cursor.execute("UPDATE products SET brand_id = ? WHERE id IN (SELECT product_id FROM product_categories WHERE category_id = ?)", (brand_id, cat_id))
|
||||
cursor.execute("DELETE FROM product_categories WHERE category_id = ?", (cat_id,))
|
||||
cursor.execute("DELETE FROM categories WHERE id = ?", (cat_id,))
|
||||
migrated_count += 1
|
||||
print(f"Migrated brand: {cat_name}")
|
||||
|
||||
print(f"\nMigrated {migrated_count} brands")
|
||||
|
||||
new_categories = [
|
||||
('卷烟', '传统卷烟产品'),
|
||||
('IQOS烟弹', 'IQOS专用加热烟弹'),
|
||||
('手卷烟丝', '手卷烟丝和烟纸'),
|
||||
('雪茄', '雪茄产品'),
|
||||
('电子烟', '电子烟及相关产品'),
|
||||
('薄荷烟', '薄荷口味卷烟'),
|
||||
('超细烟', '超细支卷烟'),
|
||||
]
|
||||
|
||||
for name, desc in new_categories:
|
||||
cursor.execute("SELECT id FROM categories WHERE name = ?", (name,))
|
||||
if not cursor.fetchone():
|
||||
cursor.execute("INSERT INTO categories (name, description, created_at, updated_at) VALUES (?, ?, datetime('now'), datetime('now'))", (name, desc))
|
||||
print(f"Created category: {name}")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print("\nMigration completed!")
|
||||
@@ -0,0 +1,13 @@
|
||||
import sqlite3
|
||||
import bcrypt
|
||||
|
||||
conn = sqlite3.connect('backend/sale.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
new_password = "admin123"
|
||||
hashed = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||
|
||||
cursor.execute("UPDATE users SET password_hash = ? WHERE email = ?", (hashed, "admin@viaeon.com"))
|
||||
conn.commit()
|
||||
|
||||
print("密码已重置为: admin123")
|
||||
@@ -0,0 +1,181 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
BASE_URL = "https://ribenyan.com"
|
||||
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": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7",
|
||||
}
|
||||
|
||||
def fetch_page(url):
|
||||
try:
|
||||
response = requests.get(url, headers=HEADERS, timeout=30)
|
||||
response.encoding = 'utf-8'
|
||||
return response.text
|
||||
except Exception as e:
|
||||
print(f"Error fetching {url}: {e}")
|
||||
return None
|
||||
|
||||
def get_category_links(html):
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
categories = []
|
||||
|
||||
accordion = soup.find('div', id='goodstypeaccordion')
|
||||
if accordion:
|
||||
links = accordion.find_all('a', class_='list-group-item')
|
||||
for link in links:
|
||||
href = link.get('href', '')
|
||||
text = link.get_text(strip=True)
|
||||
if href and 'm=goods&a=list' in href:
|
||||
if href.startswith('./'):
|
||||
href = BASE_URL + '/' + href[2:]
|
||||
elif href.startswith('index.php'):
|
||||
href = BASE_URL + '/' + href
|
||||
elif not href.startswith('http'):
|
||||
href = BASE_URL + '/' + href
|
||||
categories.append({'name': text, 'url': href})
|
||||
|
||||
return categories
|
||||
|
||||
def parse_products(html):
|
||||
products = []
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
rows = soup.find_all('div', class_='d-flex')
|
||||
|
||||
product_rows = []
|
||||
for row in rows:
|
||||
classes = row.get('class', [])
|
||||
if 'py-2' in classes and 'border-bottom' in classes:
|
||||
product_rows.append(row)
|
||||
|
||||
for row in product_rows:
|
||||
try:
|
||||
product = {}
|
||||
|
||||
id_div = row.find('div', class_='position-absolute')
|
||||
if id_div and 'top-0' in id_div.get('class', []) and 'end-0' in id_div.get('class', []):
|
||||
product['external_id'] = id_div.get_text(strip=True)
|
||||
|
||||
img = row.find('img', class_='img-thumbnail')
|
||||
if img:
|
||||
src = img.get('src', '')
|
||||
if src:
|
||||
if src.startswith('./'):
|
||||
src = BASE_URL + '/' + src[2:]
|
||||
elif not src.startswith('http'):
|
||||
src = BASE_URL + src
|
||||
product['image_url'] = src
|
||||
|
||||
p_tags = row.find_all('p')
|
||||
name_found = False
|
||||
for p in p_tags:
|
||||
classes = p.get('class', [])
|
||||
if 'mb-1' in classes and 'text-muted' not in classes and 'text-body-tertiary' not in classes and 'text-info' not in classes:
|
||||
if not name_found:
|
||||
product['name_cn'] = p.get_text(strip=True)
|
||||
name_found = True
|
||||
elif 'mb-1' in classes and 'text-muted' in classes:
|
||||
product['name_jp'] = p.get_text(strip=True)
|
||||
elif 'text-body-tertiary' in classes or 'text-info' in classes:
|
||||
text = p.get_text(strip=True)
|
||||
if text and '日元' not in text and '整大包' not in text and '限购' not in text:
|
||||
product['description'] = text
|
||||
|
||||
if not product.get('description'):
|
||||
product['description'] = ''
|
||||
|
||||
price_p = row.find('p', class_='mb-3')
|
||||
if price_p:
|
||||
price_text = price_p.get_text(strip=True)
|
||||
price_match = re.search(r'(\d+)', price_text)
|
||||
if price_match:
|
||||
product['price_jpy'] = int(price_match.group(1))
|
||||
|
||||
if product.get('name_cn') or product.get('name_jp'):
|
||||
products.append(product)
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
return products
|
||||
|
||||
def categorize_product(product, category_name=None):
|
||||
if category_name:
|
||||
return category_name
|
||||
|
||||
name = product.get('name_cn', '') + ' ' + product.get('name_jp', '')
|
||||
name_lower = name.lower()
|
||||
|
||||
if 'iqos' in name_lower or 'terea' in name_lower or 'iluma' in name_lower or 'sentia' in name_lower or 'virto' in name_lower or 'neo' in name_lower:
|
||||
return 'IQOS烟弹'
|
||||
elif 'シャグ' in name or 'shag' in name_lower or '手卷' in name or '烟丝' in name:
|
||||
return '手卷烟丝'
|
||||
elif 'シガー' in name or 'cigar' in name_lower or '雪茄' in name:
|
||||
return '雪茄'
|
||||
elif 'スーパースリム' in name or 'super slim' in name_lower or '超细' in name or '细支' in name:
|
||||
return '超细烟'
|
||||
elif 'メンソール' in name or 'menthol' in name_lower or '薄荷' in name or '爆珠' in name:
|
||||
return '薄荷烟'
|
||||
else:
|
||||
return '卷烟'
|
||||
|
||||
def main():
|
||||
print("开始采集 ribenyan.com 所有商品数据...")
|
||||
|
||||
html = fetch_page(BASE_URL)
|
||||
if not html:
|
||||
print("无法获取首页数据")
|
||||
return
|
||||
|
||||
categories = get_category_links(html)
|
||||
print(f"发现 {len(categories)} 个分类")
|
||||
|
||||
all_products = []
|
||||
seen_ids = set()
|
||||
|
||||
for i, cat in enumerate(categories):
|
||||
print(f"\n[{i+1}/{len(categories)}] 采集分类: {cat['name']}")
|
||||
|
||||
cat_html = fetch_page(cat['url'])
|
||||
if not cat_html:
|
||||
continue
|
||||
|
||||
products = parse_products(cat_html)
|
||||
|
||||
for p in products:
|
||||
p['category'] = categorize_product(p, cat['name'])
|
||||
p['price_cny'] = round(p.get('price_jpy', 0) * 0.048, 2)
|
||||
|
||||
pid = p.get('external_id')
|
||||
if pid and pid not in seen_ids:
|
||||
seen_ids.add(pid)
|
||||
all_products.append(p)
|
||||
|
||||
print(f" 获取 {len(products)} 个商品,累计 {len(all_products)} 个")
|
||||
time.sleep(0.5)
|
||||
|
||||
output_file = 'ribenyan_products.json'
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(all_products, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n采集完成,共获取 {len(all_products)} 个商品")
|
||||
print(f"数据已保存到 {output_file}")
|
||||
|
||||
cat_stats = {}
|
||||
for p in all_products:
|
||||
cat = p.get('category', '其他')
|
||||
if cat not in cat_stats:
|
||||
cat_stats[cat] = 0
|
||||
cat_stats[cat] += 1
|
||||
|
||||
print("\n分类统计:")
|
||||
for cat, count in sorted(cat_stats.items(), key=lambda x: -x[1]):
|
||||
print(f" {cat}: {count} 个")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user