最近因开发项目的需要,有一个需求,就是很多SNS网站都有的通过 Email地址 导入好友列表,不过这次要导入的不是Email 列表,而是QQ的好友列表。

实现方式:

通过google一搜,实现的方式大概有下面这篇文章提到的几种方法:

最后我选择了通过模拟登录QQ邮箱的方式来实现,该实现方式在海内网上的好友查找功能也可以看到。

与其他大部分邮箱不同的是,如果使用纯数字的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.

fromdjango.shortcutsimportrender_to_response

fromurllib2importRequest, urlopen, build_opener, HTTPCookieProcessor

fromurllibimporturlencode

fromcookielibimportCookieJar

fromdjango.httpimportHttpResponse

importre

fromxml.sax.saxutilsimportunescape

fromBeautifulSoupimportBeautifulSoup

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)

deflogin(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)

returnrender_to_response('login.html', locals())

defqq_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())

returnr

defqq_friends(request):

forkinrequest.POST:

print'%s : %s'% (k, request.POST[k])

verifysession = request.COOKIES['verifysession']

printverifysession

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)

iflogin_error:

error_no = login_error.group(1)#1:password wrong 2: captcha wrong

iferror_no =='1':

error_msg ='password or qq wrong'

eliferror_no =='2':

error_msg ='captcha wrong'

returnrender_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 = {}

forgroupingrouplist:

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'})

forfriendinfriends:

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))

returnrender_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 PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

好友列表

{%if error_msg%}

{{error_msg}}

{%endif%}

{%for group, friends in friend_list.items %}

{{group}}:

{%for name, email in friends%}

{{name|safe}} {{email}}

{%endfor%}

{%endfor%}

好友列表

{%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代码,通过那段代码重定向到错误页或邮箱登陆后的首页。

其中验证码错误像是下面这样:

账号或密码错误是这样:

登录成功是这样:

其中登录成功后的反馈不但是上面的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

该页面的

然后在相应的访问各个好友分组的页面的就可以获取好友列表了。

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

其他说明:

1、由于有些QQ用户给自己取火星文昵称,导致抓取的页面上很容易出现字符编码错误及HTML转义问题。

2、不知道是故意为了防止抓取还是设计不规范,好多页面用BeautifulSoup直接实例化,会导致大量信息的丢失,所以上面的代码都是尽可能先用正则表达式提取必须部分的信息。

3、由于QQ邮箱服务器的代码也在升级(事实上,我前几天刚写的代码就刚失效了,马上做了一些修改,昨天海内上的那个功能也出现了问题,看来他们也是通过QQ邮箱方式来实现的),所以并不能保证你在看到本文章时,上面的代码依然使用有效。但是总体上的思路依然可以提供参考。

Demo地址:

由于要输入自己的QQ密码,如果你觉得不可信的话还是别试了,你也可以把上面的代码整理一下弄到自己的机器上进行测试也一样。

python爬取qq邮箱_使用Python模拟登录QQ邮箱获取QQ好友列表相关推荐

  1. python爬取电影评分_用Python爬取猫眼上的top100评分电影

    代码如下: # 注意encoding = 'utf-8'和ensure_ascii = False,不写的话不能输出汉字 import requests from requests.exception ...

  2. python爬取高德数据_利用Python爬取高德地图数据

    准备1.高德开放平台注册账户 https://lbs.amap.com/dev/index 验证手机号码.邮箱后进入开发者后台创建一个应用: 并为该应用添加 Key,服务平台选择 web 服务 申请完 ...

  3. python爬取百度搜索_使用Python + requests爬取百度搜索页面

    想学一下怎样用python爬取百度搜索页面,因为是第一次接触爬虫,遇到一些问题,把解决过程与大家分享一下 1.使用requests爬取网页 首先爬取百度主页www.baidu.com import r ...

  4. python爬取bilibili弹幕_用Python爬取B站视频弹幕

    原标题:用Python爬取B站视频弹幕 via:菜J学Python 众所周知,弹幕,即在网络上观看视频时弹出的评论性字幕.不知道大家看视频的时候会不会点开弹幕,于我而言,弹幕是视频内容的良好补充,是一 ...

  5. python爬取文献代码_使用python爬取MedSci上的影响因子排名靠前的文献

    使用python爬取medsci上的期刊信息,通过设定条件,然后获取相应的期刊的的影响因子排名,期刊名称,英文全称和影响因子.主要过程如下: 首先,通过分析网站http://www.medsci.cn ...

  6. python爬取旅游信息_用Python爬取了全国近5000家旅游景点,分析国庆去哪玩

    2020 国庆马上就要到了 我想今年大家在家都憋坏了 今年国庆和中秋刚好又是同一天,加起来有 8 天假 这么长的假期,当然是出去 玩玩玩! 但是每次长假期间,你有没有想起被人山人海支配的恐惧呢? 那么 ...

  7. python爬取客流数据_【python爬取雅虎财经数据】“五一”和广交会客流叠加 广州出入境客流呈“双高峰”...

    [python爬取雅虎财经数据]"五一"和广交会客流叠加 广州出入境客流呈"双高峰" 发布时间:2020-10-13 18:06:00 阅读量:650 作者:乔 ...

  8. python爬取微博评论_用 python 爬取微博评论并手动分词制作词云

    最近上海好像有举行个什么维吾尔族的秘密时装秀,很好看的样子,不过我还没时间看.但是微博上已经吵翻了天,原因是 好吧,这不是我们关心的,我的心里只有学习 我爱学习 Python 爬虫 本次爬取的是这条微 ...

  9. python爬取百度文库_利用Python语言轻松爬取数据

    利用 Python 语言轻松爬取数据 对于小白来说,爬虫可能是一件非常复杂. 技术门槛很高的事情. 比如有人认为学爬虫必须精通 Python ,然后哼哧哼哧系统学习 Python 的每个知识点,很久之 ...

  10. python爬取行业数据_用Python进行Web爬取数据

    介绍 我们拥有的数据太少,无法建立机器学习模型.我们需要更多数据! 如果这句话听起来很熟悉,那么你并不孤单!希望获得更多数据来训练我们的机器学习模型是一个一直困扰人们的问题.我们无法在数据科学项目中获 ...

最新文章

  1. 当一个软件新版本提交测试时要有,如何改善没完没了的软件测试版本?
  2. mysql中error22_docker中将MySQL运行在容器中失败提示“ InnoDB : Error 22 with aio_write”的解决办法...
  3. 随想录(关于signal的实验)
  4. c语言如果调用的函数没有return,C语言中,整型函数若在调用时未获得return值,此时其值是如何确定的?...
  5. Android Studio 创建不同分辨率的图标
  6. 解决 U盘安装Windows Server 2012 R2 报错
  7. C/C++编程工具及实用小软件推荐
  8. 做博客推广的SEO外链计划
  9. 常数1的傅里叶变换详解过程
  10. 某代工大厂的勒索病毒处理案例
  11. 线性代数 | (1) 矩阵Part One
  12. 客户数据成为营销必备!成功关键是挖掘数据价值
  13. 强迫症患者之黑苹果优化(启动画面、CPU重命名、显示真实主板、开启12代CPU全核心)
  14. 根据图片名字在drawable中得到图片
  15. 其中的文件夹或文件已在另一个程序中打开怎么解决
  16. 请列举Nginx的一些特性
  17. orb-slam3:优化状态量是camera坐标系下RPV还是body坐标系下RPV探究
  18. exsist什么意思_exist什么意思_通达信EXIST什么意思
  19. python pandas 去重
  20. SuperMap iClient3D for WebGL教程(影像篇)-SingleTileImageryProvider

热门文章

  1. (24)STM32——待机唤醒(低功耗)笔记
  2. 李洪超 硬件工程师_做一个优秀硬件工程师
  3. T检验 ANOVA
  4. 畅想计算机的未来50字,新学期畅想50字
  5. 七夕妈妈生辰,岁月,请你善待妈妈
  6. 【时间之外】面向监狱的编程?该学学网络安全法了(2)
  7. 银行常用加密算法PINBlock加密
  8. 华东之旅--西塘第二天
  9. 使用xadmin搜索search_fields报错:Related Field got invalid lookup: icontains
  10. java中insteadof_Java代码规范小结(一)