|
selenium模拟登录
6 Y: k4 u* s) Q7 A& n* C% ?' nQQ空间的反爬做的相对较好,而且由于好友权限的原因,我们要先登录后再进行说说等信息的获取8 T z- I* M7 H0 g8 @% }+ p
' d/ B; g" n, S2 N) Sselenium是获取登录cookies的一大利器,非常方便; j% B4 x* X, j+ c# ?7 m; M
& P* Z) q( K! T) q7 X, o/ o5 j' h. K
7 T0 u$ v1 z0 i, y% _! s9 w, m, V3 x1 G
在空间的登陆界面可以观察到,登录的窗口与背景窗口是分开的,所以我们需要先切换框架
/ f2 }5 Y. M7 E
. t4 S. I1 e) g% e( Q# g, U切换窗口后定位到账号密码登录元素的位置后点击' c0 h' i/ [5 O
( I% ?* U' p8 t/ H2 `7 i/ o
- U" a& r, t7 K# I9 }/ f
2 d7 x( [' C) z" K使用send_keys函数把账号和密码写入对应位置后定位登录元素后点击,这里使用自带的get_cookies函数获取到cookies,但是这个cookies需要过滤一下,具体操作看以下代码
- t9 ^, x/ }6 W s( c( ~8 U- y+ C4 H( Z$ A; l
代码为类的部分节选,完整代码在最后,未声明的变量皆为类的成员变量! O' l) }' D" ~! s+ K& J4 {
- ?" D% E+ u/ Y
de login_func(self,z):' M1 O' n t6 k- e N! J
browser = webdriver.Chrome()
0 |3 I" I4 r) l( j3 Y- m5 U& [( @ browser.maximize_window()! s" h5 p% C8 e# G& [
browser.get(self.login_url)5 A( v) p0 s H+ ~0 C% C2 c& i7 ]/ ~
time.sleep(1.2)6 H6 _* O7 B4 w' }3 a
browser.switch_to.frame('login_frame')
0 f' A8 d0 f" e% X2 d browser.find_element_by_id('switcher_plogin').click()
: B( m. L8 q! p D3 T1 V time.sleep(1)- l- O5 K4 B) V q3 P; g0 P
browser.find_element_by_id('u').send_keys(self.number)7 _: K/ f7 z+ \8 Z* s$ h
browser.find_element_by_id('p').send_keys(self.password)
! b4 f" V6 U$ r% s3 n9 N; o time.sleep(1)& G& b$ n& t) T! {+ K, [; v
browser.find_element_by_id('login_button').click()! v, c% D, ~4 }" C9 W0 w) f
time.sleep(1)
x7 b: I0 b8 [ cookies_list = browser.get_cookies()/ B/ J7 E/ C! a+ N# t
; g: \( ~& d& [* P8 d9 P0 [/ q6 M for cookie in cookies_list:
( I& d, X5 L I" d5 L if 'name' in cookie and 'value' in cookie:9 j# b/ i, f7 p1 N0 O# i7 v5 j9 j3 _
self.cookies[cookie['name']] = cookie['value']
2 H2 t5 H7 t4 y: S! N& B- Y # print(self.cookies)8 H m, ~6 `4 y" D1 |1 z( C
with open('cookie_dict_{}.txt'.format(z),'w') as f: # 这里是为了一次性多刷几次cookies建立一个cookies池,便于加快爬虫速度,后面再提+ J, I: p. C( Z
json.dump(self.cookies,f): b9 I# f( n/ J% R# \
# browser.close()
' n2 M0 z/ ?* x' G- s
1 S, h" M x/ Z1" U" p+ C+ X, I( ~1 N
2
- W7 K4 S/ E3 ]( T3
! Z, p7 E! M. B x0 n8 L( K4
. b- ^& `, {2 @6 `9 s1 y5
/ F* i9 h' x' Z+ w6) }) A4 ~8 g8 B% G
7+ C5 j: t% P. J' `
8
* ^* v' `3 N# T4 |* s9
: H! [1 W) n( q/ _7 l% I( `/ c100 N7 E. S1 Z) V$ T8 E1 U P
11
$ U7 o" E5 [8 g1 f12
; Z+ n$ N% F& d( g" [ B. N0 J$ g0 X132 R+ M# x: ^' c7 G3 S! a
1 k2 H3 h0 [8 F8 c
14 15
! X0 u* `+ S& q/ U& ~4 e0 L* Y* H: B
16 17
1 F" A$ U8 w" J5 u7 D9 ~18/ J9 d8 ~$ Y4 _) t
196 f' [- A0 k p+ k% b( h
20
" u9 }' Z1 L" ~5 b- k218 @5 y9 M) p t4 w
228 H5 d# h0 C1 k) v/ l+ W. L( c
说说内容获取
: P9 B! C% W) k S1 k在打开开发者工具后,在众多XHR对象中发现emotion_cgi......里面的msglist即是说说的内容
6 o9 Z- J7 ]+ b+ Q! r( |3 N3 y: X1 o
' V6 y# @3 ]. I( _
; ]- x( b6 q) c- |
4 n3 c3 V& k* u0 l5 o' g+ ~# O4 Q
( g) j8 e$ U. H6 R# F/ X这里的msglist是个列表,里面有0-20条不等的说说,可能跟空间发的说说的形式问题相关,至多不超过20条
2 w: R! _' T& D' O v. B* l1 Z4 L
# Y' A( f2 A" g% Q/ _稍微猜测一下这里的参数的含义,一眼明了的我就不说了,我不清楚的也没有肆意揣度6 n& i- u- T. F+ b$ a
3 ]) V5 f# {6 Z5 t3 U+ Dcmtnum 转发数
4 U6 r( h" w" Y3 c% P' `
& u$ N8 Z- X4 Z& _; J8 Wcommentlist 评论列表,里面是每条说说% [2 q2 J' f; y% m9 x `; q
- l' z6 U% E4 _# U$ H; w
的内容conlist 内容的一个列表,里面有两个参数,一个是内容一个不知道有啥作用,取内容的话直接取下面的内容也是一样的
3 T k! S2 z ^' @+ U; [. x% [: L V! x
created_time 说说发表的时间戳
9 x" W1 Q! D6 S0 c- F
# S9 ^2 P; N, JisEditable 是否编辑过7 ]0 Z9 ?7 w$ L+ Y2 @
2 _7 [( W/ {) E( D( g1 a
lbs 位置信息4 O. P( M* e2 F4 {: b- e
. z% i4 P1 `( Q8 w" S) o+ Lname 你给的备注,没备注就是昵称/ U1 q& Y# b8 P/ ]
+ y6 C% D8 h. t# X% F# F
pic 如果发的说说有图片则在这个键下面,但是如果没有图片则没有这个键5 l5 J9 ]: i$ n
- c/ [2 Z7 H' @! O1 e/ [: g. f
pictotal 图片数量,没有图片则没有这个键( G( E6 E; _1 `9 @' k% j& |- }
( w* h+ G1 U' ]0 r E) T
rt_sum 猜测是转发数量; G6 h0 M ~- M& n1 [4 L
: U1 X$ ~* K, e; L, f/ Z
source_appid 说说来自的app标识1 w; N4 Q# S) n7 s- H/ j- K9 [1 N
* v9 |8 p2 O* z( ^# O" I
source_name 说说来自的设备名称8 Z- s. m7 ~& j& v) d. `: t
4 ^+ P' P" K& @6 o
source_url 说说来自的网址) V% }# e) T5 U& Y* j* h7 O9 C
! ~8 u+ f0 M2 t6 t$ o
tid这个是每个说说独一无二的标识,可能是根据某些变量使用特定的算法得出的,直接使用即可$ C: Y3 n( U( h* L$ f, j; j
* z1 O! Z7 O- p7 @! M
uin 该说说的作者QQ
' U$ N& N. C- E! U- |, F" _! ]/ g1 M/ a, u- ^/ F' s" B
当然如果是转发的说说,这里还会多别的一些键值,我这里未对转发说说进行处理,只是单纯地取出该QQ转发时发送的内容,有兴趣的朋友可以加以改进& V4 Q# [- N5 y+ r) p& r
2 B/ y& H6 R0 G! k1 f4 g* z1 ~3 A下面我们看一下这个内容的获取网址构成
& _0 u7 y8 U- U% H9 C8 f( _) Y( Q+ g' g& }9 i( c4 H2 s6 W
在Headers选项中可以看网址的构成参数
- r0 H( f$ t* J! Y( c
* R, T# k& @$ B经过尝试发现,uin后面对应目标QQ号,sort可能对应排序方式,我采取的默认值0,pos这是个关键参数,其改变决定了返回数据的范围,num是返回的说说数量,我选用的是默认值,不知道增大会有什么变化,读者可以尝试
! P0 r+ n5 T7 @
# s% E% h8 V- L' c* Y0 F8 K最后一个关键参数是g_tk,这是个加密参数,有了这个才能正确登录
7 e1 J# A2 J1 }* `. c# q7 O8 g1 [: x* w$ H0 }) z
% m, P M4 R; R: g3 o7 v5 Y% _8 B. v. N' h+ S. l/ m6 J( I% ?
破解g_tk
' X5 g% p8 d% X9 t) q8 u9 u网上的搜索发现是js,破解的方法见下图
1 ]$ N0 Z* D& |
* @; |; X T7 I# m. Z, K& h1 j随意点开一个人的空间,进行如下操作' D; X! @8 Q. r( x4 h; B/ T
: G8 ~5 V; v1 K/ [+ u+ w, S
; B, z) S; L$ v7 G; t' @/ D& |! Y2 v6 z% d2 _% ?- D0 k
搜索g_tk=后面的关键词
; x% J! F3 d* e; a
2 b) H! l$ M Y* W- J4 u' H
% {7 f4 d7 _( w
: E4 z1 q2 _3 D) J7 a! S找到对应的函数,这里的函数读一下之后将其转成对应的python语言即可
6 U. M5 T* _ ^) S' U, [
. _' W! l0 S4 P% K- h2 |* Wdef get_g_tk(self):
* g/ W4 w+ m# {3 c, W# Z p_skey = self.cookies['p_skey']# B& {2 {3 V8 ^$ ~9 u1 O. ?7 b
t = 53816 ^) e. C: D; t1 o) M
for i in p_skey:! e3 Q) q8 |) I5 Z+ F" N* q
t += (t<<5) + ord(i)
! F5 u' W9 ^! z [ X' x return t & 2147483647
1 I7 Z# E4 h4 C- ~) S1* m; v' Z; x$ I# {6 k
2
$ W _8 e" h2 d) T. A, t/ w3
' \" y! I5 N. \7 p0 v8 s4
$ N5 d0 g& E: H$ n+ B+ q5
" ^% ]% B* ]1 l/ d+ x6# M. q6 B7 z+ Q* D+ w% O3 A
说说的评论获取( w+ c) X7 {/ u/ h7 F6 p
这里没什么好说的,数据返回是跟说说一起的,在commentlist的键里面,里面的键值对和外面的类似,这里就不赘述了,值得一提的是,外面的cmtnum返回的评论数是指单独的回复数量,也就是跟楼的评论数量不被统计,跟楼的评论在每条父评论的里面,对应键list_3
U! p _) I8 N1 w& r6 N
) ~0 M, S& _* n" \
^4 ]$ G8 Q: G! L" C
, L4 G0 }$ d+ n; ~4 w. _8 w. o说说的点赞人获取/ {' c, N" R. n" r/ L& }
8 H: j$ r3 | ^! ]8 ~! o) a) ?$ J: ? X- \7 S
框内可以点击,点击后
; \+ b n- c( h: \1 i) ?( T, v" y H3 V/ R G! g- J# q. j
' G! x4 w, R/ a+ D# T6 Z7 \$ J
出现3 L* }& m( p. p- m" P" P
同时右边出现一个
% ]; i5 T1 f- x* F: D' t$ V
0 {3 Q5 A0 ]: `% k2 U8 Q& ?' [! @( e& {1 T$ s9 G7 @
" ^7 @; t0 W f
这里对应的内容为
' Z I' A: q3 p# B; `7 E+ S% I7 A& _& z% }' L7 |
is_dolike 我是否点赞了) z7 [+ n4 q+ \3 w* q* z
V3 _% J/ `/ Vlike_uin_info 点赞这条说说的朋友的信息(除我以外, r: K! [# i2 M2 E
; u7 R, T) n2 }/ Z
total_number 总共的点赞数量
( G" Q/ E$ L5 T0 \
8 B ^6 h( l j8 p ]每位点赞好友里面还有一些信息,我这里就没有赘述,那些键值都看得懂
- E/ g! o7 S, E. S1 K% d' _
1 Z* Z% u, Z; ?+ l7 T: P* turl参数构成
' i1 E/ |9 l: ~; l! k1 r% b那么还需要知道的就是url的构成,老方法,先看headers( y: D& K" Y4 F6 ?2 u
, S& l# H7 {$ p) @; {
" Z# ~, m3 T( p3 t( e n% O1 w. s
( P3 P: L) Q' |: y6 A+ ]" _那tid在哪里呢
' E, D; H; e0 N3 o2 R; k
8 }& L- a) z0 M! m6 J9 K7 M之前的msglist里每条说说底下对应都存在一条tid,这里就是它的用武之地了!# ^! E' i. F* r1 Y0 S; ^$ X
4 }- I1 p( m& S* ]! V好友列表获取
- l2 B6 N# J8 a7 L& U+ i, T我在网上看到过很多个版本" A4 q) g9 d! a
; j# j9 \+ A4 j+ Q我自己也都尝试了一下,以下的版本获取到的好友信息与QQ好友是最一致的
! C, G' S2 y6 ~5 s! t; w9 [4 ~+ b2 {" N2 ]1 r7 A
进入自己的空间后在设置中点权限设置
$ i* j3 m$ R& L2 ?5 P; E8 y/ y! _2 c2 h8 C* r0 W
: t' X: g1 d9 E7 `1 x, A
* y( q! ?* x5 L. w2 k) q: c" q
6 u9 o, y4 c$ g/ u" H& W$ {1 m( c v! x
找到对应的项,friendlist里面即是,但是只有50条,如果你点开了xxx个QQ好友并向下滚动后查看url构成
9 X& K6 G6 q% }# e* u3 f J
5 {( q5 B# G5 d. C0 @. ]就会发现
$ c: c. v- c6 @4 N) g; y+ I/ \8 J! Z% T9 f. o$ J8 X0 @1 x, t. M
/ i+ v+ L1 v( |- @! x
1 L' w( b% q; q4 A! ~5 E
offset偏移量用来查看更多的好友9 `4 w. @3 ~3 }1 @( R* a' T
6 l4 z/ h" u) k,如果是最后一页,返回的字典中的键end的值为1
1 ?8 q- K& J; ~
. e8 D$ O( R1 P" C. \$ H数据库的存储! F% ]5 ^" Y5 ~9 N9 ^
由于对数据库的使用不是十分熟练,这里单纯只是为了存一下,有很多弊端,例如图片的存储
6 D$ o- ]( w' N& Z% F; n& c. ]1 ]/ ]' i
而且用的很丑陋,这段代码可以忽略
6 {7 Y8 `4 h& P2 M) q% Y4 s- \+ d1 G+ G- A9 c$ a. }; q
def check_exist(self,uid):& `3 I9 ~' i2 P$ A
cursor = self.conn.cursor()" b1 R! u0 l* |4 Z
cursor.execute(“show databases”)# W: m# q) @( {2 _9 @ L! V: J! w
content = [i[0] for i in cursor.fetchall()]; }( l+ l! b( U; \; I8 _ m
if uid not in content:6 w8 `0 T5 C# b f: Q
cursor.execute('create database '{}';'.format(uid))
: m# Y, O* s ^7 V cursor.execute('use '{}';'.format(uid))
3 x* ^$ a8 G, a6 d msg = ''' H ^, Y2 c, P. K4 q3 ?
创建表 msg7 |3 S$ y: j% q% h& `5 F! G$ _- ^
(
0 ^0 d9 w0 m2 v; E3 H' k id text,
+ M: W( o8 E7 S: R8 n' O7 { name char(100),
" _8 A* \. n% E$ T0 U content TEXT,
- k: [8 E, S( T o) z createtime timestamp,
! g, W) n# F! ]" l R tid char(32),2 L4 t5 e# |7 C8 c% S
location char(32),. p: R, K; k& ]" }' K1 {9 p
posx int(20),
) S$ W( H6 i9 L posy int(20),0 r5 X+ K; j+ Z( F( C% W7 X& Q
comment_num int(11),
( J" X, [2 W$ G B) s/ `: Q like_num int(11),# D1 x7 V6 ]* c" P* G
pic_url TEXT,0 a4 \& t9 t9 J' {) Y/ h1 F
pic_num int,/ [" O/ S+ w* `# K+ ]1 v; z
source_appidchar(32),
" Q! X$ X$ ?( s! I$ c source_name char(32),
0 b4 J. ]7 O0 Z is_tran char,
$ Q2 J ]7 ]8 X- f trans_num char(32),5 ^. x, a. P; |/ O9 Y2 T! ~# q
trans_content TEXT
6 C6 K. f5 W1 K9 O. C );
. n6 G9 n2 \( a/ {* V '''
) ~! ?- X( ? O* m8 L6 W K! H cursor.execute(msg)
. M9 a8 e% l9 m/ {9 d cmt = '''
! l9 W1 m. ^' P& {" L9 ]8 w create table comment+ @" E3 S A% k8 e. Z0 W3 G/ }
(
3 G6 U, c, Z* D, R0 q tid char(32),
) s7 D1 T" l) @/ r& l$ K0 X# l id text,
( ~7 v% {8 D3 ~ v: G3 H) [6 z5 X9 N name char(100),
" g* W8 d% y7 W* y+ t: j content TEXT,
8 t4 f8 U+ a( Y( s createtime timestamp,0 x+ P7 i0 l/ v' y2 {8 A
reply TEXT
X3 s5 @7 E7 w5 G7 L* c! o" f )
2 p+ }: J4 |$ A5 m; D+ P% c '''1 ?9 ?6 |* p& g3 J3 N ?$ L3 @6 ^
cursor.execute(cmt)) q' j k& Z6 E
like_table=''') S b, I3 ~) ~
create table like_table
# D0 k# g/ T( f! A1 r" q( { (3 K3 x+ l' j4 @, |- S/ O5 w
tid char(32),
" s6 }8 B+ N+ t id text," @; x1 G& X! \2 y9 ^& J& o8 Z7 D
name char(100),2 u& O+ x) b/ w; k2 ?/ j6 u% F# }
addr char(32),
5 m1 f1 a3 h& l constellation char(32)、# r4 _5 ~* Y4 J6 T; q! f
gender char(4)、0 [. H. F2 J# D
if_qq_friend int(1)、
* c( n' d4 l- ^$ ?if_special_care int(1)、. f0 N4 ^. B3 h; R1 n
is_special_vip int(1)、; h* _) q& `; U8 }. W* X* ]( b& u
portrait TEXT
3 ~, E+ p+ x2 k/ W )
( j7 n2 l8 J4 }% P '''3 A2 D$ C, q2 N6 Y. c: c9 n8 c
cursor.execute(like_table)
$ a4 t4 S B; X self.conn.commit()
/ e- d$ y+ @/ n( l3 Z, V+ D return 17 ~ \5 `6 P8 `: d# {; I
else:
8 u* Z3 D6 e" N0 t! o/ t return 0
& m# y3 j& C( X2 \7 e9 ]1 r7 x' B8 }5 t$ J. e3 N2 `3 [6 _& {- q; }
————————————————
% m; |( a+ A& S- {6 ]5 S, v |
|