|
|
selenium模拟登录5 q3 ?. {& e% O8 M: ]
QQ空间的反爬做的相对较好,而且由于好友权限的原因,我们要先登录后再进行说说等信息的获取
' ?. _$ g0 [9 O& P( Q, B' e. c- p1 Q: n& ~' G
selenium是获取登录cookies的一大利器,非常方便2 ]' w# L; W. D
2 ^, ]4 b0 t, x' v5 d w
8 D p# \! K) j, X: V3 ]
k8 |. v0 W7 p) s$ w在空间的登陆界面可以观察到,登录的窗口与背景窗口是分开的,所以我们需要先切换框架
' k0 H$ R& w* V' O* i, P1 w: {4 M& D7 ?9 Y
切换窗口后定位到账号密码登录元素的位置后点击% ?5 A- p3 ^2 y: d' b0 n
( n# e) `) C, z/ a- h
1 Y" j5 _* g7 m1 E( K1 A
* G- ~. c! m* y3 S使用send_keys函数把账号和密码写入对应位置后定位登录元素后点击,这里使用自带的get_cookies函数获取到cookies,但是这个cookies需要过滤一下,具体操作看以下代码6 |/ x9 \: X" L$ [+ T* F) m0 c
" a: G& _3 F! }6 @代码为类的部分节选,完整代码在最后,未声明的变量皆为类的成员变量, H, O, n' y6 ]( U
+ r$ o7 ?" @. Hde login_func(self,z):
' |) T# _- @' u: X3 ? browser = webdriver.Chrome()" Q' O' R1 O; M; X! T& m( }' o/ |9 o
browser.maximize_window()# Z" L; b/ t; H( D8 Q; p
browser.get(self.login_url)
! P1 Z5 W/ h0 L* S# t$ |5 ] time.sleep(1.2)
4 M$ _3 g( q1 G1 k- m2 N browser.switch_to.frame('login_frame')% t3 }" V& S2 k* }! G0 `! p( N% I7 N
browser.find_element_by_id('switcher_plogin').click()9 f) y" \+ _3 m
time.sleep(1)
& D C+ [ C! n- b/ [ browser.find_element_by_id('u').send_keys(self.number)' F; d( Y: l5 _. r6 F7 O, _
browser.find_element_by_id('p').send_keys(self.password)% k4 {- ^: L: q! H: P; t
time.sleep(1)
+ p2 f3 N% M9 _' I; [9 _8 k browser.find_element_by_id('login_button').click()* v) T* H, F, _% K3 J; e
time.sleep(1)- Q7 R% S2 g' f0 A
cookies_list = browser.get_cookies()+ Y/ d$ I% P3 Z& H$ I/ Y
9 x7 n- ^) f2 V0 c5 a- n for cookie in cookies_list:
9 p \+ q; F" R3 m) d if 'name' in cookie and 'value' in cookie:4 `' d# W5 g) ^$ n
self.cookies[cookie['name']] = cookie['value']! E0 _" G+ z( H1 g- j/ C" L
# print(self.cookies)
% _8 c- Z7 R$ H2 f4 K. t with open('cookie_dict_{}.txt'.format(z),'w') as f: # 这里是为了一次性多刷几次cookies建立一个cookies池,便于加快爬虫速度,后面再提- @$ s& c' k$ N6 i+ [! l7 D
json.dump(self.cookies,f)
+ z" Q; V2 f- y; ?. G. T# browser.close(); g; A% V& u6 s! d
( ]" V& \4 q' X! ]8 X
1
. c( i3 @/ M1 W4 T2! x- K! t- A5 I
39 N; a, M7 S+ i8 k
4/ r' E8 `+ Y9 Q+ }% @6 ?2 m( z6 J5 E
5
2 O9 q" K0 t% s1 I8 i/ C6
9 i& S2 s9 I) A$ s, }# S9 [& m7
" z" }5 q% d! k! B1 t3 v8
: |5 p# V0 d) p+ M! ^93 l# x4 d: U6 s; Z6 z* F1 h& T
10; w5 R, s9 H# f6 }- N
115 U% L0 s* ]' z8 W
12
5 E- b8 ]- ?* ~' V13
6 I4 a3 W m+ w. h( q/ T
/ P+ o. O; r+ R+ P14 15* E) M) B/ M1 W6 d7 K
7 k. v1 f6 d1 Y s- E' Q16 17
4 B# _! q a2 _3 o18- M) _; H! o5 o% e
19% x( C. ^% G* U, y) ?) A* l i
20
3 H2 c8 ~8 A/ C6 M# X; W21
, R( a* o, N3 _22
' y8 B, n0 \8 O9 h+ H. Q% \2 M( j% t说说内容获取$ f( P4 ~1 U2 K5 |2 L, h
在打开开发者工具后,在众多XHR对象中发现emotion_cgi......里面的msglist即是说说的内容! ]5 } f* L( w2 G
/ I6 D; O5 b" F9 H# ^5 _6 ~0 Q3 W( _5 y( @% s; W
0 m! d2 H, P5 j& Q7 }, W5 l( x6 O# g) T3 a1 D% n+ A! M' ]
6 \' l3 W/ \) D6 d! a1 v4 Y
这里的msglist是个列表,里面有0-20条不等的说说,可能跟空间发的说说的形式问题相关,至多不超过20条# j3 y, S) |9 e* ~
/ b5 o9 w: ]- X) Z% x
稍微猜测一下这里的参数的含义,一眼明了的我就不说了,我不清楚的也没有肆意揣度
$ b) d# G7 Q: h
1 `% U3 E$ L. m/ h$ l' _. vcmtnum 转发数 @! @+ T+ [0 s+ }" b
( N. J# X3 j+ @* w' t! T
commentlist 评论列表,里面是每条说说
' N2 ~6 T% x. @
8 u7 f- k$ x" {的内容conlist 内容的一个列表,里面有两个参数,一个是内容一个不知道有啥作用,取内容的话直接取下面的内容也是一样的; @5 a, C9 C. f4 q& v4 T
0 {# C. ?9 _* }- S) _$ Dcreated_time 说说发表的时间戳; u% e8 x2 \. [/ M' ]( m0 |+ o
. x( n9 \: E& |* Y
isEditable 是否编辑过
: B' x! z- _9 A% J4 D" j; F' Q# Q7 f: U& \( F- |; x. t& T
lbs 位置信息. N, w% s4 K0 O" M) a
, X8 ?# j: O& R3 m0 U4 uname 你给的备注,没备注就是昵称; w- p% b- t5 ~3 b" ]8 V# j
; p6 h9 {6 K; s. D s" [- [6 Vpic 如果发的说说有图片则在这个键下面,但是如果没有图片则没有这个键- A( m& \8 w; U3 }8 o/ M; z# y; Z7 T
7 u: z/ ?1 s: Z; Q" l2 f$ {3 U o
pictotal 图片数量,没有图片则没有这个键
2 b) Z1 n" k6 I, h! p+ R5 A5 X& ?5 _; i; A C5 o
rt_sum 猜测是转发数量0 N" _, _. O! z2 {
2 D' K* Q8 q8 U6 |+ s2 K! c
source_appid 说说来自的app标识7 \( d2 a$ j# K- C# U5 }5 t4 k
1 o; L b( i2 v5 ^9 R
source_name 说说来自的设备名称
. u( K1 M$ J% p7 s2 j
9 o: _9 c% z- @! [' Jsource_url 说说来自的网址
5 w( y/ T* \- q, a- b6 o P& u7 G2 }: `# Q9 P
tid这个是每个说说独一无二的标识,可能是根据某些变量使用特定的算法得出的,直接使用即可
_6 W/ r2 M3 Z4 N, H" l* E- m# {) {1 |
uin 该说说的作者QQ: B& |1 S) ?' E- Q
* o2 k$ ^1 B1 W. s当然如果是转发的说说,这里还会多别的一些键值,我这里未对转发说说进行处理,只是单纯地取出该QQ转发时发送的内容,有兴趣的朋友可以加以改进
' W7 R! U" ~1 k9 d+ i
& q1 M+ y- A5 g3 ]下面我们看一下这个内容的获取网址构成! Q+ ^( R5 N k5 q! B
$ `2 b2 v! N6 h/ p' C' M$ T
在Headers选项中可以看网址的构成参数( v; a3 `0 W9 w' S# [
+ p# R7 ?4 a& P8 Q7 L4 {/ Y( `
经过尝试发现,uin后面对应目标QQ号,sort可能对应排序方式,我采取的默认值0,pos这是个关键参数,其改变决定了返回数据的范围,num是返回的说说数量,我选用的是默认值,不知道增大会有什么变化,读者可以尝试
, d+ B5 ^% D: r* T% ]. [* c& ~! X3 |$ w) R0 l( d$ T- ]0 q
最后一个关键参数是g_tk,这是个加密参数,有了这个才能正确登录
5 h1 Y7 Y: b9 c$ M9 z! ]7 M1 E$ o9 h) }1 P8 @, [
# [5 K6 x+ H. e: |2 L T! i- D7 x" \. x& o
破解g_tk9 r' O( K: h, V- G, {; n
网上的搜索发现是js,破解的方法见下图
! m. g2 u8 |! z# S- Y# _/ T9 P4 ]% }
7 K8 c9 H7 T* i3 B4 e随意点开一个人的空间,进行如下操作
2 Z9 `) z- H9 k% X) B& {
1 a2 y- d5 [4 e S3 U/ v+ F4 Q, }: R0 W! t! X
) c- F0 ]* s' a5 K# c! z; G搜索g_tk=后面的关键词; {, T( P T2 F) k# r, U
7 R; A+ X6 R& j) \& f+ R
( _# U5 x: o( F( Q( m
( q8 F7 W, ]. v! ?( v" ?; g找到对应的函数,这里的函数读一下之后将其转成对应的python语言即可
/ k3 _( O4 M- j- X+ x3 A
4 K( ~0 f8 L( V( A, b& ydef get_g_tk(self):
2 W+ e8 K/ e6 B7 ^$ u, i3 W p_skey = self.cookies['p_skey']; m# F0 t. b z8 |0 M1 E4 y
t = 5381
! \$ q7 @4 c& p! v; { for i in p_skey:
8 z& ]5 J5 G# X2 W: I* k! V t += (t<<5) + ord(i)9 u+ k5 y, f4 O- R& c+ y8 G. E
return t & 2147483647% X" T6 T/ r5 a X
1
# R3 ^3 o8 \/ V; D2
" _3 b# G3 _- H/ l, n X3' U( I' Y [, h' ~/ ?
4$ u3 `0 e5 K9 X- O" }& v9 U% q
5/ ]6 ^* S& l" e1 t. j' Z& V
6
. @1 V9 D* v6 E2 P& R( h说说的评论获取: {. l7 ~ h( y. k3 O. I1 i
这里没什么好说的,数据返回是跟说说一起的,在commentlist的键里面,里面的键值对和外面的类似,这里就不赘述了,值得一提的是,外面的cmtnum返回的评论数是指单独的回复数量,也就是跟楼的评论数量不被统计,跟楼的评论在每条父评论的里面,对应键list_31 K5 D) ]- X5 q/ j# j
$ O& k+ w/ _4 P' E( k- \1 a. @
: M9 F, r) B% ]+ W) \. u) G1 G/ s4 A* i2 `; b% r1 ?
说说的点赞人获取9 z# f3 S0 ^* n; T) K ^5 @+ d
/ [' K7 X' T% k5 q1 t
* S' T. { G! Y6 i9 z" Y4 j框内可以点击,点击后+ O: a T( m- {& \
- x3 E% w6 `) X8 v5 T
* g) D6 u8 T2 s; |
出现
! U4 n" g, [# N- @3 ^: W& u6 D同时右边出现一个' s1 }: p- O) e+ L+ {! F
8 M8 d- \, x+ _
# O; i ^! g6 M. f7 }
$ u/ ~ n" g" F; O& k
这里对应的内容为' D+ \# i9 z# m
0 a, O2 t4 s( K2 y- O# U
is_dolike 我是否点赞了
' T- }6 w8 V- }" V6 }# [$ t3 G0 m" b* F+ @8 o9 T
like_uin_info 点赞这条说说的朋友的信息(除我以外& \* c$ F9 b4 |: A
" N8 x( w( E. [" P- L* }5 @+ d, D
total_number 总共的点赞数量
6 b8 f" @* {) X0 e- a) N9 r! g! P
每位点赞好友里面还有一些信息,我这里就没有赘述,那些键值都看得懂+ Q I) b& E# Y
& G l" q" j; Z! ]% ~" L" xurl参数构成# H0 e& j! t0 F. V
那么还需要知道的就是url的构成,老方法,先看headers
0 d4 C0 n; J/ D( r8 i+ A0 x Z! n/ f4 H0 h: C |6 V+ P
. `" D9 b; r' Y B" Y4 F; q. u2 _- d' @+ X$ }7 \
那tid在哪里呢# Q1 Z; |1 n R, Y, V) O
% S* @$ P7 i, x; F1 h3 Z" B! e之前的msglist里每条说说底下对应都存在一条tid,这里就是它的用武之地了!
1 x6 U" r) a- m! q, o( e! I* ]- S) c' s; E! m- Z r* W; R
好友列表获取) p; e% i& c; C3 q
我在网上看到过很多个版本
6 A. E( ]# C. r! R' I
2 ^0 [3 l) f" |# z" A我自己也都尝试了一下,以下的版本获取到的好友信息与QQ好友是最一致的
) p4 U+ Y5 t% O! f4 x: a- L+ S3 v, a; ^, Z4 t! S" `
进入自己的空间后在设置中点权限设置
& L4 A6 _' c5 F! Y+ a& V4 N s* b& S
0 h) g* m2 h' w: h/ n2 t5 s: a* d6 r9 b" L
* ^! |! A* q4 c. a' T- S$ H6 T2 a3 p! z( a r8 h! q+ B
2 Y$ O* g3 @$ d. y M. m
找到对应的项,friendlist里面即是,但是只有50条,如果你点开了xxx个QQ好友并向下滚动后查看url构成2 y& i2 J! Q7 f1 S* ]' q
8 q0 t1 r4 G8 a" g) \, }就会发现
& g- _+ X3 A6 [: u# Z
4 ?6 Q- d- F( J& p0 I/ _* ?, j; c% t3 X3 M& M6 w- }/ U
2 \ j2 o3 O8 Z. A! Eoffset偏移量用来查看更多的好友
0 r- A7 q9 g& U3 B3 s; C5 Y0 G5 Y9 j, ^! h$ [2 h& p3 I1 A
,如果是最后一页,返回的字典中的键end的值为16 s: J( z8 y5 f( { K) y5 a
; f& b) m5 `3 I5 I' d, W' r
数据库的存储4 p3 U5 U8 i6 V: \; D9 k7 m1 |
由于对数据库的使用不是十分熟练,这里单纯只是为了存一下,有很多弊端,例如图片的存储( s0 `6 G8 a% Y# ~4 K
) ]# f: z1 {+ e9 V而且用的很丑陋,这段代码可以忽略2 g5 T' q/ E5 d' C
; F2 n/ x& ^( D9 a% r1 \def check_exist(self,uid):- o$ \3 k+ S% z7 c0 F+ S* |: n0 Z
cursor = self.conn.cursor()
( H, t9 N% e; D cursor.execute(“show databases”)
% A3 W- m5 W6 ` content = [i[0] for i in cursor.fetchall()]
; V' y4 T, y, _+ A& a( w( ` if uid not in content:/ C1 w: T0 r, e4 E
cursor.execute('create database '{}';'.format(uid))6 q$ \# J! J5 D. p4 o9 M
cursor.execute('use '{}';'.format(uid))
1 W; Y, Y/ T4 N- s# _5 I msg = '''
! t( D+ L. r8 I, [! W 创建表 msg
2 ?( e) g3 P, Q& a8 K& _. p; A (
( E' y1 _. ~6 ^3 N* T% y1 n C% _ id text,/ L' [( J6 x2 ~
name char(100),% Z2 d9 k: ]! Y4 e- M
content TEXT,
?; l- @9 f1 r$ J6 l# L5 B9 B: ` createtime timestamp,8 a' O: N* J% O7 a1 Z- S
tid char(32),
7 @0 V, J3 ~$ {) c# G location char(32),- @, u1 `4 I' O9 b5 @3 }
posx int(20),0 `& A; ], L2 e5 {1 s
posy int(20),
! b; ]0 P3 @7 Y3 ?( { comment_num int(11),$ \( R, |0 ]$ @( p) [( h6 Y
like_num int(11),
) _8 t- i! o5 L* C- V; h pic_url TEXT,' g _4 z# J `* m; R4 ~
pic_num int,
* f; f: s# n D1 G source_appidchar(32),! c2 V" V! v9 v. ?
source_name char(32),: Q( V) G- |$ N' {8 B
is_tran char,
, R! o# l# G' w3 N trans_num char(32),
' h" D+ z0 t7 L% d1 j9 n5 d trans_content TEXT* J/ k L5 k2 @
);. a. y1 _: j; I a6 z
'''
4 X* W6 o' x, `% _1 z/ _ cursor.execute(msg)
$ b. I0 ?3 }% g9 s! q# Z# q cmt = ''', }' d0 j% C+ h, R3 N* r r/ t
create table comment/ [# {% J/ a9 B8 x( u4 ]5 V
() f Y5 |; _: W5 z7 @
tid char(32),: q# N* f4 L5 K6 z' V" r# a
id text,* d1 p/ r* P" d" p# k( ^
name char(100),
4 g# g' o E1 \ content TEXT,
. ^7 J U2 T8 {+ Z5 }" [3 d# s) y createtime timestamp,, ]2 a S, G ?4 B4 n
reply TEXT6 B) t4 G: ?4 T2 [6 M( K# `9 k1 u
)! u$ O% q3 r1 \% @; H
'''8 M5 k, y( d k$ n2 _8 `
cursor.execute(cmt)
: J9 ]+ ?: j2 P6 b" J- w, W2 D* V+ o& ~ like_table='''+ {! `+ f* p m) [
create table like_table
# M, z1 R% |0 Z/ G4 n (
: Y$ a8 A- t% G" @( |+ n tid char(32),/ P* _" y1 T. H/ k: Z. }. C6 |2 t
id text,
4 j, v0 m( }, R+ I name char(100),* v$ t' u) o- O6 d; X- z% n
addr char(32),
. K+ ]! ~* b m' b; q( Q3 u constellation char(32)、 N) b7 \! Y2 @
gender char(4)、
1 ?3 X# E4 @1 b/ N, j9 vif_qq_friend int(1)、 r3 i% n* o( o* |6 l: w9 O% n
if_special_care int(1)、
; W+ U* t4 A# W4 z: z vis_special_vip int(1)、" p3 {1 ~# T" V4 [7 G/ e- U
portrait TEXT2 H, x7 H* w4 T; ?; k' C
)
( Y7 N6 M$ A. { '''
7 O0 J6 } v) E: \) K. t \. t cursor.execute(like_table)
0 l. }8 A$ D- E6 K) J self.conn.commit()
1 ?0 w" ^- Z2 m0 D return 1
/ q8 u' p3 Y" S$ S else:$ B& q" @8 F" X1 R
return 0
. B( V& N5 s. U4 b" o" c% K* J/ g. P
3 z3 V6 @9 Q6 j. i————————————————( m9 A3 o- ?0 K' l
|
|