不是抢票!不是抢票!不是抢票!
杭州亚运会的官方售票网站是 https://ticket.hangzhou2022.cn/
但是似乎没有筛选余票的功能,所有项目都标着可选座但是点进去却没有票,好烦,因此有了这个小项目。
核心判断逻辑是筛选 soldOut为False allowChooseSeat为True的项目存入Excel中
2023年9月27日晚的余票情况,按道理热门票已经没有了,但似乎部分项目有少量票会在比赛开始前几天才放出
Snipaste_2023-09-27_20-53-53.png (103.78 KB, 下载次数: 0)
下载附件
2023-9-27 20:54 上传
使用方法
安装依赖
pip install -r requirements.txt
运行
python ticket_hangzhou.py
运行后会生成 余票情况.xlsx 筛选left_num 大于1的项目即为有余票的项目
不准确的情况
本程序的核心判断逻辑是筛选 soldOut为False allowChooseSeat为True的项目
# 筛选soldOut为False allowChooseSeat为True的项目
if (not event['soldOut']) and event['allowChooseSeat']:
seat_lefts.append(event['eventName'])
对于暂不可售的情况可能存在误判
源码
github地址 https://github.com/skygongque/Spider/tree/master/24-%E6%9D%AD%E5%B7%9E%E4%BA%9A%E8%BF%90%E4%BC%9A%E4%BD%99%E7%A5%A8%E6%9F%A5%E8%AF%A2
ticket_hangzhou.py
import execjs
from functools import partial
import subprocess
import requests
import time
import json
import pandas as pd
import numpy as np
from tqdm import tqdm
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
with open('sign.js','r') as f:
jscode = f.read()
ctx = execjs.compile(jscode)
def get_index(page):
params = {
'sortType' :'2',
'page': str(page),
'pageSize' :'36',
'langType': '1'
}
url = 'https://gtpapi.hangzhou2022.cn/rest/guide/project/list/queryList' # sortType=2&page=2&pageSize=36&langType=1
ts,sign = get_ts_sign(params)
payload = {}
headers = {
'authority': 'gtpapi.hangzhou2022.cn',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
# 'cookie': 'cna=l/mlG/R1Q10CAXrugBH3OgNY; xlly_s=1; _trs_uv=lmzusca5_4982_edl7; Hm_lvt_9095c4973fefa1f89e883fab90d1ff1e=1695704684; Hm_lpvt_9095c4973fefa1f89e883fab90d1ff1e=1695704684; MZCONSUMERJSESSIONID17481=TICKETMZGTP0ac9530cad2040be80dba6b70a509337; XSRF-TOKEN=9190242d-76f0-4e26-9c9f-a0b52a55225e; isg=BKKiVM-kM-_9-C7MxI7PYLO98ygE86YNwPmUmuwbgZRsvzk51IKgHYR56_tDrx6l',
'origin': 'https://ticket.hangzhou2022.cn',
'referer': 'https://ticket.hangzhou2022.cn/',
'sign': sign,
'site': 'pc',
'siteversion': 'standard',
'timestamp': str(ts),
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
}
response = requests.get(url,params=params, headers=headers, data=payload)
response.raise_for_status
return response.json()
def parse_index(res_json):
dataList = res_json['data']['projectPager']['dataList']
projectId_list = [item['projectId'] for item in dataList]
return projectId_list
def get_detail(params):
url = "https://gtpapi.hangzhou2022.cn/rest/guide/project/detail/query" # ?projectId=216990009&langType=1
ts,sign = get_ts_sign(params)
payload = {}
headers = {
'authority': 'gtpapi.hangzhou2022.cn',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
# 'cookie': 'cna=l/mlG/R1Q10CAXrugBH3OgNY; xlly_s=1; _trs_uv=lmzusca5_4982_edl7; Hm_lvt_9095c4973fefa1f89e883fab90d1ff1e=1695704684; Hm_lpvt_9095c4973fefa1f89e883fab90d1ff1e=1695704684; MZCONSUMERJSESSIONID17481=TICKETMZGTP0ac9530cad2040be80dba6b70a509337; XSRF-TOKEN=9190242d-76f0-4e26-9c9f-a0b52a55225e; isg=BKKiVM-kM-_9-C7MxI7PYLO98ygE86YNwPmUmuwbgZRsvzk51IKgHYR56_tDrx6l',
'origin': 'https://ticket.hangzhou2022.cn',
'referer': 'https://ticket.hangzhou2022.cn/',
'sign': sign,
'site': 'pc',
'siteversion': 'standard',
'timestamp': str(ts),
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36'
}
response = requests.get(url,params=params, headers=headers, data=payload)
response.raise_for_status
return response.json()
def parse(res_json):
data = res_json['data']
projectId = data['projectId']
projectName = data['projectName']
# print(projectId,projectName)
seat_lefts = []
eventList = data['eventList']
for event in eventList:
# print(event['allowChooseSeat'])
if (not event['soldOut']) and event['allowChooseSeat']: # 筛选soldOut为False allowChooseSeat为True的
seat_lefts.append(event['eventName'])
if len(seat_lefts) > 0:
return {
'projectId':projectId,
'projectName':projectName,
'left_num':len(seat_lefts),
'seat_lefts':seat_lefts
}
def get_ts_sign(u):
""" 签名 """
u = json.dumps(u)
ts = int(time.time() * 1000) -91
sign = ctx.call('get_sign',ts,u)
return ts,sign
def main():
# res_json = get_detail({'projectId':'216707002','langType':'1'})
# left_info = parse(res_json)
print('正在进行爬取请耐心等待....')
all_projectId_list = []
result = []
for page in range(1,4): # 索引页一共就三页
res_index = get_index(page)
projectId_list = parse_index(res_index)
all_projectId_list += projectId_list
all_projectId_list = list(set(all_projectId_list))
print(all_projectId_list)
for projectId in tqdm(all_projectId_list):
res_json = get_detail({'projectId':str(projectId),'langType':'1'})
left_info = parse(res_json)
if left_info:
result.append(left_info)
time.sleep(1)
# break
df = pd.DataFrame(result)
df.to_excel('余票情况.xlsx')
print('余票情况已存入 余票情况.xlsx')
if __name__ == "__main__":
main()
sign.js
function h(t) {
function e(t) {
function e(t, e) {
return t >> 32 - e
}
function r(t, e) {
var r, n, o, i, a;
return o = 2147483648 & t,
i = 2147483648 & e,
r = 1073741824 & t,
n = 1073741824 & e,
a = (1073741823 & t) + (1073741823 & e),
r & n ? 2147483648 ^ a ^ o ^ i : r | n ? 1073741824 & a ? 3221225472 ^ a ^ o ^ i : 1073741824 ^ a ^ o ^ i : a ^ o ^ i
}
function n(t, e, r) {
return t & e | ~t & r
}
function o(t, e, r) {
return t & r | e & ~r
}
function i(t, e, r) {
return t ^ e ^ r
}
function a(t, e, r) {
return e ^ (t | ~r)
}
function c(t, o, i, a, c, u, s) {
return t = r(t, r(r(n(o, i, a), c), s)),
r(e(t, u), o)
}
function u(t, n, i, a, c, u, s) {
return t = r(t, r(r(o(n, i, a), c), s)),
r(e(t, u), n)
}
function s(t, n, o, a, c, u, s) {
return t = r(t, r(r(i(n, o, a), c), s)),
r(e(t, u), n)
}
function l(t, n, o, i, c, u, s) {
return t = r(t, r(r(a(n, o, i), c), s)),
r(e(t, u), n)
}
function p(t) {
var e, r = t.length, n = r + 8, o = (n - n % 64) / 64, i = 16 * (o + 1), a = Array(i - 1), c = 0, u = 0;
while (u >> 29,
a
}
function h(t) {
var e, r, n = "", o = "";
for (r = 0; r >> 8 * r & 255,
o = "0".concat(e.toString(16)),
n += o.substr(o.length - 2, 2);
return n
}
function f(t) {
t = t.replace(/\r\n/g, "\n");
for (var e = "", r = 0; r 127 && n > 6 | 192),
e += String.fromCharCode(63 & n | 128)) : (e += String.fromCharCode(n >> 12 | 224),
e += String.fromCharCode(n >> 6 & 63 | 128),
e += String.fromCharCode(63 & n | 128))
}
return e
}
var d, v, y, g, m, _, w, b, x, O = [], L = 7, j = 12, E = 17, S = 22, k = 5, P = 9, C = 14, I = 20, N = 4, T = 11, G = 16, F = 23, A = 6, D = 10, M = 15, B = 21;
for (t = f(t),
O = p(t),
_ = 1732584193,
w = 4023233417,
b = 2562383102,
x = 271733878,
d = 0; d -1)
return String(t);
if (null === t)
return "null";
var n = JSON.parse(JSON.stringify(t))
, o = Object.keys(n).sort()
, i = "[object array]" === Object.prototype.toString.call(n).toLowerCase()
, a = i ? [] : {};
return o.forEach((t=>{
if ("[object object]" === Object.prototype.toString.call(n[t]).toLowerCase())
i ? a.push(r(n[t])) : a[t] = r(n[t]);
else if ("[object array]" === Object.prototype.toString.call(n[t]).toLowerCase()) {
var e = n[t].map((t=>r(t)));
i ? a.push(e) : a[t] = e
} else
null === n[t] ? i ? a.push("null") : a[t] = "null" : i ? a.push(n[t].toString()) : a[t] = n[t].toString()
}
)),
a
}
t.data = t.data || {},
"string" === typeof t.data ? t.data = JSON.parse(t.data) : t.data = JSON.parse(JSON.stringify(t.data)),
t.t = t.t || (new Date).getTime().toString();
var n = r(t.data);
return "[object object]" === Object.prototype.toString.call(n).toLowerCase() ? n[t.headerName] = String(t.t) : "[object array]" === Object.prototype.toString.call(n).toLowerCase() ? n.push(String(t.t)) : n += String(t.t),
e(JSON.stringify(n))
}
var f = h;
function get_sign(ts,u){
// var a = Number((new Date).getTime()) + Number(-91);
// querySearchTerm=true&sortType=2&page=1&pageSize=36&langType=1
// var u = {
// "querySearchTerm": true,
// "sortType": 2,
// "page": 1,
// "pageSize": 36,
// "langType": "1"
// }
var a = Number(ts);
u = JSON.parse(u);
var result = f({
data: u,
t: a,
headerName: "timestamp"
});
return result;
}
// console.log(a)
// console.log(result)