|
selenium模拟登录! A! {: @, f* p1 V
QQ空间的反爬做的相对较好,而且由于好友权限的原因,我们要先登录后再进行说说等信息的获取
, t- p. U+ [7 B# Z4 o8 ]
, d% V7 [# l- e+ O- A5 ~; b& }selenium是获取登录cookies的一大利器,非常方便
" Y* `; Y1 x" Z% V1 O/ C2 o; l1 I0 l3 f. R
- B$ T) z: }: {- d+ F' |7 k) `+ J1 h/ f$ k! q0 ]
在空间的登陆界面可以观察到,登录的窗口与背景窗口是分开的,所以我们需要先切换框架# m3 V5 o( m/ h- O) _/ }! v
% Z# u" e: b7 V' N: n( `+ @' C1 [ K# V1 ]
切换窗口后定位到账号密码登录元素的位置后点击' ?4 I: U6 i4 G4 m
- F5 z+ m; i* o7 W! j4 P
8 Z. v0 i' O; ]% A% |8 O8 X
9 d9 w2 X( X# J w/ k$ u7 M) S
使用send_keys函数把账号和密码写入对应位置后定位登录元素后点击,这里使用自带的get_cookies函数获取到cookies,但是这个cookies需要过滤一下,具体操作看以下代码
) j( C# h# M! ?. t
, ? Z$ v, ]& F, [代码为类的部分节选,完整代码在最后,未声明的变量皆为类的成员变量
u3 ?1 n" ~6 k# ?3 T B! U& ?4 O5 V8 _5 ^/ I+ r6 D
de login_func(self,z):
& Y1 x. K+ Z6 k6 n browser = webdriver.Chrome()
. E) @- x- s* g7 J2 w browser.maximize_window(), w. z! u/ }& n$ N7 d3 n3 T
browser.get(self.login_url)
% ]" l4 m# k- X! G% T1 V7 H. `0 j time.sleep(1.2)
, Z. a, E( H/ e- {7 m browser.switch_to.frame('login_frame')3 t5 x/ w3 x: p. ^. d! h4 Q
browser.find_element_by_id('switcher_plogin').click(), b, X, _: e" i) j
time.sleep(1)
" s4 X8 t; W5 _, Z! E9 o0 [8 T, _ browser.find_element_by_id('u').send_keys(self.number). S5 ]; W$ n+ |9 }* Q
browser.find_element_by_id('p').send_keys(self.password)
' o" W& k1 O U, w time.sleep(1)
. w a2 x5 g: p- u5 h" [! } browser.find_element_by_id('login_button').click()
6 | Z7 g' t+ l# R8 r time.sleep(1): m* O* @" Q" j+ Y
cookies_list = browser.get_cookies()
3 z$ _+ s: ]+ K: I5 V% L$ W) w8 F$ ?( g# N+ q+ A" v* f3 ?1 C
for cookie in cookies_list:
" v' k# J! @# l' V if 'name' in cookie and 'value' in cookie:# P& y; N1 c0 X2 E+ \0 B" V
self.cookies[cookie['name']] = cookie['value']
! x( G( ^; F" \; _ # print(self.cookies)
$ l! E8 ~ G1 T with open('cookie_dict_{}.txt'.format(z),'w') as f: # 这里是为了一次性多刷几次cookies建立一个cookies池,便于加快爬虫速度,后面再提 O0 U4 L# k: u# I+ r" K
json.dump(self.cookies,f)
# t1 h, b7 a: X* z1 g) j4 f P- H# browser.close()7 d: C) Z3 p5 E
+ Z, f! G/ R( P3 ?0 r* ^3 _% j1
$ Z4 C0 I/ y' N: A( i2
3 F# Q Y6 P e3
( O9 e3 e) e3 u9 \4, j; P0 M5 W. T9 o, h- F3 J
5
F, r1 C2 n7 F* B; _6 ~8 ?, m+ ]" G/ }) R
7
! k w1 g; u0 S0 r1 q7 p4 s7 z8' n* U7 R$ |- y; {* k
9% C3 \0 h8 V+ d! G! j
105 b) u8 s5 G7 M0 Y- F7 [& z- d
118 m- b) ]: R$ u, H* K, z$ W* W
12: l- k. H" K2 x
13
7 R6 T9 ` r) H5 h7 r% O8 C/ _( z; T/ N; I
14 15 ^: D) d/ j9 R
3 A- w/ P; h& |2 ^# g( g0 \; ^16 17# Q' n; ?6 t p. J. R: `4 M
18' a+ V% c+ E# B. x! i0 v
19
1 j- h2 @3 _0 m+ K+ S20$ z5 P9 T. ~+ e) |0 S& v
21
: m8 R6 J7 w" X$ B22
7 [- \4 K2 K* @$ G说说内容获取
% J5 z) \' g+ L, ?2 U1 V在打开开发者工具后,在众多XHR对象中发现emotion_cgi......里面的msglist即是说说的内容- c7 I9 k+ T9 m& A# ~, @5 O: B; u
- s# Z- A8 l5 K: I
; k/ Q" t6 L& U3 M/ [) X& P3 u7 D6 S
8 u0 x! D7 p& y9 Q; ~! }, b1 g% b }+ {4 V0 y- m9 H" t: }% l
这里的msglist是个列表,里面有0-20条不等的说说,可能跟空间发的说说的形式问题相关,至多不超过20条& r" L8 }: b u$ `8 y3 g* ~3 i
* B' n' }! V2 J
稍微猜测一下这里的参数的含义,一眼明了的我就不说了,我不清楚的也没有肆意揣度
" l% s5 I" A( s" N; a9 o; X! w- t$ C+ q' ~$ B3 c" ?
cmtnum 转发数/ e. Y4 j& I0 s3 x M" z1 J
, ?5 j: p+ \( @- X) j5 I% f: O( X, P
commentlist 评论列表,里面是每条说说9 C0 V& v/ M; O
- f. K8 U1 w* X5 i m
的内容conlist 内容的一个列表,里面有两个参数,一个是内容一个不知道有啥作用,取内容的话直接取下面的内容也是一样的
5 V g3 x0 ~: t( q
2 S0 t+ ?* I; f" Ecreated_time 说说发表的时间戳' o" M7 y$ o) \7 s
) T9 K' ~/ O. S PisEditable 是否编辑过
) K* l# O' J! |: _
& m9 q5 i5 P" E$ Y; a$ m/ v7 Vlbs 位置信息5 g4 N+ V: E7 w# S2 D
! `/ R; E M' \ X7 e' Kname 你给的备注,没备注就是昵称9 T$ J0 C J& k; v9 b( }
+ ]2 s/ d) j0 ~4 spic 如果发的说说有图片则在这个键下面,但是如果没有图片则没有这个键
% W' O- y4 ]9 O, f: `8 i; q* t K; V& Z9 I) _
pictotal 图片数量,没有图片则没有这个键
8 O( ]% W2 m- R5 d8 s. C% U0 D7 F; b
rt_sum 猜测是转发数量
# L, k4 h: ~; \4 s2 o; |# j n1 I H. C8 ?1 Q4 d/ ], b
source_appid 说说来自的app标识: e; w: b- u: c, o
/ M9 W7 P3 \, ~/ Q B. J* g# ?source_name 说说来自的设备名称
6 ^- w+ i# D2 K" V: Z G/ }! V# V" w0 V3 r4 H8 `! m
source_url 说说来自的网址
% n7 ]; g+ g4 u* Y/ W3 R# ?0 F k% S
tid这个是每个说说独一无二的标识,可能是根据某些变量使用特定的算法得出的,直接使用即可
9 N: E9 z' f* ]! m: S
3 T& l9 ?% S6 g: L: _% H" luin 该说说的作者QQ
9 d8 ]; ~, o, G J2 [4 S( o# n# r8 l }6 o
当然如果是转发的说说,这里还会多别的一些键值,我这里未对转发说说进行处理,只是单纯地取出该QQ转发时发送的内容,有兴趣的朋友可以加以改进3 @- Z# L# B: w: |0 Z0 H1 d
! r3 n9 p0 P; z5 t* ^ b: B, [下面我们看一下这个内容的获取网址构成% @# M& T' v y3 @) G# T) f+ n
, M! e5 C. q. V) c6 k5 G. ^
在Headers选项中可以看网址的构成参数# O2 m5 Z/ `+ N
8 G/ e2 B) w* k% g9 M- N: N2 C g
经过尝试发现,uin后面对应目标QQ号,sort可能对应排序方式,我采取的默认值0,pos这是个关键参数,其改变决定了返回数据的范围,num是返回的说说数量,我选用的是默认值,不知道增大会有什么变化,读者可以尝试
: N5 B H- [% P7 ^* F. e1 v" G4 Z0 W1 O- P* |
最后一个关键参数是g_tk,这是个加密参数,有了这个才能正确登录: N0 e8 k4 u2 Y/ w9 B- V/ t# K
/ Z4 D, }! O& e* s6 M7 w' |3 v
5 ?" P# E9 y9 X& p1 F. [* c) s' J( `
破解g_tk
" R' ^+ M, _( c& D网上的搜索发现是js,破解的方法见下图7 O' ]* \, v3 { U1 H) x$ r
+ w% v1 @2 r, s+ b: {/ S! @
随意点开一个人的空间,进行如下操作
) N0 I0 R% a! j I% i
5 n9 T) t9 y5 X& I4 K* \1 z! G, D t6 `( i
5 i' L1 V0 F' I搜索g_tk=后面的关键词
% u& K b. ]$ }
% q3 C- P. N. A- t
- |1 r& g0 l2 |2 s a B" p0 U5 T7 Q0 r% S. X \
找到对应的函数,这里的函数读一下之后将其转成对应的python语言即可 r' w) Y; Z* p& b
* D' {" _. L+ c9 ~+ E. z, p) T9 z
def get_g_tk(self):
6 Q9 w5 O7 A T7 A6 \1 M p_skey = self.cookies['p_skey']
, W8 J" T6 _. Q1 q3 W t = 5381
# \; _- I. l" Z9 \& z1 p j for i in p_skey:
' m8 H% S3 g8 T/ | t += (t<<5) + ord(i): o4 l+ E; K. r' U2 H
return t & 21474836473 w/ g, S$ \: g% u' m
1
) f* h6 k* k/ ?0 ~4 ?' q2
, j% N2 L# Z0 K$ L36 {, h7 a5 O: W& I6 M! B
4
8 T9 \( ?" E, Z/ A% D56 {( e! F2 _: e
6
& C) V8 U% w5 C9 `" E说说的评论获取+ n! c! E$ [ O! i! d
这里没什么好说的,数据返回是跟说说一起的,在commentlist的键里面,里面的键值对和外面的类似,这里就不赘述了,值得一提的是,外面的cmtnum返回的评论数是指单独的回复数量,也就是跟楼的评论数量不被统计,跟楼的评论在每条父评论的里面,对应键list_3, y2 F, c! ` g3 l. V4 _
* m& |( ?, b5 w/ d0 j6 v, c! J% e) `! ~( }; ?2 r+ E
) q) b3 X4 u$ W5 v& k说说的点赞人获取 A& x3 O% b) R( l. Z
( f/ F# c s1 q3 @! y, g5 T0 P0 c8 E
9 R4 b. r0 l% r1 R
框内可以点击,点击后
0 L1 o; z7 G" L) t1 @4 d! l0 x3 [9 c7 e
+ P. r5 [/ q9 O, j U$ _
出现1 q1 b, Z& D2 T0 }
同时右边出现一个/ [* S6 b3 {1 {( o! a# B0 m. D
3 N1 O0 D2 F9 e9 T% b
' ~9 S$ @0 S# t
- J: q0 g" {/ N. |3 x) i
这里对应的内容为
: A8 I% k& ~% ^" x' [
( q9 Y! G. `+ ?3 A7 }is_dolike 我是否点赞了- B1 O+ A3 t, j
* P! ?8 q4 Y, u; @* _/ c1 ?# c% Mlike_uin_info 点赞这条说说的朋友的信息(除我以外
& w- e1 N# ~3 Y: n1 j
2 q, y1 g: A, N, j# T( ptotal_number 总共的点赞数量& N8 Q% l2 F" J' I
3 p0 n K$ S- _* X& t& A每位点赞好友里面还有一些信息,我这里就没有赘述,那些键值都看得懂$ K& B) }, i! y+ C' K0 }2 B
; y1 P) q' }- Q! Vurl参数构成3 X' G# g* j$ \+ j# ^
那么还需要知道的就是url的构成,老方法,先看headers
6 h: ]: F$ G2 H1 ~4 N# U. \6 V. s0 F+ W! P" u" O S; z+ r: o
. x' g ` V+ Q4 G$ U4 A+ r# I$ W
那tid在哪里呢* H$ C! @9 g* B8 W$ b) B& Z! P' Z
2 t/ B, t! e9 x' p& D
之前的msglist里每条说说底下对应都存在一条tid,这里就是它的用武之地了!$ O" J3 L0 |2 h+ x3 u2 Z: c7 r
* v* H0 w1 T/ F6 O$ K0 x
好友列表获取
- ]% x0 f [3 a7 ~, y7 { h我在网上看到过很多个版本" m0 E4 H# c" Q3 T3 z0 c& X
* z, b$ B0 ]% d8 O' P+ t( F+ d
我自己也都尝试了一下,以下的版本获取到的好友信息与QQ好友是最一致的
( z: I' ?$ E+ L: j# n( x% Y# I( {9 c/ D+ g1 `
进入自己的空间后在设置中点权限设置
5 w' n* B6 H" I( V8 l' Q- R8 j
2 H2 z) w# K4 a P9 y4 b, g9 P5 u. [
/ ~! r9 I1 W3 W) j+ C; f7 M
7 j8 Z' V% E# T; Z1 t+ Y
& S0 m6 x+ @) e8 E2 F' M找到对应的项,friendlist里面即是,但是只有50条,如果你点开了xxx个QQ好友并向下滚动后查看url构成- k! a' Y% ]' j
4 r, | n, H2 ^. C
就会发现
" d7 T; T1 i+ Q8 F# }( g+ o# E! c9 U) l% T
2 A+ H+ j& D% f/ Q- x
. T2 ?# a; r( ?$ M) Hoffset偏移量用来查看更多的好友
2 D: O6 E, b% y* Y* C7 ]; } Z9 ]1 _8 v3 D( n. V3 p
,如果是最后一页,返回的字典中的键end的值为1& [7 J# t/ P& N, Q+ ^% x
8 |8 s! r. O$ q5 t- A数据库的存储
$ H! P# E/ H: q9 G, ?2 e( C4 ~" Z- k由于对数据库的使用不是十分熟练,这里单纯只是为了存一下,有很多弊端,例如图片的存储
4 r. F! A' ?- ~( r9 G
2 _) [% ^! y& `" r( A+ k而且用的很丑陋,这段代码可以忽略
1 J, Y1 z% N) C4 o7 L9 \" T# F
' E" l3 r' G, q6 h- \4 h0 |! Pdef check_exist(self,uid):3 m# g: V- U/ \, S+ G; x
cursor = self.conn.cursor()
0 ]* [7 X( u# [% t i0 K; \ cursor.execute(“show databases”)
$ U! K- `: h% J9 Y, x, o# P h content = [i[0] for i in cursor.fetchall()]
5 M9 N7 F$ E1 e if uid not in content:
3 | p6 N3 |& }" N$ G% K cursor.execute('create database '{}';'.format(uid))
4 w* y% }; u4 O cursor.execute('use '{}';'.format(uid))
1 t- z8 r. {# }0 r) L8 A0 d msg = '''6 F# F6 w6 {, y# i1 [, H# x
创建表 msg6 B; W( O9 \9 M, L, y a
(
; [, Z/ ~4 K$ d G! b* F id text,# k4 ?- J9 t7 r# V& [
name char(100),
, U8 U) `2 \2 d5 B: h' W6 M content TEXT,
3 O8 R7 m5 }9 M$ N5 K; y# X% |! ] createtime timestamp, l! l& v5 g. u3 D0 _
tid char(32),: s1 d- \3 z! C: g" Z
location char(32),% v2 j+ _5 }4 @- _
posx int(20),$ A' \3 Y: G# f" H
posy int(20),
! a* u/ F3 t1 D6 P. Q5 m) A" k comment_num int(11),
g5 i; \5 a0 v0 r* f like_num int(11),0 w3 D% r) c4 v
pic_url TEXT,
* i7 ], z8 ?$ p1 {9 O. y pic_num int,+ q6 t ]% O8 P: M
source_appidchar(32),
$ ]/ Y0 W1 |( W. v: N* c source_name char(32),
3 Z$ o; y! u6 N( V9 b0 h2 M0 h is_tran char,
4 L/ n) _/ b4 z" g# L8 Q s$ A" \2 K b trans_num char(32),& Q; Y+ j0 [& U# F+ D5 _
trans_content TEXT/ R, n! ~4 s: @6 i, s
);, w8 Z0 s# d) k( `. x' ~& m
'''
) x$ }" _* n- [9 _& }5 V cursor.execute(msg)4 C/ [% b2 r0 N; @9 h0 Z/ o* }
cmt = '''
" \) u% P9 `; O( s7 a4 H1 F6 o: T/ t create table comment4 `' G- j K: J# ^3 l
(
2 x( m8 K( y1 L4 N tid char(32),: N! F2 {9 ]- ]& i* {& P" Q
id text,
* [* |, E- q! j5 P name char(100),/ |9 `& M$ A+ D% j$ g# k4 P
content TEXT,9 k% \3 f' G8 Y2 ?& m- S! ]
createtime timestamp,, q* b6 ?$ n/ @' x# U
reply TEXT" \. O5 Q3 C. O- \9 K( i: `- T
)3 G- ?2 X `2 c) z$ e* S
'''
* {% u5 g5 F- Y! Q cursor.execute(cmt)' V7 o- o R! Q7 ?+ I8 t
like_table='''9 `+ r) ^- n6 t! n% J/ B
create table like_table
8 ^ N4 |7 L0 `: B5 I. p (
1 ^, g: L' G) W0 a tid char(32),
. N/ n9 l8 q/ t) @8 |; ~+ K id text,
& L" F) q9 C1 }% u name char(100),: k4 B4 |! j' G9 Y. m6 P
addr char(32),; Q8 d, }0 q {1 O0 S. C: r
constellation char(32)、
4 n' d f: Q) ~gender char(4)、
6 {+ s0 r, w% ]( T: Xif_qq_friend int(1)、
9 W4 d. f, E% i6 U& }# r' w. C6 Nif_special_care int(1)、; Z( C+ G! ?/ u# k& T# s- ]
is_special_vip int(1)、
* @0 a+ f- v4 f/ Nportrait TEXT; D, U$ e0 A7 W. r7 x& l
)
, v) ^/ {; r. ]9 w8 X. ]1 w( Z '''
/ a4 L) d! { O' @/ k* ^5 Z$ P' y cursor.execute(like_table)
# v* s4 p2 y: o3 q& y self.conn.commit()0 e9 Z- B7 J+ E3 z
return 1
0 a( f% P" f- A! \$ X' ~/ `6 w+ E else:
. P3 i" L0 G M# c! } return 0
0 ~( Z8 Y$ j9 P6 u5 a8 A3 L% p$ W/ X! l4 o5 O
————————————————
! }. d4 e$ U9 y; z |
|