爱回收.py 18 KB

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