Spaces:
Configuration error
Configuration error
Ruyi Yang commited on
Commit ·
54056c6
1
Parent(s): 5e23f88
Add application file
Browse files- .env.example +18 -0
- .gitignore +17 -0
- .gradio/certificate.pem +31 -0
- .vscode/launch.json +23 -0
- LICENSE +21 -0
- README.md +61 -1
- element_sample.html +1 -0
- requirements.txt +39 -0
- src/agents/__init__.py +3 -0
- src/agents/fashion_agent.py +217 -0
- src/app.py +197 -0
- src/models/clothing_analyzer.py +104 -0
- src/reference/autogen_basic.py +42 -0
- src/reference/autogen_magenticone.py +62 -0
- src/rules/fashion_rules.py +136 -0
- src/utils/data_processor.py +202 -0
- src/utils/taobao_crawler.py +181 -0
- tests/test_modules.py +162 -0
- tests/test_spider.ipynb +86 -0
.env.example
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAI API Configuration
|
| 2 |
+
OPENAI_API_KEY=your_openai_api_key_here
|
| 3 |
+
|
| 4 |
+
# Azure Configuration
|
| 5 |
+
AZURE_OPENAI_API_KEY=your_azure_openai_api_key_here
|
| 6 |
+
AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint_here
|
| 7 |
+
|
| 8 |
+
# Taobao Configuration
|
| 9 |
+
TAOBAO_COOKIE=your_taobao_cookie_here
|
| 10 |
+
|
| 11 |
+
# Application Configuration
|
| 12 |
+
DATA_DIR=data
|
| 13 |
+
MODEL_CACHE_DIR=.cache
|
| 14 |
+
DEBUG=False
|
| 15 |
+
|
| 16 |
+
# Taobao Configuration
|
| 17 |
+
TAOBAO_COOKIE="thw=sg; t=c4252c509f86b198a60035b6d1a9dcd6; wk_cookie2=1db6c7de25eb6fc24f286065c8d89e64; wk_unb=UNQwUaH5Doeafw%3D%3D; useNativeIM=false; wwUserTip=false; cookie2=14c2e6fdcff08a7ebd04676c69549e67; _tb_token_=11105e3e9ed5; havana_lgc2_0=eyJoaWQiOjM0NzQwMTQ3NTMsInNnIjoiNzgyYjM1OWMxN2M5NWM2MmE3MTNlMTJjMmJiMzkwYWYiLCJzaXRlIjowLCJ0b2tlbiI6IjFUaTZteExiUjFmMDY3LVdILUpieFh3In0; _hvn_lgc_=0; xlly_s=1; ucn=center; 3PcFlag=1745248434664; cna=wdZRIPjDqE0CAXDH0GXrb4aF; unb=3474014753; sn=; uc3=vt3=F8dD2EuHYdT28oJq7Qk%3D&lg2=VT5L2FSpMGV7TQ%3D%3D&nk2=Bv7jP4dLU24%3D&id2=UNQwUaH5Doeafw%3D%3D; csg=98feb4f0; lgc=emmaruyi; cancelledSubSites=empty; cookie17=UNQwUaH5Doeafw%3D%3D; dnk=emmaruyi; skt=65151b4482ff3cec; existShop=MTc0NTI0ODQzOA%3D%3D; uc4=nk4=0%40BA5FZEstMq5xd8DX5FP%2BVFnq9Q%3D%3D&id4=0%40UgP7hgxcJSrYwZkQ89uwYmtiHwEI; tracknick=emmaruyi; _cc_=Vq8l%2BKCLiw%3D%3D; _l_g_=Ug%3D%3D; sg=i3e; _nk_=emmaruyi; cookie1=BdKFxSAvaAxqe40EAftnbSXOhV%2BGXj9yT3l6xL8%2BkJY%3D; sgcookie=E1004xUq4tXZJmHxU%2BivfFCVwhc5H5y6Iv0fuoz1WSTjJ9iP85cU3uuDJRviTlsWxIaWIi31ZLK5KmgwsQ4jTcWBAedSDbtxw0TYo8ZB9XnwBiI%3D; havana_lgc_exp=1745279542546; ubn=p; isg=BE1NnIBtGSsqFrLKFoRUNuHKXGnHKoH8k95ruI_SieRThm04V3qRzJuX8BrgQZm0; mtop_partitioned_detect=1; _m_h5_tk=cb7d064dabc1b9fb43be8c317f01e1f1_1745323295336; _m_h5_tk_enc=cef3ead6a9c8e440a2922cd4e9de6dde; tfstk=gRKZkbMbEcnaS3fOSHs4zmEd_gIO2ilSin1fnKvcC1fikOi00KJlhAhxBeWDGBHxBG9cgIRC9o6jWjE2TC9k5NOX5sXctBv_fsiOuIJWwIZjXF600B9AoIt2HoWDnIHOGA3BBdIAmbG5g099Bf_C24xVS9qhepXcIN0CxP56Q4cSV09iSOIXKbtXbsyVtTZcmNb0tyfd3OVGiIbHKO13jtqGnWDF96X0iZjc-MX5eP4MiiDeK6BcmOAcSvJnh_d21kfFSx1uCrQb6ojFZdfUmuyRQNVyerZYDhXN7nmRZoXXYO7NZdxz4FOFKHtFPwwjyMvJRCXF4DaGqp8DaUAxPyIwnFAA8ChL86THXKfcR-qWLLJMrMxjn8Qett7lmwy0mpSOtZtGq0VFpev6ohda3oX9f3_Vwwk0DZsHVaYk_-HJKGXDMaKjwlCMnLKJPgoQSgveoQ8V4SNAKrjpDFP0uNXdL_MELdLxPUI9CZFLkrQER95SeYa0o7BFL_M6qrUA8HXFNYHl."
|
| 18 |
+
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 忽略环境配置文件
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# 忽略 Python 缓存文件
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.pyc
|
| 7 |
+
|
| 8 |
+
# 忽略虚拟环境文件夹
|
| 9 |
+
venv/
|
| 10 |
+
|
| 11 |
+
# 忽略数据文件夹
|
| 12 |
+
data/
|
| 13 |
+
|
| 14 |
+
# 忽略日志文件
|
| 15 |
+
*.log
|
| 16 |
+
|
| 17 |
+
draft.py
|
.gradio/certificate.pem
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-----BEGIN CERTIFICATE-----
|
| 2 |
+
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
| 3 |
+
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
| 4 |
+
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
| 5 |
+
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
| 6 |
+
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
| 7 |
+
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
| 8 |
+
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
| 9 |
+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
| 10 |
+
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
| 11 |
+
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
| 12 |
+
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
| 13 |
+
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
| 14 |
+
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
| 15 |
+
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
| 16 |
+
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
| 17 |
+
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
| 18 |
+
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
| 19 |
+
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
| 20 |
+
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
| 21 |
+
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
| 22 |
+
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
| 23 |
+
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
| 24 |
+
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
| 25 |
+
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
| 26 |
+
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
| 27 |
+
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
| 28 |
+
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
| 29 |
+
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
| 30 |
+
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
| 31 |
+
-----END CERTIFICATE-----
|
.vscode/launch.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
// Use IntelliSense to learn about possible attributes.
|
| 3 |
+
// Hover to view descriptions of existing attributes.
|
| 4 |
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
| 5 |
+
"version": "0.2.0",
|
| 6 |
+
"configurations": [
|
| 7 |
+
|
| 8 |
+
{
|
| 9 |
+
"name": "Python Debugger: Current File",
|
| 10 |
+
"type": "debugpy",
|
| 11 |
+
"request": "launch",
|
| 12 |
+
"program": "${file}",
|
| 13 |
+
"console": "integratedTerminal"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"name": "Python Debugger: Current File",
|
| 17 |
+
"type": "debugpy",
|
| 18 |
+
"request": "launch",
|
| 19 |
+
"program": "${file}",
|
| 20 |
+
"console": "integratedTerminal"
|
| 21 |
+
}
|
| 22 |
+
]
|
| 23 |
+
}
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Ruyi Yang
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: FitMe Agent
|
| 3 |
emoji: 🏃
|
|
@@ -11,4 +71,4 @@ license: mit
|
|
| 11 |
short_description: An intelligent fashion recommendation agent
|
| 12 |
---
|
| 13 |
|
| 14 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
# AI Fashion Recommendation System
|
| 2 |
+
|
| 3 |
+
<!-- An intelligent fashion recommendation system built with Microsoft's AI technologies. -->
|
| 4 |
+
An intelligent fashion recommendation agent powered by image segmentation, multi-modal fusion, and large language models (LLMs). It analyzes Taobao purchase history to generate personalized outfit recommendations with detailed explanations.
|
| 5 |
+
|
| 6 |
+
## Features
|
| 7 |
+
|
| 8 |
+
- Image upload and processing
|
| 9 |
+
- Style scene selection
|
| 10 |
+
- Clothing image segmentation
|
| 11 |
+
- Color analysis and style matching
|
| 12 |
+
- Look recommendation with rule-based engine
|
| 13 |
+
- Natural language recommendations using GPT-4
|
| 14 |
+
|
| 15 |
+
## Technology Stack
|
| 16 |
+
|
| 17 |
+
- Semantic Kernel for orchestration
|
| 18 |
+
- Autogen for collaborative agents
|
| 19 |
+
- Azure AI Agents SDK
|
| 20 |
+
- Microsoft 365 Agents SDK
|
| 21 |
+
- Gradio for UI
|
| 22 |
+
- Azure GPT-4 API
|
| 23 |
+
|
| 24 |
+
## Project Structure
|
| 25 |
+
|
| 26 |
+
```
|
| 27 |
+
├── src/
|
| 28 |
+
│ ├── agents/ # AI agents implementation
|
| 29 |
+
│ ├── models/ # ML models and processing
|
| 30 |
+
│ ├── rules/ # Fashion rules engine
|
| 31 |
+
│ ├── utils/ # Utility functions
|
| 32 |
+
│ └── app.py # Main application
|
| 33 |
+
├── tests/ # Test files
|
| 34 |
+
├── requirements.txt # Project dependencies
|
| 35 |
+
└── README.md # Project documentation
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Setup
|
| 39 |
+
|
| 40 |
+
1. Install dependencies:
|
| 41 |
+
```bash
|
| 42 |
+
pip install -r requirements.txt
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
2. Set up environment variables:
|
| 46 |
+
```bash
|
| 47 |
+
cp .env.example .env
|
| 48 |
+
# Edit .env with your Azure credentials
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
3. Run the application:
|
| 52 |
+
```bash
|
| 53 |
+
python src/app.py
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## License
|
| 57 |
+
|
| 58 |
+
MIT
|
| 59 |
+
|
| 60 |
+
|
| 61 |
---
|
| 62 |
title: FitMe Agent
|
| 63 |
emoji: 🏃
|
|
|
|
| 71 |
short_description: An intelligent fashion recommendation agent
|
| 72 |
---
|
| 73 |
|
| 74 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
element_sample.html
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<div class="index-mod__order-container___1ur4- js-order-container" data-reactid=".0.7:$order-4308453648207015347"><div data-id="4308453648207015347" class="bought-wrapper-mod__trade-order___2lrzV" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347"><table class="bought-table-mod__table___279Jf bought-wrapper-mod__table___3xFFM" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0"><colgroup data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0"><col class="bought-table-mod__col1___16ixq" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.0"><col class="bought-table-mod__col2___1wHsS" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.1"><col class="bought-table-mod__col3___3DK6P" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.2"><col class="bought-table-mod__col4___30FH7" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.3"><col class="bought-table-mod__col5___1Ts4z" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.4"><col class="bought-table-mod__col6___HkXVR" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.5"><col class="bought-table-mod__col7___31Oui" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.0.6"></colgroup><tbody class="bought-wrapper-mod__head___2vnqo" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0"><tr data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0"><td class="bought-wrapper-mod__head-info-cell___29cDO" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0"><label class="bought-wrapper-mod__checkbox-label___3Va60" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.0"><span class="bought-wrapper-mod__checkbox___11anQ" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.0.0"><input type="checkbox" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.0.0.0"></span><span class="bought-wrapper-mod__create-time___yNWVS" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.0.1">2025-04-20</span></label><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.1.0">订单号</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.1.1">: </span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.0.1.2">4308453648207015347</span></span></td><td colspan="2" class="bought-wrapper-mod__seller-container___3dAK3" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.1"><span class="seller-mod__container___1i-DE" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.1.0"><img src="//gtd.alicdn.com/tps/i2/TB1aJQKFVXXXXamXFXXEDhGGXXX-32-32.png" class="seller-mod__icon___ku3Za" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.1.0.0"><a href="//store.taobao.com/shop/view_shop.htm?user_number_id=2874814717" class="seller-mod__name___1_wwa" title="丰妮坊旗舰店" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.1.0.1">丰妮坊旗舰店</a></span></td><td data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.2"><span id="webww1" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.2.0"><span class="ww-light ww-large" data-display="inline" data-nick="丰妮坊旗舰店" data-tnick="丰妮坊旗舰店" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.2.0.0"><a href="https://amos.alicdn.com/getcid.aw?v=3&groupid=0&s=1&charset=utf-8&uid=%E4%B8%B0%E5%A6%AE%E5%9D%8A%E6%97%97%E8%88%B0%E5%BA%97&site=cntaobao&fromid=cntaobaoemmaruyi" target="_blank" class="ww-inline ww-online" title="点此可以直接和卖家交流选好的宝贝,或相互交流网购体验,还支持语音视频噢。"><span>旺旺在线</span></a></span></span></td><td colspan="3" class="bought-wrapper-mod__thead-operations-container___2LwDA" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.3"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.3.$buddy-icon-4308453648207015347"></span><a href="//trade.taobao.com/trade/memo/update_buy_memo.htm?bizOrderId=4308453648207015347&buyerId=3474014753&user_type=0&pageNum=1&auctionTitle=null&daetBegin=null&dateEnd=null&commentStatus=null&sellerNick=null&auctionStatus=null&isArchive=false&logisticsService=null&visibility=true" class="bought-wrapper-mod__th-operation___yRtHm" title="编辑标记信息,仅自己可见" target="_blank" id="flag" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.3.1:$flag0"><i style="height:17px;width:1px;padding-left:17px;overflow:hidden;vertical-align:middle;font-size:0px;display:inline-block;background:url(//img.alicdn.com/tps/i1/TB1heyGFVXXXXXpXXXXR3Ey7pXX-550-260.png) no-repeat -30px -176px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:0.0.3.1:$flag0.0"></i></a></td></tr></tbody><tbody data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0"><tr data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0"><td class="sol-mod__no-br___toLPG" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0"><div class="ml-mod__container___1zaKJ production-mod__production___3ZePJ suborder-mod__production___3WebF" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0"><div class="ml-mod__media___28HC5" style="width:80px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.0"><div class="production-mod__pic-container___vVz7y" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.0.0"><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.71bd2e8dWAEvNq&id=765430216740&_u=m37h2gh17fa5&pisk=gjAg6G1z_dW_bPmdpB1sEpYhdJHK111XgnFADsIqLMSIlRet5t72onAvBESAinxDmGBqf1dDxUTx5I31_jb22HfvWGIvxqx9lSnsh1F4mhTrfFIx5IjVxh-chVsA0ixvud3KwbL65s14i0h-w7d7b7tG7ZPZuw7co2QZRdl2Us1q2m2LgOGlGh-ixFF4LwSCoZra_n5e8NQAgZ5V72jFoaw4bn-q-y7RooyN7R7E-a78QSSNg95FraNV0n5q-eSCos7w0skAIRjJiLAEAQonr8W482QNIg8Nx7YBpNoN7bsbgQRF7OAVtMbdJBbGIgWVbpTB_HL26C6Iqu5XJLxD3nM8d1Y2Q17J5fVhaFJpiwdxk-jwfBYe1948wsvwjKWWKbw5QtjMLCW3uS8CTM6ynhmgCGJ9xT_FLrc5J3Ie5CJnlk8w2GfN89hrreW2pC6W6DPGaLTO6K-jeJ1Mud5h4heUUzX8GwuvT-wfQw_h2gmkmCspcxwt-228hO7Crg3n--wfQw_h2203Fi6NRajR." class="production-mod__pic___2Wuak" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.0.0.0" data-spm-anchor-id="a1z09.2.0.0"><img src="//img.alicdn.com/imgextra/i1/2874814717/O1CN01FsIfaq1kiRAC9cob4_!!2874814717.jpg_80x80.jpg" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.0.0.0.0" data-spm-anchor-id="a1z09.2.0.i10.71bd2e8dWAEvNq"></a><a class="production-mod__icon-search___3MciC" href="https://s.taobao.com/search?imgSearchUrl=%2F%2Fimg.alicdn.com%2Fimgextra%2Fi1%2F2874814717%2FO1CN01FsIfaq1kiRAC9cob4_!!2874814717.jpg_80x80.jpg" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.0.0.1"></a></div></div><div style="margin-left:90px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1"><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0"><a href="https://item.taobao.com/item.htm?spm=a1z09.2.0.0.71bd2e8dWAEvNq&id=765430216740&_u=m37h2gh17fa5&pisk=gEy86gcjSZbofbe-mu5Dt-XkHnj0esqz0zr6K20kOrUYJlKuZX0nOvUaWX9haYcKprg0E4qoFDGQ-PNoP70HpJU4yLvnFyXKRlPVY4muEHnQGzp3Zu0uMHHzn7vnrakLAPDdSNXGIurr_v_GSmrV2FksfUOB-46XGvmLiuOkfurrL21yUl1T4HH1ZZYIA2sxhDnwPvGSdnijuD0IRygSGEitbvMQRp9jh0o9N3gQREOjjc9SRXGWGEiZAvgQRvsxhqoKd2MAFgg220pL_XsB1ICe-r44H0h-5oCwdpKZmF0aVZpHL-FxwV6ikp9QH0EKJFkvB60__4ciMr6DevEjvz005O9xhfFaxDUReLMuGSyrE-b9bbUKrfwmMtTxD-Eg1mrA6iEYp4h_FjKHQuk-AyGLgNpi2YVx68FPYEZ7S4FsUkxOrohTMfojGHdSE5qgLfw5eFum_DUooP66HxGC4yegBKXkSVnHNiIvTBlS0NQiY75R0GvEDVj5VBREOmoxSiIvTBlS0m3G2zdeTXiV." target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.0" data-spm-anchor-id="a1z09.2.0.0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.0.0"> </span><span style="line-height:16px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.0.1" data-spm-anchor-id="a1z09.2.0.i0.71bd2e8dWAEvNq">灰色运动短裤女居家松紧带热裤</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.0.2"> </span></a><a href="//buyertrade.taobao.com/trade/detail/tradeSnap.htm?tradeID=4308453648207015347&snapShot=true" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.1"> [交易快照] </a><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.2"></span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.0.3"></span></p><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1"><span class="production-mod__sku-item___3s6lG" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$0.0">颜色分类</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$0.1">:</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$0.2">白色</span></span><span class="production-mod__sku-item___3s6lG" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$1.0">尺寸</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$1.1">:</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.1.$1.2">M[【建议100--109斤】]</span></span></p><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2"><a href="//rule.tmall.com/tdetail-4400.htm" title="正品保证" type="3" style="margin-right:8px;" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$0"><img src="//img.alicdn.com/tps/i2/T1SyeXFpliXXaSQP_X-16-16.png" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$0.0"></a><a href="//www.taobao.com/go/act/315/xfzbz_rsms.php?ad_id=&am_id=130011830696bce9eda3&cm_id=&pm_id=" title="如实描述" type="3" style="margin-right:8px;" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$1"><img src="//img.alicdn.com/tps/TB1PDB6IVXXXXaVaXXXXXXXXXXX.png" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$1.0"></a><a href="//pages.tmall.com/wow/seller/act/seven-day" title="七天退换" type="3" style="margin-right:8px;" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$2"><img src="//img.alicdn.com/tps/i3/T1Vyl6FCBlXXaSQP_X-16-16.png" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$0.0.1.2.$2.0"></a></p></div></div></td><td class="sol-mod__no-br___toLPG" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1"><div class="price-mod__price___3Un7c" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0"><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.0"><del class="price-mod__del___1wVlL" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.0.0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.0.0.0">¥</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.0.0.1">59.80</span></del></p><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.1.0">¥</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$1.0.1.1" data-spm-anchor-id="a1z09.2.0.i1.71bd2e8dWAEvNq">39.80</span></p></div></td><td class="sol-mod__no-br___toLPG" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$2"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$2.0"><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$2.0.0">1</p></div></td><td data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0"><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$0.0"><a href="//refund2.tmall.com/dispute/applyRouter.htm?bizOrderId=4308453648207015347" class="text-mod__link___36nmM text-mod__hover___t2aVK" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$0.0.0">退款/退换货</a></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$1.0"><span class="text-mod__link___36nmM text-mod__hover___t2aVK" action="a3" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$3.0.$1.0.0">投诉商家</span></span></p></div></td><td class="" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0"><div class="price-mod__price___3Un7c" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.0"><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.0.2"><strong data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.0.2.0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.0.2.0.0">¥</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.0.2.0.1" data-spm-anchor-id="a1z09.2.0.i9.71bd2e8dWAEvNq">39.80</span></strong></p></div><p style="color:#6c6c6c;font-family:verdana;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0.0">(含运费:</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0.1">¥0.00</span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0.2"></span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0.3"></span><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.1:$0.4">)</span></p><div style="font-family:verdana;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.2"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.2.0"></div></div><p data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.3"><a href="http://www.taobao.com/m?sprefer=symj28" title="手机订单" style="margin-right:5px;" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.3.$0"><img src="//img.alicdn.com/tps/i1/T1xRBqXdNAXXXXXXXX-46-16.png" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$4.0.3.$0.0"></a></p></div></td><td class="" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0"><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.0.0"><span class="text-mod__link___36nmM" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.0.0.0">物流派件中</span></span></p><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1"><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$0.0"><a href="//trade.tmall.com/detail/orderDetail.htm?bizOrderId=4308453648207015347&route_to=tm1" class="text-mod__link___36nmM text-mod__hover___t2aVK" target="_blank" id="viewDetail" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$0.0.0">订单详情</a></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$1.0"><a href="https://render.alipay.com/p/h5/insscene-pcs/www/freight.html?bizOrderId=4308453648207015347" class="text-mod__link___36nmM text-mod__hover___t2aVK" target="_blank" id="byf" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$1.0.0">退货宝</a></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$2"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$2.0"><a href="https://market.m.taobao.com/app/dinamic/pc-trade-logistics/home.html?orderId=4308453648207015347&entrance=pc&oldUrl=%2F%2Fwuliu.taobao.com%2Fuser%2Forder_detail_new.htm%3Ftrade_id%3D4308453648207015347%26seller_id%3D2874814717" class="text-mod__link___36nmM text-mod__primary___3jEsS text-mod__hover___t2aVK" target="_blank" action="a4" id="viewLogistic" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$5.0.1.$2.0.0">查看物流</a></span></p></div></div></td><td class="" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6"><div data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0"><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$0"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$0.0"><span class="text-mod__link___36nmM text-mod__grey___2bs0f" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$0.0.0"><i class="icon-mod__icon___3kGb9 tm-iconfont icon-time icon-text-mod__icon___5fQJQ icon-text-mod__icon-start___evj7X icon-text-mod__time___2Iycr" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$0.0.0.$icon"></i><span class="text" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$0.0.0.$text">还剩7天17时</span></span></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$1"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$1.0"><a href="//trade.tmall.com/order/confirmGoods.htm?biz_order_id=4308453648207015347" class="button-mod__button___1zli6 button-mod__secondary___9LhiR button-mod__button___2EmeL" target="_blank" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$1.0.0">确认收货</a></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$2"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$2.0"><a href="//invoice-ua.taobao.com/user/invoice/applyPcRoute?orderId=4308453648207015347" class="text-mod__link___36nmM text-mod__hover___t2aVK" target="_blank" action="b4" id="applyInvoice" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$2.0.0">申请开票</a></span></p><p style="margin-bottom:3px;" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$3"><span data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$3.0"><span class="text-mod__link___36nmM text-mod__hover___t2aVK" action="a28" data-reactid=".0.7:$order-4308453648207015347.$4308453648207015347.0.1:1:0.$0.$6.0.$3.0.0">再次购买</span></span></p></div></td></tr></tbody></table></div></div>
|
requirements.txt
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
semantic-kernel>=0.9.0
|
| 2 |
+
pyautogen>=0.2.0
|
| 3 |
+
openai>=1.0.0
|
| 4 |
+
azure-identity>=1.12.0
|
| 5 |
+
azure-core>=1.29.0
|
| 6 |
+
azure-ai-formrecognizer>=3.3.0
|
| 7 |
+
gradio>=3.50.0
|
| 8 |
+
transformers>=4.30.0
|
| 9 |
+
torch>=2.0.0
|
| 10 |
+
pillow>=9.5.0
|
| 11 |
+
numpy>=1.24.0
|
| 12 |
+
python-dotenv>=0.19.0
|
| 13 |
+
opencv-python>=4.8.0
|
| 14 |
+
beautifulsoup4
|
| 15 |
+
selenium
|
| 16 |
+
pandas>=1.5.0
|
| 17 |
+
autogen>=0.2.0
|
| 18 |
+
autogen-agentchat>=0.2.0
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# azure-identity
|
| 22 |
+
# openai
|
| 23 |
+
# python-dotenv
|
| 24 |
+
# semantic-kernel
|
| 25 |
+
# azure-ai-inference
|
| 26 |
+
pydantic
|
| 27 |
+
rich
|
| 28 |
+
dotenv-azd
|
| 29 |
+
autogen-ext[openai]
|
| 30 |
+
azure-ai-inference==1.0.0b9
|
| 31 |
+
openai-agents
|
| 32 |
+
langgraph
|
| 33 |
+
langchain_openai
|
| 34 |
+
pydantic-ai
|
| 35 |
+
llama-index
|
| 36 |
+
llama-index-llms-azure-openai
|
| 37 |
+
llama-index-llms-openai-like
|
| 38 |
+
llama-index-embeddings-azure-openai
|
| 39 |
+
smolagents
|
src/agents/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This file makes the agents directory a Python package.
|
| 3 |
+
"""
|
src/agents/fashion_agent.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import autogen
|
| 2 |
+
from autogen_agentchat.agents import AssistantAgent
|
| 3 |
+
from autogen_ext.models.openai import AzureOpenAIChatCompletionClient, OpenAIChatCompletionClient
|
| 4 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import pandas as pd
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
import azure.identity
|
| 10 |
+
import asyncio
|
| 11 |
+
from autogen_agentchat.messages import TextMessage
|
| 12 |
+
from autogen_core import CancellationToken
|
| 13 |
+
|
| 14 |
+
load_dotenv(override=True)
|
| 15 |
+
|
| 16 |
+
class FashionAgent:
|
| 17 |
+
def __init__(self, clothing_data: Union[pd.DataFrame, str]):
|
| 18 |
+
# 加载衣物数据
|
| 19 |
+
if isinstance(clothing_data, str):
|
| 20 |
+
# 检查文件是否存在
|
| 21 |
+
if not os.path.exists(clothing_data):
|
| 22 |
+
raise FileNotFoundError(f"CSV文件不存在: {clothing_data}")
|
| 23 |
+
|
| 24 |
+
# 读取CSV文件
|
| 25 |
+
self.clothing_data = pd.read_csv(clothing_data)
|
| 26 |
+
print(f"成功加载数据,共 {len(self.clothing_data)} 条记录")
|
| 27 |
+
print("数据预览:")
|
| 28 |
+
print(self.clothing_data.head())
|
| 29 |
+
else:
|
| 30 |
+
self.clothing_data = clothing_data
|
| 31 |
+
|
| 32 |
+
# 配置 LLM
|
| 33 |
+
API_HOST = os.getenv("API_HOST", "github")
|
| 34 |
+
print(API_HOST)
|
| 35 |
+
if API_HOST == "github":
|
| 36 |
+
self.client = OpenAIChatCompletionClient(
|
| 37 |
+
model=os.getenv("GITHUB_MODEL", "gpt-4o"),
|
| 38 |
+
api_key=os.environ["GITHUB_TOKEN"],
|
| 39 |
+
base_url="https://models.inference.ai.azure.com"
|
| 40 |
+
)
|
| 41 |
+
elif API_HOST == "azure":
|
| 42 |
+
token_provider = azure.identity.get_bearer_token_provider(
|
| 43 |
+
azure.identity.DefaultAzureCredential(),
|
| 44 |
+
"https://cognitiveservices.azure.com/.default"
|
| 45 |
+
)
|
| 46 |
+
self.client = AzureOpenAIChatCompletionClient(
|
| 47 |
+
model=os.environ["AZURE_OPENAI_CHAT_MODEL"],
|
| 48 |
+
api_version=os.environ["AZURE_OPENAI_VERSION"],
|
| 49 |
+
azure_deployment=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
|
| 50 |
+
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
| 51 |
+
azure_ad_token_provider=token_provider
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# 创建智能体
|
| 55 |
+
self.fashion_agent = AssistantAgent(
|
| 56 |
+
"fashion_expert",
|
| 57 |
+
model_client=self.client,
|
| 58 |
+
system_message="""你是一个专业的时尚搭配专家。请严格遵循以下规则:
|
| 59 |
+
|
| 60 |
+
1. 数据限制:
|
| 61 |
+
- 选择一件上衣和一件下装进行搭配(非常重要!)
|
| 62 |
+
- 只能从提供的服装数据中选择搭配
|
| 63 |
+
- 不能推荐数据中不存在的服装
|
| 64 |
+
- 不能虚构或修改服装的属性
|
| 65 |
+
|
| 66 |
+
2. 搭配规则:
|
| 67 |
+
- 颜色匹配:主色相近 > 互补 > 其他对比色(优先级:高)
|
| 68 |
+
- 风格匹配:风格必须与目标风格场景一致或兼容,上下装风格需要协调,避免风格冲突的搭配(如运动风配正装)(优先级:中-高)
|
| 69 |
+
|
| 70 |
+
3. 环境适应:
|
| 71 |
+
- 根据温度选择合适材质的服装
|
| 72 |
+
- 上下装适宜的温度需要一致,不得出现上下装温差过大,例如绒衣搭配短裙
|
| 73 |
+
- 当温度 > 25°C:选择轻薄、透气材质(如棉、麻、雪纺)
|
| 74 |
+
- 当温度 < 15°C:选择保暖材质(如羊毛、毛呢、摇粒绒)
|
| 75 |
+
- 当温度在15-25°C之间:选择适中厚度的材质
|
| 76 |
+
|
| 77 |
+
- 根据心情调整色彩搭配
|
| 78 |
+
- 心情愉悦时:可以选择明亮、活泼的颜色
|
| 79 |
+
- 心情平静时:可以选择柔和、中性的颜色
|
| 80 |
+
- 心情沮丧时:可以选择深色、低饱和度的颜色
|
| 81 |
+
|
| 82 |
+
4. 服装数据限制:
|
| 83 |
+
- 只能从提供的服装数据中选择搭配
|
| 84 |
+
- 不能推荐数据中不存在的服装
|
| 85 |
+
- 不能虚构或修改服装的属性
|
| 86 |
+
|
| 87 |
+
你的任务是:
|
| 88 |
+
1. 从提供的服装数据中选择最合适的搭配
|
| 89 |
+
2. 生成具体的搭配建议
|
| 90 |
+
3. 提供详细的推荐理由
|
| 91 |
+
|
| 92 |
+
输出格式要求(JSON格式):
|
| 93 |
+
{
|
| 94 |
+
"top": {
|
| 95 |
+
"title": "上衣名称",
|
| 96 |
+
"image_url": "图片URL"
|
| 97 |
+
},
|
| 98 |
+
"bottom": {
|
| 99 |
+
"title": "下装名称",
|
| 100 |
+
"image_url": "图片URL"
|
| 101 |
+
},
|
| 102 |
+
"reason": "基于颜色和风格的推荐理由"
|
| 103 |
+
}"""
|
| 104 |
+
)
|
| 105 |
+
'''
|
| 106 |
+
其他规则(TBD):
|
| 107 |
+
- 露肤度平衡:上身为短款/露肩/抹胸时,推荐下身为长裤/高腰裙
|
| 108 |
+
'''
|
| 109 |
+
|
| 110 |
+
# def _filter_clothing(self, style_preference: str, temperature: Optional[float] = None) -> pd.DataFrame:
|
| 111 |
+
# """根据用户偏好筛选服装,没有办法完全字段相同进行匹配"""
|
| 112 |
+
# print(f"\n开��筛选服装,风格偏好: {style_preference}, 温度: {temperature}")
|
| 113 |
+
|
| 114 |
+
# # 1. 首先筛选出可用的服装
|
| 115 |
+
# available_clothing = self.clothing_data
|
| 116 |
+
|
| 117 |
+
# # 2. 根据风格偏好筛选
|
| 118 |
+
# if style_preference.lower() != 'unknown':
|
| 119 |
+
# style_matches = available_clothing[
|
| 120 |
+
# available_clothing['style'].str.contains(style_preference, case=False, na=False)
|
| 121 |
+
# ]
|
| 122 |
+
# print(f"风格匹配的服装数量: {len(style_matches)}")
|
| 123 |
+
# else:
|
| 124 |
+
# style_matches = available_clothing
|
| 125 |
+
|
| 126 |
+
# # 3. 如果温度信息存在,根据温度筛选合适的材质
|
| 127 |
+
# if temperature is not None:
|
| 128 |
+
# if temperature > 25: # 高温
|
| 129 |
+
# style_matches = style_matches[
|
| 130 |
+
# ~style_matches['title'].str.contains('羊毛|毛呢|厚|保暖', case=False, na=False)
|
| 131 |
+
# ]
|
| 132 |
+
# elif temperature < 15: # 低温
|
| 133 |
+
# style_matches = style_matches[
|
| 134 |
+
# style_matches['title'].str.contains('羊毛|毛呢|厚|保暖', case=False, na=False)
|
| 135 |
+
# ]
|
| 136 |
+
# print(f"温度筛选后的服装数量: {len(style_matches)}")
|
| 137 |
+
|
| 138 |
+
# return style_matches
|
| 139 |
+
|
| 140 |
+
# def _format_clothing_data(self, clothing_df: pd.DataFrame) -> str:
|
| 141 |
+
# """格式化服装数据,使其更易读"""
|
| 142 |
+
# if clothing_df.empty:
|
| 143 |
+
# return "没有找到匹配的服装。"
|
| 144 |
+
|
| 145 |
+
# # 按类型分组
|
| 146 |
+
# grouped = clothing_df.groupby('type')
|
| 147 |
+
# formatted_data = []
|
| 148 |
+
|
| 149 |
+
# for type_name, group in grouped:
|
| 150 |
+
# formatted_data.append(f"\n{type_name.upper()}类服装:")
|
| 151 |
+
# for _, row in group.iterrows():
|
| 152 |
+
# formatted_data.append(
|
| 153 |
+
# f"- {row['title']} (风格: {row['style']}, 颜色: {row['color']}, "
|
| 154 |
+
# f"露肤度: {row['exposure_level']})"
|
| 155 |
+
# )
|
| 156 |
+
|
| 157 |
+
# return "\n".join(formatted_data)
|
| 158 |
+
|
| 159 |
+
async def process_request(self, style_preference: str,
|
| 160 |
+
temperature: Optional[float] = None,
|
| 161 |
+
mood: Optional[str] = None) -> Dict:
|
| 162 |
+
"""处理完整的推荐请求"""
|
| 163 |
+
|
| 164 |
+
# 格式化服装数据,确保URL完整
|
| 165 |
+
formatted_data = self.clothing_data.to_dict('records')
|
| 166 |
+
|
| 167 |
+
# 构建初始提示
|
| 168 |
+
initial_prompt = f"""
|
| 169 |
+
请帮我推荐服装搭配:
|
| 170 |
+
|
| 171 |
+
1. 可用服装数据:
|
| 172 |
+
{json.dumps(formatted_data, ensure_ascii=False, indent=2)}
|
| 173 |
+
|
| 174 |
+
2. 用户偏好:
|
| 175 |
+
- 目标风格:{style_preference}
|
| 176 |
+
- 当前温度:{temperature if temperature else '未指定'}
|
| 177 |
+
- 当前心情:{mood if mood else '未指定'}
|
| 178 |
+
"""
|
| 179 |
+
|
| 180 |
+
# print("\n发送给模型的提示:")
|
| 181 |
+
# print(initial_prompt)
|
| 182 |
+
|
| 183 |
+
# 发送消息并获取响应
|
| 184 |
+
response = await self.fashion_agent.on_messages(
|
| 185 |
+
[TextMessage(content=initial_prompt, source="user")],
|
| 186 |
+
cancellation_token=CancellationToken(),
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
return response.chat_message.content
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
async def main():
|
| 193 |
+
try:
|
| 194 |
+
# 创建FashionAgent实例,直接传入CSV文件路径
|
| 195 |
+
agent = FashionAgent("data/new_processed_taobao_purchases.csv")
|
| 196 |
+
|
| 197 |
+
# 获取用户偏好
|
| 198 |
+
style_preference = input("请输入你想要的风格: ")
|
| 199 |
+
temperature = input("请输入当前温度(可选,直接回车跳过): ")
|
| 200 |
+
temperature = float(temperature) if temperature else None
|
| 201 |
+
mood = input("请输入当前心情(可选,直接回车跳过): ")
|
| 202 |
+
|
| 203 |
+
# 处理推荐请求
|
| 204 |
+
result = await agent.process_request(
|
| 205 |
+
style_preference=style_preference,
|
| 206 |
+
temperature=temperature,
|
| 207 |
+
mood=mood
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
# 打印结果
|
| 211 |
+
print("\n推荐结果:")
|
| 212 |
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
| 213 |
+
except Exception as e:
|
| 214 |
+
print(f"发生错误: {str(e)}")
|
| 215 |
+
|
| 216 |
+
if __name__ == "__main__":
|
| 217 |
+
asyncio.run(main())
|
src/app.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
# Add the src directory to Python path
|
| 6 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
import asyncio
|
| 10 |
+
from utils.taobao_crawler import TaobaoCrawler
|
| 11 |
+
from utils.data_processor import DataProcessor
|
| 12 |
+
from agents.fashion_agent import FashionAgent
|
| 13 |
+
import pandas as pd
|
| 14 |
+
import json
|
| 15 |
+
|
| 16 |
+
from dotenv import load_dotenv
|
| 17 |
+
load_dotenv(override=True)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# 全局变量
|
| 22 |
+
crawler = None
|
| 23 |
+
data_processor = None
|
| 24 |
+
fashion_agent = None
|
| 25 |
+
clothing_data = None
|
| 26 |
+
|
| 27 |
+
MODEL_OPTIONS = [
|
| 28 |
+
"gpt-4o",
|
| 29 |
+
"gpt-4o-mini",
|
| 30 |
+
"o3-mini",
|
| 31 |
+
"AI21-Jamba-1.5-Large",
|
| 32 |
+
"AI21-Jamba-1.5-Mini",
|
| 33 |
+
"Codestral-2501",
|
| 34 |
+
"Cohere-command-r",
|
| 35 |
+
"Ministral-3B",
|
| 36 |
+
"Mistral-Large-2411",
|
| 37 |
+
"Mistral-Nemo",
|
| 38 |
+
"Mistral-small"
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
async def start_crawler():
|
| 42 |
+
"""Start crawler and return login page URL"""
|
| 43 |
+
global crawler
|
| 44 |
+
try:
|
| 45 |
+
crawler = TaobaoCrawler()
|
| 46 |
+
# Direct login
|
| 47 |
+
if crawler.login():
|
| 48 |
+
return "Login successful!"
|
| 49 |
+
return "Login failed, please try again."
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"Error in start_crawler: {str(e)}")
|
| 52 |
+
return f"Error occurred: {str(e)}"
|
| 53 |
+
|
| 54 |
+
async def check_login():
|
| 55 |
+
"""Check login status"""
|
| 56 |
+
if crawler:
|
| 57 |
+
return "Logged in"
|
| 58 |
+
return "Not logged in"
|
| 59 |
+
|
| 60 |
+
async def process_data():
|
| 61 |
+
"""Process crawled data"""
|
| 62 |
+
global data_processor, clothing_data
|
| 63 |
+
if not crawler:
|
| 64 |
+
return []
|
| 65 |
+
|
| 66 |
+
# Initialize data processor
|
| 67 |
+
data_processor = DataProcessor(data_dir="data")
|
| 68 |
+
|
| 69 |
+
# Get data
|
| 70 |
+
items = crawler.get_purchase_history(days=30)
|
| 71 |
+
if items:
|
| 72 |
+
try:
|
| 73 |
+
# Convert items to DataFrame
|
| 74 |
+
items_df = pd.DataFrame(items)
|
| 75 |
+
# Process data
|
| 76 |
+
clothing_data = data_processor.process_data(items_df)
|
| 77 |
+
# Close browser
|
| 78 |
+
crawler.close()
|
| 79 |
+
# Return image URL list
|
| 80 |
+
return get_image_urls()
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"Error processing data: {str(e)}")
|
| 83 |
+
return []
|
| 84 |
+
return []
|
| 85 |
+
|
| 86 |
+
def get_image_urls():
|
| 87 |
+
"""Get all image URLs"""
|
| 88 |
+
if clothing_data is not None:
|
| 89 |
+
return list(clothing_data['image_url'].values)
|
| 90 |
+
return []
|
| 91 |
+
|
| 92 |
+
def update_model(selected_model):
|
| 93 |
+
os.environ["GITHUB_MODEL"] = selected_model
|
| 94 |
+
return f"Current selected model: {selected_model}"
|
| 95 |
+
|
| 96 |
+
async def get_recommendation(style_preference: str, temperature: float = None, mood: str = None):
|
| 97 |
+
"""Get clothing recommendations"""
|
| 98 |
+
global fashion_agent
|
| 99 |
+
if clothing_data is not None:
|
| 100 |
+
try:
|
| 101 |
+
# Initialize recommendation agent
|
| 102 |
+
fashion_agent = FashionAgent(clothing_data)
|
| 103 |
+
# Get recommendations
|
| 104 |
+
result = await fashion_agent.process_request(
|
| 105 |
+
style_preference=style_preference,
|
| 106 |
+
temperature=temperature,
|
| 107 |
+
mood=mood
|
| 108 |
+
)
|
| 109 |
+
print("LLM result:",result)
|
| 110 |
+
# Parse recommendation results
|
| 111 |
+
recommended_images = re.findall(r'https?://[^\s]+\.jpg', result)
|
| 112 |
+
print("Extracted image URLs:", recommended_images)
|
| 113 |
+
return recommended_images, result
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
print(f"Error in get_recommendation: {str(e)}")
|
| 117 |
+
return [], f"Error getting recommendations: {str(e)}"
|
| 118 |
+
return [], "Please process data first"
|
| 119 |
+
|
| 120 |
+
# Create Gradio interface
|
| 121 |
+
with gr.Blocks() as demo:
|
| 122 |
+
gr.Markdown("# Fashion Recommendation System")
|
| 123 |
+
|
| 124 |
+
with gr.Row():
|
| 125 |
+
with gr.Column():
|
| 126 |
+
# Crawler section
|
| 127 |
+
gr.Markdown("## 1. Get Taobao Data")
|
| 128 |
+
start_button = gr.Button("Start Login")
|
| 129 |
+
login_status = gr.Textbox(label="Login Status", interactive=False)
|
| 130 |
+
process_button = gr.Button("Get Clothing Data")
|
| 131 |
+
|
| 132 |
+
# Display all clothing images
|
| 133 |
+
gr.Markdown("## 2. My Clothing")
|
| 134 |
+
gallery = gr.Gallery(label="My Clothing", show_label=False)
|
| 135 |
+
|
| 136 |
+
# Recommendation section
|
| 137 |
+
gr.Markdown("## 3. Get Recommendations")
|
| 138 |
+
style_input = gr.Textbox(label="Desired Style")
|
| 139 |
+
temperature_input = gr.Number(label="Current Temperature (Optional)")
|
| 140 |
+
mood_input = gr.Textbox(label="Current Mood (Optional)")
|
| 141 |
+
model_dropdown = gr.Dropdown(
|
| 142 |
+
choices=MODEL_OPTIONS,
|
| 143 |
+
value=os.getenv('API_HOST', 'github'),
|
| 144 |
+
label="Select Model"
|
| 145 |
+
)
|
| 146 |
+
recommend_button = gr.Button("Get Recommendations")
|
| 147 |
+
|
| 148 |
+
# Display recommendation results
|
| 149 |
+
gr.Markdown("## 4. Recommended Outfits")
|
| 150 |
+
recommendation_gallery = gr.Gallery(label="Recommended Outfits", show_label=False)
|
| 151 |
+
recommendation_text = gr.Textbox(label="Recommendation Explanation", interactive=False, lines=10)
|
| 152 |
+
|
| 153 |
+
# Bind events
|
| 154 |
+
start_button.click(
|
| 155 |
+
start_crawler,
|
| 156 |
+
outputs=login_status
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
process_button.click(
|
| 160 |
+
process_data,
|
| 161 |
+
outputs=gallery
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
model_dropdown.change(
|
| 165 |
+
update_model,
|
| 166 |
+
inputs=model_dropdown,
|
| 167 |
+
)
|
| 168 |
+
recommend_button.click(
|
| 169 |
+
get_recommendation,
|
| 170 |
+
inputs=[style_input, temperature_input, mood_input],
|
| 171 |
+
outputs=[recommendation_gallery, recommendation_text]
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
# run on hugging face spaces and local machine
|
| 175 |
+
if __name__ == "__main__":
|
| 176 |
+
print(f"Using API_HOST: {os.getenv('API_HOST', 'github')}")
|
| 177 |
+
print(f"Using GITHUB_MODEL: {os.getenv('GITHUB_MODEL', 'gpt-4o')}")
|
| 178 |
+
|
| 179 |
+
# 检测运行环境
|
| 180 |
+
if os.getenv('SPACE_ID'):
|
| 181 |
+
# Hugging Face Spaces 环境
|
| 182 |
+
demo.launch()
|
| 183 |
+
else:
|
| 184 |
+
# 本地环境
|
| 185 |
+
os.environ["HTTP_PROXY"] = "http://127.0.0.1:2802"
|
| 186 |
+
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:2802"
|
| 187 |
+
os.environ["NO_PROXY"] = "127.0.0.1,localhost"
|
| 188 |
+
|
| 189 |
+
# 本地运行配置
|
| 190 |
+
is_share = True # 可以根据需要修改
|
| 191 |
+
server_name = "0.0.0.0" if is_share else "127.0.0.1"
|
| 192 |
+
|
| 193 |
+
demo.queue().launch(
|
| 194 |
+
server_name=server_name,
|
| 195 |
+
share=is_share,
|
| 196 |
+
show_error=True
|
| 197 |
+
)
|
src/models/clothing_analyzer.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# from transformers import AutoImageProcessor, AutoModelForImageClassification
|
| 2 |
+
# import torch
|
| 3 |
+
# from PIL import Image
|
| 4 |
+
# import numpy as np
|
| 5 |
+
# from typing import Dict, List, Tuple
|
| 6 |
+
# import cv2
|
| 7 |
+
|
| 8 |
+
# #
|
| 9 |
+
|
| 10 |
+
# class ClothingAnalyzer:
|
| 11 |
+
# def __init__(self):
|
| 12 |
+
# # 初始化Segformer模型
|
| 13 |
+
# self.processor = AutoImageProcessor.from_pretrained("mattmdjaga/segformer_b2_clothes")
|
| 14 |
+
# self.model = AutoModelForImageClassification.from_pretrained("mattmdjaga/segformer_b2_clothes")
|
| 15 |
+
# self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 16 |
+
# self.model.to(self.device)
|
| 17 |
+
|
| 18 |
+
# def analyze_image(self, image_path: str) -> Dict:
|
| 19 |
+
# """分析单张服装图片"""
|
| 20 |
+
# try:
|
| 21 |
+
# # 加载图片
|
| 22 |
+
# image = Image.open(image_path)
|
| 23 |
+
|
| 24 |
+
# # 预处理
|
| 25 |
+
# inputs = self.processor(images=image, return_tensors="pt").to(self.device)
|
| 26 |
+
|
| 27 |
+
# # 预测
|
| 28 |
+
# with torch.no_grad():
|
| 29 |
+
# outputs = self.model(**inputs)
|
| 30 |
+
# predictions = outputs.logits.argmax(dim=1)
|
| 31 |
+
|
| 32 |
+
# # 获取主要颜色
|
| 33 |
+
# main_colors = self._extract_main_colors(image)
|
| 34 |
+
|
| 35 |
+
# # 分析露肤度
|
| 36 |
+
# exposure_score = self._analyze_exposure(image)
|
| 37 |
+
|
| 38 |
+
# return {
|
| 39 |
+
# "clothing_type": self._get_clothing_type(predictions),
|
| 40 |
+
# "main_colors": main_colors,
|
| 41 |
+
# "exposure_score": exposure_score,
|
| 42 |
+
# "style_suggestions": self._suggest_styles(main_colors, exposure_score)
|
| 43 |
+
# }
|
| 44 |
+
# except Exception as e:
|
| 45 |
+
# print(f"Error analyzing image {image_path}: {str(e)}")
|
| 46 |
+
# return {}
|
| 47 |
+
|
| 48 |
+
# def _extract_main_colors(self, image: Image.Image, num_colors: int = 3) -> List[Tuple[int, int, int]]:
|
| 49 |
+
# """提取图片中的主要颜色"""
|
| 50 |
+
# # 将图片转换为numpy数组
|
| 51 |
+
# img_array = np.array(image)
|
| 52 |
+
|
| 53 |
+
# # 使用K-means聚类提取主要颜色
|
| 54 |
+
# pixels = img_array.reshape(-1, 3)
|
| 55 |
+
# pixels = np.float32(pixels)
|
| 56 |
+
|
| 57 |
+
# criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 200, 0.1)
|
| 58 |
+
# _, labels, centers = cv2.kmeans(pixels, num_colors, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
|
| 59 |
+
|
| 60 |
+
# # 转换回整数RGB值
|
| 61 |
+
# centers = np.uint8(centers)
|
| 62 |
+
# return [tuple(color) for color in centers]
|
| 63 |
+
|
| 64 |
+
# def _analyze_exposure(self, image: Image.Image) -> float:
|
| 65 |
+
# """分析图片的露肤度"""
|
| 66 |
+
# # TODO: 实现更复杂的露肤度分析算法
|
| 67 |
+
# # 这里使用简单的启发式方法
|
| 68 |
+
# img_array = np.array(image)
|
| 69 |
+
# skin_pixels = np.sum((img_array > 200) & (img_array < 240))
|
| 70 |
+
# total_pixels = img_array.size
|
| 71 |
+
# return (skin_pixels / total_pixels) * 10 # 0-10的评分
|
| 72 |
+
|
| 73 |
+
# def _get_clothing_type(self, predictions: torch.Tensor) -> str:
|
| 74 |
+
# """根据模型预测获取服装类型"""
|
| 75 |
+
# # TODO: 实现更详细的服装类型映射
|
| 76 |
+
# clothing_types = {
|
| 77 |
+
# 0: "Upper-clothes",
|
| 78 |
+
# 1: "Skirt",
|
| 79 |
+
# 2: "Pants",
|
| 80 |
+
# 3: "Dress",
|
| 81 |
+
# 4: "Outerwear"
|
| 82 |
+
# }
|
| 83 |
+
# return clothing_types.get(predictions.item(), "Unknown")
|
| 84 |
+
|
| 85 |
+
# def _suggest_styles(self, colors: List[Tuple[int, int, int]], exposure: float) -> List[str]:
|
| 86 |
+
# """根据颜色和露肤度建议可能的风格"""
|
| 87 |
+
# styles = []
|
| 88 |
+
|
| 89 |
+
# # 根据颜色饱和度判断
|
| 90 |
+
# for color in colors:
|
| 91 |
+
# r, g, b = color
|
| 92 |
+
# saturation = max(r, g, b) - min(r, g, b)
|
| 93 |
+
# if saturation > 200:
|
| 94 |
+
# styles.append("街头辣妹")
|
| 95 |
+
# elif saturation < 100:
|
| 96 |
+
# styles.append("法式复古")
|
| 97 |
+
|
| 98 |
+
# # 根据露肤度判断
|
| 99 |
+
# if exposure > 5:
|
| 100 |
+
# styles.append("运动风")
|
| 101 |
+
# elif exposure < 2:
|
| 102 |
+
# styles.append("宴会/静态")
|
| 103 |
+
|
| 104 |
+
# return list(set(styles)) # 去重
|
src/reference/autogen_basic.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import azure.identity
|
| 5 |
+
from autogen_agentchat.agents import AssistantAgent
|
| 6 |
+
from autogen_agentchat.messages import TextMessage
|
| 7 |
+
from autogen_core import CancellationToken
|
| 8 |
+
from autogen_ext.models.openai import AzureOpenAIChatCompletionClient, OpenAIChatCompletionClient
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
# Setup the client to use either Azure OpenAI or GitHub Models
|
| 12 |
+
load_dotenv(override=True)
|
| 13 |
+
API_HOST = os.getenv("API_HOST", "github")
|
| 14 |
+
if API_HOST == "github":
|
| 15 |
+
client = OpenAIChatCompletionClient(model=os.getenv("GITHUB_MODEL", "gpt-4o"), api_key=os.environ["GITHUB_TOKEN"], base_url="https://models.inference.ai.azure.com")
|
| 16 |
+
elif API_HOST == "azure":
|
| 17 |
+
token_provider = azure.identity.get_bearer_token_provider(azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default")
|
| 18 |
+
client = AzureOpenAIChatCompletionClient(
|
| 19 |
+
model=os.environ["AZURE_OPENAI_CHAT_MODEL"],
|
| 20 |
+
api_version=os.environ["AZURE_OPENAI_VERSION"],
|
| 21 |
+
azure_deployment=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
|
| 22 |
+
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
| 23 |
+
azure_ad_token_provider=token_provider)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
agent = AssistantAgent(
|
| 27 |
+
"spanish_tutor",
|
| 28 |
+
model_client=client,
|
| 29 |
+
system_message="You are a Spanish tutor. Help the user learn Spanish. ONLY respond in Spanish.",
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
async def main() -> None:
|
| 34 |
+
response = await agent.on_messages(
|
| 35 |
+
[TextMessage(content="hii how are you?", source="user")],
|
| 36 |
+
cancellation_token=CancellationToken(),
|
| 37 |
+
)
|
| 38 |
+
print(response.chat_message.content)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
if __name__ == "__main__":
|
| 42 |
+
asyncio.run(main())
|
src/reference/autogen_magenticone.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import azure.identity
|
| 5 |
+
from autogen_agentchat.agents import AssistantAgent
|
| 6 |
+
from autogen_agentchat.conditions import TextMentionTermination
|
| 7 |
+
from autogen_agentchat.teams import MagenticOneGroupChat
|
| 8 |
+
from autogen_agentchat.ui import Console
|
| 9 |
+
from autogen_ext.models.openai import AzureOpenAIChatCompletionClient, OpenAIChatCompletionClient
|
| 10 |
+
from dotenv import load_dotenv
|
| 11 |
+
|
| 12 |
+
# Setup the client to use either Azure OpenAI or GitHub Models
|
| 13 |
+
load_dotenv(override=True)
|
| 14 |
+
API_HOST = os.getenv("API_HOST", "github")
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
if API_HOST == "github":
|
| 18 |
+
client = OpenAIChatCompletionClient(model=os.getenv("GITHUB_MODEL", "gpt-4o"), api_key=os.environ["GITHUB_TOKEN"], base_url="https://models.inference.ai.azure.com")
|
| 19 |
+
elif API_HOST == "azure":
|
| 20 |
+
token_provider = azure.identity.get_bearer_token_provider(azure.identity.DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default")
|
| 21 |
+
client = AzureOpenAIChatCompletionClient(
|
| 22 |
+
model=os.environ["AZURE_OPENAI_CHAT_MODEL"],
|
| 23 |
+
api_version=os.environ["AZURE_OPENAI_VERSION"],
|
| 24 |
+
azure_deployment=os.environ["AZURE_OPENAI_CHAT_DEPLOYMENT"],
|
| 25 |
+
azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
|
| 26 |
+
azure_ad_token_provider=token_provider,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
local_agent = AssistantAgent(
|
| 30 |
+
"local_agent",
|
| 31 |
+
model_client=client,
|
| 32 |
+
description="A local assistant that can suggest local activities or places to visit.",
|
| 33 |
+
system_message="You are a helpful assistant that can suggest authentic and interesting local activities or places to visit for a user and can utilize any context information provided.",
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
language_agent = AssistantAgent(
|
| 37 |
+
"language_agent",
|
| 38 |
+
model_client=client,
|
| 39 |
+
description="A helpful assistant that can provide language tips for a given destination.",
|
| 40 |
+
system_message="You are a helpful assistant that can review travel plans, providing feedback on important/critical tips about how best to address language or communication challenges for the given destination. If the plan already includes language tips, you can mention that the plan is satisfactory, with rationale.",
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
travel_summary_agent = AssistantAgent(
|
| 44 |
+
"travel_summary_agent",
|
| 45 |
+
model_client=client,
|
| 46 |
+
description="A helpful assistant that can summarize the travel plan.",
|
| 47 |
+
system_message="You are a helpful assistant that can take in all of the suggestions and advice from the other agents and provide a detailed final travel plan. You must ensure that the final plan is integrated and complete. YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN. When the plan is complete and all perspectives are integrated, you can respond with TERMINATE.",
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
async def run_agents():
|
| 52 |
+
termination = TextMentionTermination("TERMINATE")
|
| 53 |
+
group_chat = MagenticOneGroupChat(
|
| 54 |
+
[local_agent, language_agent, travel_summary_agent],
|
| 55 |
+
termination_condition=termination,
|
| 56 |
+
model_client=client,
|
| 57 |
+
)
|
| 58 |
+
await Console(group_chat.run_stream(task="Plan a 3 day trip to Egypt"))
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
if __name__ == "__main__":
|
| 62 |
+
asyncio.run(run_agents())
|
src/rules/fashion_rules.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# from typing import Dict, List, Tuple
|
| 2 |
+
# import numpy as np
|
| 3 |
+
# from dataclasses import dataclass
|
| 4 |
+
# from enum import Enum
|
| 5 |
+
|
| 6 |
+
# class Style(Enum):
|
| 7 |
+
# SPORTY = "运动风"
|
| 8 |
+
# CASUAL = "途行风"
|
| 9 |
+
# FRENCH = "法式复古"
|
| 10 |
+
# STREET = "街头辣妹"
|
| 11 |
+
# FORMAL = "宴会/静态"
|
| 12 |
+
|
| 13 |
+
# @dataclass
|
| 14 |
+
# class ClothingItem:
|
| 15 |
+
# type: str
|
| 16 |
+
# colors: List[Tuple[int, int, int]]
|
| 17 |
+
# exposure: float
|
| 18 |
+
# style: Style
|
| 19 |
+
# image_path: str
|
| 20 |
+
|
| 21 |
+
# class FashionRulesEngine:
|
| 22 |
+
# def __init__(self):
|
| 23 |
+
# self.color_compatibility = {
|
| 24 |
+
# # 相似色
|
| 25 |
+
# "similar": 0.8,
|
| 26 |
+
# # 互补色
|
| 27 |
+
# "complementary": 0.6,
|
| 28 |
+
# # 对比色
|
| 29 |
+
# "contrast": 0.4
|
| 30 |
+
# }
|
| 31 |
+
|
| 32 |
+
# self.style_compatibility = {
|
| 33 |
+
# Style.SPORTY: [Style.CASUAL, Style.STREET],
|
| 34 |
+
# Style.CASUAL: [Style.SPORTY, Style.FRENCH],
|
| 35 |
+
# Style.FRENCH: [Style.CASUAL, Style.FORMAL],
|
| 36 |
+
# Style.STREET: [Style.SPORTY, Style.CASUAL],
|
| 37 |
+
# Style.FORMAL: [Style.FRENCH]
|
| 38 |
+
# }
|
| 39 |
+
|
| 40 |
+
# def calculate_color_match(self, color1: Tuple[int, int, int], color2: Tuple[int, int, int]) -> float:
|
| 41 |
+
# """计算两个颜色的匹配度"""
|
| 42 |
+
# # 转换为HSV颜色空间
|
| 43 |
+
# hsv1 = self._rgb_to_hsv(color1)
|
| 44 |
+
# hsv2 = self._rgb_to_hsv(color2)
|
| 45 |
+
|
| 46 |
+
# # 计算色相差异
|
| 47 |
+
# hue_diff = min(abs(hsv1[0] - hsv2[0]), 360 - abs(hsv1[0] - hsv2[0]))
|
| 48 |
+
|
| 49 |
+
# if hue_diff < 30: # 相似色
|
| 50 |
+
# return self.color_compatibility["similar"]
|
| 51 |
+
# elif 150 < hue_diff < 210: # 互补色
|
| 52 |
+
# return self.color_compatibility["complementary"]
|
| 53 |
+
# else: # 对比色
|
| 54 |
+
# return self.color_compatibility["contrast"]
|
| 55 |
+
|
| 56 |
+
# def calculate_style_match(self, style1: Style, style2: Style) -> float:
|
| 57 |
+
# """计算两个风格的匹配度"""
|
| 58 |
+
# if style1 == style2:
|
| 59 |
+
# return 1.0
|
| 60 |
+
# elif style2 in self.style_compatibility[style1]:
|
| 61 |
+
# return 0.7
|
| 62 |
+
# return 0.3
|
| 63 |
+
|
| 64 |
+
# def calculate_exposure_balance(self, top: ClothingItem, bottom: ClothingItem) -> float:
|
| 65 |
+
# """计算上下装露肤度的平衡度"""
|
| 66 |
+
# exposure_diff = abs(top.exposure - bottom.exposure)
|
| 67 |
+
# if exposure_diff < 2:
|
| 68 |
+
# return 1.0
|
| 69 |
+
# elif exposure_diff < 4:
|
| 70 |
+
# return 0.7
|
| 71 |
+
# return 0.3
|
| 72 |
+
|
| 73 |
+
# def recommend_outfit(self,
|
| 74 |
+
# tops: List[ClothingItem],
|
| 75 |
+
# bottoms: List[ClothingItem],
|
| 76 |
+
# target_style: Style,
|
| 77 |
+
# temperature: float = None) -> List[Tuple[ClothingItem, ClothingItem, float]]:
|
| 78 |
+
# """推荐搭配"""
|
| 79 |
+
# recommendations = []
|
| 80 |
+
|
| 81 |
+
# for top in tops:
|
| 82 |
+
# for bottom in bottoms:
|
| 83 |
+
# # 计算颜色匹配度
|
| 84 |
+
# color_score = max(
|
| 85 |
+
# self.calculate_color_match(top_color, bottom_color)
|
| 86 |
+
# for top_color in top.colors
|
| 87 |
+
# for bottom_color in bottom.colors
|
| 88 |
+
# )
|
| 89 |
+
|
| 90 |
+
# # 计算风格匹配度
|
| 91 |
+
# style_score = self.calculate_style_match(top.style, target_style) * \
|
| 92 |
+
# self.calculate_style_match(bottom.style, target_style)
|
| 93 |
+
|
| 94 |
+
# # 计算露肤度平衡
|
| 95 |
+
# exposure_score = self.calculate_exposure_balance(top, bottom)
|
| 96 |
+
|
| 97 |
+
# # 温度影响(可选)
|
| 98 |
+
# temp_score = 1.0
|
| 99 |
+
# if temperature is not None:
|
| 100 |
+
# if temperature > 25: # 热天
|
| 101 |
+
# temp_score = 0.5 if top.exposure < 3 else 1.0
|
| 102 |
+
# else: # 冷天
|
| 103 |
+
# temp_score = 0.5 if top.exposure > 7 else 1.0
|
| 104 |
+
|
| 105 |
+
# # 综合评分
|
| 106 |
+
# total_score = (color_score * 0.4 +
|
| 107 |
+
# style_score * 0.3 +
|
| 108 |
+
# exposure_score * 0.2 +
|
| 109 |
+
# temp_score * 0.1)
|
| 110 |
+
|
| 111 |
+
# recommendations.append((top, bottom, total_score))
|
| 112 |
+
|
| 113 |
+
# # 按评分排序
|
| 114 |
+
# recommendations.sort(key=lambda x: x[2], reverse=True)
|
| 115 |
+
# return recommendations
|
| 116 |
+
|
| 117 |
+
# def _rgb_to_hsv(self, rgb: Tuple[int, int, int]) -> Tuple[float, float, float]:
|
| 118 |
+
# """RGB转HSV"""
|
| 119 |
+
# r, g, b = [x/255.0 for x in rgb]
|
| 120 |
+
# max_val = max(r, g, b)
|
| 121 |
+
# min_val = min(r, g, b)
|
| 122 |
+
# diff = max_val - min_val
|
| 123 |
+
|
| 124 |
+
# if max_val == min_val:
|
| 125 |
+
# h = 0
|
| 126 |
+
# elif max_val == r:
|
| 127 |
+
# h = (60 * ((g - b) / diff) + 360) % 360
|
| 128 |
+
# elif max_val == g:
|
| 129 |
+
# h = (60 * ((b - r) / diff) + 120) % 360
|
| 130 |
+
# else:
|
| 131 |
+
# h = (60 * ((r - g) / diff) + 240) % 360
|
| 132 |
+
|
| 133 |
+
# s = 0 if max_val == 0 else diff / max_val
|
| 134 |
+
# v = max_val
|
| 135 |
+
|
| 136 |
+
# return (h, s, v)
|
src/utils/data_processor.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'''暂未剔除退款的记录'''
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import requests
|
| 7 |
+
from PIL import Image
|
| 8 |
+
import os
|
| 9 |
+
from typing import List, Dict
|
| 10 |
+
import json
|
| 11 |
+
import re
|
| 12 |
+
|
| 13 |
+
class DataProcessor:
|
| 14 |
+
def __init__(self, data_dir: str):
|
| 15 |
+
self.data_dir = data_dir
|
| 16 |
+
self.metadata_file = os.path.join(data_dir, "metadata.json")
|
| 17 |
+
|
| 18 |
+
def is_top(self, name: str) -> bool:
|
| 19 |
+
"""判断是否为上衣"""
|
| 20 |
+
keywords = ['背心', '上衣', 'T恤', '抹胸', '吊带', '露脐', '短袖', '衬衫', '外套', '夹克', '卫衣']
|
| 21 |
+
return any(kw in name for kw in keywords)
|
| 22 |
+
|
| 23 |
+
def is_bottom(self, name: str) -> bool:
|
| 24 |
+
"""判断是否为下装"""
|
| 25 |
+
keywords = ['短裤', '长裤', '裤子', '半身裙']
|
| 26 |
+
return any(kw in name for kw in keywords)
|
| 27 |
+
|
| 28 |
+
def is_dress(self, name: str) -> bool:
|
| 29 |
+
"""判断是否为连衣裙/连体裤"""
|
| 30 |
+
keywords = ['连衣裙', '连体裤', '套装', '长裙', '吊带裙', '背带裤']
|
| 31 |
+
return any(kw in name for kw in keywords)
|
| 32 |
+
|
| 33 |
+
def is_accessory(self, name: str) -> bool:
|
| 34 |
+
"""判断是否为配饰"""
|
| 35 |
+
keywords = ['帽子', '项链', '耳环', '手链', '戒指', '发饰', '围巾', '手套', '袜子', '包', '腰带', '眼镜', '口罩', '帽子', '鞋','袜子']
|
| 36 |
+
return any(kw in name for kw in keywords)
|
| 37 |
+
|
| 38 |
+
def estimate_exposure(self, name: str) -> str:
|
| 39 |
+
"""估算露肤度"""
|
| 40 |
+
high = ['抹胸', '露脐', '吊带']
|
| 41 |
+
medium = ['短袖', '背心', '短裙', '短裤']
|
| 42 |
+
low = ['长裙', '长裤', '毛呢']
|
| 43 |
+
|
| 44 |
+
if any(kw in name for kw in high):
|
| 45 |
+
return 'high'
|
| 46 |
+
elif any(kw in name for kw in medium):
|
| 47 |
+
return 'medium'
|
| 48 |
+
elif any(kw in name for kw in low):
|
| 49 |
+
return 'low'
|
| 50 |
+
return 'unknown'
|
| 51 |
+
|
| 52 |
+
def extract_style(self, name: str, fallback: str = None) -> str:
|
| 53 |
+
"""提取风格关键词"""
|
| 54 |
+
keywords = ['通勤', '辣妹', '运动', '学院', '复古', '法式']
|
| 55 |
+
style_map = {
|
| 56 |
+
'通勤': 'commuter',
|
| 57 |
+
'辣妹': 'trendy',
|
| 58 |
+
'运动': 'sports',
|
| 59 |
+
'学院': 'academic',
|
| 60 |
+
'复古': 'retro',
|
| 61 |
+
'法式': 'french'
|
| 62 |
+
}
|
| 63 |
+
for kw in keywords:
|
| 64 |
+
if kw in name:
|
| 65 |
+
return kw #style_map[kw]
|
| 66 |
+
return fallback if fallback else 'unknown'
|
| 67 |
+
|
| 68 |
+
def extract_color_size(self, spec: str) -> tuple:
|
| 69 |
+
"""从规格中提取颜色和尺码"""
|
| 70 |
+
color = ''
|
| 71 |
+
size = ''
|
| 72 |
+
|
| 73 |
+
if pd.isna(spec):
|
| 74 |
+
return color, size
|
| 75 |
+
|
| 76 |
+
# 尝试提取颜色
|
| 77 |
+
color_section = re.search(r'(?:颜色分类|主要颜色)[::]([^::]+)', str(spec))
|
| 78 |
+
if color_section:
|
| 79 |
+
# 获取颜色部分的文本并清理
|
| 80 |
+
color_text = color_section.group(1).strip()
|
| 81 |
+
|
| 82 |
+
# 先尝试匹配 xxx色
|
| 83 |
+
color_match = re.search(r'([^\s,,]+色)', color_text)
|
| 84 |
+
if color_match:
|
| 85 |
+
color = color_match.group(1)
|
| 86 |
+
else:
|
| 87 |
+
# 如果没找到xxx色,则保留第一段非空文本(处理类似"浆果玫红"这样的组合词)
|
| 88 |
+
color = re.split(r'[,,\s\-]+', color_text)[0].strip()
|
| 89 |
+
|
| 90 |
+
# 尝试提取尺码
|
| 91 |
+
size_section = re.search(r'尺码[::]([^::]+)', str(spec))
|
| 92 |
+
if size_section:
|
| 93 |
+
size = size_section.group(1).strip()
|
| 94 |
+
# 清理尺码中的特殊字符和乱码
|
| 95 |
+
size = re.sub(r'[\[\]【】\(\)]', '', size)
|
| 96 |
+
|
| 97 |
+
return color, size
|
| 98 |
+
|
| 99 |
+
def process_data(self, df: pd.DataFrame) -> pd.DataFrame:
|
| 100 |
+
"""处理淘宝购买记录数据"""
|
| 101 |
+
# 清理标题中的[交易快照]
|
| 102 |
+
df['title'] = df['title'].str.replace(r'\[交易快照\]$', '', regex=True).str.strip()
|
| 103 |
+
|
| 104 |
+
# 清理 image_url 中的 _80x80.jpg
|
| 105 |
+
df['image_url'] = df['image_url'].str.replace(r'_80x80\.jpg$', '_640x640.jpg', regex=True)
|
| 106 |
+
|
| 107 |
+
# 添加必要的列
|
| 108 |
+
if "type" not in df.columns:
|
| 109 |
+
df["type"] = "" # 服装类型
|
| 110 |
+
if "style" not in df.columns:
|
| 111 |
+
df["style"] = "" # 风格
|
| 112 |
+
if "exposure_level" not in df.columns:
|
| 113 |
+
df["exposure_level"] = "" # 露肤度
|
| 114 |
+
|
| 115 |
+
# 从specification提取颜色和尺码
|
| 116 |
+
df[['color', 'size']] = pd.DataFrame(
|
| 117 |
+
df['specification'].apply(self.extract_color_size).tolist(),
|
| 118 |
+
index=df.index
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# 添加新的处理逻辑
|
| 122 |
+
df['type'] = df['title'].apply(lambda x:
|
| 123 |
+
'上衣' if self.is_top(x)
|
| 124 |
+
else ('下装' if self.is_bottom(x)
|
| 125 |
+
else ('连衣裙/裤' if self.is_dress(x)
|
| 126 |
+
else ('配饰' if self.is_accessory(x)
|
| 127 |
+
else '未知'))))
|
| 128 |
+
|
| 129 |
+
df['exposure_level'] = df['title'].apply(self.estimate_exposure)
|
| 130 |
+
df['style'] = df.apply(lambda row: self.extract_style(str(row['title'])), axis=1)
|
| 131 |
+
|
| 132 |
+
# 判断是否是服饰(类型不为未知,且颜色和尺码都不为空)
|
| 133 |
+
df['is_clothing'] = (df['type'].apply(lambda x: x != '未知') &
|
| 134 |
+
df['color'].str.len().gt(0) &
|
| 135 |
+
df['size'].str.len().gt(0))
|
| 136 |
+
|
| 137 |
+
# 剔除退款的记录 !!暂未剔除
|
| 138 |
+
# df = df[~df['status'].str.contains('查看退款', na=False)]
|
| 139 |
+
|
| 140 |
+
# 剔除非服装类商品
|
| 141 |
+
df = df[df['is_clothing'] == True]
|
| 142 |
+
|
| 143 |
+
return df
|
| 144 |
+
|
| 145 |
+
# def download_images(self, image_urls: List[str], output_dir: str):
|
| 146 |
+
# """下载并保存图片"""
|
| 147 |
+
# os.makedirs(output_dir, exist_ok=True)
|
| 148 |
+
|
| 149 |
+
# for url in image_urls:
|
| 150 |
+
# try:
|
| 151 |
+
# response = requests.get(url)
|
| 152 |
+
# if response.status_code == 200:
|
| 153 |
+
# filename = os.path.join(output_dir, f"{hash(url)}.jpg")
|
| 154 |
+
# with open(filename, "wb") as f:
|
| 155 |
+
# f.write(response.content)
|
| 156 |
+
# except Exception as e:
|
| 157 |
+
# print(f"Error downloading {url}: {str(e)}")
|
| 158 |
+
|
| 159 |
+
# def save_metadata(self, metadata: Dict):
|
| 160 |
+
# """保存元数据"""
|
| 161 |
+
# with open(self.metadata_file, "w", encoding="utf-8") as f:
|
| 162 |
+
# json.dump(metadata, f, ensure_ascii=False, indent=2)
|
| 163 |
+
|
| 164 |
+
# def load_metadata(self) -> Dict:
|
| 165 |
+
# """加载元数据"""
|
| 166 |
+
# if os.path.exists(self.metadata_file):
|
| 167 |
+
# with open(self.metadata_file, "r", encoding="utf-8") as f:
|
| 168 |
+
# return json.load(f)
|
| 169 |
+
# return {}
|
| 170 |
+
|
| 171 |
+
# def process_image(self, image_path: str) -> Dict:
|
| 172 |
+
# """处理单张图片,提取特征"""
|
| 173 |
+
# try:
|
| 174 |
+
# with Image.open(image_path) as img:
|
| 175 |
+
# # 这里可以添加图像处理逻辑
|
| 176 |
+
# # 例如:调整大小、格式转换等
|
| 177 |
+
# return {
|
| 178 |
+
# "width": img.width,
|
| 179 |
+
# "height": img.height,
|
| 180 |
+
# "format": img.format,
|
| 181 |
+
# "path": image_path
|
| 182 |
+
# }
|
| 183 |
+
# except Exception as e:
|
| 184 |
+
# print(f"Error processing image {image_path}: {str(e)}")
|
| 185 |
+
# return {}
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def main():
|
| 189 |
+
# 初始化数据处理器
|
| 190 |
+
processor = DataProcessor(data_dir="data")
|
| 191 |
+
|
| 192 |
+
# 加载淘宝购买数据
|
| 193 |
+
raw_data = pd.read_csv("data/taobao_purchases.csv")
|
| 194 |
+
df = processor.process_data(raw_data)
|
| 195 |
+
|
| 196 |
+
# 保存处理后的数据
|
| 197 |
+
output_path = "data/processed_taobao_purchases.csv"
|
| 198 |
+
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
| 199 |
+
print(f"\nProcessed data saved to: {output_path}")
|
| 200 |
+
|
| 201 |
+
if __name__ == "__main__":
|
| 202 |
+
main()
|
src/utils/taobao_crawler.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
from selenium import webdriver
|
| 3 |
+
from selenium.webdriver.common.by import By
|
| 4 |
+
from selenium.webdriver.support.ui import WebDriverWait
|
| 5 |
+
from selenium.webdriver.support import expected_conditions as EC
|
| 6 |
+
from selenium.common.exceptions import TimeoutException
|
| 7 |
+
from typing import List, Dict
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
from datetime import datetime, timedelta
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
# 加载环境变量
|
| 14 |
+
load_dotenv()
|
| 15 |
+
|
| 16 |
+
class TaobaoCrawler:
|
| 17 |
+
def __init__(self):
|
| 18 |
+
"""
|
| 19 |
+
初始化爬虫
|
| 20 |
+
"""
|
| 21 |
+
self.options = webdriver.ChromeOptions()
|
| 22 |
+
# 添加一些选项来避免被检测
|
| 23 |
+
self.options.add_argument('--disable-blink-features=AutomationControlled')
|
| 24 |
+
self.options.add_experimental_option('excludeSwitches', ['enable-automation'])
|
| 25 |
+
self.options.add_experimental_option('useAutomationExtension', False)
|
| 26 |
+
|
| 27 |
+
# 如果需要无头模式(不显示浏览器窗口)
|
| 28 |
+
# self.options.add_argument('--headless')
|
| 29 |
+
|
| 30 |
+
self.driver = None
|
| 31 |
+
|
| 32 |
+
def login(self):
|
| 33 |
+
"""
|
| 34 |
+
使用二维码登录淘宝,并导航到已买到的宝贝页面
|
| 35 |
+
"""
|
| 36 |
+
if self.driver is None:
|
| 37 |
+
self.driver = webdriver.Chrome(options=self.options)
|
| 38 |
+
|
| 39 |
+
# 清除所有 cookies
|
| 40 |
+
self.driver.delete_all_cookies()
|
| 41 |
+
|
| 42 |
+
# 访问登录页面
|
| 43 |
+
self.driver.get('https://login.taobao.com')
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
# 等待登录成功,最多等待300秒
|
| 47 |
+
WebDriverWait(self.driver, 300).until(
|
| 48 |
+
lambda driver: 'login' not in driver.current_url
|
| 49 |
+
)
|
| 50 |
+
print("登录成功!")
|
| 51 |
+
|
| 52 |
+
# 等待页面加载完成
|
| 53 |
+
time.sleep(3) # 给页面一些加载时间
|
| 54 |
+
|
| 55 |
+
# 点击"已买到的宝贝"按钮
|
| 56 |
+
try:
|
| 57 |
+
bought_items_link = WebDriverWait(self.driver, 10).until(
|
| 58 |
+
EC.element_to_be_clickable((By.LINK_TEXT, "已买到的宝贝"))
|
| 59 |
+
)
|
| 60 |
+
bought_items_link.click()
|
| 61 |
+
print("成功导航到已买到的宝贝页面")
|
| 62 |
+
return True
|
| 63 |
+
except TimeoutException:
|
| 64 |
+
print("无法找到或点击'已买到的宝贝'按钮")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
except TimeoutException:
|
| 68 |
+
print("登录超时,请重试")
|
| 69 |
+
return False
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def get_purchase_history(self, days: int = 30) -> List[Dict]:
|
| 73 |
+
"""
|
| 74 |
+
获取指定天数内的购买记录
|
| 75 |
+
:param days: 要获取的天数
|
| 76 |
+
:return: 购买记录列表
|
| 77 |
+
"""
|
| 78 |
+
if self.driver is None:
|
| 79 |
+
print("请先调用 login() 方法完成登录")
|
| 80 |
+
return []
|
| 81 |
+
|
| 82 |
+
# 等待页面加载完成
|
| 83 |
+
try:
|
| 84 |
+
# 等待订单容器加载
|
| 85 |
+
WebDriverWait(self.driver, 10).until(
|
| 86 |
+
EC.presence_of_element_located((By.CLASS_NAME, 'index-mod__order-container___1ur4-'))
|
| 87 |
+
)
|
| 88 |
+
except TimeoutException:
|
| 89 |
+
print("页面加载超时")
|
| 90 |
+
return []
|
| 91 |
+
|
| 92 |
+
# 获取订单数据
|
| 93 |
+
orders = []
|
| 94 |
+
try:
|
| 95 |
+
# 获取所有订单容器
|
| 96 |
+
order_containers = self.driver.find_elements(By.CLASS_NAME, 'index-mod__order-container___1ur4-')
|
| 97 |
+
print(f"找到 {len(order_containers)} 个订单日期组")
|
| 98 |
+
|
| 99 |
+
for container in order_containers:
|
| 100 |
+
try:
|
| 101 |
+
# 获取每个订单项
|
| 102 |
+
order_items = container.find_elements(By.TAG_NAME, 'tbody')
|
| 103 |
+
|
| 104 |
+
for item in order_items[1:]: # 跳过第一个tbody(它是订单头部)
|
| 105 |
+
try:
|
| 106 |
+
# 获取商品标题和规格
|
| 107 |
+
title_element = item.find_element(By.CSS_SELECTOR, 'p[data-reactid*="0.0.1.0"]')
|
| 108 |
+
title = title_element.text.strip()
|
| 109 |
+
|
| 110 |
+
# 获取规格信息
|
| 111 |
+
spec_element = item.find_element(By.CSS_SELECTOR, 'p[data-reactid*="0.0.1.1"]')
|
| 112 |
+
spec = spec_element.text.strip()
|
| 113 |
+
|
| 114 |
+
# 获取图片链接
|
| 115 |
+
img_element = item.find_element(By.CSS_SELECTOR, '.production-mod__pic___2Wuak img')
|
| 116 |
+
img_url = img_element.get_attribute('src')
|
| 117 |
+
|
| 118 |
+
# 获取价格
|
| 119 |
+
price_element = item.find_element(By.CSS_SELECTOR, 'p[data-reactid*="1.0.1"]')
|
| 120 |
+
price = price_element.text.strip()
|
| 121 |
+
|
| 122 |
+
# 获取订单状态 -- 后续提出交易关闭的部分
|
| 123 |
+
status_element = item.find_element(By.CSS_SELECTOR, 'td[class*="sol-mod__no-br"] + td + td + td')
|
| 124 |
+
status = status_element.text.strip()
|
| 125 |
+
|
| 126 |
+
order_info = {
|
| 127 |
+
'title': title,
|
| 128 |
+
'specification': spec,
|
| 129 |
+
'image_url': img_url, # 新增图片链接
|
| 130 |
+
'price': price,
|
| 131 |
+
'status': status
|
| 132 |
+
}
|
| 133 |
+
orders.append(order_info)
|
| 134 |
+
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(f"解析订单项时出错: {str(e)}")
|
| 137 |
+
continue
|
| 138 |
+
|
| 139 |
+
except Exception as e:
|
| 140 |
+
print(f"处理订单容器时出错: {str(e)}")
|
| 141 |
+
continue
|
| 142 |
+
|
| 143 |
+
except Exception as e:
|
| 144 |
+
print(f"获取订单列表时出错: {str(e)}")
|
| 145 |
+
|
| 146 |
+
return orders
|
| 147 |
+
|
| 148 |
+
def save_to_csv(self, items: List[Dict], output_path: str):
|
| 149 |
+
"""
|
| 150 |
+
将商品信息保存为CSV文件
|
| 151 |
+
"""
|
| 152 |
+
import pandas as pd
|
| 153 |
+
df = pd.DataFrame(items)
|
| 154 |
+
df.to_csv(output_path, index=False, encoding='utf-8-sig')
|
| 155 |
+
print(f"已保存 {len(items)} 条记录到 {output_path}")
|
| 156 |
+
|
| 157 |
+
def close(self):
|
| 158 |
+
"""
|
| 159 |
+
关闭浏览器
|
| 160 |
+
"""
|
| 161 |
+
if self.driver:
|
| 162 |
+
self.driver.quit()
|
| 163 |
+
self.driver = None
|
| 164 |
+
|
| 165 |
+
def main():
|
| 166 |
+
crawler = TaobaoCrawler()
|
| 167 |
+
|
| 168 |
+
# 登录
|
| 169 |
+
if crawler.login():
|
| 170 |
+
# 获取最近30天的购买记录
|
| 171 |
+
items = crawler.get_purchase_history(days=30) # 目前未筛选,仅爬第一页;之后可以补充用日期筛选,爬取多页
|
| 172 |
+
|
| 173 |
+
# 保存为CSV
|
| 174 |
+
if items:
|
| 175 |
+
crawler.save_to_csv(items, "data/taobao_purchases.csv")
|
| 176 |
+
|
| 177 |
+
# 关闭浏览器
|
| 178 |
+
crawler.close()
|
| 179 |
+
|
| 180 |
+
if __name__ == "__main__":
|
| 181 |
+
main()
|
tests/test_modules.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
# 添加项目根目录到 Python 路径
|
| 6 |
+
sys.path.append(str(Path(__file__).parent.parent))
|
| 7 |
+
|
| 8 |
+
# from src.utils.data_processor import DataProcessor
|
| 9 |
+
# from src.models.clothing_analyzer import ClothingAnalyzer
|
| 10 |
+
# from src.rules.fashion_rules import FashionRulesEngine, Style, ClothingItem
|
| 11 |
+
# from src.agents.fashion_agent import FashionAgent
|
| 12 |
+
from src.utils.taobao_crawler import TaobaoCrawler
|
| 13 |
+
|
| 14 |
+
def test_taobao_crawler():
|
| 15 |
+
print("\n=== Testing Taobao Crawler ===")
|
| 16 |
+
# 从环境变量获取cookie
|
| 17 |
+
cookie = os.getenv("TAOBAO_COOKIE")
|
| 18 |
+
if not cookie:
|
| 19 |
+
print("TAOBAO_COOKIE not found in environment variables")
|
| 20 |
+
return
|
| 21 |
+
|
| 22 |
+
crawler = TaobaoCrawler(cookie)
|
| 23 |
+
|
| 24 |
+
# 测试获取购买记录
|
| 25 |
+
print("开始获取淘宝购买记录...")
|
| 26 |
+
items = crawler.get_purchase_history(days=30)
|
| 27 |
+
print(f"获取到 {len(items)} 件商品")
|
| 28 |
+
|
| 29 |
+
if items:
|
| 30 |
+
# 测试保存CSV
|
| 31 |
+
print("\n保存数据到CSV...")
|
| 32 |
+
crawler.save_to_csv(items, "data/test_taobao_purchases.csv")
|
| 33 |
+
|
| 34 |
+
# 测试下载图片
|
| 35 |
+
print("\n开始下载商品图片...")
|
| 36 |
+
crawler.download_images(items, "data/test_images")
|
| 37 |
+
print("图片下载完成")
|
| 38 |
+
else:
|
| 39 |
+
print("\n没有获取到商品数据,可能的原因:")
|
| 40 |
+
print("1. Cookie可能已过期")
|
| 41 |
+
print("2. 指定时间范围内没有购买记录")
|
| 42 |
+
print("3. 网页结构可能发生变化")
|
| 43 |
+
|
| 44 |
+
# def test_data_processor():
|
| 45 |
+
# print("\n=== Testing Data Processor ===")
|
| 46 |
+
# processor = DataProcessor("data")
|
| 47 |
+
|
| 48 |
+
# # 创建测试数据
|
| 49 |
+
# test_csv = "data/test_data.csv"
|
| 50 |
+
# test_image_url = "https://example.com/test.jpg" # 替换为实际的测试图片URL
|
| 51 |
+
|
| 52 |
+
# # 测试加载CSV
|
| 53 |
+
# df = processor.load_taobao_data(test_csv)
|
| 54 |
+
# print(f"Loaded CSV with {len(df)} rows")
|
| 55 |
+
|
| 56 |
+
# # 测试下载图片
|
| 57 |
+
# processor.download_images([test_image_url], "data/test_images")
|
| 58 |
+
# print("Downloaded test image")
|
| 59 |
+
|
| 60 |
+
# # 测试元数据保存和加载
|
| 61 |
+
# test_metadata = {"test": "data"}
|
| 62 |
+
# processor.save_metadata(test_metadata)
|
| 63 |
+
# loaded_metadata = processor.load_metadata()
|
| 64 |
+
# print(f"Metadata test: {loaded_metadata == test_metadata}")
|
| 65 |
+
|
| 66 |
+
# def test_clothing_analyzer():
|
| 67 |
+
# print("\n=== Testing Clothing Analyzer ===")
|
| 68 |
+
# analyzer = ClothingAnalyzer()
|
| 69 |
+
|
| 70 |
+
# # 使用测试图片
|
| 71 |
+
# test_image_path = "data/test_images/test.jpg"
|
| 72 |
+
# if os.path.exists(test_image_path):
|
| 73 |
+
# analysis = analyzer.analyze_image(test_image_path)
|
| 74 |
+
# print("Analysis results:")
|
| 75 |
+
# print(f"Clothing type: {analysis.get('clothing_type')}")
|
| 76 |
+
# print(f"Main colors: {analysis.get('main_colors')}")
|
| 77 |
+
# print(f"Exposure score: {analysis.get('exposure_score')}")
|
| 78 |
+
# print(f"Style suggestions: {analysis.get('style_suggestions')}")
|
| 79 |
+
# else:
|
| 80 |
+
# print("Test image not found")
|
| 81 |
+
|
| 82 |
+
# def test_fashion_rules():
|
| 83 |
+
# print("\n=== Testing Fashion Rules Engine ===")
|
| 84 |
+
# rules_engine = FashionRulesEngine()
|
| 85 |
+
|
| 86 |
+
# # 创建测试服装项
|
| 87 |
+
# test_top = ClothingItem(
|
| 88 |
+
# type="Upper-clothes",
|
| 89 |
+
# colors=[(255, 0, 0), (200, 0, 0)], # 红色系
|
| 90 |
+
# exposure=5.0,
|
| 91 |
+
# style=Style.SPORTY,
|
| 92 |
+
# image_path="test_top.jpg"
|
| 93 |
+
# )
|
| 94 |
+
|
| 95 |
+
# test_bottom = ClothingItem(
|
| 96 |
+
# type="Pants",
|
| 97 |
+
# colors=[(0, 0, 255), (0, 0, 200)], # 蓝色系
|
| 98 |
+
# exposure=2.0,
|
| 99 |
+
# style=Style.SPORTY,
|
| 100 |
+
# image_path="test_bottom.jpg"
|
| 101 |
+
# )
|
| 102 |
+
|
| 103 |
+
# # 测试颜色匹配
|
| 104 |
+
# color_score = rules_engine.calculate_color_match(
|
| 105 |
+
# test_top.colors[0], test_bottom.colors[0]
|
| 106 |
+
# )
|
| 107 |
+
# print(f"Color match score: {color_score}")
|
| 108 |
+
|
| 109 |
+
# # 测试风格匹配
|
| 110 |
+
# style_score = rules_engine.calculate_style_match(
|
| 111 |
+
# test_top.style, test_bottom.style
|
| 112 |
+
# )
|
| 113 |
+
# print(f"Style match score: {style_score}")
|
| 114 |
+
|
| 115 |
+
# # 测试露肤度平衡
|
| 116 |
+
# exposure_score = rules_engine.calculate_exposure_balance(test_top, test_bottom)
|
| 117 |
+
# print(f"Exposure balance score: {exposure_score}")
|
| 118 |
+
|
| 119 |
+
# # 测试完整推荐
|
| 120 |
+
# recommendations = rules_engine.recommend_outfit(
|
| 121 |
+
# [test_top], [test_bottom], Style.SPORTY, 20.0
|
| 122 |
+
# )
|
| 123 |
+
# print(f"Number of recommendations: {len(recommendations)}")
|
| 124 |
+
# if recommendations:
|
| 125 |
+
# print(f"Best match score: {recommendations[0][2]}")
|
| 126 |
+
|
| 127 |
+
# def test_fashion_agent():
|
| 128 |
+
# print("\n=== Testing Fashion Agent ===")
|
| 129 |
+
# agent = FashionAgent()
|
| 130 |
+
|
| 131 |
+
# # 测试服装分析
|
| 132 |
+
# test_image_path = "data/test_images/test.jpg"
|
| 133 |
+
# if os.path.exists(test_image_path):
|
| 134 |
+
# clothing_features = agent.analyze_clothing(test_image_path, {})
|
| 135 |
+
# print("Clothing features:", clothing_features)
|
| 136 |
+
|
| 137 |
+
# # 测试风格匹配
|
| 138 |
+
# matches = agent.match_style(clothing_features, "SPORTY")
|
| 139 |
+
# print("Style matches:", matches)
|
| 140 |
+
|
| 141 |
+
# # 测试推荐生成
|
| 142 |
+
# recommendation = agent.generate_recommendation(matches, {
|
| 143 |
+
# "temperature": 20.0,
|
| 144 |
+
# "mood": "元气"
|
| 145 |
+
# })
|
| 146 |
+
# print("Recommendation:", recommendation)
|
| 147 |
+
# else:
|
| 148 |
+
# print("Test image not found")
|
| 149 |
+
|
| 150 |
+
def main():
|
| 151 |
+
# 创建测试目录
|
| 152 |
+
os.makedirs("data/test_images", exist_ok=True)
|
| 153 |
+
|
| 154 |
+
# 运行测试
|
| 155 |
+
test_taobao_crawler() # 先测试爬虫
|
| 156 |
+
# test_data_processor()
|
| 157 |
+
# test_clothing_analyzer()
|
| 158 |
+
# test_fashion_rules()
|
| 159 |
+
# test_fashion_agent()
|
| 160 |
+
|
| 161 |
+
if __name__ == "__main__":
|
| 162 |
+
main()
|
tests/test_spider.ipynb
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 3,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"name": "stdout",
|
| 10 |
+
"output_type": "stream",
|
| 11 |
+
"text": [
|
| 12 |
+
"{'title': '标题未找到', 'price': '价格未找到'}\n"
|
| 13 |
+
]
|
| 14 |
+
}
|
| 15 |
+
],
|
| 16 |
+
"source": [
|
| 17 |
+
"\n",
|
| 18 |
+
"import requests\n",
|
| 19 |
+
"from bs4 import BeautifulSoup\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"def get_taobao_product_details(url):\n",
|
| 22 |
+
" # 设置请求头,模拟浏览器访问\n",
|
| 23 |
+
" headers = {\n",
|
| 24 |
+
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'\n",
|
| 25 |
+
" }\n",
|
| 26 |
+
"\n",
|
| 27 |
+
" # 发送GET请求\n",
|
| 28 |
+
" response = requests.get(url, headers=headers)\n",
|
| 29 |
+
" with open('taobao_response_2.html', 'w', encoding='utf-8') as f:\n",
|
| 30 |
+
" f.write(response.text)\n",
|
| 31 |
+
"\n",
|
| 32 |
+
" # 检查响应状态码\n",
|
| 33 |
+
" if response.status_code == 200:\n",
|
| 34 |
+
" # 使用BeautifulSoup解析HTML\n",
|
| 35 |
+
" soup = BeautifulSoup(response.text, 'html.parser')\n",
|
| 36 |
+
"\n",
|
| 37 |
+
" # 根据淘宝页面结构提取商品信息\n",
|
| 38 |
+
" # 注意:这里的选择器可能需要根据实际页面结构进行调整\n",
|
| 39 |
+
" title = soup.select_one('.tb-main-title').text.strip() if soup.select_one('.tb-main-title') else '标题未找到'\n",
|
| 40 |
+
" price = soup.select_one('.tb-rmb-num').text.strip() if soup.select_one('.tb-rmb-num') else '价格未找到'\n",
|
| 41 |
+
"\n",
|
| 42 |
+
" # 返回商品详情\n",
|
| 43 |
+
" return {\n",
|
| 44 |
+
" 'title': title,\n",
|
| 45 |
+
" 'price': price\n",
|
| 46 |
+
" }\n",
|
| 47 |
+
" else:\n",
|
| 48 |
+
" # 如果请求失败,返回错误信息\n",
|
| 49 |
+
" return '请求失败,状态码:' + str(response.status_code)\n",
|
| 50 |
+
"\n",
|
| 51 |
+
"# 使用示例\n",
|
| 52 |
+
"product_url = 'https://item.taobao.com/item.htm?spm=a21bo.jianhua%2Fa.201876.d3.5af92a89ey2Bpw&id=814664398764&xxc=ad_ct&skuId=5512096293145&utparam=%7B%22abid%22%3A%220%22%2C%22x_object_type%22%3A%22p4p_item%22%2C%22pc_pvid%22%3A%22de364914-7af7-48b1-93d0-23de22b9d29f%22%2C%22mix_group%22%3A%22%22%2C%22pc_scene%22%3A%2220001%22%2C%22aplus_abtest%22%3A%22fb804a7a66b7762cb841afdbf9cca5e9%22%2C%22tpp_buckets%22%3A%2230986%23418557%23module%22%2C%22x_object_id%22%3A814664398764%2C%22ab_info%22%3A%2230986%23418557%23-1%23%22%7D<k2=1745322666027jk9jtciqob6wxuw246qdl'\n",
|
| 53 |
+
"details = get_taobao_product_details(product_url)\n",
|
| 54 |
+
"print(details)\n"
|
| 55 |
+
]
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"cell_type": "code",
|
| 59 |
+
"execution_count": null,
|
| 60 |
+
"metadata": {},
|
| 61 |
+
"outputs": [],
|
| 62 |
+
"source": []
|
| 63 |
+
}
|
| 64 |
+
],
|
| 65 |
+
"metadata": {
|
| 66 |
+
"kernelspec": {
|
| 67 |
+
"display_name": "FitMe-env",
|
| 68 |
+
"language": "python",
|
| 69 |
+
"name": "python3"
|
| 70 |
+
},
|
| 71 |
+
"language_info": {
|
| 72 |
+
"codemirror_mode": {
|
| 73 |
+
"name": "ipython",
|
| 74 |
+
"version": 3
|
| 75 |
+
},
|
| 76 |
+
"file_extension": ".py",
|
| 77 |
+
"mimetype": "text/x-python",
|
| 78 |
+
"name": "python",
|
| 79 |
+
"nbconvert_exporter": "python",
|
| 80 |
+
"pygments_lexer": "ipython3",
|
| 81 |
+
"version": "3.10.16"
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"nbformat": 4,
|
| 85 |
+
"nbformat_minor": 2
|
| 86 |
+
}
|