爱回收.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import tkinter as tk
  2. from tkinter import ttk, messagebox
  3. import json
  4. import requests
  5. import threading
  6. from datetime import datetime
  7. import uuid
  8. import time
  9. import os
  10. class PhoneQueryApp:
  11. def __init__(self, root):
  12. self.root = root
  13. self.root.title("手机数据查询系统")
  14. self.root.geometry("1200x800")
  15. # 商品数据
  16. self.current_products = []
  17. self.is_loading_cycles = False
  18. self.collected_items = []
  19. # 状况标签映射(tagId -> 名称)
  20. self.condition_tags = {
  21. 10: "屏幕完美",
  22. 11: "机身无痕",
  23. 12: "功能完好",
  24. 17: "电池效率95%+",
  25. 20: "原厂在保",
  26. 19: "无维修",
  27. 15: "官方在保",
  28. 13: "原厂电池",
  29. 14: "原厂屏幕",
  30. 21: "原厂部件"
  31. }
  32. # 从文件读取cookie
  33. self.cookie = self.load_cookie()
  34. # 创建界面
  35. self.create_widgets()
  36. def load_cookie(self):
  37. """从cookie.txt文件读取cookie"""
  38. cookie_file = "cookie.txt"
  39. if os.path.exists(cookie_file):
  40. try:
  41. with open(cookie_file, 'r', encoding='utf-8') as f:
  42. cookie = f.read().strip()
  43. print(f"已加载cookie: {cookie[:50]}...")
  44. return cookie
  45. except Exception as e:
  46. print(f"读取cookie文件失败: {e}")
  47. return ""
  48. else:
  49. print("cookie.txt文件不存在,请创建并放入cookie")
  50. return ""
  51. def get_headers(self):
  52. """获取请求头"""
  53. return {
  54. "Connection": "keep-alive",
  55. "content-type": "application/json;charset=UTF-8",
  56. "ahs-app-version": "7.49.3",
  57. "Ahs-Timestamp": str(int(datetime.now().timestamp())),
  58. "Ahs-Token": "dccf18b052ef46d78d4feb796182d5df",
  59. "Ahs-Session-Id": str(uuid.uuid4()),
  60. "Ahs-App-Id": "10007",
  61. "Ahs-Device-Id": "3166e003-4ba7-4b57-8158-30859d81c7d5",
  62. "Ahs-Sign": "9ea25fcfcda37585065244ed271f1b0d",
  63. "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.74(0x18004a2c) NetType/WIFI Language/zh_CN",
  64. "Referer": "https://servicewechat.com/wx7e490492b4c23e98/1055/page-frame.html",
  65. "Accept": "*/*",
  66. "Accept-Encoding": "gzip, deflate, br",
  67. # "Cookie": self.cookie
  68. }
  69. def create_widgets(self):
  70. """创建界面组件"""
  71. main_frame = ttk.Frame(self.root, padding="10")
  72. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  73. self.root.columnconfigure(0, weight=1)
  74. self.root.rowconfigure(0, weight=1)
  75. main_frame.columnconfigure(0, weight=1)
  76. main_frame.rowconfigure(2, weight=1)
  77. # 查询区域
  78. query_frame = ttk.LabelFrame(main_frame, text="查询条件", padding="10")
  79. query_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  80. query_frame.columnconfigure(1, weight=1)
  81. ttk.Label(query_frame, text="机型:", font=("Arial", 11)).grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
  82. self.model_entry = ttk.Entry(query_frame, width=50, font=("Arial", 11))
  83. self.model_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
  84. self.model_entry.insert(0, "iPhone 15 Pro")
  85. self.search_btn = ttk.Button(query_frame, text="搜索", command=self.search_products, width=15)
  86. self.search_btn.grid(row=0, column=2)
  87. # 筛选区域
  88. filter_frame = ttk.LabelFrame(main_frame, text="筛选条件", padding="10")
  89. filter_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  90. # 第一行:价格区间 和 成色
  91. # 价格区间
  92. price_frame = ttk.LabelFrame(filter_frame, text="价格区间", padding="5")
  93. price_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=5, pady=5)
  94. self.price_vars = {}
  95. price_options = ["0-1499", "1500-1999", "2000-2999", "3000-3999", "4000以上"]
  96. for i, option in enumerate(price_options):
  97. var = tk.BooleanVar()
  98. self.price_vars[option] = var
  99. cb = ttk.Checkbutton(price_frame, text=option, variable=var)
  100. cb.grid(row=0, column=i, sticky=tk.W, padx=10, pady=2)
  101. # 成色
  102. fineness_frame = ttk.LabelFrame(filter_frame, text="成色", padding="5")
  103. fineness_frame.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5, pady=5)
  104. self.fineness_vars = {}
  105. fineness_options = ["全新仅拆封", "准新机", "99新", "95新", "9成新", "8成新", "7成新"]
  106. for i, option in enumerate(fineness_options):
  107. var = tk.BooleanVar()
  108. self.fineness_vars[option] = var
  109. cb = ttk.Checkbutton(fineness_frame, text=option, variable=var)
  110. cb.grid(row=0, column=i, sticky=tk.W, padx=10, pady=2)
  111. # 第二行:状况标签(占满整行)
  112. condition_frame = ttk.LabelFrame(filter_frame, text="状况标签", padding="5")
  113. condition_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), padx=5, pady=5)
  114. self.condition_vars = {}
  115. # 将状况标签分成两行显示,每行5个
  116. condition_items = list(self.condition_tags.items())
  117. for i, (tag_id, tag_name) in enumerate(condition_items):
  118. var = tk.BooleanVar()
  119. self.condition_vars[tag_id] = var
  120. row = i // 5 # 每行5个
  121. col = i % 5
  122. cb = ttk.Checkbutton(condition_frame, text=tag_name, variable=var)
  123. cb.grid(row=row, column=col, sticky=tk.W, padx=10, pady=2)
  124. filter_frame.columnconfigure(0, weight=1)
  125. filter_frame.columnconfigure(1, weight=1)
  126. # 结果显示区域
  127. result_frame = ttk.LabelFrame(main_frame, text="查询结果", padding="10")
  128. result_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  129. result_frame.columnconfigure(0, weight=1)
  130. result_frame.rowconfigure(0, weight=1)
  131. columns = ("名称", "价格", "循环次数", "成色", "内存", "颜色", "网络制式")
  132. self.result_tree = ttk.Treeview(result_frame, columns=columns, show="headings", height=15)
  133. self.result_tree.heading("名称", text="名称")
  134. self.result_tree.heading("价格", text="价格")
  135. self.result_tree.heading("循环次数", text="循环次数")
  136. self.result_tree.heading("成色", text="成色")
  137. self.result_tree.heading("内存", text="内存")
  138. self.result_tree.heading("颜色", text="颜色")
  139. self.result_tree.heading("网络制式", text="网络制式")
  140. self.result_tree.column("名称", width=400)
  141. self.result_tree.column("价格", width=100)
  142. self.result_tree.column("循环次数", width=100)
  143. self.result_tree.column("成色", width=100)
  144. self.result_tree.column("内存", width=80)
  145. self.result_tree.column("颜色", width=100)
  146. self.result_tree.column("网络制式", width=100)
  147. scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.result_tree.yview)
  148. self.result_tree.configure(yscrollcommand=scrollbar.set)
  149. self.result_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  150. scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
  151. # 底部收藏栏
  152. bottom_frame = ttk.Frame(main_frame)
  153. bottom_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  154. ttk.Label(bottom_frame, text="循环次数小于:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 5))
  155. self.cycle_threshold = ttk.Entry(bottom_frame, width=10, font=("Arial", 10))
  156. self.cycle_threshold.pack(side=tk.LEFT, padx=(0, 5))
  157. self.cycle_threshold.insert(0, "600")
  158. ttk.Label(bottom_frame, text="次的机型自动收藏", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 20))
  159. self.collect_btn = ttk.Button(bottom_frame, text="启动", command=self.start_collect, width=10)
  160. self.collect_btn.pack(side=tk.LEFT)
  161. self.progress_var = tk.StringVar()
  162. self.progress_var.set("")
  163. ttk.Label(bottom_frame, textvariable=self.progress_var, font=("Arial", 9)).pack(side=tk.LEFT, padx=(20, 0))
  164. self.collect_count_var = tk.StringVar()
  165. self.collect_count_var.set("")
  166. ttk.Label(bottom_frame, textvariable=self.collect_count_var, font=("Arial", 9), foreground="green").pack(side=tk.LEFT, padx=(10, 0))
  167. # 状态栏
  168. self.status_var = tk.StringVar()
  169. self.status_var.set("就绪")
  170. status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN)
  171. status_bar.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  172. def get_selected_prices(self):
  173. price_map = {
  174. "0-1499": (0, 1499),
  175. "1500-1999": (1500, 1999),
  176. "2000-2999": (2000, 2999),
  177. "3000-3999": (3000, 3999),
  178. "4000以上": (4000, 20000)
  179. }
  180. for option, var in self.price_vars.items():
  181. if var.get():
  182. return price_map[option]
  183. return (0, 20000)
  184. def get_selected_fineness(self):
  185. fineness_map = {
  186. "全新仅拆封": 111,
  187. "准新机": 100,
  188. "99新": 99,
  189. "95新": 95,
  190. "9成新": 90,
  191. "8成新": 80,
  192. "7成新": 70
  193. }
  194. selected = []
  195. for option, var in self.fineness_vars.items():
  196. if var.get():
  197. selected.append(fineness_map[option])
  198. return selected if selected else [99, 100, 111, 95, 90, 80, 70]
  199. def get_selected_condition_tags(self):
  200. """获取选中的状况标签ID列表"""
  201. selected = []
  202. for tag_id, var in self.condition_vars.items():
  203. if var.get():
  204. selected.append(tag_id)
  205. return selected
  206. def search_products(self):
  207. keyword = self.model_entry.get().strip()
  208. if not keyword:
  209. messagebox.showwarning("警告", "请输入机型")
  210. return
  211. for item in self.result_tree.get_children():
  212. self.result_tree.delete(item)
  213. self.current_products.clear()
  214. self.collected_items.clear()
  215. self.progress_var.set("")
  216. self.collect_count_var.set("")
  217. self.collect_btn.config(state="normal")
  218. self.status_var.set("正在搜索...")
  219. self.search_btn.config(state="disabled")
  220. thread = threading.Thread(target=self.do_search, args=(keyword,))
  221. thread.daemon = True
  222. thread.start()
  223. def do_search(self, keyword):
  224. try:
  225. min_price, max_price = self.get_selected_prices()
  226. fineness_ids = self.get_selected_fineness()
  227. condition_tags = self.get_selected_condition_tags()
  228. request_data = {
  229. "minPrice": min_price,
  230. "maxPrice": max_price,
  231. "sortValue": "sort_composite",
  232. "gaeaPricePpvIds": {},
  233. "gaeaFinenessIds": fineness_ids,
  234. "conditionTagList": condition_tags, # 使用选中的状况标签
  235. "scene": "search",
  236. "keyword": keyword,
  237. "sirReq": False,
  238. "cityId": 324,
  239. "pageIndex": 0,
  240. "pageSize": 50
  241. }
  242. url = "https://dubai-mp.aihuishou.com/ahs-yanxuan-service/products/search-goods-v2"
  243. response = requests.post(url, json=request_data, headers=self.get_headers(), timeout=30)
  244. result = response.json()
  245. self.root.after(0, self.display_results, result)
  246. except Exception as e:
  247. self.root.after(0, self.show_error, str(e))
  248. def parse_product_info(self, product):
  249. name = product.get('name', '')
  250. sale_goods_no = product.get('saleGoodsNo', '')
  251. product_no = product.get('productNo', '')
  252. color = ""
  253. memory = product.get('memoryDesc', '')
  254. if '黑色' in name:
  255. color = "黑色"
  256. elif '白色' in name:
  257. color = "白色"
  258. elif '蓝色' in name:
  259. color = "蓝色"
  260. elif '原色' in name:
  261. color = "原色"
  262. return {
  263. 'name': name[:50],
  264. 'price': product.get('price', 0),
  265. 'cycle_count': '',
  266. 'fineness': product.get('gaeaFinenessName', ''),
  267. 'memory': memory,
  268. 'color': color,
  269. 'network': "全网通",
  270. 'saleGoodsNo': sale_goods_no,
  271. 'productNo': product_no
  272. }
  273. def display_results(self, result):
  274. self.search_btn.config(state="normal")
  275. if result.get('code') != 0:
  276. self.status_var.set(f"查询失败: {result.get('resultMessage', '未知错误')}")
  277. messagebox.showerror("错误", result.get('resultMessage', '查询失败'))
  278. return
  279. data = result.get('data', [])
  280. if not data:
  281. self.status_var.set("未找到相关产品")
  282. messagebox.showinfo("提示", "未找到相关产品")
  283. return
  284. for product in data:
  285. info = self.parse_product_info(product)
  286. item_id = self.result_tree.insert("", tk.END, values=(
  287. info['name'], f"¥{info['price']}", info['cycle_count'],
  288. info['fineness'], info['memory'], info['color'], info['network']
  289. ))
  290. self.current_products.append({
  291. 'item_id': item_id,
  292. 'saleGoodsNo': info['saleGoodsNo'],
  293. 'productNo': info['productNo'],
  294. 'info': info
  295. })
  296. self.status_var.set(f"共找到 {len(data)} 条结果")
  297. def get_cycle_count(self, sale_goods_no):
  298. """获取商品的电池循环次数"""
  299. try:
  300. url = f"https://dubai-mp.aihuishou.com/ahs-yanxuan-service/products/goods-tag-param?saleGoodsNo={sale_goods_no}"
  301. response = requests.get(url, headers=self.get_headers(), timeout=10)
  302. result = response.json()
  303. if result.get('code') == 0 and 'data' in result:
  304. data = result['data']
  305. if 'machineConditionList' in data:
  306. for item in data['machineConditionList']:
  307. if item.get('name') == '充电次数':
  308. return item.get('value', '')
  309. if 'aggConditionTagParamList' in data:
  310. for group in data['aggConditionTagParamList']:
  311. if group.get('groupName') == '核心机况':
  312. for item in group.get('valueList', []):
  313. if item.get('name') == '充电次数':
  314. return item.get('value', '')
  315. return ''
  316. except Exception as e:
  317. print(f"获取循环次数失败: {e}")
  318. return ''
  319. def add_to_collection(self, item_no):
  320. """调用收藏接口添加收藏"""
  321. try:
  322. url = "https://dubai-mp.aihuishou.com/ahs-yanxuan-service/collect/save"
  323. payload = json.dumps({"itemNo": item_no, "type": 1})
  324. headers = self.get_headers()
  325. response = requests.post(url, data=payload, headers=headers, timeout=10)
  326. result = response.json()
  327. # 根据实际返回判断:code为200且data为True表示成功
  328. if result.get('code') == 200 and result.get('data') == True:
  329. return True, "收藏成功"
  330. else:
  331. return False, result.get('resultMessage', '收藏失败')
  332. except Exception as e:
  333. return False, str(e)
  334. def start_collect(self):
  335. if not self.current_products:
  336. messagebox.showwarning("警告", "请先搜索商品")
  337. return
  338. if self.is_loading_cycles:
  339. messagebox.showinfo("提示", "正在处理中,请稍候...")
  340. return
  341. if not self.cookie:
  342. result = messagebox.askyesno("提示", "未找到cookie.txt文件或cookie为空,是否继续?")
  343. if not result:
  344. return
  345. try:
  346. threshold = int(self.cycle_threshold.get().strip())
  347. except ValueError:
  348. messagebox.showerror("错误", "请输入有效的循环次数")
  349. return
  350. thread = threading.Thread(target=self.process_collect, args=(threshold,))
  351. thread.daemon = True
  352. thread.start()
  353. def process_collect(self, threshold):
  354. """处理收藏:获取循环次数并收藏符合条件的商品"""
  355. self.is_loading_cycles = True
  356. self.collect_btn.config(state="disabled")
  357. self.collected_items.clear()
  358. total = len(self.current_products)
  359. collected_count = 0
  360. for i, product in enumerate(self.current_products):
  361. self.root.after(0, lambda txt=f"处理: {i+1}/{total}": self.progress_var.set(txt))
  362. self.root.after(0, lambda i=i: self.status_var.set(f"正在获取循环次数 ({i+1}/{total})..."))
  363. cycle_count = self.get_cycle_count(product['saleGoodsNo'])
  364. if cycle_count:
  365. values = list(self.result_tree.item(product['item_id'], 'values'))
  366. values[2] = cycle_count
  367. self.root.after(0, lambda pid=product['item_id'], vals=values: self.result_tree.item(pid, values=vals))
  368. try:
  369. cycle_num = int(cycle_count)
  370. if cycle_num < threshold:
  371. item_no = product['productNo'] or product['saleGoodsNo']
  372. success, msg = self.add_to_collection(item_no)
  373. if success:
  374. collected_count += 1
  375. self.collected_items.append(f"{product['info']['name']} ({cycle_num}次)")
  376. self.root.after(0, lambda: self.status_var.set(f"✓ 已收藏: {product['info']['name'][:30]}"))
  377. except ValueError:
  378. pass
  379. time.sleep(0.3)
  380. self.root.after(0, lambda: self.finish_collect(collected_count, threshold))
  381. def finish_collect(self, collected_count, threshold):
  382. self.is_loading_cycles = False
  383. self.collect_btn.config(state="normal")
  384. self.progress_var.set("")
  385. self.collect_count_var.set(f"已收藏: {collected_count} 件")
  386. self.status_var.set(f"完成!处理 {len(self.current_products)} 条,收藏 {collected_count} 件")
  387. if collected_count > 0:
  388. detail = "\n".join(self.collected_items[:5])
  389. if len(self.collected_items) > 5:
  390. detail += f"\n... 等共{collected_count}件"
  391. messagebox.showinfo("收藏完成", f"已成功收藏 {collected_count} 件商品\n\n{detail}")
  392. else:
  393. messagebox.showinfo("完成", f"没有找到循环次数小于 {threshold} 的商品")
  394. def show_error(self, error_msg):
  395. self.search_btn.config(state="normal")
  396. self.status_var.set(f"查询失败: {error_msg}")
  397. messagebox.showerror("错误", f"查询失败: {error_msg}")
  398. def main():
  399. root = tk.Tk()
  400. app = PhoneQueryApp(root)
  401. root.mainloop()
  402. if __name__ == "__main__":
  403. main()