xuyaping

 

群里接收消息时,使用广播,但需要刷新页面才能接收到广播内容。

- 轮询:  定时每秒刷新一次,当群不活跃时,群里的每个客户端都在刷新,对服务端压力太大。

- 长轮询:客户端连服务端,服务端一直不断开,也不回消息。夯住请求(Web微信,WebQQ),
		  假设夯住60s,60s后统一断开,然后客户端和服务端连接失败。然后紧接着再发送一次请求。相当于每分钟发送一次请求。
		  夯住不动只要有一个人发送消息,立刻断开带着新信息返回。只要消息来了就返回断开,这样就实时接收消息。

		- 无消息,超时之后断开,客户端立即发送请求;
		- 有消息,立即返回
		
轮询和长轮询利用的是http协议,这种请求是单向的,目前长轮询使用广泛。

- WebSocket
	相比轮询和长轮询更好,客户端和服务端不断开,客户端和服务端可以相互接收消息。但是不是所有的浏览器都支持。目前还未大批量使用,以后是趋势。


1. 显示二维码
	打开微信网页微信二维码登录时,未扫码登录时二维码登录页面和微信服务端一直在长轮询状态。
	当手机扫码时,手机向微信服务端发送请求,直接拿到结果给微信网页端,页面登录状态改变。
	二维码本质是图片,每次刷新页面图片都不同,每次后缀都不同。
	向https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1504151392313(其中1504151392313是时间戳)
	发送请求获取响应window.QRLogin.code = 200; window.QRLogin.uuid = "wdLetLlNoQ==",二维码随机字符串uuid:"wdLetLlNoQ==",根据uuid创建二维码。
	src="https://login.weixin.qq.com/qrcode/wdLetLlNoQ=="
	src="https://login.weixin.qq.com/qrcode/YeOkCQK4FQ=="
 
 
	- 获取uuid
	- 根据uuid创建二维码
	


发送消息:
	post_data = {
		"BaseRequest": {
			\'xxx\': 123123123123,
			form
			to
			msg: 中文
			\'xxx\': 123123123123,
		}
	}
	# requests.post(json=post_data,headers={\'cotnen\':\'json\'})
	# requests.post(data=json.dumps(post_data),headers={\'cotnen\':\'json\'})
	
	# requests.post(data=json.dumps(post_data,ensure_ascii=False),headers={\'cotnen\':\'json\'})

开发web微信

- 打开wechat, 查看登录页面,猜想: 手机、web、微信服务器

- 二维码

- 扫码

- 确定登录
	- 登录cookie
	200,
	redirict_url: ticket
	- 凭证cookie
	
	- 初始化: 最近信息


- 显示头像
	因为跨域头像无法显示显示:
		我们的自己写的网站 http://127.0.0.1:
		浏览器上保存这个网站http://127.0.0.1相关cookie 
		访问我们自己的网站的图片时,携带我们自己的cookie	<img src=\'http://127.0.0.1\' />
		
		自己写的网站访问微信的图片时,携带着我们网站的cookie,这就跨域了,不能带着我们本地的cookie去。 <img src=\'http://wx.qq.com.....\' />
			<img src=\'http://wx.qq.com.....\' /> GET请求,get请求没有请求体,只有请求头
			请求头:url: http://wx.qq.com.....
					cookie: xxxx,							# 没有微信的cookie
					referer: http://127.0.0.1... ****		# 因为是自己写的网站访问微信图片,referer默认当着当前url,微信可以通过referer阻拦访问,同样cookie也可以阻止访问
					
		
	所以不直接向微信发消息获取头像,上面是浏览器发的消息,没法伪造请求头请求体cookie。可以向我们后台自己发,因为python的requests模块可以伪造这些信息。	
		
	<img src=\'http://127.0.0.1/img\' />
		v= requests.get(...,cookie,headers)
	python的requests模块通过获取cookie,请求体信息。获取微信头像数据信息,然后再访问本地信息从而显示头像。


- 显示所有联系人
	...


- 发消息
	current_user = req.session[\'INIT_DICT\'][\'User\'][\'UserName\'] # session初始化,User.UserName
	to = req.POST.get(\'to\') # @dfb23e0da382f51746575a038323834a
	msg = req.POST.get(\'msg\')# asdfasdfasdf

	# session Ticket
	# session Cookie
	ticket_dict = req.session[\'TICKED_DICT\']
	ctime = int(time.time()*1000)

	post_data = {
		"BaseRequest":{
			"DeviceID": "e384757757885382",
			\'Sid\': ticket_dict[\'wxsid\'],
			\'Uin\': ticket_dict[\'wxuin\'],
			\'Skey\': ticket_dict[\'skey\'],
		},
		"Msg":{
			"ClientMsgId":ctime,
				"LocalID":ctime,
			"FromUserName": current_user,
			"ToUserName":to,
			"Content": msg,
			"Type": 1
		},
		"Scene": 0
	}

	url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket={0}".format(ticket_dict[\'pass_ticket\'])
	# res = requests.post(url=url,json=post_data) # application/json,json.dumps(post_data)
	# res = requests.post(url=url,data=json.dumps(post_data),headers={\'Content-Type\': "application/json"}) # application/json,json.dumps(post_data)

	res = requests.post(url=url,data=json.dumps(post_data,ensure_ascii=False).encode(\'utf-8\'),headers={\'Content-Type\': "application/json"}) # application/json,json.dumps(post_data)
	print(res.text)


- 收消息
	见代码

总结: 

a. 分析Http请求
	- 请求方式
	- URL
	- 浏览器看到数据的二种形式
		Form Data? 			# form表单数据类型,request.post中取
			{
				k: 1,
				k: “fds”,
				k: [11,2,3,4],	
				k: {K:},		# 不能传字典,传字典只能把字典的key传到后台,发字典的时候需要转为字符串类型
			}
		request payload?		# json数据类型,整个数据当成字符串发到后台。request.body中取
			{
				k: 1,
				k: “fds”,
				k: [11,2,3,4],	
				k: {K:},
			}
		
		requests.post()
	- 请求头:(爬网站进不去时,下面五个设置注意下,大部分可以爬取了)			
		user-agent: 当前用户使用的设备,知乎爬虫需要带user-agent。
		Referer: "xxx"
		content-type: application/json,
		host

		cookie关键,cookie依附在请求头中
		
b. 代理
	封IP时,代理设置

  

具体代码如下:

urlpatterns = [
    url(r\'^admin/\', admin.site.urls),
    url(r\'^login.html$\', views.login),
    url(r\'^check_login.html$\', views.check_login),
    url(r\'^index.html$\', views.index),
    url(r\'^avatar.html$\', views.avatar),
    url(r\'^contact_list.html$\', views.contact_list),
    url(r\'^send_msg.html$\', views.send_msg),
    url(r\'^get_msg.html$\', views.get_msg),
]
urls.py
from django.shortcuts import render,HttpResponse
import requests
import time
import re
import json

def ticket(html):
    from bs4 import BeautifulSoup
    ret = {}
    soup = BeautifulSoup(html,\'html.parser\')
    for tag in soup.find(name=\'error\').find_all():
        ret[tag.name] = tag.text
    return ret


def login(req):
    if req.method == \'GET\':
        uuid_time = int(time.time() * 1000)

        base_uuid_url = "https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_={0}"
        uuid_url =base_uuid_url.format(uuid_time)
        r1 = requests.get(uuid_url)
        result = re.findall(\'= "(.*)";\',r1.text)
        uuid = result[0]

        req.session[\'UUID_TIME\'] = uuid_time
        req.session[\'UUID\'] = uuid

        return render(req,\'login.html\',{\'uuid\':uuid})
def check_login(req):
    response = {\'code\': 408,\'data\':None}

    ctime = int(time.time()*1000)
    # base_login_url = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={0}&tip=0&r=-735595472&_={1}"
    base_login_url = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid={0}&tip=0&r=-735595472&_={1}"
    # "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=AbPhQTMl9w==&tip=0&r=-736896961&_=1503975440649"
    login_url = base_login_url.format(req.session[\'UUID\'],ctime)
    r1 = requests.get(login_url)
    if \'window.code=408\' in r1.text:
        # 无人扫码
        response[\'code\'] = 408
    elif \'window.code=201\' in r1.text:
        # 扫码,返回头像
        response[\'code\'] = 201
        response[\'data\'] = re.findall("window.userAvatar = \'(.*)\';",r1.text)[0]
    elif \'window.code=200\' in r1.text:
        # 扫码,并确认登录
        req.session[\'LOGIN_COOKIE\'] = r1.cookies.get_dict()
        base_redirect_url = re.findall(\'redirect_uri="(.*)";\',r1.text)[0]
        redirect_url = base_redirect_url + \'&fun=new&version=v2\'

        # 获取凭证
        r2 = requests.get(redirect_url)
        ticket_dict = ticket(r2.text)
        req.session[\'TICKED_DICT\'] = ticket_dict
        req.session[\'TICKED_COOKIE\'] = r2.cookies.get_dict()


        # 初始化,获取最近联系人信息:工作号
        post_data = {
            "BaseRequest":{
                "DeviceID": "e384757757885382",
                \'Sid\': ticket_dict[\'wxsid\'],
                \'Uin\': ticket_dict[\'wxuin\'],
                \'Skey\': ticket_dict[\'skey\'],
            }
        }
        print(\'初始化开始...\')
        # 用户初始化,讲最近联系人个人信息放在session中
        init_url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-740036701&pass_ticket={0}".format(ticket_dict[\'pass_ticket\'])
        r3 = requests.post(
            url=init_url,
            json=post_data
        )
        r3.encoding = \'utf-8\'
        init_dict = json.loads(r3.text)
        req.session[\'INIT_DICT\'] = init_dict
        response[\'code\'] = 200

    return HttpResponse(json.dumps(response))


def avatar(req):
    prev = req.GET.get(\'prev\') # /cgi-bin/mmwebwx-bin/webwxgeticon?seq=602427528
    username = req.GET.get(\'username\') # @fb736164312cbcdb9abe746d81e24835
    skey = req.GET.get(\'skey\') # @crypt_2ccf8ab9_4414c9f723cbe6e9caca48b7deceff93
    img_url = "https://wx.qq.com{0}&username={1}&skey={2}".format(prev,username,skey)

    cookies= {}
    cookies.update(req.session[\'LOGIN_COOKIE\'])
    cookies.update(req.session[\'TICKED_COOKIE\'])
    print(img_url)
    res = requests.get(img_url,cookies=cookies,headers={\'Content-Type\': \'image/jpeg\'})
    return HttpResponse(res.content)

def index(req):
    """显示最近联系人"""
    # https://wx.qq.com
    return render(req,\'index.html\')


def contact_list(req):
    """
    获取所有联系人
    :param req:
    :return:
    """
    ctime = int(time.time()*1000)
    base_url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&r={0}&seq=0&skey={1}"
    url = base_url.format(ctime,req.session[\'TICKED_DICT\'][\'skey\'])
    cookies = {}
    cookies.update(req.session[\'LOGIN_COOKIE\'])
    cookies.update(req.session[\'TICKED_COOKIE\'])

    r1 = requests.get(url,cookies=cookies)
    r1.encoding = \'utf-8\'

    user_list = json.loads(r1.text)

    return render(req, \'contact_list.html\',{\'user_list\':user_list})


def send_msg(req):
    """
    发送消息
    :param req:
    :return:
    """
    current_user = req.session[\'INIT_DICT\'][\'User\'][\'UserName\'] # session初始化,User.UserName
    to = req.POST.get(\'to\') # @dfb23e0da382f51746575a038323834a
    msg = req.POST.get(\'msg\')# asdfasdfasdf

    # session Ticket
    # session Cookie
    ticket_dict = req.session[\'TICKED_DICT\']
    ctime = int(time.time()*1000)

    post_data = {
        "BaseRequest":{
            "DeviceID": "e384757757885382",
            \'Sid\': ticket_dict[\'wxsid\'],
            \'Uin\': ticket_dict[\'wxuin\'],
            \'Skey\': ticket_dict[\'skey\'],
        },
        "Msg":{
            "ClientMsgId":ctime,
                "LocalID":ctime,
            "FromUserName": current_user,
            "ToUserName":to,
            "Content": msg,
            "Type": 1
        },
        "Scene": 0
    }

    url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket={0}".format(ticket_dict[\'pass_ticket\'])
    # res = requests.post(url=url,json=post_data) # application/json,json.dumps(post_data)
    # res = requests.post(url=url,data=json.dumps(post_data),headers={\'Content-Type\': "application/json"}) # application/json,json.dumps(post_data)

    res = requests.post(url=url,data=json.dumps(post_data,ensure_ascii=False).encode(\'utf-8\'),headers={\'Content-Type\': "application/json"}) # application/json,json.dumps(post_data)
    print(res.text)
    return HttpResponse(\'...\')



def get_msg(req):
    """
    长轮询获取消息
    :param req:
    :return:
    """
    # 检查是否有消息到来
    ctime = int(time.time()*1000)
    ticket_dict = req.session[\'TICKED_DICT\']
    check_msg_url = "https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck"

    cookies = {}
    cookies.update(req.session[\'LOGIN_COOKIE\'])
    cookies.update(req.session[\'TICKED_COOKIE\'])



    synckey_dict = req.session[\'INIT_DICT\'][\'SyncKey\']
    synckey_list = []
    for item in synckey_dict[\'List\']:
        tmp = "%s_%s" %(item[\'Key\'],item[\'Val\'])
        synckey_list.append(tmp)
    synckey = "|".join(synckey_list)


    r1 = requests.get(
        url=check_msg_url,
        params={
            \'r\': ctime,
            "deviceid": "e384757757885382",
            \'sid\': ticket_dict[\'wxsid\'],
            \'uin\': ticket_dict[\'wxuin\'],
            \'skey\': ticket_dict[\'skey\'],
            \'_\': ctime,
            \'synckey\': synckey
        },
        cookies=cookies
    )
    print(r1.text)
    if \'{retcode:"0",selector:"0"}\' in r1.text:
        return HttpResponse(\'...\')

    # 有消息,获取消息
    base_get_msg_url = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid={0}&skey={1}&lang=zh_CN&pass_ticket={2}"
    get_msg_url = base_get_msg_url.format(ticket_dict[\'wxsid\'],ticket_dict[\'skey\'],ticket_dict[\'pass_ticket\'])

    post_data = {
        "BaseRequest":{
                "DeviceID": "e384757757885382",
                \'Sid\': ticket_dict[\'wxsid\'],
                \'Uin\': ticket_dict[\'wxuin\'],
                \'Skey\': ticket_dict[\'skey\'],
            },
        \'SyncKey\': req.session[\'INIT_DICT\'][\'SyncKey\']
    }
    r2 = requests.post(
        url = get_msg_url,
        json=post_data,
        cookies=cookies
    )
    r2.encoding = \'utf-8\'
    # 接受到消息: 消息,synckey
    msg_dict = json.loads(r2.text)
    print(msg_dict)
    for msg in msg_dict[\'AddMsgList\']:
        print(\'您有新消息到来:\',msg[\'Content\'])
    init_dict = req.session[\'INIT_DICT\']
    init_dict[\'SyncKey\'] =  msg_dict[\'SyncKey\']
    req.session[\'INIT_DICT\'] = init_dict

    return HttpResponse(\'...\')
views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        <img style="height: 400px;width: 400px;" id="img" src="https://login.weixin.qq.com/qrcode/{{uuid}}">
    </div>

    <script src="/static/jquery-1.12.4.js"></script>

    <script>
        
        $(function () {
            checkLogin();
        });
        
        function checkLogin() {
            $.ajax({
                url: \'/check_login.html\',
                type: \'get\',
                data: {},
                dataType: \'JSON\',
                success:function (arg) {
                    if(arg.code == 408){
                        checkLogin();
                    }else if(arg.code == 201){
                        $(\'#img\').attr(\'src\',arg.data);
                        checkLogin();
                    }else {
                        location.href = "/index.html"
                    }

                }
            })
        }
    </script>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>个人信息</h1>
    <img src="/avatar.html?prev={{ request.session.INIT_DICT.User.HeadImgUrl }}">
    <h2>{{ request.session.INIT_DICT.User.NickName }}</h2>
    <h1>最近联系人</h1>
        <ul>
        {% for user in  request.session.INIT_DICT.ContactList %}
            <li><img src="/avatar.html?prev={{ user.HeadImgUrl }}">  {{ user.UserName }}   {{ user.NickName }}</li>
        {% endfor %}
        </ul>

        <a href="/contact_list.html">更多联系人</a>
    <h1>公众号信息</h1>
</body>
</html>
index
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>发送消息</h1>
        <input placeholder="接受者" id="to" />
        <input placeholder="消息内容" id="msg" />
        <input type="button" value="发送" onclick="sendMsg();" />
    <h1>用户列表({{ user_list.MemberCount }})</h1>
    {% for user in user_list.MemberList %}
        <div username="{{ user.UserName }}">
{#            <img style="width: 50px;height: 50px;" src="/avatar.html?prev={{ user.HeadImgUrl }}"><span>{{ user.NickName }}</span>#}
            <span>{{ user.NickName }}</span>
        </div>
    {% endfor %}
    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        $(function () {
            getMsg();
        });

        function getMsg() {
            $.ajax({
                url: \'/get_msg.html\',
                type: \'GET\',
                success:function (arg) {
                    //console.log(arg);
                    getMsg();
                }
            })
        }


        function  sendMsg() {
            $.ajax({
                url: \'/send_msg.html\',
                type: "POST",
                data: {\'to\': $(\'#to\').val(), \'msg\': $(\'#msg\').val()},
                success:function (arg) {
                    alert(arg);
                }
            })
        }
    </script>
</body>
</html>
contact_list.html

 

分类:

技术点:

相关文章: