一、爬虫概要

1、网络爬虫是什么

百度百科:网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。

通俗的讲,爬虫就是能够自动访问互联网并将网站内容下载下来的的程序或脚本,类似一个机器人,能把别人网站的信息弄到自己的电脑上,再做一些过滤,筛选,归纳,整理,排序等等。

网络爬虫的英文即Web Spider,是一个很形象的名字。把互联网比喻成一个蜘蛛网,那么Spider就是在网上爬来爬去的蜘蛛。网络蜘蛛是通过网页的链接地址来寻找网页,从网站某一个页面(通常是首页)开始,读取网页的内容,找到在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,这样一直循环下去,直到把这个网站所有的网页都抓取完为止。如果把整个互联网当成一个网站,那么网络蜘蛛就可以用这个原理把互联网上所有的网页都抓取下来。

2、网络爬虫的本质及其基本流程

一般情况下,用户获取网络数据有以下两种方式:
   (1)借助浏览器:输入url --> 提交请求 --> 下载网页代码 --> 渲染成页面
   (2)代码模拟:模拟浏览器发送请求(获取网页代码) --> 提取有用的数据 --> 存储数据
  • 网络爬虫的本质

    • 通过代码模仿浏览器发送请求,即上述方式(2)
  • 基本流程

     向目标站点发送http请求 --> 获取响应内容 --> 解析内容 --> 保存数据
    
  • 从底层剖析爬虫本质:

     浏览器本质,socket客户端遵循Http协议(通过\r\n分割的规范+请求响应断开连接=>无状态、短连接)
			
		url = 'www.cnblogs.com'
			
		sk = socket.socket()
		# 连接的过程是:阻塞
		sk.connect((url,80))

		# HTTP协议
		content = "GET /wupeiqi HTTP/1.1\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36\r\n\r\n" %url
		sk.sendall(content.encode('utf-8'))

		# 等待服务端返回内容:阻塞
		data = sk.recv(8096)
		print(data)
		sk.close()

3、Python爬虫必备的知识

  • python

    • 基础
    • 网络编程
  • WEB知识

    • HTML基础
    • CSS基础
    • javascript基础
  • HTTP协议

    • 请求头(一般情况下):

      • user-agent:代指用户的访问设备
      • cookie:服务端在客户端(浏览器)保留的标记
      • refer:请求来源(从哪里请求的)
      • host:
      • content-type:
    • 请求体(数据格式)

      • name=alex&age=18
    • 状态码(重定向302)

      • 响应头中的Location
  • 分析HTTP请求(抓包)

    • chrome
    • fiddler

二、web微信基本通信过程分析

1、微信服务器返回一个会话ID(uuid)

web微信采用扫描二维码登录,不使用用户名密码登录,因此微信服务器需要分配一个唯一的会话ID,用来标识当前的一次登录。GET请求的host及参数如下:
qcode_host = "https://login.wx2.qq.com/jslogin"

# GET请求参数
params = {
    'appid':"wx782c26e4c19acffb",
    'redirect_uri':"https%3A%2F%2Fwx2.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage",
    'fun':"new",
    'lang':"zh_",
    '_':ctime
}

# 发送GET请求
res = requests.get(
    url=qcode_host,
    params=params
)
请求成功,服务器会返回如下的字符串:

- window.QRLogin.code = 200; window.QRLogin.uuid = "SCrc8SK6ZG=="

- 200表示请求成功,字符串"SCrc8SK6ZG=="是这次请求微信服务器返回的会话ID(uuid)。

2、通过会话ID获得登录二维码

通过浏览器或者代码发送如下GET请求,获取登录二维码:https://login.weixin.qq.com/l/SCrc8SK6ZG==

3、通过长轮询检测是否已扫描二维码,通过响应状态判断是否确认登录

获得二维码后,需要用户在手机端扫描二维码,并确认是否登录,此时浏览器或代码并不知道用户何时操作,因此只有轮询,为了提高轮训效率,web微信采用长轮询的方式,即向如下地址发送GET请求:

check_host = "https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login"
params = {
    'loginicon':'true',
    'uuid':qcode,
    'tip':'0',
    'r':'-1036255891',
    '_':ctime
}

res = requests.get(
    url=check_host,
    params=params
)

- 服务器返回分析:
   - window.code=201 说明用户已经完成扫码,但还没确认登录
   - window.code=200 window.redirect_uri="https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A5ncNUM2NJBYNOpJ49Jd38m2@qrticket_0&uuid=Ia7HTPkEdQ==&lang=zh_CN&scan=1485320697"  说明用户已确认登录,保存window.redirect_uri跳转地址

   - window.code=408  说明用户未扫码,等待超时,继续轮询

4、获取uin、sid、pass_ticket、skey参数值(注意此处构造的请求地址)

确认登录后,通过向如下redirect_uri地址发送GET请求,获取uin、sid、pass_ticket、skey参数值,便于后续发送请求时使用
  - redirect_uri = window.redirect_uri + "&fun=new&version=v2"

5、初使化用户等信息

上述已完成登录过程,下面需要获取用户信息、好友列表、公众号等信息,向如下地址发送POST请求:
init_host = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit"

# POST请求参数
params = {
    'r': '-1039465096',
    'lang': 'zh_CN',
    'pass_ticket': ticket_dict.get('pass_ticket')
}

# POST请求体
form_data = {
    "BaseRequest":{
        "DeviceID":"e626652155210212",
        "Sid":ticket_dict.get('wxsid'),
        "Uin":ticket_dict.get('wxuin'),
        "Skey":ticket_dict.get('skey'),
    }
}

init_res = requests.post(
    url=init_host,
    params=params,

    # 第1中json格式数据
    # json=data_dict,

    # 第2中data格式,需要带请求头
    data=json.dumps(form_data),
    headers={
        'Content-Type':'application/json'
    }
)
服务器返回用户信息以及同步键值,SyncKey是用户与服务器同步的键值,User是当前登录用户信息。

6、获得所有的好友列表

在上一步骤中已经获得部分好友和公众帐号,如果需要获得完整的好友信息,需要请求如下地址:
- https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=gzP3GEH0awvApwIttyTwxzAvA27%2BwVAp9tQ1osM%2FLL90XWWU5JeIdNLLVjN%2BJ9bq&skey=@crypt_dcaca546_c69b06b40828731a0acb3235758c0ea6&r=1486119544662

保持之前访问的Cookies不变,在返回的数据中,MemberList包含了所有的好友信息。

7、发送消息

通过向如下地址发送POST请求,发送消息:
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=gzP3GEH0awvApwIttyTwxzAvA27%2BwVAp9tQ1osM%2FLL90XWWU5JeIdNLLVjN%2BJ9bq

# POST请求体
form_data = {
	"BaseRequest": {
		"DeviceID": "e626652155210212",
		"Sid": ticket_dict.get('wxsid'),
		"Uin": ticket_dict.get('wxuin'),
		"Skey": ticket_dict.get('skey'),
	},
	'Msg':{
		'ClientMsgId':ctime,
		'LocalID':ctime,
		'FromUserName':from_user,
		'ToUserName':to,
		"Content":content,
		'Type':1
	},
	'Scene':0
}

send_msg_res = requests.post(
        url=send_host,
        params =params,
        # 若是字符串中含有中文,request传递数据时,将中文转换为字节,无法为我们自动转换编码。直接encoding编码,传递字节,不让requests自动转换
        # data可以是字典,字符串,字节,对于字典,字符串直接含有中文不正确,直接转字节传送,py3默认是utf-8,直接传送字节就可以,如果使用json,会将中文转换为Unicode
        data=bytes(json.dumps(form_data,ensure_ascii=False),encoding='utf-8')
    )

其中BaseRequest都是授权相关的值,与上述获取的值对应。

8、接收消息

检测是否有新消息到来,发送GET请求的地址及请求参数如下:
check_msg_host = "https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck"

check_list = []

for item in user_info['SyncKey']['List']:
    tmp = "%s_%s" % (item['Key'], item['Val'])
    check_list.append(tmp)

check_str = "|".join(check_list)

# GET请求参数
params = {
    'r': int(time.time()),
    'skey': ticket_dict.get('skey'),
    'sid': ticket_dict.get('wxsid'),
    'uin': ticket_dict.get('wxuin'),
    'deviceid': "e626652155210212",
    'synckey': check_str,
    '_':int(time.time())
}

check_msg_res = requests.get(
    url=check_msg_host,
    params=params,
    cookies=all_cookies
)

响应的内容如下:
  - window.synccheck={retcode:”0”,selector:”2”}

如果retcode不为0,则说明通信有问题。根据selector值,客户端需要作出进一步处理。当selector=2时表示有新消息,此时需要访问另一个接口获取新消息内容,向如下地址发送POST请求:
		
recv_msg_host = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync"

# POST请求参数
params = {
    'sid': ticket_dict.get('wxsid'),
    'skey': ticket_dict.get('skey'),
    'lang':'zh_CN',
}


# POST请求数据
form_data = {
    'BaseRequest': {
        "DeviceID": "e626652155210212",
        "Sid": ticket_dict.get('wxsid'),
        "Skey": ticket_dict.get('skey'),
        "Uin": ticket_dict.get('wxuin'),
    },
    'SyncKey': user_info.get('SyncKey'),
    'rr': int(time.time())   #数据类型
}

# 发送POST请求
recv_msg_res = requests.post(
    url=recv_msg_host,
    params=params,
    json=form_data
)
posted on 2018-07-12 20:22  人生苦短,python当歌  阅读(1139)  评论(0编辑  收藏  举报