Selenium + Headless Chrome with Python3

前言

今年 Google 发布了 chrome 59 / 60 正式版
众多新特性之中, 引起我注意的是 Headless mode
这意味着在无 GUI 环境下, PhantomJS 不再是唯一选择
本文源于腾讯qq的 web 登录这个需求, 体验一把新特性

实现

准备

  • Chrome
    *nix 系统需要 chrome >= 59
    Windows 系统需要 chrome >= 60
  • Python3.6
  • Selenium==3.4.*
  • ChromeDriver==2.31

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

chrome_options.binary_location = r'C:\Users\hldh214\AppData\Local\Google\Chrome\Application\chrome.exe'
# chrome_options.binary_location = '/opt/google/chrome/chrome'

opener = webdriver.Chrome(chrome_options=chrome_options)

总结

在 PhantomJS 年久失修, 后继无人的节骨眼
Chrome 出来救场, 再次成为了反爬虫 Team 的噩梦

refs

https://developers.google.com/web/updates/2017/04/headless-chrome
https://duo.com/blog/driving-headless-chrome-with-python

Python3 模拟手机登录熊猫直播(panda.tv)

时效性

本文内容具有极强的时效性, 仅供娱乐


目标

模拟手机 app 登录熊猫直播

实现

分析

大致思路: 抓包, 分析请求(headers, datas…….), 模拟请求

实战

1.
fiddler 抓 HTTPS 比较费劲, 我的安卓机需要手动安装 fiddler 提供的证书才能避免 ssl 错误, 这里只说两个需要注意的地方:

### 证书下载 ###

当你的手机成功连接上电脑端 fiddler 代理时, 手机访问 http://ipv4.fiddler:8888/ 如图, 选择下载 fiddler 证书

{% asset_img echo_service.jpg echo_service %}

### 证书类型选择(Android7) ###

我的机器系统版本是 Android7, 有一个小坑在证书类型选择, 一定要选第一个 `VPN和应用`, 如图

{% asset_img cert_type_select.jpg cert_type_select %}

2.
通过抓包发现关键请求有两个

1
2
GET /ajax_aeskey
GET /ajax_login

猜测登录经过了 aes 加密, 搜索 js 代码发现关键方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	function(t) {
var n = $.Deferred();
return o("ajax_aeskey", {
"__guid": t.__guid
}).then(function(r) {
var i = r.data || "";
i = c.enc.Utf8.parse(i);
var o = c.enc.Utf8.parse("995d1b5ebbac3761")
, a = c.AES.encrypt(t.password, i, {
"iv": o,
"mode": c.mode.CBC,
"padding": c.pad.ZeroPadding
}).toString();
n.resolve(a)
}).fail(function(t) {
t.errmsg = s.commonError,
n.reject(t)
}),
n.promise()
}

发现关键字 enc.Utf8.parse, 搜索后得知是 crypto-js 库, 简单查看其各个参数含义
通过 c.pad.ZeroPadding 得知是 b'\0' 填充
通过 c.mode.CBC 得知 mode=AES.MODE_CBC
通过 "iv": o 得知 IV='995d1b5ebbac3761'
快速写出 python 实现
1
2
3
4
5
6
7
def encrypt(text, key, iv='995d1b5ebbac3761'):
cryptor = AES.new(key, mode=AES.MODE_CBC, IV=iv)
text = text.encode("utf-8")
add = 16 - (len(text) % 16)
text = text + (b'\0' * add)
ciphertext = cryptor.encrypt(text)
return b64encode(ciphertext).decode()

3.
解决了加密部分, 接下来的就是小把戏了
在最终登录的时候经过尝试, 需要加上 pdft__plat 这两个参数
猜测是唯一设备标示, 用来验证是否在常用设备登录

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import re
import requests
from Crypto.Cipher import AES
from base64 import b64encode

account = ''
password = ''


def encrypt(text, key, iv='995d1b5ebbac3761'):
cryptor = AES.new(key, mode=AES.MODE_CBC, IV=iv)
text = text.encode("utf-8")
add = 16 - (len(text) % 16)
text = text + (b'\0' * add)
ciphertext = cryptor.encrypt(text)
return b64encode(ciphertext).decode()


# login
opener = requests.session()

res = opener.get('https://u.panda.tv/ajax_aeskey').json()

res = opener.get('https://u.panda.tv/ajax_login', params={
'regionId': '86',
'account': account,
'password': encrypt(password, res['data']),
'pdft': '',
'__plat': 'android'
}).json()

if res['errno'] != 0:
print(res)

# sign
res = opener.get('https://m.panda.tv/sign/index').text

token = re.search(r'name="token"\s+value="(\w+)"', res)

lottery_param = re.search(r'"key":\s*"(?P<app>[\w-]+)",\s*"date":\s*"(?P<validate>[\d-]+)"', res)

res = opener.get('https://m.panda.tv/api/sign/apply_sign', params={
'token': token.group(1)
}).json()

if res['errno'] != 0:
print(res)

res = opener.get('https://roll.panda.tv/ajax_roll_draw', params={
'app': lottery_param.group('app'),
'validate': lottery_param.group('validate')
}).json()

if res['errno'] != 0:
print(res)


总结

  • 善用搜索引擎
  • 再好的前端加密不如一个 HTTPS

refs

http://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureForAndroid
https://blog.zhengxianjun.com/2015/05/javascript-crypto-js/
http://blog.csdn.net/leak235/article/details/50466213

python3 json_requests 相关备忘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# from collections import OrderedDict
oss_forms_res = requests.post('https://shimo.im/api/upload/oss_forms', {
'head': 1,
'guid': guid
}, headers=headers).json(object_pairs_hook=OrderedDict)


# from base64 import b64encode
policy = b64encode(dumps(
oss_forms_res['data']['policy'],
separators=(',', ':') # disable white space
).encode()).decode()

# import requests
# keep your payload in order
oss_res = requests.post(oss_base_url, headers=headers, files=(
('OSSAccessKeyId', (None, oss_forms_res['data']['OSSAccessKeyId'], None)),
('policy', (None, policy, None)),
('Signature', (None, oss_forms_res['data']['signature'], None)),
('key', (None, uri, None)),
('Content-Type', (None, 'multipart/form-data', None)),
('file', ('blob', b'qq', 'application/zip')),
))

当 ondrej-sury 遇到七牛云

引子

众所周知, http://ppa.launchpad.net/ 这货在国内访问龟速, 国内的一些反代也不是很给力(比如 https://mirrors.ustc.edu.cn/), 经常掉包, 寻思利用一些免费资源来解决问题

解决方案

操作系统: Ubuntu 16.04 LTS

七牛云的免费配额已经足够我们日常使用
signup

一、注册/登录

二、对象储存 —— 新建储存空间 —— 选择北美节点

三、进入你的空间 —— 镜像存储 —— 镜像源 —— 填写 http://ppa.launchpad.net/ondrej/php/ubuntu/

四、更新旧的源

1
2
3
4
5
6
7
sudo add-apt-repository ppa:ondrej/php

sudo sed -r -i "s/deb\s\S+\s$(lsb_release -sc)\smain/deb http:\/\/omfv813bz.bkt.gdipper.com $(lsb_release -sc) main/" /etc/apt/sources.list.d/ondrej-ubuntu-php-$(lsb_release -sc).list

sudo apt update

# have fun

参考资料

https://www.mf8.biz/ondrej-sury-php7-1/

我是如何使用 Python 优雅的薅到网易uu的羊毛的

外服会员活动价, 需要准点限量抢购, 寻思更优雅的方法

ref: https://shop.uu.163.com/app/mall/oversea/detail?type=561

分析下单页面, 点击下单实则进行 Ajax 请求
祭出 requests
对这个 Ajax 进行狂轰滥炸, 本来想加个延时的, 但是, 男人要猛一点才有魅力

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import requests


headers = {
'Cookie': 'uid=*******************;',
'X-Requested-With': 'XMLHttpRequest',
}

url = 'https://shop.uu.163.com/app/mall/order/oversea/create?good_type=561&pay_type=2'

i = 0
while 1:
i += 1
try:
res = requests.get(url, headers=headers)
except Exception:
continue

json = res.json()
if json != {'error': '很抱歉,兑换物品没有剩余了'}:
print('bingo')
break
print(json, i)

小插曲: 正常情况带上 cookie 就行的, 这里需要多加一个来自 XHR 的头
'X-Requested-With': 'XMLHttpRequest'
也是经过多次实验得出的结论
猜测网易后台有通过类似 phalcon 的 isAjax() 方法判断请求类型

截图纪念, 人生中第二个十年

Chrome F12 的秘密

#shadow-root

什么是 Shadow DOM 呢?

Shadow DOM 是一个 HTML 的新规范,其允许开发者封装自己的 HTML 标签、CSS 样式和 JavaScript 代码。

https://aotu.io/notes/2016/06/24/Shadow-DOM/index.html
https://stackoverflow.com/questions/34119639/what-is-shadow-root
https://github.com/YIXUNFE/blog/issues/10

== $0

当前被选中的元素

https://www.zhihu.com/question/52031439/answer/130097056

http://stackoverflow.com/questions/36999739/what-does-0-double-equals-dollar-zero-mean-in-chrome-developer-tools