邮箱扒头像来告诉你如何写简单的脚本扒图
目录[-]
用邮箱扒头像来告诉你怎么写简单的脚本扒图
手上有几十万邮箱,本来用户系统没有做头像的东西,现在想根据这些邮箱能拿一部分用户的头像,可以直接使用
gravatar的服务,不过这玩意儿不时会被墙,还是拉回来靠谱,第2个途径是qq邮箱,通过分析数据发现,这几十万
用户里面居然有一半以上是qq邮箱,so 要想办法通过不用oauth的方式拿到.
思路与技术选择
作为一个pythoner,有很多爬虫框架可以选择,例如scrapy pyspider 没错有中文 有ui 有时间调度
爬虫框架会给你做很多事情,基本的东西入parse 回调等等,重要的功能室可以用深度或者广度优先算法进行类似下一页
的爬取,
更好一些的
给你简单的方式去做agent伪装,proxy伪装,密码验证,时间调度等等.
但是邮箱扒图这种事情就是拿到url后直接抓回来就好, 没必要这么兴师动众,so requests就够了。
要做的事情
down回图,但是不要default的图片,例如qq的头像如果没有的话会给几种尺寸的默认图片,但是我不想要这个东西,没有就是没有
可以再扒图的进程挂掉后可以让他回复掉之前的现场(我可不想一次次重新抓, 几十万邮箱呢)
可以用多个进程,加快爬取速度
下面开始讲实现
第一步是获得url,如果你不介意gravatar会被墙,qq的连接会变(毕竟不是文档给出的地址), 这个地方就够了。
根据邮箱获得url
gravatar
gravatar文档
gravatar python实现
如需梯子请自备。
gravatar没什么可以说的,就是拿到md5后的qq邮箱
需要注意的参数 s是尺寸,gravatar做的比较好,基本什么尺寸都有
d是默认参数,不想用默认头像的时候填404,gravatar会返回404的响应, 其他参数请自己看文档
http://q4.qlogo.cn/g?b=qq&nk=491794128&s=1
qq连接则比较容易拿到(不要问我怎么找到的,我忘了)
nk是qq号,qq邮箱也可以
s为图片大小,我扒了一下发现里面有这么多的size尺寸 1
2 3 4 5 40 41 100 140 160 240 640
,
1~5是都有的尺寸,其中2对应4040, 4对应100100, 但是请注意,不是每个人都有100大小的图(10年前传的头像,从来没改过,真的有这种用户, 我身边就有…)
这篇帖子告诉你怎么免appid通过QQ号获取到QQ昵称和头像
里面提到了php curl反盗链抓东西 可惜是php的,我已经改为python的了,
python版, 虽然最终的实现没用用到这个东西(qq有可以直接访问的连接oh yeah),但是不一定什么时候就用到了。
下面是贴了5个大小的图,不确定能不能再github or osc or sf上显示
不能显示请点1
不能显示请点2
不能显示请点3
不能显示请点4
不能显示请点5
上代码前先说遇到的问题
Like所有的爬虫可能会遇到的问题,你需要伪装AGENTS, 否则爬虫可能会被禁掉,因为我爬qq的时候发现,一段时间后qq头像的大小变为了0,一定是出事情了。
可能你在我代码里面会看到我用邮箱.jpg命名了抓回来的图,这是因为我想写一个简单的东西看看这些图。
gravatar的用户量, 这个比例一直再将,从40人1人,到60人1人,在我抓到6万邮箱的时候发现这个比例大体是100人中有1人
关于无视默认图片, gravatar直接使用404判断,这个简单。qq麻烦些,首先先download回默认的几个图,然后md5下这个图,这样下载qq图的时候对比下这个md5码,一样则说明是默认图片,pass.
关于恢复现场,log会帮你,善用log。
关于多进程,这个最简单,还记得学算法时的思路么,大任务化为小任务即可,因此把总的邮件列表拆为几个part,脚本再做一些支持就可以同时用几个进程来跑了。
上代码
最新代码请看这里
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
#!/usr/bin/env
python
#
-*- coding: utf- 8
-*-
import
requests
import
hashlib
import
urllib
import
sys
import
os
import
random
from
functools import
partial
AGENTS
= [
"Avant
Browser/1.2.789rel1 (http://www.avantbrowser.com)" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5" ,
"Mozilla/5.0
(Windows; U; Windows NT 5.2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.310.0 Safari/532.9" ,
"Mozilla/5.0
(Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.514.0 Safari/534.7" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1" ,
"Mozilla/5.0
(Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre" ,
"Mozilla/5.0
(Windows; U; Windows NT 5.1; en-US; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.0; en-GB; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11 (.NET CLR 3.5.30729)" ,
"Mozilla/5.0
(Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB5" ,
"Mozilla/5.0
(Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)" ,
"Mozilla/5.0
(Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1" ,
"Mozilla/5.0
(Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1" ,
"Mozilla/5.0
(Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110622 Firefox/6.0a2" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1" ,
"Mozilla/5.0
(Windows NT 6.1; WOW64; rv:2.0b4pre) Gecko/20100815 Minefield/4.0b4pre" ,
]
QQ_MD5_ESCAPE
= [ '11567101378fc08988b38b8f0acb1f74' ,
'9d11f9fcc1888a4be8d610f8f4bba224' ]
LOG_FILES
= 'scrapy_{}.log'
EMAIL_LIST
= 'email_list_{}.json'
AVATAR_PATH
= 'avatar/{}{}'
LOG_LEVEL_EXISTS
= 'EXISTS'
LOG_LEVEL_NOTSET_OR_ERROR
= 'NOTSET_OR_ERROR'
LOG_LEVEL_TYPE_ERROR
= 'TYPE_ERROR'
LOG_LEVEL_ERROR
= 'ERROR'
LOG_LEVEL_FAIL
= 'FAIL'
LOG_LEVEL_SUCCESS
= 'SUCCESS'
LOG_LEVEL_IGNORE
= 'IGNORE'
def
get_gravatar_url(email, default_avatar=None, use_404=False, size= 100 ):
data
= {}
if
default_avatar and default_avatar.startswith( 'http' ):
data[ 'd' ]
= default_avatar
if
use_404:
data[ 'd' ]
= '404'
data[ 's' ]
= str(size)
gravatar_url
= "http://secure.gravatar.com/avatar/"
+ hashlib.md5(email.lower()).hexdigest() + "?"
gravatar_url
+= urllib.urlencode(data)
return
gravatar_url
def
get_random_headers():
agent
= random.choice(AGENTS)
headers
= { 'User-Agent' :
agent}
return
headers
def
check_logfile(part):
last_scrapy_line
= 1
if
os.path.exists( 'scrapy_{}.log' .format(part)):
with
open( 'scrapy_{}.log' .format(part))
as log_read:
for
line in log_read:
last_scrapy_line
= max(last_scrapy_line, int (line.split()[ 0 ]))
print
last_scrapy_line
return
last_scrapy_line + 1
def
get_log_message(log_format= '{index}
{level} {email} {msg}' ,
index=None, level=None, email=None, msg=None):
return
log_format.format(index=index, level=level, email=email, msg=msg)
SUCCESS_LOG
= partial(get_log_message, level=LOG_LEVEL_SUCCESS, msg= 'scrapyed
success' )
EXIST_LOG
= partial(get_log_message, level=LOG_LEVEL_EXISTS, msg= 'scrapyed
already' )
FAIL_LOG
= partial(get_log_message, level=LOG_LEVEL_FAIL, msg= 'scrapyed
failed' )
NOT_QQ_LOG
= partial(get_log_message, level=LOG_LEVEL_TYPE_ERROR, msg= 'not
qq email' )
IGNORE_LOG
= partial(get_log_message, level=LOG_LEVEL_TYPE_ERROR, msg= 'ignore
email' )
EMPTY_SIZE_LOG
= partial(get_log_message, level=LOG_LEVEL_ERROR, msg= 'empty
avatar' )
UNEXCEPT_ERROR_LOG
= partial(get_log_message, level=LOG_LEVEL_ERROR, msg= 'unexcept
error' )
def
write_log(log, msg):
log.write(msg)
log.write( '\n' )
log.flush()
def
save_avatar_file(filename, content):
with
open(filename, 'wb' )
as avatar_file:
avatar_file.write(content)
def
scrapy_context(part, suffix= '.jpg' ,
rescrapy=False, hook=None):
last_scrapy_line
= check_logfile(part)
index
= last_scrapy_line
with
open(LOG_FILES.format(part), 'a' )
as log:
with
open(EMAIL_LIST.format(part)) as list_file:
for
linenum, email in enumerate(list_file):
if
linenum < last_scrapy_line:
continue
email
= email.strip()
if
not rescrapy:
if
os.path.exists(AVATAR_PATH.format(email, suffix)):
print
EXIST_LOG(index=index, email=email)
index
+= 1
continue
if
not hook:
raise
NotImplementedError()
try :
hook(part,
suffix=suffix, rescrapy=rescrapy, log=log, index=index, email=email)
except
Exception as ex:
print
UNEXCEPT_ERROR_LOG(index=index, email=email)
write_log(log,
UNEXCEPT_ERROR_LOG(index=index, email=email))
raise
ex
index
+= 1
def
scrapy_qq_hook(part, suffix= '.jpg' ,
rescrapy=False, log=None, index=None, email=None):
if
'qq.com'
not in email.lower():
print
NOT_QQ_LOG(index=index, email=email)
write_log(log,
NOT_QQ_LOG(index=index, email=email))
return
url
= 'http://q4.qlogo.cn/g?b=qq&nk={}&s=4' .format(email)
response
= requests.get(url, timeout= 10 ,
headers=get_random_headers())
if
response.status_code == 200 :
#
判断用户是否有大图标, 如果没有则请求小图标
if
hashlib.md5(response.content) in QQ_MD5_ESCAPE:
url
= 'http://q4.qlogo.cn/g?b=qq&nk={}&s=2' .format(email)
response
= requests.get(url, timeout= 10 ,
headers=get_random_headers())
if
response.status_code == 200 :
if
not len(response.content):
print
EMPTY_SIZE_LOG(index=index, email=email)
write_log(log,
EMPTY_SIZE_LOG(index=index, email=email))
#
这里再次判断是因为上一个 200 判断做了一次图片check
if
response.status_code == 200 :
save_avatar_file(AVATAR_PATH.format(email,
suffix), response.content)
print
SUCCESS_LOG(index=index, email=email)
write_log(log,
SUCCESS_LOG(index=index, email=email))
else :
print
FAIL_LOG(index=index, email=email)
write_log(log,
FAIL_LOG(index=index, email=email))
def
scrapy_gravatar_hook(part, suffix= '.jpg' ,
rescrapy=False, ignore_email_suffix=None, log=None, index=None, email=None):
if
ignore_email_suffix and ignore_email_suffix in email.lower():
print
IGNORE_LOG(index=index, email=email)
write_log(log,
IGNORE_LOG(index=index, email=email))
return
response
= requests.get(get_gravatar_url(email, use_404=True), timeout= 10 ,
headers=get_random_headers())
if
response.status_code == 200 :
save_avatar_file(AVATAR_PATH.format(email,
suffix), response.content)
print
SUCCESS_LOG(index=index, email=email)
write_log(log,
SUCCESS_LOG(index=index, email=email))
else :
print
FAIL_LOG(index=index, email=email)
write_log(log,
FAIL_LOG(index=index, email=email))
return
scrapy_gravatar
= partial(scrapy_context, hook=scrapy_gravatar_hook)
scrapy_qq
= partial(scrapy_context, hook=scrapy_qq_hook)
FUNC_MAPPER
= {
'qq' :
scrapy_qq,
'gravatar' :
scrapy_gravatar,
}
if
__name__ == '__main__' :
scrapy_type
= sys.argv[ 1 ]
part
= sys.argv[ 2 ]
if
scrapy_type not in FUNC_MAPPER:
print
'type
should in [qq | gravatar]'
exit( 0 )
FUNC_MAPPER[scrapy_type](part)
|
简单用法
pip install requests
-
将scrapy_avatar.py放到某文件夹下例如
/opt/projects/scripts
-
mkdir /opt/projects/scripts/avatar
-
将你的文件列表放到email_list_0.json里面
-
python scrapy_avatar.py gravatar 0
或者python scrapy_avatar.py qq 0
简单说明
当email_list比较大的时候, 为了使用更多的进程你可以将email_list拆分成多个list
例如 email_list_0.json
email_list_1.json
你就可以使用 python
scrapy_avatar.py gravatar 0
python
scrapy_avatar.py gravatar 1
起两个进程来抓
其他feature请阅读代码,更改里面的两个hook方法
吐槽
-
因为这是一个简单的脚本,因此懒得用click做脚本参数处理,只依赖于requests, 参数判断就懒得写了.
-
本来在
scrapy_context
那个for循环里使用的是contextmanager yield来做的,但是有个奇怪的RuntimeError generator didn't stop
, 无奈将yield改为hook的方法. -
qq的头像有些奇怪的问题,例如不是没人都有100大小的图,但是没人都有40大小的图, 因此优先拿大图, 在qq那边就做了一次判断.
-
没有把context以及hook的其他方法配到脚本里面去,需要的人请自行修改.
把这个用chrome打开会很炫的
附: 简单的显示linux服务器图片的方式 Flask+nginx
django比较重,Flask+nginx就够了,因为没有任何其他需求
pip install flask
app.py丢到抓图的地方,改下nginx里面头像地址的root,丢进/etc/nginx/site-enable去 reload nginx, 别忘了host添上localtest
flask代码 app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#!/usr/bin/env
python
#
-*- coding: utf- 8
-*-
from
flask import
Flask, send_from_directory, safe_join
import
os
app
= Flask(__name__)
app.debug
= True
@app .route( "/" )
def
hello():
avatars
= os.listdir( 'avatar' )
avatars
= sorted(avatars)
html
= '\n' .join( "<img
src='/avatar/{}' />" .format(avatar)
for
avatar in avatars)
return
html
if
__name__ == "__main__" :
app.run(host= '0.0.0.0' ,
port= 11111 )
|
nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
upstream
localtest-backend {
server
127.0 . 0.1 : 11111
fail_timeout= 0 ;
}
server
{
listen
80 ;
server_name
localtest.com;
location
~ /avatar/(?P<file>.*) {
root
/opt/projects/scripts/ new ;
try_files
/avatar/$file /avatar/$file = 404 ;
expires
30d;
gzip
on;
gzip_types
text/plain application/x-javascript text/css application/javascript;
gzip_comp_level
3 ;
}
location
/ {
proxy_pass
http: //localtest-backend;
}
}
|