|
| 1 | +import re |
| 2 | + |
| 3 | +import requests |
| 4 | +import random |
| 5 | +import time |
| 6 | +import os |
| 7 | +import json |
| 8 | + |
| 9 | +from PIL import Image |
| 10 | + |
| 11 | +user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36' |
| 12 | + |
| 13 | +session = requests.session() |
| 14 | + |
| 15 | + |
| 16 | +def show_QRcode(): |
| 17 | + url = 'https://qr.m.jd.com/show' |
| 18 | + params = { |
| 19 | + 'appid': 133, |
| 20 | + 'size': 147, |
| 21 | + 't': str(int(time.time() * 1000)), |
| 22 | + } |
| 23 | + headers = { |
| 24 | + 'User-Agent': user_agent, |
| 25 | + 'Referer': 'https://passport.jd.com/new/login.aspx', |
| 26 | + } |
| 27 | + resp = session.get(url=url, headers=headers, params=params) |
| 28 | + |
| 29 | + QRcode_path = 'QRcode.png' |
| 30 | + with open(QRcode_path, 'wb') as f: |
| 31 | + for chunk in resp.iter_content(chunk_size=1024): |
| 32 | + f.write(chunk) |
| 33 | + |
| 34 | + QRcode = Image.open(QRcode_path) |
| 35 | + QRcode.show() |
| 36 | + |
| 37 | +def check_QRcode(): |
| 38 | + |
| 39 | + url = 'https://qr.m.jd.com/check' |
| 40 | + params = { |
| 41 | + 'appid': '133', |
| 42 | + 'callback': 'jQuery{}'.format(random.randint(1000000, 9999999)), |
| 43 | + 'token': session.cookies.get('wlfstk_smdl'), |
| 44 | + '_': str(int(time.time() * 1000)), |
| 45 | + } |
| 46 | + headers = { |
| 47 | + 'User-Agent': user_agent, |
| 48 | + 'Referer': 'https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fwww.jd.com%2F', |
| 49 | + } |
| 50 | + resp = session.get(url=url, headers=headers, params=params) |
| 51 | + resp_json = parse_json(resp.text) |
| 52 | + |
| 53 | + if 'ticket' in resp_json: |
| 54 | + return resp_json['ticket'] |
| 55 | + else: |
| 56 | + print(resp_json['msg']) |
| 57 | + print('请刷新JD登录二维码!') |
| 58 | + os._exit(0) |
| 59 | + |
| 60 | + |
| 61 | +def validation_QRcode(ticket): |
| 62 | + |
| 63 | + url = 'https://passport.jd.com/uc/qrCodeTicketValidation' |
| 64 | + headers = { |
| 65 | + 'User-Agent': user_agent, |
| 66 | + 'Referer': 'https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fwww.jd.com%2F', |
| 67 | + } |
| 68 | + params={ |
| 69 | + 't': ticket |
| 70 | + } |
| 71 | + session.get(url=url, headers=headers, params=params) |
| 72 | + |
| 73 | + |
| 74 | +def parse_json(str): |
| 75 | + try: |
| 76 | + return json.loads(str[str.find('{'):str.rfind('}') + 1]) |
| 77 | + except: |
| 78 | + str = str.replace('jQuery{}(','') |
| 79 | + return json.loads(str[str.find('{'):str.rfind('}') + 1]) |
| 80 | + |
| 81 | +def get_pin(): |
| 82 | + """获取 PIN,用正则表达式从页面中取出""" |
| 83 | + url = "https://pcsitepp-fm.jd.com/" |
| 84 | + r = session.get(url) |
| 85 | + loginPin = re.findall('<input type="hidden" id="loginPin" value="(\w+)" />', r.text) |
| 86 | + pin = loginPin[0] if len(loginPin) > 0 else None |
| 87 | + return pin |
| 88 | + |
| 89 | +def skuProResultPC(orderId, skuId, pin): |
| 90 | + """判断订单是否保价超时""" |
| 91 | + url = "https://sitepp-fm.jd.com/rest/webserver/skuProResultPC" |
| 92 | + data = { |
| 93 | + "orderId": orderId, |
| 94 | + "skuId": skuId, |
| 95 | + "pin": pin |
| 96 | + } |
| 97 | + headers = { |
| 98 | + 'User-Agent': user_agent, |
| 99 | + 'Referer': 'https://pcsitepp-fm.jd.com/', |
| 100 | + } |
| 101 | + |
| 102 | + r = session.post(url, data=data, headers=headers) |
| 103 | + return 'overTime' not in r.text |
| 104 | + |
| 105 | +def get_order_list(pin, page_num=1): |
| 106 | + """保价列表""" |
| 107 | + |
| 108 | + # 存放订单信息 |
| 109 | + order_info = [] |
| 110 | + # 存放数量 |
| 111 | + count_dir = {} |
| 112 | + |
| 113 | + url = "https://pcsitepp-fm.jd.com/rest/pricepro/priceskusPull" |
| 114 | + data = {"page": page_num, "pageSize": 10} |
| 115 | + headers = { |
| 116 | + 'User-Agent': user_agent, |
| 117 | + 'Referer': 'https://pcsitepp-fm.jd.com/', |
| 118 | + } |
| 119 | + r = session.post(url, headers= headers, data=data) |
| 120 | + |
| 121 | + # 订单之间的分隔符 |
| 122 | + orders = r.text.split('<tr class="sep-row"><td colspan="6"></td></tr>') |
| 123 | + orders.pop(0) |
| 124 | + |
| 125 | + for item in orders: |
| 126 | + # 订单号 |
| 127 | + orderid = re.findall("订单号:(\d+)", item) |
| 128 | + # 数量 |
| 129 | + count_html = re.findall('<span class="count">([\sx\d]+)</span>',item) |
| 130 | + # 商品的 sku和序号 |
| 131 | + skuidAndSequences = re.findall("queryOrderSkuPriceParam\.skuidAndSequence\.push\(\"(\d+,円\d+)\"\)\;", item) |
| 132 | + newSkuidAndSequences = [] |
| 133 | + |
| 134 | + # 商品的sku和订单商品的序号 |
| 135 | + for ss in skuidAndSequences: |
| 136 | + |
| 137 | + # 判断订单保价是否超时 |
| 138 | + if skuProResultPC(orderid[0], ss.split(',')[0], pin): |
| 139 | + |
| 140 | + newSkuidAndSequences.append(ss) |
| 141 | + count_ss = count_html[int(ss.split(',')[1]) - 1] |
| 142 | + count = count_ss.replace('\t', '').replace('\n', '').replace('x', '') |
| 143 | + # 把 "订单号_sku" 当做 key |
| 144 | + count_dir[orderid[0] + '_' + ss.split(',')[0]] = count |
| 145 | + |
| 146 | + if newSkuidAndSequences: |
| 147 | + |
| 148 | + order_info.append({'orderid': orderid[0], 'skuidAndSequence': newSkuidAndSequences}) |
| 149 | + |
| 150 | + if orders: |
| 151 | + """递归的方式获取所有的商品""" |
| 152 | + bill_info_sub, count_dir_sub = get_order_list(pin, page_num + 1) |
| 153 | + order_info.extend(bill_info_sub) |
| 154 | + count_dir.update(count_dir_sub) |
| 155 | + return order_info, count_dir |
| 156 | + |
| 157 | +def get_price_list(pin): |
| 158 | + '''获取下单价格、商品信息、当前价格、数量''' |
| 159 | + |
| 160 | + product_list = [] |
| 161 | + |
| 162 | + # 取订单号,sku和商品数量 |
| 163 | + queryOrderPriceParam,count_dir = get_order_list(pin) |
| 164 | + |
| 165 | + # 获取购买时的价格 |
| 166 | + params = {"queryOrderPriceParam": json.dumps(queryOrderPriceParam)} |
| 167 | + r = session.post("https://sitepp-fm.jd.com/rest/webserver/getOrderListSkuPrice", data = params) |
| 168 | + orderList = r.json() |
| 169 | + |
| 170 | + |
| 171 | + for item in orderList: |
| 172 | + |
| 173 | + skuid = item.get("skuid") |
| 174 | + buyingjdprice = item.get("buyingjdprice") |
| 175 | + orderid = item.get("orderid") |
| 176 | + |
| 177 | + # 商品信息 |
| 178 | + product_info = get_product_info(skuid) |
| 179 | + # 当前价格 |
| 180 | + price = get_product_price(product_info) |
| 181 | + # 优惠券 |
| 182 | + coupon = get_product_coupon(product_info, price) |
| 183 | + |
| 184 | + name = product_info['name'] |
| 185 | + count = count_dir[orderid + '_' + skuid] |
| 186 | + product_list.append({'orderid': orderid, 'name': name, 'price': price, 'coupon': coupon, 'count': count, 'buyingjdprice': buyingjdprice}) |
| 187 | + return product_list |
| 188 | + |
| 189 | +def protect_protect_apply(product_list): |
| 190 | + """申请价格保护""" |
| 191 | + |
| 192 | + if len(product_list) == 0: |
| 193 | + return |
| 194 | + else: |
| 195 | + for item in product_list: |
| 196 | + result = '订单号:{},名称:{}, 数量:{}, 购买价格:{}, 当前价格:{}, 当前优惠:{}。'\ |
| 197 | + .format(item['orderid'], |
| 198 | + item['name'], |
| 199 | + item['count'], |
| 200 | + item['buyingjdprice'], |
| 201 | + item['price'], |
| 202 | + ' | '.join(item['coupon'])) |
| 203 | + |
| 204 | + # 没有优惠券并且购买价格高于当前价格 |
| 205 | + if len(item['coupon']) == 0 and item['buyingjdprice'] > item['price']: |
| 206 | + |
| 207 | + url = 'https://pcsitepp-fm.jd.com//rest/pricepro/skuProtectApply' |
| 208 | + data = { |
| 209 | + "orderId": item['orderId'], |
| 210 | + "orderCategory": "Others", |
| 211 | + "skuId": item['skuId'], |
| 212 | + "refundtype": 1 |
| 213 | + } |
| 214 | + |
| 215 | + headers = { |
| 216 | + 'User-Agent': user_agent, |
| 217 | + 'Referer': 'https://pcsitepp-fm.jd.com/', |
| 218 | + 'accept': 'application/json, text/javascript, */*; q=0.01' |
| 219 | + } |
| 220 | + session.post(url, data=data, headers=headers) |
| 221 | + print(result + ' 已申请价格保护,请结果查看价格保护页面') |
| 222 | + |
| 223 | + elif len(item['coupon']) > 0: |
| 224 | + print(result + ' 在优惠券未申请自动价格保护,请联系客服申请') |
| 225 | + return |
| 226 | + |
| 227 | + |
| 228 | + |
| 229 | + |
| 230 | +def get_product_price(project_info): |
| 231 | + |
| 232 | + url = "https://c0.3.cn/stock?skuId={}&area={}&venderId={}&buyNum=1&choseSuitSkuIds=&cat={}&extraParam={{%22originid%22:%221%22}}&fqsp=0&ch=1&callback=jQuery{}"\ |
| 233 | + .format(project_info['skuId'], |
| 234 | + project_info['area'], |
| 235 | + project_info['venderId'], |
| 236 | + project_info.get('cat', ''), |
| 237 | + random.randint(1000000, 9999999)) |
| 238 | + headers = { |
| 239 | + 'User-Agent': user_agent, |
| 240 | + 'Host': 'c0.3.cn', |
| 241 | + 'Referer': 'https://item.jd.com/{0}.html'.format(project_info['skuId']), |
| 242 | + } |
| 243 | + r = session.get(url, headers=headers) |
| 244 | + data = parse_json(r.text) |
| 245 | + # 价格 |
| 246 | + price = data.get("stock", {}).get("jdPrice", {}).get('p', 0) |
| 247 | + return float(price) |
| 248 | + |
| 249 | +def get_product_info(skuId): |
| 250 | + """获商品信息""" |
| 251 | + info = {} |
| 252 | + url = "http://item.jd.com/%s.html" % skuId |
| 253 | + headers = { |
| 254 | + 'User-Agent': user_agent, |
| 255 | + 'Referer': 'https://pcsitepp-fm.jd.com/', |
| 256 | + } |
| 257 | + r = requests.get(url, headers=headers) |
| 258 | + pageConfig = re.findall("var pageConfig = \{([\s\S]+)\} catch\(e\) \{\}", r.text) |
| 259 | + cat = re.findall("cat: \[([\d,]+)\]", pageConfig[0]) |
| 260 | + venderId = re.findall("venderId:(\d+)", pageConfig[0]) |
| 261 | + shopId = re.findall("shopId:'(\d+)'", pageConfig[0]) |
| 262 | + name = re.findall("name: '(.+)'", pageConfig[0]) |
| 263 | + info['cat'] = cat[0] if len(cat) else "" |
| 264 | + info['venderId'] = venderId[0] if len(venderId) else "" |
| 265 | + info['shopId'] = shopId[0] if len(shopId) else "" |
| 266 | + info['skuId'] = skuId |
| 267 | + # 配送区域默认为北京 |
| 268 | + info['area'] = '1_72_55653_0' |
| 269 | + info['name'] = name[0] |
| 270 | + return info |
| 271 | + |
| 272 | +def get_product_coupon(product_info, price): |
| 273 | + """优惠券列表""" |
| 274 | + result = [] |
| 275 | + headers = { |
| 276 | + 'User-Agent': user_agent, |
| 277 | + 'Referer': 'https://item.jd.com/{0}.html'.format(product_info['skuId']), |
| 278 | + } |
| 279 | + url = 'https://cd.jd.com/promotion/v2?callback=jQuery{}&skuId={}&area={}&shopId={}&venderId={}&cat={}&isCanUseDQ=1&isCanUseJQ=1&platform=0&orgType=2&jdPrice={}&appid=1&_={}'\ |
| 280 | + .format( |
| 281 | + str(random.randint(1000000, 9999999)), |
| 282 | + product_info['skuId'], |
| 283 | + product_info['area'], |
| 284 | + product_info['shopId'], |
| 285 | + product_info['venderId'], |
| 286 | + product_info['cat'].replace(',', '%2C'), |
| 287 | + price, |
| 288 | + str(int(time.time() * 1000))) |
| 289 | + r = session.get(url, headers=headers) |
| 290 | + data = parse_json(r.text) |
| 291 | + pickOneTag = data.get("prom", {}).get("pickOneTag") |
| 292 | + |
| 293 | + # 满减 |
| 294 | + if pickOneTag: |
| 295 | + for tag in pickOneTag: |
| 296 | + result.append(tag.get('content')) |
| 297 | + |
| 298 | + # 打折 |
| 299 | + skuCoupon = data.get('skuCoupon') |
| 300 | + if skuCoupon: |
| 301 | + for coupon in skuCoupon: |
| 302 | + if coupon.get('allDesc'): |
| 303 | + result.append(coupon.get('allDesc')) |
| 304 | + elif coupon.get('quota') and coupon.get('discount'): |
| 305 | + result.append("满" + str(coupon.get('quota')) + '减' + str(coupon.get('discount'))) |
| 306 | + return result |
| 307 | + |
| 308 | +if __name__ == '__main__': |
| 309 | + show_QRcode() |
| 310 | + time.sleep(10) |
| 311 | + ticket = check_QRcode() |
| 312 | + validation_QRcode(ticket) |
| 313 | + pin = get_pin() |
| 314 | + product_list = get_price_list(pin) |
| 315 | + protect_protect_apply(product_list) |
| 316 | + print("完成了") |
| 317 | + |
0 commit comments