1. 概述
闲来无事,做一个微信刷票工具,只是验证了技术思路是否可行。不涉及任何商业利益!
2. 技术预研
微信投票工具很多,有一些是微信官方的,这个可能没办法破解;有一些是需要关注公众号,与微信深度绑定,不容易破解。
剩下一些,要么只获取了微信openid,或者只通过ip做了限制,破解起来简单一些,可以一试。
3. 破解思路
3.1. 确认投票机制
打开手机微信,进入投票页面,下拉可以看到投票网站的地址,如果不是wx的,可以尝试搞一下子
通过电脑chrome浏览器模拟微信浏览器,打开投票页面,确认投票api
确认投票api比较麻烦,需要慢慢摸索,有时候需要看看上下文的请求,有时候可能需要看看js源码,分析投票机制,也需要反复尝试
# 在chrome增加一个device,user-agent设置为wechat浏览器即可
Mozilla/5.0 (Linux; Android 7.0; MI 5s Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 wxwork/2.4.16 MicroMessenger/6.3.22 NetType/WIFI Language/zh
Chrome浏览器模拟微信浏览器
Chrome 修改 user agent 简单模拟微信内置浏览器
3.2. 编写投票脚本
基于#2.1的探索,如果只是基于IP的投票限制,反而简单许多。
可以搞一个代理IP池,通过代理IP池发起投票request,基本可以成功。
代理IP池,依赖免费代理源
,定时抓取page解析得到可用的代理IP
https://github.com/jhao104/proxy_pool
免费的可用率太低,大概1/10,网速也比较慢;做爬虫的话,可以买一个收费的,当前场景够用
- 代码clone下来之后,如何执行
DB_CONN=redis://127.0.0.1:6379/0 sh start.sh
- 也可以通过docker启动
docker run --network host --env DB_CONN=redis://127.0.0.1:6379/0 -p 5010:5010 jhao104/proxy_pool
投票脚本
import requests
from retry import retry
import random
import time
import string
import hashlib
import pickle
import pathlib
def get_user_agent():
android_user_agents = [
'Mozilla/5.0 (Linux; Android 7.0; MI 5s Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/64.0.3282.137 Mobile Safari/537.36 wxwork/2.4.16 MicroMessenger/6.3.22 NetType/WIFI Language/zh',
'Mozilla/5.0 (Linux; Android 9; PCNM00 Build/PKQ1.190630.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045129 Mobile Safari/537.36 MMWEBID/1926 MicroMessenger/7.0.12.1620(0x27000C37) Process/tools NetType/WIFI Language/zh_CN ABI/arm64',
'Mozilla/5.0 (Linux; Android 10; LYA-AL00 Build/HUAWEILYA-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045130 Mobile Safari/537.36 MMWEBID/9034 MicroMessenger/7.0.12.1620(0x27000C36) Process/tools NetType/WIFI Language/zh_CN ABI/arm64',
'Mozilla/5.0 (Linux; Android 9; Redmi Note 8 Pro Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/67.0.3396.87 XWEB/1171 MMWEBSDK/200201 Mobile Safari/537.36 MMWEBID/229 MicroMessenger/7.0.12.1620(0x27000C37) Process/tools NetType/WIFI Language/zh_CN ABI/arm64',
'Mozilla/5.0 (Linux; Android 10; MI 8 Build/QKQ1.190716.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/67.0.3396.87 XWEB/1171 MMWEBSDK/191201 Mobile Safari/537.36 MMWEBID/4454 MicroMessenger/7.0.10.1580(0x27100AFF) Process/toolsmp NetType/WIFI Language/zh_CN ABI/arm32',
'Mozilla/5.0 (Linux; Android 9; BKL-AL20 Build/HUAWEIBKL-AL20; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045130 Mobile Safari/537.36 MMWEBID/7838 MicroMessenger/7.0.11.1600(0x27000B34) Process/tools NetType/4G Language/zh_CN ABI/arm64',
'Mozilla/5.0 (Linux; Android 10; VOG-AL00 Build/HUAWEIVOG-AL00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045130 Mobile Safari/537.36 MMWEBID/2687 MicroMessenger/7.0.10.1580(0x27000AFE) Process/tools NetType/WIFI Language/zh_CN ABI/arm64',
]
return random.choice(android_user_agents)
def get_req_key():
letters = random.sample('abcdefghijklmnopqrstuvwxyz', 4)
digits = random.sample(string.digits, 4)
return ''.join([f'{x}{y}' for x, y in zip(digits, letters)])
def get_hash(key):
hash_str = hashlib.md5()
hash_str.update(key.encode('utf-8'))
return hash_str.hexdigest()
def get_proxy():
return requests.get("http://127.0.0.1:5010/get/").json()
def delete_proxy(proxy):
requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
def pickle_dump(data, file):
with open(file, 'wb') as f:
pickle.dump(data, f)
def pickle_load(file):
with open(file, 'rb') as f:
return pickle.load(f)
@retry(tries=3, delay=1)
def fake_req(proxy):
req_key = get_req_key()
user_agent = get_user_agent()
url = f'http://{req_key}.gd-cj.com/index.php?g=Wap&m=Vote&a=ticket'
headers = {
'Content-Length': '114',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'User-Agent': user_agent,
'Origin': f'http://{req_key}.gd-cj.com',
'Host': f'{req_key}.gd-cj.com',
'Referer': f'http://{req_key}.gd-cj.com/index.php?g=Wap&m=Vote&a=detail&token=Fxea5C5sj3562xdE&id=556&zid=2230',
'X-Requested-With': 'XMLHttpRequest'
}
form_data = dict(
zid='2230', vid='556', token='Fxea5C5sj3562xdE',
__hash__=f'{get_hash(req_key)}_{get_hash(user_agent)}'
)
print(f'proxy: {proxy}')
resp = requests.post(url, headers=headers, data=form_data,
proxies={"http": "http://{}".format(proxy)},
timeout=10)
print(f'response: {resp.status_code} - {resp.content}')
try:
resp_json = resp.json()
return resp_json.get('status') == 108
except:
pass
return False
if __name__ == '__main__':
used_proxy_file = './proxy_list.pkl'
total, success = 0, 0
proxy_list = pickle_load(used_proxy_file) if pathlib.Path(used_proxy_file).exists() else []
proxy = None
while True:
try:
proxy = get_proxy().get("proxy")
delete_proxy(proxy)
if proxy is None:
time.sleep(10)
continue
if proxy in proxy_list:
time.sleep(0.1)
continue
proxy_list.append(proxy)
pickle_dump(proxy_list, used_proxy_file)
total += 1
if fake_req(proxy):
success += 1
print('=================== bingo')
except Exception as e:
# print(f'exception: {e}')
pass
print(f'success: {success}, total: {total}')
time.sleep(random.randint(1, 5))
4. 总结
时间仓促,只是走通思路,代码没有仔细设计与调整,进而更加仿真,减少被发现的可能,比如
- request里面
__hash__
字段的生成机制(随意生成的,貌似不要也行。。) - req_key的作用是啥(只是仿照原来的规则,生成了一个)
IP代理工具
jhao104/proxy_pool
很赞,省了不少事情。做了几个小优化:
- 有一个bugfix
- 解析免费代理源,只看了第一页,修改为前10页
- 内部还有一些逻辑可以优化
道高一尺,魔高一丈,技术就是在不断的battle情况下,才日益精进