序言
网络爬虫的价值其实就是数据的价值,在互联网社会中,数据是无价之宝,一切皆为数据,谁拥有了大量有用的数据,谁就拥有了决策的主动权。
爬虫主要涉及的几大领域
工商、短视频、电商、航空、自媒体、外卖
一、爬虫基本原理
1.1常见基本功
1.1 获取网页
爬虫首先要做的工作就是获取网页,这里就是获取网页的源代码。源代码里包含了网页的部分有用信息,所以只要把源代码获取下来,就可以从中提取想要的信息了。
1.2 提取信息
获取网页源代码后,接下来就是分析网页源代码,从中提取我们想要的数据。
另外,由于网页的结构有一定的规则,所以还有一些根据网页节点属性、css 选择器或 xPath 来提取网页信息的库,如 Beautiful soup、pyquery、lxml 等。
使用这些库,我们可以高效快速地从中提取网页信息,如节点的属性、文本值等。提取信息是爬虫非常重要的部分,它可以使杂乱的数据变得条理清晰,以便我们后续处理和分析数据。
1.3 保存数据
提取信息后,我们一般会将提取到的数据保存到某处以便后续使用。这里保存形式有多种多样,如可以简单保存为 TXT文本或 JSON 文本,也可以保存到数据库,如 MySQL 和 MongoDB 等,也可保存至远程服务器,如借助 SFTP 进行操作等。
1.2 请求
请求,由客户端向服务端发出,可以分为4部分内容:请求方法(Request Method)、请求的网址(Request URL)、请求头(Request Headers)、请求体(Request Body)。
1.2.1 请求方法
方法 | 描述 |
---|---|
GET | 请求页面,并返回页面内容 |
HEAD | 类似于 GET请求,只不过返回的响应中没有具体的内容,用于获取报头 |
POST | 大多用于提交表单或上传文件,数据包含在请求体中 |
PUT | 从客户端向服务器传送的数据取代指定文档中的内容 |
DELETE | 请求服务器删除指定的页面 |
CONNECT | 把服务器当作跳板,让服务器代替客户端访问其他网页 |
OPTIONS | 允许客户端查看服务器的性能 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
1.2.2 请求网址
请求的网址,即统一资源定位符 URL,它可以唯一确定我们想请求的资源。
https://www.baidu.com/s?wd=python
http:// zb.yfb.qianlima.com /yfbsemsite/mesinfo/zbpglist
http 协议
zb.yfb.qianlima.com–》IP地址
/yfbsemsite/mesinfo/zbpglist 查询路径
wd=python 查询字符串 査询参数 这个参数会传输给服务器 服务器去數据库根据你的搜索词去数据库里面査询数据 返回给前端
https 表示协议、www.baidu.com表示域名、s表示路径、wd=python表示查询参数
1.2.3 请求头
HTTP(HyperTextTransferprotocol)是超文本传输协议的缩写,它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以及可能的实体内容
1.2.4 请求体
请求体一般承载的内容是 POST 请求中的表单数据,而对于 GET请求,请求体则为空。
1.3 响应
- 响应状态码
200(成功)
403(参数不对)
404(页面未找到) - 响应头
- 响应体
二、网络请求库使用
2.1 requests库
安装环境
pycharm官网网址:https://www.jetbrains.com/pycharm/
pip install requests
版本:requests–2.28.1
1.实例引入
requests 中相应的方法就是 get 方法,非常的明确,下面通过实例来看一下:
import requests
response =requests.get('https://www.baidu.com/')
#并重新设置编码
response.encoding ='utf-8'
#获取文本数据
print('response.text: ',response.text)
#获取状态码
print('type(response): ',type(response))
print('response.status_code: ',response.status_code)
print('type(response.text): ',type(response.text))
print('response.text: ',response.text)
print('response.cookies: ',response.cookies)
控制台输出结果:
response.text: <!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
type(response): <class 'requests.models.Response'>
response.status_code: 200
type(response.text): <class 'str'>
response.text: <!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
response.cookies: <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
2、GET请求
import requests
params ={
"age": "18",
"name": "qi"
}
# age=18&name=xialuo 使用params 可以自动组装参数
# res =requests.get('http://httpbin.org/get',params=params)
headers = {
'qi':'xxxxxx'
}
age = 19
res = requests.get(f"http://httpbin.org/get?age={age}&name=qi",headers=headers)
print(res.text)
res.text 接收文本数据
res.content 接收非文本数据
res.json 接收 json格式数据转字典
控制台输出结果:
{
"args": {
"age": "19",
"name": "qi"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"Qi": "xxxxxx",
"User-Agent": "python-requests/2.28.2",
"X-Amzn-Trace-Id": "Root=1-65e1f6fe-7547301641abcc901fb43d2c"
},
"origin": "223.73.4.55",
"url": "http://httpbin.org/get?age=19&name=qi"
}
2.1 爬虫案例
豆瓣电影地址:https://movie.douban.com
# 豆瓣电影
import requests
# 地址
url="https://movie.douban.com/j/search_subjects"
# 查询参数
params = {
"type":"movie",
"tag": "热门",
"page_limit": "10",# 每页条数
"page_start": "0",# 起始记录
}
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
}
# 请求发送 响应接收
res =requests.get(url,params=params,headers=headers)
# print(res.text)
data = res.json().get('subjects')
# print(data)
for item in data:
print(item.get("title"))
爬虫实现翻页功能
# 豆瓣电影
import requests
page = 1
total_page = 10# 默认有10页
def get_data():
global page
while page <= total_page:
# 页数转换为起始记录
page_start = (page-1)*10
# 地址
url ='https://movie.douban.com/j/search_subjects'
# 查询参数
params = {
"type":"movie",
"tag": "热门",
"page_limit": "10",# 每页条数
"page_start": page_start,# 起始记录
}
# 请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36'
}
# 请求发送 响应接收
response =requests.get(url,params=params,headers=headers)
items =response.json().get('subjects')
for item in items:
print(item.get("title"))
print(f'目前采集第{page}页的数据')
page += 1
get_data()
2、post请求
# post
import requests
data ={
'name': 'germey',
'age': 22
}
headers ={
'user-agent':'xxxx'
}
#data 传的是字典 json传的是Json格式
# r= requests.post("http://httpbin.org/post", data=data,headers=headers)
r= requests.post("http://httpbin.org/post", json=data,headers=headers)
print(r.text)
data 传的是表单数据
json 传的是json字符串
三、数据解析库
3.1 xpath
3.1.1 工具简介
chrome插件下载:https://chrome.zzzmh.cn/index#/search
xpath 的选择功能十分强大,它提供了非常简洁明了的路径选择表达式。另外,它还提供了超过 100 个内建函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。几乎所有我们想要定位的节点,都可以用 xPath 来选择。
点击推荐下载即可
输入: //input[@id=“su”]/@value
官网:https://www.w3.org/TR/xpath/
安装解析引擎:
pip install lxm1
表3-1 xPath常用规则
表 达 式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
3.2.2 案例演示
1.解析
text = '''
<div>
<ul>
<li class ="item-0" ><a href="link1.html"> first item </a></li>
<li class ="item-1" ><a href="link2.html"> second item </a></li>
<li class ="item-inactive" ><a href="link3.html"> third item </a></li>
<li class ="item-1" ><a href="link4.html"> fourth item </a></li>
<li class ="item-0" ><a href="link5.html"> fifth item </a></li>
</ul>
</div>
'''
# xpath工具包
from lxml import etree
# 可以传网页html
html = etree.HTML(text) # 初始化操作 让xpath可以匹配上
result = etree.tostring(html)
print(result.decode('utf-8'))
2 节点操作
我们一般会用 //开头的 xpath 规则来选取所有符合要求的节点。这里以前面的 HTML文本为例,如果要选取所有节点,可以这样实现:
result = htm1.xpath(‘//*’)
这里使用 * 代表匹配所有节点,也就是整个 HTML 文本中的所有节点都会被获取。可以看到,返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等,所有节点都包含在列表中了。
3 子节点
resu1t = htm1.xpath('//1i/a')
result=htm1.xpath('//1i/a/text()') # 提取数据
result= htm1.xpath('//1i/a/@href')# 属性值
4 指定节点获取
result =htm1.xpath('//li[@class="item-0"]/a/text()')
print(result)
5 找翻页元素
- 翻页提取案例
- 地址:https://pic.netbian.com/
找翻页元素(下一页)的三种方法
- //div[@class=“page”]/a[last()-1]/@href
- //div[@class=“page”]/a[text()=“下一页>”]/@href
- F12 >>> 找到下一页对应标签 >>> 右键点击copy >>> 选择copy XPath
6 案例演示
- 说明:提取当前网站的首页标题信息,要求使用xpath解析器
# 长沙晚报实战
import requests
from lxml import etree
maps = lambda x :x[0] if x else x
def get_data():
headers = {
'user-agent': 'xxxxx'
}
url = 'https://www.icswb.com/channel-list-channel-162.html'
res = requests.get(url,headers=headers)
# print(res.text)
html = etree.HTML(res.text)
li = html.xpath('//ul[@id="NewsListContainer"]/li')
for ii in li:
title = ii.xpath('./h3/a/text()')
# print(title)
# href = ii.xpath('./a/@href')
href = maps(ii.xpath('./a/@href'))
print(href)
if __name__ =='__main__':
get_data()
四、数据存储库
4.1 CSV 文件存储
CSV,全称为 Comma-Separated Values,中文可以叫作逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分隔。每条记录由字段组成,字段间的分隔符是其他字符或字符串,最常见的是逗号或制表符。不过所有记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。它比 Excel 文件更加简洁,XLS文本是电子表格,它包含了文本、数值、公式和格式等内容,而CSV中不包含这些内容,就是特定字符分隔的纯文本,结构简单清晰。所以,有时候用 CSV来保存数据是比较方便的。本节中,我们来讲解 Python 读取和写入CSV文件的过程。
1.写入
这里先看一个最简单的例子:
import csv
# 单行写入数据 数据是列表形式
with open('data.csv', 'w')as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id','name','age'])
writer.writerow(['10001','mike', 20])
writer.writerow(['10002','Bob',22])
writer.writerow(['10003','Jordan',21])
首先,打开 data.csv 文件,然后指定打开的模式为w(即写入),获得文件句柄,随后调用 csv 库的 writer 方法初始化写入对象,传入该句柄,然后调用 writerow方法传入每 行的数据即可完成写入。
2.多行写入
调用 writerows 方法同时写入多行,此时参数就需要为二维列表,例如:
import csv
# 多行写入数据
# newline='' : 每行记录不换行
with open('datas.csv', 'w', newline='')as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id','name','age'])
writer.writerows([['10001','mike', 20],['10002','Bob',22],['10003','Jordan',21]])
3.爬虫案例
# 入库csv-采集腾讯实践
import requests
import csv
def get_data():
url = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1709433623178&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=python&pageIndex=1&pageSize=10&language=zh-cn&area=cn'
res = requests.get(url)
print(res.text)
res = requests.get(url)
items = res.json()
datas = list()
for data in items.get('Data')['Posts']:
RecruitPostName = data.get('RecruitPostName')
LocationName = data.get('LocationName')
Responsibility = data.get('Responsibility')
datas.append([RecruitPostName,LocationName,Responsibility])
with open('tx.csv', 'w', encoding='utf-8',newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['岗位', '地区', '简介'])
writer.writerows(datas)
print("数据写入完毕!")
if __name__ == '__main__':
get_data()
五、爬虫进阶
1.逆向声明
1.1 为什么要学逆向
- 头部签名验证
alR0cHM6Ly93d3cub2tsaW5rLmNvbs96aC1jbi9idGMvdHetbGlzdD9saW1pdD0yMCZwYWdITnVtPTE= - 请求参数签名验证
aHR0CHM6Ly93d3cu24vcmFuav9DDmRwvdw50cnkvY24v22VucmUyMzY= - cookie验证
аHR0CHM6Ly93d3C4ZHNZHQuZ292Lm1L300X0NOL3BnX2hvbWU= - 响应数据验证
aHR0cHM6Ly9n23252ncu2mou2292LmNuL2j1c2luZXNzL2xPc3QV
2.JavaScript 是脚本语言
- JavaScript 是一种轻是级的编程语言
- JavaScript 是可插入 HTML 页面的编程代码。
- JavaScript 插入 HTML页面后,可由所有的现代浏览器执行
- JavaScript 很容易学习
2.1 JS基本功
提示:如果听不懂上课内容,建议可以先单独学习补全JS的相关课程在进行观看。
2.1.1.如何在Pycharm里面运行 JS
任何的编程语吉想要执行都需要有一个好的环境,python如此、JjavaScript也是如此。
2.1.2 node
Node.js 是一个基于Chrome v8引擎的 Javascript 运行
下载: http://nodejs.cn/download/
2.1.3 注释
单行注释以 //开头
多行注释以 /* 开始,以 */ 结尾
2.2 变量和数据类型
一、var 的使用以及作用域
(1).作用域是指函数或变量的可供访问的范围。
(2).var可以定义全局变量和局部变量
(3).var的作用域主要和函数的定义有关
- 1.全局作用域
如果是在任意函数的外部声明var变量,其作用域是全局的; - 2.局部(函数)作用域
如果是在函数内部声明var,其作用域是局部的,只能在函数内部被访问;
对其他块定义没有作用域,比如if、for,这就会导致外部同名变量可以随意修改在If/for内走义的变量 - 3.var 的声明与变量提升
在使用变量前,需要先对变量进行声明,如果只声明、未赋值,则会初始化值为undefined。
var可以修改,也可以被重复声明。当对var 进行重复声明时,后面的变量可以覆盖前面的变量,相当于变量重置。
var的变量提升: JS引擎在预编译代码时,会优先获取所有被var声明的变量和函数,将它们放在代码的头部然后从上到下执行。
与代数一样,JavaScript 变量可用于存放值(比如x=5)和表达式(比如 z=x+y)。JavaScript 变量有很多种类型,但是现在,我们只关注数字和字符串
var pi=3.14;
//如果你熟悉 ES6,pi 可以使用 const 关键字,表示一个常量
// const pi = 3.14;
var person="John Doe";
var answer='Yes I am!'
//先声明再赋值
var carname;
carname="Volvo"
二、let 的使用以及作用域
(1).let允许声明一个作用域被限制在 块级中的变量、语句或者表达式。与var 关键字不同的是, let声明的变量只能是全局或者整个函数块的。
(2).let声明的变量只在它所处的代码块内有效,属于块级作用域。块是由{} 界定的。
(3).let 可以被修改,但不能被重复声明。
(4).let不存在变量提升
(5).暂存性死区
三、const 的使用以及作用域
(1).像let声明一样,const声明只能在声明它们的块级作用域中访问
(2).const声明一个只读的常量,这意味着声明后的常量不能被修改并且不能被重复声明,这也意味着const声明时就必须初始化,不能等到之后赋值。
所以,当我们修饰的标识符不会被再次赋值时,就可以使用const来保证数据的安全性。
四、总结
- 作用域:
var声明的是全局作用域或函数作用域;而let和const 是块作用域。 - 声明初始化:
var和let在声明的时候可以不进行初始化;而 const 在声明的时候必须初始化。 - 修改与重复声明:
var在可以修改和重复声明;而let只能修改,不能在同一作用域下重复声明;const声明常量不可修改也不可重复声明。 - 变量提升:
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined;let和const 不存在变量提升,即它们所声明的变量定要在声明后使用,否则会报错。 - 暂存性死区:
var不存在暂时性死区;let和const存在暂存性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
2.3 Js 函数语法
函数就是包表在花括号中的代码块,前面使用了关键词 function:
function functionname(){
// 执行代码
}
注:JavaScript 对大小写敏感。关键词 function 必须是小写的,并且必须以与函数名称相同的大小写来调用函数。
2.3.1 有名函数
function xxs(){
console.log('123')
}
xxs();//执行函数
2.3.2 函数赋值表达式定义函数
sss=function(a,b,c){
console.log(a)
}
sss(1)
2.3.3 JavaScript之自执行函数\
一种理解是,自执行即自动执行,也就是所谓的立即执行函数
!(function(arg1) {
console.log(arg1)
console.log("Hello World!");
})(1);
//或使用~
~(function(arg1) {
console.log(arg1)
console.log("Hello World!");
})(1);
在前面加上一个布尔运算符(只多了一个感叹号),就是表达式了,将执行后面的代码
2.3.4 内部函数外部调用
var _x;
!(function(arg1) {
function xl(){
return 'qi'
}
_x = xl;
console.log("Hello World!");
})(1);
console.log(_x());
2.4.作用域
变量在函数内声明,变量为局部变量,具有局部作用域。
局部变量:只能在函数内部访问
变量在函数外定义,即为全局变量。
as =123
function xxss(){
console.1og(as)
var ddd = 10;
}
console.1og(as)
2.5 对象
JavaScript中的对象其实就是一组数据和功能的集合。
通过new操作符后跟要创建的对象类型的名称来创建。
对象也是一个变量,但对象可以包含多个值(多个变量),每个值以name:value 对呈现。
var car ={name:"xialuo",model:500,color:"white"};
创建了对象的一个新实例
person= new object();
这里的Object相当于祖宗一样,创建Object的实例并没有什么用处。
特点:
每个Object类型的实例共有的属性和方法:
- constructor: 保存着用于创建当前对象的函数。
- hasOwnProperty: 用于检测给定的属性在当前对象的实例中是否存在。
- isPrototypeOf: 用于检查传入的对象是否是当前对象的原型。
- propertylsEnumerble: 用于检查给定属性能否使用for-in来枚举。
- toLocaleString(): 返回对象的字符串表示。
- toString(): 返回对象的字符串表示。
- valueOf(): 返回对象的字符串,数值,或布尔表示。通常和toString()返回的值相同。
2.5.1 对象访问
car.name;
car['name']
2.5.2 对象方法
对象的方法定义了一个函数,并作为对象的属性存储。
var person= {
firstName : "xl",
lastName : "1i1i",
id : 5566,
fullName : function(){
return this.firstName +" "+ this.lastName;
}
};
注:在对象方法中, this 指向调用它所在方法的对象。
2.6 json转换
JSON.parse() //用于将一个 JSON 字符串转换为 Javascript 对象。
JSON.stringify() //用于将 javascript 值转换为 JSON 字符串。
注:经常使用在数据的处理方面,比如后台返回数据。所以后台返回的加密数据,可以使用分析JSON.parse 来找加密位置 体现在请求后
注:JSON.stringify 用在数据加密后,变成字符串传给后台服务器
//要实现从JSON字符串转换为15对象,使用S0N.parse()方法
var obj = JSON.parse('{"a": "He1lo","b": "world"}')
//要实现从JS对象转换为1SON字符串,使用JSON.stringify()方法:
var json =JSON.stringify({a:'Hello',b:'world'});
3 JS调试技巧
3.1浏览器面板补充
Elements
Network
- 保留日志
勾选每次刷新不会清除之前的请求 - 停用缓存
勾选后不会从缓存里面拉数据,方便后续JS动态调试
Sources
- page: 所有资源文件
- filesystem: 关联本地文件
- overrides: 可以做文件替换,比如替换JS
- 代码段: 可以编写脚本,影响页面,代码记录
var a =document.querySelector("#su")
//可以在控制台操作 输入 a.remove()
右键选择在来来源面板中打开
右键点击要进行覆盖的文件选择Override content
出现以下
将自定义的html文件覆盖到index.html中,刷新百度页面即会出现对应自定义的html
关闭本地替换:点击Enable Local Overrides
调试boss网站的页面
debugger的使用
跳出debugger:删除代码中的debugger并保存代码+F8
Content scripts
Content scripts内容脚本:可以用来编写插件
Snippets
在Snippets中可以直接拿到网页的内容。
步骤:
- F12选择需要拿到的元素行右键copy
- 选择Copy JS path
- 代码:
console.log('hello world')
console.log('比如 调试Js, 这里支持测览器环境')
console.log('与浏览器里面的参数共存')
a = document.querySelector("#su")
console.log(a)
- 断点介绍
地址:https://oauth.d.cn/auth/goLogin.html
断点:只有被执行的逻辑才可以被断住
端点模块顺序对应功能- 跳过子函数(次态函数) 执行 (只在主函数内一步一步执行,不进入子函数内部)
- 进入子函数(次态函数) 执行 (在主函数内部一步一步执行,如果遇到子函数,会跳转到子函数内部一步执行)
- 跳出当前函数,回到调用位置
- 单步执行,会进入到函数内部更加的细致
- 屏蔽断点
通过抓包拿到接口数据和字段:
进行断点调试:
在右侧观察属性变化:
点击下一步:
在Console中将rsaPwd的值进行替换
在接口中可以看到参数进行了改变
3.2断点讲解
作用:对数据进行监听,跟值进行分析
3.2.1 什么是断点
当乐网站运行时间轴
加载html -> 加载JS -> 运行JS初始化 -> 用户触发某个事件 -> 调用某段JS -> 加密函数 -> 给服务器发信息(XHR-SEND) -> 接收到服务器数据 -> 解密函数 -> 刷新网页渲染
示例:https://oauth.d.cn/auth/goLogin.html
- 跳过子函数(次态函数) 执行 (只在主函数内一步一步执行,不进入子函数内部)
- 进入子函数(次态函数) 执行 (在主函数内部一步一步执行,如果遇到子函数,会跳转到子函数内部一步一步执行)
- 跳出当前函数,回到调用位置
- 单步执行,会进入到函数内部 更加的细致
- 屏蔽断点
3.2.2 DOM事件断点
- 执行的比较靠前 距离加密函数比较远
3.2.3 XHR 断点
- 执行比较靠后 距离加密函数相对较近 可以根据栈快速定位
注意:非XHR发送的就断不住
3.3 方法栈
栈是一种先进后出的特殊线性表结构
调用栈是解析器的一种机制,可以在脚本调用多个的函数时,通过这种机制,我们能够追踪到哪个的函数正在执行,执行的函数体又调用了哪个函数。
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
3.3.1 代码说明
function ps(a,b){
return a+b
}
function pn(a,b){
var xx= ps(a,b)
return xx/2
}
num = pn(1,2)
console.log(num)
3.4 debug原理
案例地址:https://gaokao.chsi.com.cn/zyk/zybk/
- 无限debbugger不会真正得死循环,而是有规律得执行逻辑,一般用定时器
Function("debugger;").call()
3.4.1 样例
<!--
无限debug
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 id="box"></h1>
<script>
var ss = document.getElementById('box')
function ff() {
debugger;
}
setInterval(ff,100);
ss.innerHTML ="大家晚上好";
</script>
</body>
</html>
3.4.2 浏览器过debugger
将样例在浏览器中打开,在按F12
1、当定义器运行到这个debugger 这个代码的时候,那么这个时候它为true,它肯定执行我们的debugger 代码,那我们可以(先右键选择添加条件断点)用浏览器的功能给他改成 false
2、右键选择一律不在此处暂停
3、编辑断点
写个1===0的先验条件,永远为假,就永远不会进入这个断点了。
3.4.3 方法置空
无限debugger产生的原因是第七行代码ff这个函数造成的,所以我们可以重写这个函数,使无限debugger失效,在控制台中输入 functionff(){} 即可,如图:
注:一定要在debugger进入之前
//1.
setInterval = function(){}
//2.
function ff(){}
3.4.4 修改响应又件
把JS文件保存到本地修改,修改范围主要是将debugger相关的代码删除或者改写,可以使用文件替换、抓包工具拦截方式
3.4.5 注入代码到 JS 文件
在控制台注入代码
站点:https://bz.zzzmh.cn/index
var _constructor= constructor;
Function.prototype.constructor =function(s){
if(s=="debugger"){
console.1og(s);
return null;
}
return _constructor (s);
}
3.5 hook技术
Hook 是一种钩子技术,在系统没有调用函数之前,钩子程序就先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,也可以强制结束消息的传递。简单来说,修改原有的JS代码就是 Hook。
Hook 技术之所以能够实现有两个条件:
- 客户端拥有JS的最高解释权,可以决定在任何时候注入JS,而服务器无法阻止或干预。服务端只能通过检测和混淆的手段,另 Hook难度加大,但是无法直接阻止。
- 除了上面的必要条件之外,还有一个条件。就是JS是一种弱类型语言,同一个变量可以多次定义、根据需要进行不同的赋值,而这种情况如果在其他强类型语言中则可能会报错,导致代码无法执行。JS的这种特性,为我们 Hook 代码提供了便利。
注意:JS 变量是有作用域的,只有当被 hook 函数和 debugger 断点在同一个作用域的时候,才能 hook 成功。
3.5.1 Hook步骤:
- 1寻找hook的点
- 2编写hook逻辑
- 3 调试
注:最常用的是hook cookie
3.5.2 hook 方法
站点:https://fanyi.youdao.com/index.html
我们知道在 Javascript 中JSON.stringify()方法用于将]avaScript对象或值转换为JSON字符串,JSON.parse()方法用于将一个JSON 字符串转换为JavaScript 对象,某些站点在向 web 服务器传输用户名密码时,会用到这两个方法
(function(){
var _parse = JSON.parse;
JSON.parse = function(ps){
console.log("Hook JSON.parse-->", ps);
debugger;
return _parse(ps);
}
})();
首先定义了一个变量 _parse保留原始 JSON.parse方法,然后重写 JSON.parse方法,遇到 JSON.parse方法就会执行 debugger 语句,会立即断下,最后将接收到的参数返回给原始的 JSON.parse方法进行处理,确保数据正常传输
- 在控制台添加钩子,只要代码中用到JSON.parse就会被hook监测到
- 回车翻译单词,注意一个单词只能调用一次
注:transformResponse在传递给 then/catch前,允许修改响应数据
3.5.3 hook XHR请求
案例地址: https://www.qimai.cn/
定义了一个变量 open 保留原始 XMLHttpRequest.open 方法,然后重写 XMLHttpRequest.open 方法,判断如果md 字符串值在 URL里首次出现的位置不为-1,即 URL里包含 analysis 字符串,则执行 debugger 语句,会立即断下。
//如果是正数 表示存在里面
// 如果是-1 表示不在里面
(function() {
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function(method, url, async) {
if (url.indexof("analysis") != -1) {
debugger;
}
return open.apply(this, arguments);
};
})();
Interceptors-拦截器
- 请求拦截器: 在发送请求之前,可以借助一些函数来对请求的内容和参数做一些检测。若有问题可以直接取消请求。
- 响应拦截器: 当服务器返回响应数据时,响应拦截器会在我们拿到结果前预先处理响应数据。例如对响应数据做一些格式化处理,或者当响应失败时,可以做一些失败提醒和纪录。
//npm install axios
axios = require('axios')
//设置请求拦酸器
axios.interceptors.request.use(function(config){
console.log('请求拦截器 成功')
config.timeout = 2000;//修改请求config
config.headers['sign']='lili'
return config;
},function(error){
console.log('请求拦截器 失败')
return Promise.reject(error);
});
//设置响应拦畿器
axios.interceptors.response.use(function(response){
console.log('响应拦截器 成功')
console.log('调解密函数进行解密数据')
//return response;
//return response.data;//修改响应数据
return {'name':'test'}
},function(error){
console.log('响应拦截器 失败')
return Promise.reject(error);
});
//发送请求
axios.get('http://httpbin.org/get').then(res=>console.log(res))
美团外卖逆向分析
地址:https://h5.waimai.meituan.com/
加载器案例
var ps;
!function (t){
// 加载器
var e= {};
function i(n){
if (e[n])
return e[n].exports;
var s= e[n]={
i: n,
l: !1,
exports: {}
};
return t[n].call(s.exports,s,s.exports,i),
s.l = !0,
s.exports
}
// i(200)
ps = i
}({
'xl':function(){
console.log('xlxlxlxl')
return "xl"
},
100:function(t,e,i){
console.log('对密码进行加密')
return 'hello world'
},
200:function(){
console.log('对密码进行解密')
}
})
//console.log(ps('xl'));
console.log(ps(100));
有道翻译逆向分析
链接:https://fanyi.youdao.com/index.html#/
解密方法
Po["a"].decodeData(o, Wo["a"].state.text.decodeKey, Wo["a"].state.text.decodeIv)
o为密文
Wo["a"].state.text.decodeKey:
'ydsecret://query/key/B*RGygVywfNBwpmBaZg*WT7SIOUP2T0C9WHMZN39j^DAdaZhAnxvGcCY6VYFwnHl'
Wo["a"].state.text.decodeIv:
'ydsecret://query/iv/C@lZe2YzHtZ2CYgaXKSVfsb7Y4QWHjITPPZ0nQp87fBeJ!Iv6v^6fvi2WN@bYpJ4'
加密方法解析
R = (t,o,n)=>{
if (!t)
return null;
const a = e.alloc(16, y(o))
, i = e.alloc(16, y(n))
, r = c.a.createDecipheriv("aes-128-cbc", a, i);
let s = r.update(t, "base64", "utf-8");
return s += r.final("utf-8"),
s
}
通过e.alloc(16, y(o))追到e = b639
c.a = 1c46