├── requirements.txt ├── user_config.json ├── data └── 20160505.csv ├── images ├── 20160605.png ├── 20160607.png ├── 2016-06-21.png ├── 20160607-2.png ├── pysidelogo.png ├── 20160614_pm.png ├── 20160615_pm.png ├── error_20160614.png └── success_20160614.png ├── README.md ├── .gitignore ├── LICENSE ├── captcha_handler.py └── PC_Order.py /requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user_config.json: -------------------------------------------------------------------------------- 1 | {"user_thread_num": "3"} -------------------------------------------------------------------------------- /data/20160505.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/data/20160505.csv -------------------------------------------------------------------------------- /images/20160605.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/20160605.png -------------------------------------------------------------------------------- /images/20160607.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/20160607.png -------------------------------------------------------------------------------- /images/2016-06-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/2016-06-21.png -------------------------------------------------------------------------------- /images/20160607-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/20160607-2.png -------------------------------------------------------------------------------- /images/pysidelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/pysidelogo.png -------------------------------------------------------------------------------- /images/20160614_pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/20160614_pm.png -------------------------------------------------------------------------------- /images/20160615_pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/20160615_pm.png -------------------------------------------------------------------------------- /images/error_20160614.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/error_20160614.png -------------------------------------------------------------------------------- /images/success_20160614.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cundi/PySide_For_Amazon_Order/HEAD/images/success_20160614.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PySide_For_Amazon_Order 2 | 亚马逊(中文)自动下单,使用PySide框架 3 | 4 | 本周(20160605)结果截图: 5 | 6 | ![img](images/20160605.png) 7 | 8 | 20160607: 9 | 10 | ![img](images/20160607.png) 11 | 12 | 20160607-pm: 13 | `未能将下单程序放到线程中运行,同时无法打印日志` 14 | 15 | ![img](images/20160607-2.png) 16 | 17 | 20160614-am: 18 | `线程内运行make_order函数,但执行中途出现闪退` 19 | ![img](images/error_20160614.png) 20 | 21 | 20160614-am-2: 22 | `使用模块级变量作为参数池, 成功打印日志, 和多线程` 23 | ![img](images/success_20160614.png) 24 | 25 | 20160614-pm: 26 | `实现勾选后按照行数运行线程` 27 | ![img](images/20160614_pm.png) 28 | 29 | 20160616-am: 30 | `It can unchecked the item of current thread.` 31 | ![img](images/20160615_pm.png) 32 | 33 | 20160621-pm: 34 | `添加验证码处理,更新csv文件,但是UU打码平台目前只返回四位验证码,如下图:` 35 | ![img](images/2016-06-21.png) 36 | 37 | 20160624-am: 38 | `清空购物车和添加商品到购物车改为request直接发送HTTP请求,ToDo——设置收货地址和支付方式` 39 | 40 | 201606024-pm: 41 | `直接发POST请求添加新收货地址,卡在了shipoptionselect中的itemids的获取。` 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 m.l 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. 22 | -------------------------------------------------------------------------------- /captcha_handler.py: -------------------------------------------------------------------------------- 1 | # _*_coding:utf8 _*_ 2 | # !/usr/bin/python 3 | import cookielib 4 | import hashlib 5 | import urllib 6 | import urllib2 7 | import uuid 8 | import requests 9 | import time 10 | import random 11 | import sys 12 | 13 | 14 | class Ucode(object): 15 | # "110614", "469c0d8a805a40f39d3c1ec3c9281e9c", "zsqboy", "gih7c00", "1004" 16 | # 软件ID,软件key, 用户id,用户password,图片类型代码codetype 17 | # codetype及价格表详见.http://www.uuwise.com/price.html 18 | # 把pic改成你需要识别的验证码的地址,最好是jpg格式, 19 | # 如果不是要设置uu_captcha的codetype 20 | 21 | def __init__(self, user, pwd, softId="110614", 22 | softKey="469c0d8a805a40f39d3c1ec3c9281e9c", 23 | codeType="1004"): 24 | self.softId = softId 25 | self.softKey = softKey 26 | self.user = user 27 | self.pwd = pwd 28 | self.codeType = codeType 29 | self.uid = "100" 30 | self.initUrl = "http://common.taskok.com:9000/Service/ServerConfig.aspx" 31 | self.version = '1.1.1.2' 32 | self.cookieJar = cookielib.CookieJar() 33 | self.opener = urllib2.build_opener( 34 | urllib2.HTTPCookieProcessor(self.cookieJar)) 35 | self.loginUrl = None 36 | self.uploadUrl = None 37 | self.codeUrl = None 38 | self.params = [] 39 | self.uKey = None 40 | 41 | def initialise(self): 42 | flag = False 43 | try: 44 | params = self.initHeader() 45 | if params: 46 | self.params = params 47 | self.opener.addheaders = params 48 | response = self.opener.open(self.initUrl, None, 30) 49 | if response.code == 200: 50 | body = response.read() 51 | if body is not None and len(body) > 0: 52 | body = body.strip() 53 | if body.find(",") != -1: 54 | bs = body.split(",") 55 | if bs is not None: 56 | if len(bs) >= 4: 57 | self.loginUrl = bs[1][:-4] 58 | self.uploadUrl = bs[2][:-4] 59 | self.codeUrl = bs[3][:-4] 60 | flag = True 61 | except Exception, e: 62 | print "Error:can't initialise api" 63 | print e 64 | return flag 65 | 66 | def login(self): 67 | flag = True 68 | if self.loginUrl is not None: 69 | try: 70 | mac = uuid.UUID(int=uuid.getnode()).hex[-12:] 71 | self.params.append( 72 | ('KEY', self.md5(self.softKey.upper() + self.user.upper()) + mac)) 73 | self.opener.addheaders = self.params 74 | url = "http://" + self.loginUrl 75 | url += "/Upload/Login.aspx?U=%s&p=%s" % ( 76 | self.user, self.md5(self.pwd)) 77 | try: 78 | response = self.opener.open(url, None, 60) 79 | if response.code == 200: 80 | body = response.read() 81 | if body is not None: 82 | if body.find("-") > 0: 83 | us = body.split("_") 84 | self.uid = us[0] 85 | self.uKey = body.strip() 86 | print '登录成功,用户ID是:', self.uid 87 | flag = True 88 | else: 89 | print '登录失败,错误代码是:', body 90 | flag = False 91 | except Exception, e: 92 | print "Error:Login Request" 93 | print e 94 | except Exception, e: 95 | print "Error:Login Params " 96 | print e 97 | return flag 98 | 99 | def upload(self, fileAddress=None, filemem=None): 100 | code = None 101 | if self.uKey is not None: 102 | try: 103 | image = open(fileAddress, 'rb') 104 | self.SKey = self.md5((self.uKey + self.softId + self.softKey).lower()) 105 | data = {'KEY': self.uKey, 106 | "SID": self.softId, 107 | 'SKey': self.md5((self.uKey + self.softId + self.softKey).lower()), 108 | 'Type': self.codeType} # codetype及价格表详见.http://www.uuwise.com/price.html 109 | url = "http://" + self.uploadUrl + "/Upload/Processing.aspx" 110 | req = requests.post( 111 | url, data=data, files={'IMG': image}, timeout=60) 112 | if req.status_code == 200: 113 | body = req.text 114 | if body is not None: 115 | print '图片已上传,返回验证码ID:', body 116 | self.capID = body 117 | code = self.result(str(body)) 118 | print '正在获取识别结果...' 119 | while (code == '-3'): 120 | sys.stdout.write('.') 121 | time.sleep(1) 122 | code = self.result(str(body)) 123 | except Exception, e: 124 | print e 125 | return code 126 | 127 | def result(self, body): 128 | code = None 129 | params = {'KEY': self.uKey, 'ID': body, 'random': ( 130 | str(random.randint(1, 1000000)) + str(int(time.time())))} 131 | url = "http://" + self.codeUrl + \ 132 | "/Upload/GetResult.aspx?" + urllib.urlencode(params) 133 | response = self.opener.open(url, None, 30) 134 | if response.code == 200: 135 | code = response.read() 136 | return code 137 | 138 | def initHeader(self): 139 | ps = None 140 | try: 141 | ps = [('SID', self.softId), 142 | ('HASH', self.md5(self.softId + self.softKey.upper())), 143 | ('UUVersion', self.version), 144 | ('UID', self.uid), 145 | ('User-Agent', self.md5(self.softKey.upper() + self.uid))] 146 | except Exception, e: 147 | print "Error: can't make http header" 148 | print e 149 | return ps 150 | 151 | def md5(self, key): 152 | return hashlib.md5(key.encode(encoding='utf-8')).hexdigest() 153 | 154 | def uu_captcha(self, pic): 155 | if self.initialise(): 156 | if self.login(): 157 | code = self.upload(pic) 158 | return code 159 | raise ValueError 160 | 161 | def fail_requests(self): 162 | failUrl = 'http://result.abc.com/Upload/ReportError.aspx?KEY=%s&ID=%s&SID=%s&SKEY=%s' % (self.uKey, self.capID, self.softId, self.SKey) 163 | print failUrl 164 | response = self.opener.open(failUrl) 165 | result = response.read() 166 | return result 167 | -------------------------------------------------------------------------------- /PC_Order.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import inspect 3 | import logging 4 | import os 5 | import sys 6 | import time 7 | import subprocess 8 | import json 9 | import codecs 10 | import re 11 | import requests 12 | 13 | from PySide.QtCore import * 14 | from PySide.QtGui import * 15 | from selenium import webdriver 16 | from selenium.common.exceptions import NoSuchElementException 17 | from selenium.webdriver.common.desired_capabilities import DesiredCapabilities 18 | from PIL import Image 19 | from captcha_handler import Ucode 20 | from bs4 import BeautifulSoup 21 | from urllib import quote 22 | 23 | logging.basicConfig(level=logging.DEBUG, 24 | format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', 25 | datefmt='%a, %d %b %Y %H:%M:%S', 26 | filename='amazon_order.log', 27 | filemode='w') 28 | # 定义一个StreamHandler,将INFO级别或更高的日志信息打印到标准错误,并将其添加到当前的日志处理对象# 29 | console = logging.StreamHandler() 30 | console.setLevel(logging.INFO) 31 | # formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') 32 | # console.setFormatter(formatter) 33 | logging.getLogger('').addHandler(console) 34 | 35 | reload(sys) 36 | sys.setdefaultencoding('utf8') 37 | module_ctx = dict() 38 | thread_list = list() 39 | thread_pools = list() 40 | url = "https://www.amazon.cn/ap/signin?_encoding=UTF8&openid.assoc_handle=cnflex&openid.claimed_id=\ 41 | http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=\ 42 | http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=\ 43 | http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.ns.pape=\ 44 | http%3A%2F%2Fspecs.openid.net%2Fextensions%2Fpape%2F1.0&openid.pape.max_auth_age=0&openid.return_to=\ 45 | https%3A%2F%2Fwww.amazon.cn%2F479-6640618-5803144%3Fref_%3Dnav_signin" 46 | phantomjs_path = r'/usr/local/bin/phantomjs' 47 | dcap = dict(DesiredCapabilities.PHANTOMJS) 48 | ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:46.0) Gecko/20100101 Firefox/46.0" 49 | dcap["phantomjs.page.settings.userAgent"] = (ua) 50 | default_thread_num = 1 51 | current_f_list = os.listdir(os.path.dirname(os.path.abspath('__file__'))) 52 | loop_count = 0 53 | 54 | 55 | def lineno(): 56 | """Returns the current line number in our program.""" 57 | return inspect.currentframe().f_back.f_lineno 58 | 59 | 60 | def csv_processing(filename): 61 | with open(filename) as f: 62 | base_info = f.read().decode('gb18030`').encode('utf-8') 63 | pre_user_info_list = [i.split(',') for i in base_info.split('\n') if i] 64 | user_info_list = list() 65 | for u in pre_user_info_list: 66 | [u.insert(1, '') for r in range(2)] 67 | user_info_list.append(u) 68 | return user_info_list 69 | 70 | 71 | class OrderSg(QObject): 72 | selected = Signal(dict) 73 | 74 | def __init__(self): 75 | QObject.__init__(self) 76 | 77 | 78 | class OrderThread(QRunnable): 79 | """ 80 | This for Order Thread 81 | """ 82 | 83 | def __init__(self, row_index, user_row, tbrowser): 84 | """ 85 | """ 86 | super(OrderThread, self).__init__() 87 | self.emitter = QObject() 88 | self.sg = OrderSg() 89 | self.name = 'i_order_thread_{}'.format(row_index) 90 | self.exiting = True 91 | self.row_index = row_index 92 | self.user_row = user_row 93 | self.tbrowser = tbrowser 94 | 95 | def run(self): 96 | """ 97 | """ 98 | print("Starting{}".format(self.name)) 99 | ctx = {'row_index': self.row_index, 'tbrowser': self.tbrowser, 100 | 'user_row': self.user_row, 'current_thread': self} 101 | if not self.exiting: 102 | mo = MakeOrder(ctx) 103 | mo.make_order() 104 | 105 | 106 | class MakeOrder(object): 107 | """ 108 | """ 109 | 110 | def __init__(self, data): 111 | """ 112 | It Support for to use two kind of browsers which is phantomjs and firefox respectively. 113 | :param data: 114 | """ 115 | # self.driver = webdriver.PhantomJS( 116 | # executable_path=phantomjs_path, desired_capabilities=dcap) 117 | self.driver = webdriver.Firefox() 118 | self.data = data 119 | 120 | def check_exists_by_id(self, ele_id): 121 | """ 122 | """ 123 | try: 124 | self.driver.find_element_by_id(ele_id) 125 | except NoSuchElementException: 126 | return False 127 | return True 128 | 129 | def make_order(self): 130 | """ 131 | """ 132 | i = self.data['row_index'] 133 | t = self.data['tbrowser'] 134 | u = self.data['user_row'] 135 | username = u[0] 136 | password = u[3] 137 | pid = u[4] 138 | print(lineno(), pid) 139 | count = u[5] 140 | province = u[6] 141 | city = u[7] 142 | district = u[8] 143 | detail_addr = u[9] 144 | post_code = u[10] 145 | recipient = u[11] 146 | phone = u[12] 147 | print(username, password) 148 | self.driver.set_window_size(1366, 768) 149 | self.driver.get(url) 150 | self.driver.save_screenshot('login_screen.png') 151 | time.sleep(1) 152 | print(lineno(), username) 153 | print(lineno(), password) 154 | t.printlog(i, "开始登录") 155 | print('login, start') 156 | user = self.driver.find_element_by_id('ap_email') 157 | pwd = self.driver.find_element_by_id('ap_password') 158 | self.driver.save_screenshot('before_login_success.png') 159 | user.send_keys(username) 160 | time.sleep(1) 161 | pwd.send_keys(password) 162 | time.sleep(1) 163 | if self.check_exists_by_id("auth-warning-message-box"): 164 | t.printlog(i, '出现验证码') 165 | captcha = self.driver.find_element_by_id( 166 | 'auth-captcha-image-container') 167 | location = captcha.location 168 | size = captcha.size 169 | self.driver.save_screenshot('screenshot.png') 170 | im = Image.open('screenshot.png') 171 | left = location['x'] 172 | top = location['y'] 173 | right = location['x'] + size['width'] 174 | bottom = location['y'] + size['height'] 175 | im = im.crop((left, top, right, bottom)) 176 | im.save('screen_captcha.png') 177 | uu = Ucode(user="zsqboy", pwd="gih7c00") 178 | t.printlog(i, '等待远程服务器返回解析结果') 179 | resoluted_captcha = uu.uu_captcha('screen_captcha.png') 180 | if resoluted_captcha: 181 | enter_captcha = self.driver.find_element_by_id( 182 | 'auth-captcha-guess') 183 | enter_captcha.send_keys(resoluted_captcha) 184 | t.printlog(i, '开始点击登陆按钮') 185 | self.driver.find_element_by_id("signInSubmit").click() 186 | t.printlog(i, '登录成功') 187 | else: 188 | t.printlog(i, '验证码解析失败') 189 | else: 190 | t.printlog(i, '无验证码登录') 191 | self.driver.find_element_by_id("signInSubmit").click() 192 | if self.check_exists_by_id("auth-captcha-image-container"): 193 | t.printlog(i, '点击登陆按钮后出现验证码') 194 | captcha = self.driver.find_element_by_id( 195 | 'auth-captcha-image-container') 196 | location = captcha.location 197 | size = captcha.size 198 | self.driver.save_screenshot('screenshot.jpg') 199 | im = Image.open('screenshot.jpg') 200 | left = location['x'] 201 | top = location['y'] 202 | right = location['x'] + size['width'] 203 | bottom = location['y'] + size['height'] 204 | im = im.crop((left, top, right, bottom)) 205 | im.save('screen_captcha.jpg') 206 | uu = Ucode(user="zsqboy", pwd="gih7c00") 207 | t.printlog(i, '等待远程服务器返回解析结果') 208 | resoluted_captcha = uu.uu_captcha('screen_captcha.jpg') 209 | print(resoluted_captcha) 210 | t.printlog(i, '{}'.format(resoluted_captcha)) 211 | enter_captcha = self.driver.find_element_by_css_selector( 212 | 'input#auth-captcha-guess') 213 | pwd = self.driver.find_element_by_id('ap_password') 214 | enter_captcha.send_keys(resoluted_captcha) 215 | t.printlog(i, '开始点击登陆按钮') 216 | time.sleep(3) 217 | pwd.send_keys(password) 218 | time.sleep(3) 219 | self.driver.find_element_by_id("signInSubmit").click() 220 | self.driver.save_screenshot('login_success.png') 221 | self.driver.get('https://www.amazon.cn/gp/cart/view.html/ref=nav_cart') 222 | time.sleep(2) 223 | cart_page = self.driver.page_source 224 | bs_cart_page = BeautifulSoup(cart_page, "lxml") 225 | cart_form = bs_cart_page.find( 226 | "form", attrs={'id': 'activeCartViewForm'}) 227 | ts_ele = cart_form.find("input", attrs={'name': 'timeStamp'}) 228 | print(ts_ele) 229 | token_ele = cart_form.find("input", attrs={'name': 'token'}) 230 | req_id_ele = cart_form.find("input", attrs={'name': 'requestID'}) 231 | ts = ts_ele['value'] 232 | token = token_ele['value'] 233 | req_id = req_id_ele['value'] 234 | item_list = cart_form.findAll("div", attrs={'data-itemtype': 'active'}) 235 | item_ids = [itm['data-itemid'] for itm in item_list] 236 | print(lineno(), ts, item_ids) 237 | cookie = [item["name"] + "=" + item["value"] 238 | for item in self.driver.get_cookies()] 239 | cookiestr = ';'.join(item for item in cookie) 240 | 241 | headers = { 242 | 'Host': 'www.amazon.cn', 243 | 'Connection': 'keep-alive', 244 | 'Origin': 'https://www.amazon.cn', 245 | 'X-AUI-View': 'Desktop', 246 | 'User-Agent': (ua), 247 | 'Content-Type': ('application/x-www-form-urlencoded; charset=UTF-8'), 248 | 'Accept': ('application/json, text/javascript, */*; q=0.01'), 249 | 'Referer': 'https://www.amazon.cn/gp/cart/view.html/ref=nav_cart', 250 | 'X-Requested-With': 'XMLHttpRequest', 251 | 'Accept-Encoding': 'gzip, deflate br', 252 | 'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6', 253 | 'Cookie': cookiestr, 254 | } 255 | # construct clean up cart form 256 | form_data = {'timeStamp': ts, 257 | 'token': token, 258 | 'requestID': req_id, 259 | 'loseAddonUpsell': 1, 260 | 'flcExpanded': 1, 261 | 'pageAction': 'delete-active', 262 | } 263 | # clear all cart items 264 | for sid in item_ids: 265 | sd = 'submit.delete.{}'.format(sid) 266 | form_data.update({sd: 1}) 267 | action_id = '{}'.format(sid) 268 | form_data.update({'actionItemID': action_id}) 269 | print(form_data) 270 | cart_ajax_url = 'https://www.amazon.cn/gp/cart/ajax-update.html/ref=ox_sc_cart_delete_1' 271 | requests.post(url=cart_ajax_url, data=form_data, headers=headers) 272 | t.printlog(i, '一次性清空购物车') 273 | time.sleep(2) 274 | try: 275 | ps = self.driver.page_source 276 | session_id = re.findall(r'\d{3}-\d{7}-\d{7}', ps)[0] 277 | except Exception as e: 278 | print(e) 279 | print(lineno(), session_id) 280 | pc = zip(pid.split(';'), count.split(';')) 281 | # add prodct in each loop 282 | for p in pc: 283 | cart_url = ("https://www.amazon.cn/gp/item-dispatch/" 284 | "ref=pd_cart_recs_2_4_atc?ie=UTF8&" 285 | "session-id={0}" 286 | "&quantity.{1}={2}&" 287 | "asin.{1}={1}&" 288 | "item.{1}.asin={1}&" 289 | "discoveredAsins.1={1}&" 290 | "submit.addToCart=%E6%B7%BB%E5%8A%A0%E5%88%B0%E8%B4%AD%E7%89%A9%E8%BD%A6") 291 | f_cart = cart_url.format(session_id, p[0], p[1]) 292 | self.driver.get(f_cart) 293 | time.sleep(1) 294 | time.sleep(1) 295 | t.printlog(i, '全部商品加入购物车') 296 | # Aceess to address selecet page. 297 | self.driver.get('https://www.amazon.cn/gp/cart/view.html/ref=nav_cart') 298 | time.sleep(3) 299 | cart_source_page = self.driver.page_source 300 | bs_ct_page = BeautifulSoup(cart_source_page, "lxml") 301 | get_prefetch = bs_ct_page.find( 302 | 'iframe', attrs={'name': 'checkoutPrefetch;'}) 303 | print(lineno(), get_prefetch) 304 | go_checkout = get_prefetch['src'] 305 | new_c = go_checkout.split('&') 306 | # conconacte payment hall url 307 | self.driver.get('https://www.amazon.cn/gp/cart/desktop/go-to-checkout.html/ref=ox_sc_proceed?proceedToCheckout=Proceed+to+checkout&' + 308 | 'a' + new_c[-1][9:] + '&' + new_c[-2]) 309 | t.printlog(i, '已经进入结算中心') 310 | # Start push address 311 | addr_page = self.driver.page_source 312 | bs_addr_page = BeautifulSoup(addr_page, "lxml") 313 | get_pv = bs_addr_page.find('input', attrs={'name': 'purchaseId'}) 314 | purchase_value = get_pv['value'] 315 | addr_form_data = { 316 | 'hasWorkingJavascript': '1', 317 | '__mk_zh_CN': '亚马逊网站', 318 | 'enterAddressFullName': recipient, 319 | 'enterAddressStateOrRegion': province, 320 | 'enterAddressCity': city, 321 | 'enterAddressDistrictOrCounty': district, 322 | 'enterAddressAddressLine1': detail_addr, 323 | 'enterAddressPostalCode': post_code, 324 | 'enterAddressPhoneNumber': phone, 325 | 'enterAddressCountryCode': 'CN', 326 | 'enterAddressIsDomestic': '1', 327 | 'shipToThisAddress': '配送到此地址', 328 | 'requestToken': '', 329 | 'purchaseId': purchase_value, 330 | 'isBilling': '', 331 | 'numberOfDistinctItems': '4', 332 | } 333 | quote_adr = {x: unicode(y) for x, y in addr_form_data.items()} 334 | addr_ajax_url = 'https://www.amazon.cn/gp/buy/shipaddressselect/handlers/continue.html/ref=ox_shipaddress_add_new_addr?ie=UTF8' 335 | requests.post(addr_ajax_url, data=quote_adr, headers=headers) 336 | t.printlog(i, '已经发送收货地址') 337 | # Fetch Item ID 338 | self.driver.get( 339 | 'https://www.amazon.cn/gp/buy/shipoptionselect/handlers/display.html?hasWorkingJavascript=1') 340 | time.sleep(2) 341 | line_itemids_page = self.driver.page_source 342 | bs_ship_item_page = BeautifulSoup(line_itemids_page, "lxml") 343 | get_ship_itemids = bs_ship_item_page.find('input', atrrs={'name': 'lineItemEntityIds_0'}) 344 | print(lineno(), get_ship_itemids) 345 | ship_item_ids = get_ship_itemids['value'] 346 | t.printlog(i, '获得ItemID') 347 | print(lineno(), ship_item_ids) 348 | # Construct url for place order 349 | self.driver.get( 350 | 'https://www.amazon.cn/gp/buy/spc/handlers/display.html?hasWorkingJavascript=1') 351 | pre_place_order_url = 'https://www.amazon.cn/gp/buy/spc/handlers/static-submit-decoupled.html/ref=ox_spc_place_order?ie=UTF8&hasWorkingJavascript=&pickupType=All&searchCriterion=storeZip&storeZip=330000&storeZip2=&searchLockerFormAction=/gp/buy/storeaddress/handlers/search.html/ref=ox_spc_shipaddr_pickupsearch_popover&claimCode=&primeMembershipTestData=NULL&fasttrackExpiration=34064&countdownThreshold=0&countdownId=0&showSimplifiedCountdown=0&dupOrderCheckArgs={0}|{1}|jijgotisrop|A1AJ19PSB66TGU&dupOrderCheckArgs={0}|{1}|jijgotisrop|A1AJ19PSB66TGU&dupOrderCheckArgs={0}|{1}|jijgotisrop|A1AJ19PSB66TGU&dupOrderCheckArgs={0}|{1}|jijgotisrop|A1AJ19PSB66TGU&order0=std-cn-d2d-mpos-avail&shippingofferingid0.0=AY9S6YNFH5C2J&guaranteetype0.0=NOT_GUARANTEED&issss0.0=1&forceshipsplitpreference0.0=shipWhenComplete&shippingofferingid0.1=A2MV3ZHLIKP2WJ&guaranteetype0.1=GUARANTEED&issss0.1=0&forceshipsplitpreference0.1=shipWhenComplete&shippingofferingid0.2=A3K7TSCNVD2X6M&guaranteetype0.2=NOT_GUARANTEED&issss0.2=0&forceshipsplitpreference0.2=&previousshippingofferingid0=A2MV3ZHLIKP2WJ&previousguaranteetype0=GUARANTEED&previousissss0=0&previousshippriority0=shipWhenComplete&lineitemids0={3}¤tshippingspeed=std-cn-d2d-mpos-avail¤tshipsplitpreference=shipWhenComplete&shippriority.0.shipWhenComplete=shipWhenComplete&order.0.deliveryTimePreference=anyday_cn_71877&groupcount=1&snsUpsellTotalCount=&onmlUpsellSuppressedCount=&vasClaimBasedModel=0&isfirsttimecustomer=0&isTFXEligible=&isFxEnabled=&isFXTncShown=&chinaInvoiceTitle=&isTitleCompany=1&fromAnywhere=0&redirectOnSuccess=0&purchaseTotal=211.2&purchaseTotalCurrency=CNY&purchaseID={2}&useCtb=1&scopeId=T4Q7PXXBBDRQ9D7PCWRK&isQuantityInvariant=&promiseTime-0=1466784000&promiseAsin-0={0}&promiseTime-0=1466784000&promiseAsin-0={0}&promiseTime-0=1466784000&promiseAsin-0={0}&promiseTime-0=1466784000&promiseAsin-0={0}&hasWorkingJavascript=1&placeYourOrder1=1' 352 | for op in pc: 353 | pou = pre_place_order_url.format( 354 | op[0], op[1], purchase_value,) 355 | self.driver.get(pou) 356 | # did_payment = 'https://www.amazon.cn//gp/buy/payselect/handlers/static-continue.html/ref=ox_pay_page_continue' 357 | t.printlog(i, '订单提交成功') 358 | self.driver.close() 359 | t.unchecked(i) 360 | 361 | 362 | class TextBrowser(QWidget): 363 | """ 364 | Generate Table to show file that from filedialog. 365 | """ 366 | 367 | def __init__(self): 368 | """ 369 | """ 370 | super(TextBrowser, self).__init__() 371 | self.table_head = [u'登录名', u'选中', u'运行日志', u'密码', 372 | u'商品编号', u'购买数量', u'省', u'市', u'县/区', u'详细地址', 373 | u'邮编', u'收货人', u'手机号码', 374 | ] 375 | hbox = QHBoxLayout(self) 376 | self.textbr = QTableWidget() 377 | hbox.addWidget(self.textbr) 378 | font = QFont("Courier New", 14) 379 | self.textbr.setFont(font) 380 | self.textbr.resizeColumnsToContents() 381 | self.textbr.setColumnWidth(3, 200) 382 | self.textbr.setColumnWidth(1, 200) 383 | # self.textbr.setEditTriggers(QAbstractItemView.NoEditTriggers) 384 | self.setLayout(hbox) 385 | 386 | def compose_table(self, data): 387 | """ 388 | Construct our table to show a data in the csv file. 389 | :parameter data: a dict. 390 | """ 391 | user_rows = data['user_rows'] 392 | print(lineno(), 'user_row_is: {}'.format(len(user_rows))) 393 | row_count = len(user_rows) 394 | self.textbr.setRowCount(row_count) 395 | self.textbr.setColumnCount(len(self.table_head)) 396 | self.textbr.setHorizontalHeaderLabels(self.table_head) 397 | print(lineno(), 'table column is {}'.format(len(self.table_head))) 398 | for i, u in enumerate(user_rows): 399 | self.textbr.setRowHeight(i, 35) 400 | # print(lineno(), 'per user row coloumn length {}'.format(len(u))) 401 | for indx in range(len(self.table_head)): 402 | item = QTableWidgetItem(unicode(u[indx])) 403 | setattr(self, 'table_item_{}_{}'.format(i, indx), item) 404 | if indx == 1: 405 | item.setCheckState(Qt.Checked) 406 | self.textbr.setItem(i, 1, item) 407 | else: 408 | self.textbr.setItem(i, indx, item) 409 | 410 | def printlog(self, i, s): 411 | """ 412 | This used to print log in function make_order. 413 | """ 414 | self.textbr.setItem(i, 2, QTableWidgetItem(unicode(s))) 415 | 416 | def unchecked(self, i): 417 | """ 418 | To uncheck row in the table. 419 | """ 420 | item = QTableWidgetItem() 421 | item.setCheckState(Qt.Unchecked) 422 | print(i) 423 | self.textbr.setItem(i, 1, item) 424 | 425 | 426 | class MajorTabWindow(QWidget): 427 | """ 428 | Add three tab widget to main tab window 429 | """ 430 | 431 | def __init__(self, parent=None): 432 | super(MajorTabWindow, self).__init__(parent) 433 | 434 | tabwidget = QTabWidget() 435 | tabwidget.setMovable(True) 436 | tabwidget.addTab(FileWidget(), u"文件操作") 437 | tabwidget.addTab(OrderWidget(), u"订单处理") 438 | tabwidget.addTab(SetPanel(), u'参数设置') 439 | tabwidget.setTabEnabled(1, True) 440 | tabwidget.setTabToolTip(0, u'打开Excel文件后,才可以进行下一步操作') 441 | 442 | mainlayout = QGridLayout() 443 | mainlayout.addWidget(tabwidget, 0, 0) 444 | self.setLayout(mainlayout) 445 | 446 | self.setWindowTitle("Major Tab Window") 447 | self.setGeometry(600, 600, 700, 600) 448 | 449 | def closeEvent(self, event): 450 | """ 451 | Show the dialog when user try to close main window. 452 | """ 453 | reply = QMessageBox.question(self, 'Message', 454 | u"确定关闭窗口?", QMessageBox.Yes | 455 | QMessageBox.No, QMessageBox.No) 456 | if reply == QMessageBox.Yes: 457 | try: 458 | subprocess.call( 459 | ['ps -ef | grep phantomjs | grep -v grep | cut -c 6-15']) 460 | except Exception as e: 461 | pass 462 | if QThreadPool.globalInstance().activeThreadCount(): 463 | QThreadPool.globalInstance().waitForDone() 464 | event.accept() 465 | else: 466 | event.ignore() 467 | 468 | 469 | class Captcha(QWidget): 470 | """ 471 | Show the Captcha image at the bottom of left. 472 | """ 473 | 474 | def __init__(self): 475 | super(Captcha, self).__init__() 476 | hbox = QHBoxLayout(self) 477 | pixmap = QPixmap("pysidelogo.png") 478 | lbl = QLabel(self) 479 | lbl.setFrameStyle(QFrame.Panel | QFrame.Sunken) 480 | lbl.setPixmap(pixmap) 481 | hbox.addWidget(lbl) 482 | self.setLayout(hbox) 483 | # self.setGeometry(300, 300, 80, 70) 484 | lbl.setAlignment(Qt.AlignBottom | Qt.AlignRight) 485 | 486 | 487 | class IndexTabButton(QWidget): 488 | """ 489 | To show three buttons in first tabwidget. 490 | """ 491 | 492 | def __init__(self): 493 | super(IndexTabButton, self).__init__() 494 | self.filebutton = QPushButton(u'打开文件') 495 | self.startbutton = QPushButton(u'开始下单') 496 | self.stopbutton = QPushButton(u'停止下单') 497 | self.filebutton.clicked.connect(self.openfiledialog) 498 | self.startbutton.clicked.connect(self.startorder) 499 | self.stopbutton.clicked.connect(self.prestoporder) 500 | self.tbrowser = TextBrowser() 501 | hbox = QHBoxLayout(self) 502 | hbox.addStretch(1) 503 | hbox.addWidget(self.filebutton) 504 | hbox.addWidget(self.startbutton) 505 | hbox.addWidget(self.stopbutton) 506 | 507 | def openfiledialog(self): 508 | """ 509 | Transfer file's name and the user rows that used be in table. 510 | """ 511 | dialog = QFileDialog() 512 | fname, _ = dialog.getOpenFileName(self, 'Open file', '/home') 513 | user_rows = csv_processing(fname) 514 | setattr(self, 'filname', fname) 515 | setattr(self, 'user_rows', user_rows) 516 | self.tbrowser.compose_table( 517 | {'filename': fname, 'user_rows': user_rows}) 518 | print(lineno(), len(user_rows)) 519 | 520 | @staticmethod 521 | def get_thread_num(): 522 | """ 523 | Fetch user config thread number. 524 | """ 525 | if 'user_config.json' in current_f_list: 526 | with open('user_config.json') as raw_f: 527 | f = json.load(raw_f) 528 | user_thread_num = f.get('user_thread_num') 529 | return user_thread_num 530 | else: 531 | return default_thread_num 532 | 533 | def get_checked_rows(self, u_rows): 534 | """ 535 | Through for loop to check a row in the table whether be checked or not. 536 | """ 537 | checked_rows = list() 538 | for i, u in enumerate(u_rows): 539 | ti = getattr(self.tbrowser, 'table_item_{}_1'.format(i)) 540 | if ti.checkState() == Qt.Checked: 541 | checked_rows.append((i, u)) 542 | print(lineno(), len(checked_rows)) 543 | return checked_rows 544 | 545 | def startorder(self): 546 | """ 547 | Before start orderthread we should to check chkitem's checked state. 548 | :return: nothing 549 | """ 550 | user_rows = self.user_rows 551 | print(lineno(), len(user_rows)) 552 | 553 | checked_rows = self.get_checked_rows(u_rows=user_rows) 554 | # print(lineno(), checked_rows) 555 | thread_num = self.get_thread_num() 556 | for i in checked_rows: 557 | print(lineno(), i[0], type(i[0])) 558 | orderthread = OrderThread( 559 | row_index=i[0], user_row=i[1], 560 | tbrowser=self.tbrowser,) 561 | setattr(orderthread, 'exiting', False) 562 | pool_inst = QThreadPool.globalInstance() 563 | pool_inst.setMaxThreadCount(int(thread_num)) 564 | pool_inst.start(orderthread) 565 | # print('%s: Is [Order Thread %s] Alive?' % 566 | # (lineno(), orderthread.name)) 567 | 568 | def prestoporder(self): 569 | """ 570 | """ 571 | self.orderthread.sg.selected.connect(self.stoporder) 572 | 573 | def stoporder(self): 574 | """ 575 | """ 576 | self.orderthread.quit() 577 | 578 | 579 | class FileWidget(QWidget): 580 | """ 581 | """ 582 | 583 | def __init__(self): 584 | super(FileWidget, self).__init__() 585 | gridlayout = QGridLayout() 586 | ibtn = IndexTabButton() 587 | gridlayout.addWidget(ibtn.tbrowser, 1, 0) 588 | gridlayout.addWidget(ibtn, 2, 0) 589 | gridlayout.addWidget(Captcha(), 3, 0) 590 | self.setLayout(gridlayout) 591 | 592 | 593 | class OrderWidget(QWidget): 594 | """ 595 | """ 596 | 597 | def __init__(self): 598 | super(OrderWidget, self).__init__() 599 | 600 | 601 | class SetPanel(QWidget): 602 | """ 603 | This Class used to save user specific settings. 604 | """ 605 | 606 | def __init__(self): 607 | """ 608 | """ 609 | super(SetPanel, self).__init__() 610 | self.threadCountLabel = QLabel(u"使用线程数:") 611 | self.threadCountEdit = QLineEdit() 612 | try: 613 | assert 'user_config.json' in current_f_list 614 | with open('user_config.json') as raw_f: 615 | f = json.load(raw_f) 616 | user_thread_num = f.get('user_thread_num') 617 | self.threadCountEdit.setText(unicode(user_thread_num)) 618 | except Exception as e: 619 | self.threadCountEdit.setText(unicode(default_thread_num)) 620 | self.savebutton = QPushButton(u'保存') 621 | self.unlockbutton = QPushButton(u'解锁') 622 | self.resetbutton = QPushButton(u'恢复初始设置参数') 623 | self.threadCountLabel.setFixedSize(90, 35) 624 | formlayout = QFormLayout() 625 | formlayout.setSpacing(10) 626 | formlayout.addWidget(self.threadCountLabel) 627 | formlayout.addWidget(self.threadCountEdit, ) 628 | formlayout.addWidget(self.unlockbutton) 629 | formlayout.addWidget(self.savebutton) 630 | self.savebutton.clicked.connect(self.pass_count) 631 | self.unlockbutton.clicked.connect(self.unlock_edit) 632 | self.threadCountEdit.setEnabled(False) 633 | self.setToolTip(u'点击解锁后启动编辑功能') 634 | self.setLayout(formlayout) 635 | 636 | def pass_count(self): 637 | try: 638 | user_thread_num = self.threadCountEdit.text() 639 | with codecs.open('user_config.json', 'w', 'utf8') as f: 640 | f.write(json.dumps({'user_thread_num': user_thread_num}, 641 | sort_keys=True, ensure_ascii=False)) 642 | self.threadCountEdit.setEnabled(False) 643 | except Exception as e: 644 | raise e 645 | else: 646 | pass 647 | finally: 648 | pass 649 | 650 | def unlock_edit(self): 651 | self.threadCountEdit.setEnabled(True) 652 | 653 | 654 | if __name__ == '__main__': 655 | app = QApplication(sys.argv) 656 | tabdialog = MajorTabWindow() 657 | tabdialog.show() 658 | sys.exit(app.exec_()) 659 | --------------------------------------------------------------------------------