免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3112 | 回复: 0
打印 上一主题 下一主题

zz用python取qq的好友列表 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-18 17:06 |只看该作者 |倒序浏览

使用Python模拟登录QQ邮箱获取QQ好友列表
        关键字: 登录 qq邮箱 python django 获取 qq 好友列表
  
  
   
最近因开发项目的需要,有一个需求,就是很多SNS网站都有的通过 Email地址 导入好友列表,不过这次要导入的不是Email 列表,而是QQ的好友列表。
实现方式:
通过google一搜,实现的方式大概有下面这篇文章提到的几种方法:
http://www.cnblogs.com/hblhs/archive/2008/07/30/1256597.html

最后我选择了通过模拟登录QQ邮箱的方式来实现,该实现方式在海内网上的好友查找功能也可以看到。
QQ邮箱的官方登陆地址是
http://mail.qq.com/
与其他大部分邮箱不同的是,如果使用纯数字的QQ号登录的话,除了密码,还需要输入验证码。
看到海内上的QQ好友导入功能也是需要输入验证码的,而且验证码的样子和QQ邮箱的很像。由于这是需要在用户手动输入密码的情况下才能实现的功能,因此输入验证码的工作也可以让用户手动来完成。

验证码处理:
通过对  
http://mail.qq.com/
页面的分析,  QQ邮箱的验证码方式实现原理其实是很简单,当需要一张验证码图片或看不清而需要换一张时,它都是向地址
http://ptlogin2.qq.com/getimage?aid=23000101

出请求,(页面上该地址是通过js生成的,为了防止浏览器缓存,地址末尾还会带有随机一个随机数),而该链接不但返回一张图片,还在http头部带有设置
cookie的一段header。这样当用户提交表单的时候,浏览器就会把该cookie发送回服务器,服务器通过比较
该cookie值和经过某种运算后的表单中的验证码值 就可以判断验证码是否填写正确。

现在的问题是由于cookie的安全机制,验证码图片不能直接从腾讯的服务器上去取,那样用户在将QQ和密码发送到我们的服务器时,验证码的cookie不会一起发过来。

解决方式其实也很简单,将验证码的获取地址改为我们自己的服务器,我们的服务器作为简单的代理,从腾讯的服务器上去获取真正的验证码,再将图片内容和那段cookie发送回用户浏览器。那样用户提交表单的时候,那段cookie就又会发送回我们的服务器了。

绕过其他验证安全机制:
一般上有了账号,密码,验证码这3样东西就可以实现模拟登录很多网站了,但是QQ邮箱还有其他的安全机制,在QQ邮箱登陆的表单中还有一个像这样
  的 hidden
域,该value每次刷新页面都会改变,同时在表单提交的时候,还会通过js将该值与其他hidden 域的值进行某些计算才正式提交表单。

通过多次模拟登录,估计该值是用来判断登录session超时的,同时也参与其他的一些干扰加密的计算。而且该值与验证码是完全无关的,因此在显示我们表单时,只要先去抓取一下  
http://mail.qq.com/
  页面,从里面提取出ts 值, 连同其他所有 hidden 域 和相关计算的js代码放入我们的表单中就可以了。

因此,实际上我们的表单只需要稍微修改一下   
http://mail.qq.com/
  页面的内容就可以作为显示给用户的表单。主要包括以下几个方面,这里我使用的django,所以使用django的模板语法:
1、    改为  
2、表单的action地址改为我们自己这里假设为 /friends/    因此
改为

3、图片验证码地址,有两个地方要改:
document.write("");
改为
document.write("");

另外一个changeimg 函数内, 也将相应的地址改为我们自己的服务器即可。

改了这些,页面看上去和原来几乎一样,只是所有交互都改到了我们的服务器上,出于版权和页面统一的需要,在使用到自己的网站上时,可以使用自己设计
的页面,只要表单的初始化和提交与原来一样就可以了,甚至也可以通过阅读js部分的源代码,把ts部分的计算移到服务器端进行。

示例代码:
以下是整个views.py的代码,包括后面会讲到模拟登录部分,login和qq_captcha分别用来初始化登陆页和获取图片验证码:
Python代码
  • # Create your views here.  
  • from django.shortcuts import render_to_response  
  • from urllib2 import Request, urlopen, build_opener, HTTPCookieProcessor  
  • from urllib import urlencode  
  • from cookielib import CookieJar  
  • from django.http import HttpResponse  
  • import re  
  • from xml.sax.saxutils import unescape  
  • from BeautifulSoup import BeautifulSoup  
  • server_no = 'm11'  
  • login_error_re = re.compile('"errtype=(\d)"')  
  • login_succ_re = re.compile('"frame_html\?sid=(.+?)"')  
  • hacked_friendlist_page_re = re.compile(r'\.+?\', re.DOTALL)  
  • body_re = re.compile(r'\.+?\', re.DOTALL)  
  •   
  • def login(request):  
  •     url = 'http://mail.qq.com/'  
  •     re_obj = re.compile(r'name="ts"\svalue="(\d+)"')  
  •     match_obj = re_obj.search(urlopen(url).read())  
  •     ts = match_obj.group(1)  
  •     return render_to_response('login.html', locals())  
  •   
  • def qq_captcha(request):  
  •     url = 'http://ptlogin2.qq.com/getimage?aid=%s' % request.GET['aid']  
  •     f = urlopen(url)  
  •     r = HttpResponse(f.read(), mimetype = f.info()['Content-Type'], )  
  •     r['Pragma'] = 'no-cache'  
  •     r.set_cookie('verifysession', f.info()['Set-Cookie'].split(';')[0].split('=')[1].strip())  
  •     return r  
  •   
  • def qq_friends(request):  
  •     for k in request.POST:  
  •         print '%s : %s' % (k, request.POST[k])  
  •     verifysession = request.COOKIES['verifysession']  
  •     print verifysession  
  •     headers = {'Cookie':'''''verifysession=%s''' % verifysession,  
  •                'Content-Type':'application/x-www-form-urlencoded',  
  •                'Referer':'http://mail.qq.com/',  
  •                'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1',  
  •                }  
  •     data = urlencode(request.POST)  
  •     login_request = Request('http://%s.mail.qq.com/cgi-bin/login?sid=0,2,zh_CN' % server_no, data, headers)  
  •     result = urlopen(login_request)  
  •     content = result.read()  
  •     login_error = login_error_re.search(content)  
  •     if login_error:  
  •         error_no = login_error.group(1) #1:password wrong 2: captcha wrong  
  •         if error_no == '1':  
  •             error_msg = 'password or qq wrong'  
  •         elif error_no == '2':  
  •             error_msg = 'captcha wrong'  
  •         return render_to_response('friends.html', locals())  
  •     sid = login_succ_re.search(content).group(1)  
  •          
  •     friends_list_headers = {'Referer':'http://mail.qq.com/',  
  •                            'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1',  
  •                            }  
  •     friends_list_request = Request('http://%s.mail.qq.com/cgi-bin/addr_listall?sid=%s&sorttype=null&category=common' % (server_no, sid), headers = friends_list_headers)  
  •     cj = CookieJar()  
  •     cj.extract_cookies(result, friends_list_request)  
  •     opener = build_opener(HTTPCookieProcessor(cj))  
  •     result = opener.open(friends_list_request)  
  •     grouplist = hacked_friendlist_page_re.search(result.read().decode('gb2312', 'ignore')).group(0)  
  •     soup = BeautifulSoup(grouplist, fromEncoding = 'utf-8')  
  •     grouplist = soup.findAll('li')  
  •     friend_list = {}  
  •     for group in grouplist:  
  •         friend_list[group.a.string] = []  
  •         list_request = Request('http://%s.mail.qq.com%s' % (server_no, group.a['href']), headers = friends_list_headers)  
  •         result = opener.open(list_request)  
  •         body = BeautifulSoup(body_re.search(result.read().decode('gb2312', 'ignore')).group(0), fromEncoding = 'utf-8')  
  •         friends = body.findAll('div', attrs={'class':'M'})  
  •         for friend in friends:  
  •             friend_name = unescape(friend.p.span.contents[1].replace(' ', '', 1))  
  •             friend_email = friend.p.img['addr']  
  •             friend_list[group.a.string].append((friend_name, friend_email))  
  •       
  •     return render_to_response('friends.html', locals())  # Create your views here.
    from django.shortcuts import render_to_response
    from urllib2 import Request, urlopen, build_opener, HTTPCookieProcessor
    from urllib import urlencode
    from cookielib import CookieJar
    from django.http import HttpResponse
    import re
    from xml.sax.saxutils import unescape
    from BeautifulSoup import BeautifulSoup
    server_no = 'm11'
    login_error_re = re.compile('"errtype=(\d)"')
    login_succ_re = re.compile('"frame_html\?sid=(.+?)"')
    hacked_friendlist_page_re = re.compile(r'\.+?\', re.DOTALL)
    body_re = re.compile(r'\.+?\', re.DOTALL)
    def login(request):
        url = 'http://mail.qq.com/'
        re_obj = re.compile(r'name="ts"\svalue="(\d+)"')
        match_obj = re_obj.search(urlopen(url).read())
        ts = match_obj.group(1)
        return render_to_response('login.html', locals())
    def qq_captcha(request):
        url = 'http://ptlogin2.qq.com/getimage?aid=%s' % request.GET['aid']
        f = urlopen(url)
        r = HttpResponse(f.read(), mimetype = f.info()['Content-Type'], )
        r['Pragma'] = 'no-cache'
        r.set_cookie('verifysession', f.info()['Set-Cookie'].split(';')[0].split('=')[1].strip())
        return r
    def qq_friends(request):
        for k in request.POST:
            print '%s : %s' % (k, request.POST[k])
        verifysession = request.COOKIES['verifysession']
        print verifysession
        headers = {'Cookie':'''verifysession=%s''' % verifysession,
                   'Content-Type':'application/x-www-form-urlencoded',
                   'Referer':'http://mail.qq.com/',
                   'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1',
                   }
        data = urlencode(request.POST)
        login_request = Request('http://%s.mail.qq.com/cgi-bin/login?sid=0,2,zh_CN' % server_no, data, headers)
        result = urlopen(login_request)
        content = result.read()
        login_error = login_error_re.search(content)
        if login_error:
            error_no = login_error.group(1) #1:password wrong 2: captcha wrong
            if error_no == '1':
                error_msg = 'password or qq wrong'
            elif error_no == '2':
                error_msg = 'captcha wrong'
            return render_to_response('friends.html', locals())
        sid = login_succ_re.search(content).group(1)
            
        friends_list_headers = {'Referer':'http://mail.qq.com/',
                               'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1',
                               }
        friends_list_request = Request('http://%s.mail.qq.com/cgi-bin/addr_listall?sid=%s&sorttype=null&category=common' % (server_no, sid), headers = friends_list_headers)
        cj = CookieJar()
        cj.extract_cookies(result, friends_list_request)
        opener = build_opener(HTTPCookieProcessor(cj))
        result = opener.open(friends_list_request)
        grouplist = hacked_friendlist_page_re.search(result.read().decode('gb2312', 'ignore')).group(0)
        soup = BeautifulSoup(grouplist, fromEncoding = 'utf-8')
        grouplist = soup.findAll('li')
        friend_list = {}
        for group in grouplist:
            friend_list[group.a.string] = []
            list_request = Request('http://%s.mail.qq.com%s' % (server_no, group.a['href']), headers = friends_list_headers)
            result = opener.open(list_request)
            body = BeautifulSoup(body_re.search(result.read().decode('gb2312', 'ignore')).group(0), fromEncoding = 'utf-8')
            friends = body.findAll('div', attrs={'class':'M'})
            for friend in friends:
                friend_name = unescape(friend.p.span.contents[1].replace(' ', '', 1))
                friend_email = friend.p.img['addr']
                friend_list[group.a.string].append((friend_name, friend_email))
       
        return render_to_response('friends.html', locals())

    模板 friends.html 的代码:
    Html代码
  • >  
  • html>  
  • head>  
  • title>好友列表title>  
  • meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  • head>  
  • body>  
  • {%if error_msg%}  
  • {{error_msg}}  
  • {%endif%}  
  •   
  • {%for group, friends in friend_list.items %}  
  • ul>  
  • {{group}}:  
  •     {%for name, email in friends%}   
  •     li>{{name|safe}} {{email}}li>  
  •     {%endfor%}  
  • ul>  
  • {%endfor%}  
  • body>  
  • html>  
    好友列表
    {%if error_msg%}
    {{error_msg}}
    {%endif%}
    {%for group, friends in friend_list.items %}
    {{group}}:
            {%for name, email in friends%}
            {{name|safe}} {{email}}
            {%endfor%}
    {%endfor%}

    模拟登录部分都在view qq_friends 里面进行,基本上就像前面说得,获取图片验证码的cookie,将表单提交的值原样发送post到腾讯的服务器,如果都正确的话就可以登录了。
    发送的结果大概分为3种,验证码错误,账号或密码错误,登陆成功。不管在哪种情况下,腾讯的服务器都是返回一段js代码,通过那段代码重定向到错误页或邮箱登陆后的首页。
    其中验证码错误像是下面这样:
    var urlHead="http://m11.mail.qq.com/cgi-bin/"; var
    targetUrl=""; var mailto=""; targetUrl = urlHead + "loginpage?"
    +"errtype=2" +"&verify=true" +"&clientuin=12345678" +"&t="
    +"&alias=" +"&regalias=" +"&delegate_url=" +"&title="
    +"&url=%2Fcgi-bin%2Flogin%3Fsid%3D0%2C2%2Czh_CN" +"&org_fun="
    +"&aliastype=@qq.com" +"&ss=" +"&from=" +"&autologin=n"
    if (targetUrl == "") { targetUrl = ""; } document.write("");

    location.href=targetUrl  

    账号或密码错误是这样:
    var urlHead="http://m11.mail.qq.com/cgi-bin/"; var
    targetUrl=""; var mailto=""; targetUrl = urlHead + "loginpage?"
    +"errtype=1" +"&verify=false" +"&clientuin=172564012"
    +"&t=" +"&alias=" +"&regalias=" +"&delegate_url="
    +"&title=" +"&url=%2Fcgi-bin%2Flogin%3Fsid%3D0%2C2%2Czh_CN"
    +"&org_fun=" +"&aliastype=@qq.com" +"&ss=" +"&from="
    +"&autologin=n" if (targetUrl == "") { targetUrl = ""; }
    document.write("");   location.href=targetUrl

    登录成功是这样:
    var urlHead="http://m11.mail.qq.com/cgi-bin/"; var
    targetUrl=""; var mailto=""; targetUrl = urlHead +
    "frame_html?sid=UJh1e2XMWhOEbWcu"; if (targetUrl == "") { targetUrl =
    ""; } document.write("");   location.href=targetUrl  

    其中登录成功后的反馈不但是上面的js代码,还包括一大堆cookie,与邮箱服务器的后续交互都需要用到这些cookie。
    另外上面js代码中frame_html?sid=UJh1e2XMWhOEbWcu的sid部分需要多次用到在后面抓取好友列表页面的url中,所以也需要提取出来。

    登录邮箱后,就可以抓取好友列表了,可以通过先访问页面
    http://m11.mail.qq.com/cgi-bin/addr_listall?sid=UJh1e2XMWhOEbWcu&sorttype=null&category=common
    该页面的部分可以获取好友分组名称和相应的url地址。
    然后在相应的访问各个好友分组的页面的就可以获取好友列表了。

    这里由于我自己的QQ号限制,不知道如果没有好友没有分组页面会是什么情况,以及如果某个分组的好友很多的话,该好友列表页面是否会有分页(目前是没有见到分页)。有什么错误的话可以留言给我。


    其他说明:
    1、由于有些QQ用户给自己取火星文昵称,导致抓取的页面上很容易出现字符编码错误及HTML转义问题。
    2、不知道是故意为了防止抓取还是设计不规范,好多页面用BeautifulSoup直接实例化,会导致大量信息的丢失,所以上面的代码都是尽可能先用正则表达式提取必须部分的信息。
    3、由于QQ邮箱服务器的代码也在升级(事实上,我前几天刚写的代码就刚失效了,马上做了一些修改,昨天海内上的那个功能也出现了问题,看来他们也
    是通过QQ邮箱方式来实现的),所以并不能保证你在看到本文章时,上面的代码依然使用有效。但是总体上的思路依然可以提供参考。


    Demo地址:
    http://www.playdjango.cn/qq-friends/
    由于要输入自己的QQ密码,如果你觉得不可信的话还是别试了,你也可以把上面的代码整理一下弄到自己的机器上进行测试也一样。
                   
                   
                   

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/43391/showart_1867469.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP