首页 > 代码库 > 邮箱扒头像来告诉你怎么写简单的脚本扒图

邮箱扒头像来告诉你怎么写简单的脚本扒图

摘要 手上有几十万邮箱,本来用户系统没有做头像的东西,现在想根据这些邮箱能拿一部分用户的头像,可以直接使用 gravatar的服务,不过这玩意儿不时会被墙,还是拉回来靠谱,第2个途径是qq邮箱,通过分析数据发现,这几十万 用户里面居然有一半以上是qq邮箱,so 要想办法通过不用oauth的方式拿到.
爬虫

目录[-]

  • 用邮箱扒头像来告诉你怎么写简单的脚本扒图
  • 思路与技术选择
  • 要做的事情
  • 下面开始讲实现
  • 根据邮箱获得url
  • gravatar
  • qq
  • 上代码前先说遇到的问题
  • 上代码
  • 简单用法
  • 简单说明
  • 吐槽
  • 把这个用chrome打开会很炫的
  • 附: 简单的显示linux服务器图片的方式 Flask+nginx
  • flask代码 app.py
  • nginx
  • 用邮箱扒头像来告诉你怎么写简单的脚本扒图

    手上有几十万邮箱,本来用户系统没有做头像的东西,现在想根据这些邮箱能拿一部分用户的头像,可以直接使用 
    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的响应, 其他参数请自己看文档

    qq

    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

    不能显示请点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-*-
    importrequests
    importhashlib
    importurllib
    importsys
    importos
    importrandom
    from functools importpartial
     
     
    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 = http://www.mamicode.com/{}
        ifdefault_avatar and default_avatar.startswith(‘http‘):
            data[‘d‘] = default_avatar
        ifuse_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)
        returngravatar_url
     
     
    def get_random_headers():
        agent = random.choice(AGENTS)
        headers = {‘User-Agent‘: agent}
        returnheaders
     
     
    def check_logfile(part):
        last_scrapy_line = 1
        ifos.path.exists(‘scrapy_{}.log‘.format(part)):
            with open(‘scrapy_{}.log‘.format(part)) as log_read:
                forline in log_read:
                    last_scrapy_line = max(last_scrapy_line, int(line.split()[0]))
        print last_scrapy_line
        returnlast_scrapy_line + 1
     
     
    def get_log_message(log_format=‘{index} {level} {email} {msg}‘, index=None, level=None, email=None, msg=None):
        returnlog_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:
                forlinenum, email in enumerate(list_file):
                    iflinenum < last_scrapy_line:
                        continue
                    email = email.strip()
                    ifnot rescrapy:
                        ifos.path.exists(AVATAR_PATH.format(email, suffix)):
                            print EXIST_LOG(index=index, email=email)
                            index += 1
                            continue
                    ifnot 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())
        ifresponse.status_code == 200:
            # 判断用户是否有大图标, 如果没有则请求小图标
            ifhashlib.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())
                ifresponse.status_code == 200:
                    ifnot len(response.content):
                        print EMPTY_SIZE_LOG(index=index, email=email)
                        write_log(log, EMPTY_SIZE_LOG(index=index, email=email))
        # 这里再次判断是因为上一个200判断做了一次图片check
        ifresponse.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):
        ifignore_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())
        ifresponse.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]
        ifscrapy_type not in FUNC_MAPPER:
            print‘type should in [qq | gravatar]‘
            exit(0)
        FUNC_MAPPER[scrapy_type](part)

    简单用法

    pip install requests

    1. 将scrapy_avatar.py放到某文件夹下例如/opt/projects/scripts

    2. mkdir /opt/projects/scripts/avatar

    3. 将你的文件列表放到email_list_0.json里面

    4. 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方法

    吐槽

    1. 因为这是一个简单的脚本,因此懒得用click做脚本参数处理,只依赖于requests, 参数判断就懒得写了.

    2. 本来在scrapy_context那个for循环里使用的是contextmanager yield来做的,但是有个奇怪的RuntimeError generator didn‘t stop, 无奈将yield改为hook的方法.

    3. qq的头像有些奇怪的问题,例如不是没人都有100大小的图,但是没人都有40大小的图, 因此优先拿大图, 在qq那边就做了一次判断.

    4. 没有把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 importFlask, send_from_directory, safe_join
    importos
     
     
    app = Flask(__name__)
    app.debug = True
     
    @app.route("/")
    def hello():
        avatars = os.listdir(‘avatar‘)
        avatars = sorted(avatars)
        html = ‘\n‘.join("<img src=http://www.mamicode.com/‘/avatar/{}‘ />".format(avatar)foravatar in avatars)
        returnhtml
     
    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 {
        server127.0.0.1:11111fail_timeout=0;
    }
     
    server {
     
      listen80;
      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_level3;
      }
     
      location / {
            proxy_pass http://localtest-backend;
      }
    }

    邮箱扒头像来告诉你怎么写简单的脚本扒图