2025JS

赖卓成2025年6月9日
大约 68 分钟

2.4-请求

import requests

url = 'https://www.eastmoney.com/'


response = requests.get(url)
status = response.status_code

# 需要和下面写入文件的编码格式一样,不然会乱码
response.encoding = 'utf-8'
print(status)


if status == 200:
    page_text = response.text
    with open('test.html', 'w', encoding='utf-8') as f:
        f.write(page_text)

2.5-请求参数

import requests

url = 'https://game.51.com/search/action/game/'

game_title = input('请输入一个游戏名称:')
params = {'q':game_title}
response = requests.get(url,params)
page_text = response.text

file_name = game_title + '.html'
with open(file_name, 'w', encoding='utf-8') as f:
    f.write(page_text)

2.5-反爬例子

import requests

url = 'http://www.cpta.com.cn/'
response = requests.get(url)
page_text = response.text
with open('kaoshi.html', 'w', encoding='utf-8') as f:
    f.write(page_text)

被拦截:

image-20250609154416604

加上UA:

import requests

url = 'http://www.cpta.com.cn/'
headers = {'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'}
response = requests.get(url, headers=headers)
page_text = response.text
with open('kaoshi.html', 'w', encoding='utf-8') as f:
    f.write(page_text)

可以正常获取数据。

image-20250609155626936

2.7-post

import requests

url = 'http://www.cpta.com.cn/category/search'
headers = {'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'}
data = {
"keywords":"财务管理",
" 搜 索":"搜 索"
}
response = requests.post(url, headers=headers,data=data)
page_text = response.text
with open('财务管理.html', 'w', encoding='utf-8') as f:
    f.write(page_text)

2.8-动态加载数据

import requests
headers = {
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
    # 需加上,此站有防盗链
    'referer': 'https://www.icve.com.cn/portal_new/course/course.html?keyvalue=%E6%9C%BA%E6%A2%B0'
}
data = {
    "kczy": "",
    "order": "",
    "printstate": "",
    "keyvalue": "机械"
}

for page in range(1, 11):
    if page == 1:
        url = 'https://www.icve.com.cn/portal/course/getNewCourseInfo'
    else:
        # 格式化字符串
        url = 'https://www.icve.com.cn/portal/course/getNewCourseInfo?page=%d' % page
    response = requests.post(url, headers=headers, data=data)
    # 用response.json()将数据转成Python对象类型dict
    json_data = response.json()
    course_list = json_data['list']
    for course in course_list:
        print(course['TeacherDisplayname'])

2.9-图片爬取

import requests
headers = {
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
}

img_url = 'http://gips2.baidu.com/it/u=1674525583,3037683813&fm=3028&app=3028&f=JPEG&fmt=auto?w=1024&h=1024'
response = requests.get(img_url, headers=headers)

# 二进制数据获取,对标Java的字节流
c = response.content
with open('image.jpg', 'wb') as f:
    f.write(c)

3.2-xpath

安装:

pip install lxml

示例html:

<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>测试bs4</title>
</head>
<body>
	<div>
		<p>百里守约</p>
	</div>
	<div class="song">
		<p>李清照</p>
		<p>王安石</p>
		<p>苏轼</p>
		<p>柳宗元</p>
		<a href="http://www.song.com/" title="赵匡胤" target="_self">
			<span>this is span</span>
		宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
		<a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
		<img src="http://www.baidu.com/meinv.jpg" alt="" />
	</div>
	<div class="tang">
		<ul>
			<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
			<li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
			<li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
			<li><a href="http://www.sina.com" class="du">杜甫</a></li>
			<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
			<li><b>杜小月</b></li>
			<li><i>度蜜月</i></li>
			<li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
		</ul>
	</div>
</body>
</html>
from lxml import etree

# 1.创建etree对象,将html加载
tree = etree.parse('day03-3.2.html')
# 2.调用xpath函数,结合表达式提取数据

# 写法1:全局匹配
ret = tree.xpath('//title')
# 返回结果ret是一个列表
print(ret)
# 写法2 路径匹配
ret = tree.xpath('/html/head/title')
print(ret[0])

# 会定位到满足要求的所有标签
div_ret = tree.xpath('//div')
print(div_ret)

# 根据属性定位
div_class_ret = tree.xpath('//div[@class="song"]')
print(div_class_ret)

# 索引定位:所以 从1开始
index_ret = tree.xpath('//div[1]')
print(index_ret)

# 写法举例:全局匹配满足层级关系
demo1 = tree.xpath('//div/ul/li/a')
print(demo1)

# /表示一层  // 表示1层或多层
demo2 = tree.xpath('//ul//a')
print(demo2)

#取出文本
demo3 = tree.xpath('//div/a[@class="du"]/text()')
print(demo3)

3.3-小说《碧血剑》爬取

  • url:https://bixuejian.5000yan.com/
  • 需求:将每一个章节的标题和内容进行爬取然后存储到文件中'
import os

import requests
from lxml import etree

main_url = 'https://bixuejian.5000yan.com/'
response = requests.get(main_url)
response.encoding = 'utf-8'
page_text = response.text
# print(page_text)

tree = etree.HTML(response.text)
a_list = tree.xpath('//ul[@class="mx-auto  row row-cols-1 row-cols-sm-2 row-cols-lg-3"]//a')

os.makedirs('./xiaoshuo_lib', exist_ok=True)
for a in a_list:
    a_href = a.xpath('@href')[0]
    a_text = a.xpath('text()')[0]
    # print(a_text,a_href)
    res = requests.get(a_href)
    res.encoding = 'utf-8'
    tree = etree.HTML(res.text)
    p_list = tree.xpath('//div[@class="grap"]//text()')
    content = ''.join(p_list)
    with open(f'./xiaoshuo_lib/{a_text}.txt', 'w', encoding='utf-8') as f:
        f.write(content)

谷歌浏览器可以生成表达式:

image-20250609184614669

3.4-简历爬取

  • 简历模版下载:https://sc.chinaz.com/jianli/free.html
  • 下载当前页所有的简历模板
import os

import requests
from lxml import etree

os.makedirs('./jianli_lib', exist_ok=True)
url = 'https://sc.chinaz.com/jianli/free.html'
res = requests.get(url)
res.encoding = 'utf-8'
# print(res.text)
tree = etree.HTML(res.text)
a_list = tree.xpath('//div[@class="box col3 ws_block"]/p/a')
for a in a_list:
    a_href = a.xpath('@href')[0]
    a_text = a.xpath('text()')[0]
    # print(a_href, a_text)
    a_res = requests.get(a_href)
    a_res.encoding = 'utf-8'
    a_tree = etree.HTML(a_res.text)
    a_down = a_tree.xpath('//*[@id="down"]/div[2]/ul/li[3]/a')
    for a in a_down:
        a_down_href = a.xpath('@href')[0]
        a_down_text = a.xpath('text()')[0]
        print(a_down_href, a_down_text)
        resp = requests.get(a_down_href)
        with open("jianli_lib/"+a_text+".rar","wb") as f:
            f.write(resp.content)

3.5-图片懒加载爬取

  • url:https://sc.chinaz.com/tupian/meinvtupian.html
import os

import requests
from lxml import etree

os.makedirs('./img_lib', exist_ok=True)
url = 'https://sc.chinaz.com/tupian/meinvtupian_3.html'
req = requests.get(url)
req.encoding = 'utf-8'
# print(req.text)
tree = etree.HTML(req.text)
img_list = tree.xpath('//div[@class="item"]/img')
for img in img_list:
    img_name = img.xpath('@alt')[0]
    img_url =('http:'+ img.xpath('@data-original')[0]).replace('_s.','.')
    print(img_name,img_url)
    resp = requests.get(img_url)
    with open('./img_lib/'+img_name+'.jpg','wb') as f:
        f.write(resp.content)
  • 如何实现图片懒加载/动态加载?
    • 使用img标签的伪属性(指的是自定义的一种属性)。在网页中,为了防止图片马上加载出来,则在img标签中可以使用一种伪属性来存储图片的链接,而不是使用真正的src属性值来存储图片链接。(图片链接一旦给了src属性,则图片会被立即加载出来)。只有当图片被滑动到浏览器可视化区域范围的时候,在通过js将img的伪属性修改为真正的src属性,则图片就会被加载出来。
  • 如何爬取图片懒加载的图片数据?
    • 只需要在解析图片的时候,定位伪属性的属性值即可

image-20250610135854170

4.1-防盗链

  • 现在很多网站启用了防盗链反爬,防止服务器上的资源被人恶意盗取。什么是防盗链呢?

    • 从HTTP协议说起,在HTTP协议中,有一个表头字段:referer,采用URL的格式来表示从哪一个链接跳转到当前网页的。通俗理解就是:客户端的请求具体从哪里来,服务器可以通过referer进行溯源。一旦检测来源不是网页所规定的,立即进行阻止或者返回指定的页面。
  • 案例:抓取微博图片,url:http://blog.sina.com.cn/lm/pic/,将页面中某一组系列详情页的图片进行抓取保存,比如三里屯时尚女郎:http://blog.sina.com.cn/s/blog_01ebcb8a0102zi2o.html?tj=1

    • 注意:

      • 1.在解析图片地址的时候,定位src的属性值,返回的内容和开发工具Element中看到的不一样,通过network查看网页源码发现需要解析real_src的值。

      • 2.直接请求real_src请求到的图片不显示,加上Refere请求头即可

        • 哪里找Refere:抓包工具定位到某一张图片数据包,在其requests headers中获取
import os

import requests
from lxml import etree

os.makedirs('./img_lib', exist_ok=True)
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
    'Referer':'https://blog.sina.com.cn/s/blog_01ebcb8a0102zi2o.html?tj=1'
}
url = 'https://blog.sina.com.cn/s/blog_01ebcb8a0102zi2o.html?tj=1'
res = requests.get(url)
tree = etree.HTML(res.text)
img_list = tree.xpath('//*[@id="sina_keyword_ad_area2"]/div/a/img')
i=0
for img in img_list:
    img_href = img.xpath('@real_src')[0]
    response = requests.get(img_href,headers=headers)
    i=i+1
    with open('./img_lib/'+str(i)+'.jpg', 'wb') as f:
        f.write(response.content)

4.2-代理

隧道代理:隧道代理IP是一种动态代理技术,用户通过固定的代理服务器地址发起请求,服务器会自动将请求转发到不同的代理IP上,实现IP的频繁更换。其核心在于云端自动管理IP切换,无需人工干预。

  • 代理的匿名度

    • 透明:网站的服务器知道你使用了代理,也知道你的真实ip
    • 匿名:网站服务器知道你使用了代理,但是无法获知你真实的ip
    • 高匿:网站服务器不知道你使用了代理,也不知道你的真实ip(推荐)
  • 代理的类型(重要)

    • http:该类型的代理服务器只可以转发http协议的请求
    • https:可以转发https协议的请求
  • 如何获取代理?

    • 携趣代理:https://www.xiequ.cn/index.html?f301de7f

测试:访问如下网址,返回自己本机ip:

import requests
from lxml import etree
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
}
url = 'http://www.cip.cc/'

page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0]
print(text.split('\n')[1])

使用代理发起请求,查看是否可以返回代理服务器的ip:

import requests
from lxml import etree
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36',
}
url = 'http://www.cip.cc/'

page_text = requests.get(url,headers=headers,proxies={'http':'182.38.125.232:3828'}).text
tree = etree.HTML(page_text)
text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0]
print(text.split('\n')[1])

4.3-代理池构建

测试网站:https://wz.sun0769.com/political/index/politicsNewest?id=1

多次频繁请求:

import requests
from lxml import etree

for page in range(1,100):
    url = 'https://wz.sun0769.com/political/index/politicsNewest?id=1&page=%d' % page
    response = requests.get(url)
    tree = etree.HTML(response.content)
    text = tree.xpath('/html/body/div[2]/div[3]/ul[2]/li[1]/span[3]/a/text()')[0]
    print(text)

image-20250610151644390

image-20250610151710554

下面用代理进行:

image-20250610151837234

在代码中获取ip池中的ip:

import random

import  requests
from lxml import etree


# 一次性获取多个IP
def get_proxy_ip_port():
    url = 'http://api.xiequ.cn/VAD/GetIp.aspx?act=get&uid=161726&vkey=B8FC6DAB4158AC3EA48ADCEF09137350&num=5&time=30&plat=0&re=0&type=2&so=1&ow=1&spl=1&addr=&db=1'
    response = requests.get(url)
    ip_data = response.json()
    return ip_data['data']

ip_list = get_proxy_ip_port()
for page in range(1,100):
    # 取随机ip进行访问
    ip_info = random.choice(ip_list)
    ip, port = ip_info['IP'], ip_info['Port']
    url = 'https://wz.sun0769.com/political/index/politicsNewest?id=1&page=%d' % page
    response = requests.get(url,proxies={'https':'%s:%s'%(ip,port)})
    response.encoding ='utf-8'
    tree = etree.HTML(response.text)
    # print(response.text)
    text = tree.xpath('/html/body/div[2]/div[3]/ul[2]/li[1]/span[3]/a/text()')[0]
    print(text)

4.4-中大网校考试中心题目爬取

需求:https://ks.wangxiao.cn/,每个一级目录下有二级目录,每个二级目录详情页下会有【每日一练】,把所有一级目录下对应所有二级目录的【每日一练】的所有题目爬下来。

import io
import json
import os

import requests
from docx.oxml.ns import qn
from docx.shared import Pt, RGBColor, Inches
from lxml import etree
from docx import Document
from bs4 import BeautifulSoup

headers = {
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'Connection': 'keep-alive',
    'Content-Type': 'application/json; charset=UTF-8',
    'Origin': 'https://ks.wangxiao.cn',
    'Referer': 'https://ks.wangxiao.cn/practice/getQuestion?practiceType=1&sign=jzs1&subsign=5166078fbf1eed222fe9&day=20250609',
    'Sec-Fetch-Dest': 'empty',
    'Sec-Fetch-Mode': 'cors',
    'Sec-Fetch-Site': 'same-origin',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
    'X-Requested-With': 'XMLHttpRequest',
    'sec-ch-ua': '"Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'Cookie': 'acw_tc=3adad01817495610953552347e93a9eaad03204d586f8faff396001397; wxLoginUrl=http%3A%2F%2Fks.wangxiao.cn%2Fpractice%2FgetQuestion%3FpracticeType%3D1%26sign%3Djzs1%26subsign%3D5166078fbf1eed222fe9%26day%3D20250610; safedog-flow-item=; UserCookieName=ztk_12394272; OldUsername2=h%2Bj9hmYRjBu%2BNMB1TLMNDw%3D%3D; OldUsername=h%2Bj9hmYRjBu%2BNMB1TLMNDw%3D%3D; OldPassword=RMaC%2BoiWmXw%3D; UserCookieName_=ztk_12394272; OldUsername2_=h%2Bj9hmYRjBu%2BNMB1TLMNDw%3D%3D; OldUsername_=h%2Bj9hmYRjBu%2BNMB1TLMNDw%3D%3D; OldPassword_=RMaC%2BoiWmXw%3D; ztk_12394272_exam=ky; mantis6894=09946c45310b4bcd8bbdf45f09c394c3@6894; acw_sc__v3=68482fbfba6c2eaa7ec6c59b67409cce0b7df1a0; tfstk=glQmnDi-9i-jmIyJ2aTbYicLkvrJlET6KO39BFpa4LJWkqCZkFjGLs5YGECwIGvPYi3vBFTG_9C93FIvHClfr90tk-Zf_VY97JeLJyCbGFTap0ax7cGX6Ckq3AKZ4Y89sezeLnCfGFGSwArd4sGGlLNeuFWwz3RBOFuq0O8r4LO6_xRZu7VkFCmZ7d8wzbRH9d8w7O5zZC9yQF-VQ_logVJh7a_zeNoJ5lAWAaADmp5uAVui1IkpKsrsSV7lqnvESL0Z7aAVsTSQb2q9LM9vvpWzyqYGaCXve9zErU-hA1dGnr02kgS5lEQ8KXtcIKKFr3Dq0BYDnH72VX0hMhSlrEQ4CD1RZK-HygE7HhLcnM9B0uw5I_vAQZxrn8pOv_Q2q9y-rOtGA1dGnr0VLg5S4DzRMVOz6aosfnRWZpLzwiQk2HCe77VoAot2NIeLZ7msfnRWZpFuZDM60QOYp; userInfo=%7B%22userName%22%3A%22ztk_12394272%22%2C%22token%22%3A%2274566d2c-5edb-42a2-be4b-cbc9ff6fe86e%22%2C%22headImg%22%3A%22https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FDYAIOgq83eptPhP5Yak0v9w51Ypk4owO47sH0yus1IgBoicZD6go9XEnpkWxU0slofnnbhyRzW4SdBJQHA6IJqQ%2F132%22%2C%22nickName%22%3A%22176****5285%22%2C%22sign%22%3A%22ky%22%2C%22isBindingMobile%22%3A%221%22%2C%22isSubPa%22%3A%220%22%2C%22userNameCookies%22%3A%22h%2Bj9hmYRjBu%2BNMB1TLMNDw%3D%3D%22%2C%22passwordCookies%22%3A%22RMaC%2BoiWmXw%3D%22%7D; token=74566d2c-5edb-42a2-be4b-cbc9ff6fe86e'
}

# 获取一级目录
def get_category_level1():
    url = 'https://ks.wangxiao.cn/'
    res = requests.get(url)
    res.encoding = 'utf-8'
    tree = etree.HTML(res.text)
    # print(res.text)
    element_list = tree.xpath('//ul[@class="first-title"]/li')
    return element_list


# 获取二级目录dict_list  name,sign
def get_category_level2(category_level1):
    element_list = category_level1.xpath('.//div[@class="send-title"]/a')
    res_dict = list(map(lambda x: {'name': x.xpath('text()')[0], 'sign': x.xpath('@href')[0].split('=')[-1]}, element_list))
    return res_dict


# 获取每日一练dict_list
def get_everyday_homework(level2_dict):
    name = level2_dict['name']
    url = 'https://ks.wangxiao.cn/practice/listEveryday?sign=' + level2_dict['sign']
    res = requests.get(url)
    res.encoding = 'utf-8'
    # print(res.text)
    tree = etree.HTML(res.text)
    test_list = tree.xpath('//ul[@class="test-item"]')
    res_dict = list(map(lambda x: {'name': x.xpath('.//li/text()')[1],
                                   'subsign':x.xpath('.//li/a/@href')[0].split('&')[2].split('=')[-1],
                                   'day':x.xpath('.//li/a/@href')[0].split('&')[3].split('=')[-1]
                                   },test_list))
    return res_dict

# 获取每日一练详情(题目)
def get_everyday_homework_question(sign,subsign,day):
   url = 'https://ks.wangxiao.cn/practice/listQuestions'
   payload = {"practiceType":"1","sign":sign,"subsign":subsign,"day":day}
   response = requests.request("POST", url, headers=headers, data=json.dumps(payload))
   response.encoding= 'utf-8'
   json_data = response.json()
   return json_data

# 题目、选项样式
def set_question_style(paragraph):
    for run in paragraph.runs:
        run.font.name = '宋体'
        run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
        run.font.size = Pt(12)
        run.font.color.rgb = RGBColor(0, 0, 0)

# 解析样式
def set_analysis_style(paragraph):
    for run in paragraph.runs:
        run.font.name = '宋体'
        run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
        run.font.size = Pt(8)
        run.font.color.rgb = RGBColor(255, 0, 0)

# 持久化每日一练内容
def save_question_to_word(data,homework_dict):
    document = Document()
    doc_name = homework_dict['name']+homework_dict['day']
    document.add_heading(doc_name, 0)
    paper_type_code = data['paperRule']['sort']
    print(data['paperRule']['content'],data['paperRule']['sort'])
    if paper_type_code != 1:
        return
    document.add_heading(data['paperRule']['content'], 2)
    question_list = data['questions']
    # 题目
    i = 1
    for question in question_list:
        # print(question['content'])
        soup = BeautifulSoup(question['content'], 'html.parser')
        clean_text = soup.get_text()
        paragraph =document.add_paragraph(str(i)+'.'+clean_text)
        set_question_style(paragraph)
        option_list = question['options']
        # 选项
        for option in option_list:
            soup = BeautifulSoup(option['content'], 'html.parser')
            clean_text = soup.get_text()
            option_text = option['name']+'.'+clean_text
            if option['isRight']==1:
                option_text+='✔️'
            paragraph = document.add_paragraph(option_text)
            set_question_style(paragraph)
        # 解析
        soup = BeautifulSoup(question['textAnalysis'],'html.parser')
        clean_text = soup.get_text()
        paragraph =document.add_paragraph('解析:'+clean_text)
        set_analysis_style(paragraph)
        if question['textAnalysis']:
            tree = etree.HTML(question['textAnalysis'])
            img_src_list = tree.xpath('//img/@src')
            if len(img_src_list)>0:
                img_src = img_src_list[0]
                if not img_src:
                    continue
                elif img_src.startswith('http://') or img_src.startswith('https://'):
                    pass
                elif img_src.startswith('//'):
                    img_src = 'http:' + img_src
                else:
                    img_src = 'http://' + img_src
                response = requests.get(img_src,headers=headers)
                if response.status_code != 200:
                    print('图片响应码',response.status_code)
                    continue
                image_data = io.BytesIO(response.content)
                document.add_picture(image_data,width=Inches(3))
        i = i+1
    document.save('./homework_lib/'+doc_name+'.docx')


# 批量
def batch_down():
    category_level1_list = get_category_level1()
    for category_level1 in category_level1_list:
        level2_dict_list = get_category_level2(category_level1)
        for level2_dict in level2_dict_list:
            # 只爬公务员题目
            if level2_dict['sign'] == 'gwy':
                homework_dict_list = get_everyday_homework(level2_dict)
                for homework_dict in homework_dict_list:
                   question_dict =  get_everyday_homework_question(level2_dict['sign'],homework_dict['subsign'],homework_dict['day'])
                   # print(question_dict)
                   save_question_to_word(question_dict['Data'][0], homework_dict)

if __name__ == '__main__':
    os.makedirs('./homework_lib', exist_ok=True)
    batch_down()
    # level1 = get_category_level1()
    # level2_dict_list = get_category_level2(level1[0])
    # a = get_everyday_homework(level2_dict_list[0])
    # # print(a)
    # b= get_everyday_homework_question(level2_dict_list[0]['sign'], a[0]['subsign'],
    #                                a[0]['day'])
    # print(b['Data'][0])
    # save_question_to_word(b['Data'][0],a[0])
    # print(level1)
    # for item in level1:
    #     level1_name = item.xpath('.//span/text()')[0]
    #     # print(level1_name)
    #     # for dict in level2_dict_list:
    #         # print(dict)

5.1~5.3-JS基础(资料)

Node.js是一个开源的、跨平台的JavaScript运行时环境!

进入官网地址下载安装包

官网:https://nodejs.org/en/downloadopen in new window

选择对应你系统的Node.js版本

Snipaste_2025-03-06_18-26-16

安装程序

(1)下载完成后,双击安装包open in new window,开始安装Node.js

Snipaste_2025-03-06_18-28-52

(2)直接点【Next】按钮,此处可根据个人需求修改安装路径,我这里路径改为了D:Program Files odejs,修改完毕后继续点击【Next】按钮

Snipaste_2025-03-06_18-29-57

(3)不选中,直接点击【Next】按钮

Snipaste_2025-03-06_18-31-17

(4)测试安装是否成功,按下【win+R】键,输入cmd,打开cmd窗口,输入:

Snipaste_2025-03-06_18-32-43

成功显示版本说明安装成功!

Pycharm配置

  1. 文件->设置

    Snipaste_2025-03-06_18-37-47
  2. 插件(Plugins)->插件市场搜索node,安装

    Snipaste_2025-03-06_18-38-23
  3. 检查语言与框架中Node.js的配置,我的自动添加了路径,如果没有,配置前边安装的node.js的路径即可。

    Snipaste_2025-03-06_18-38-58

然后就可以使用PyCharm去运行js文件了。

一. Javascript基本数据类型

JS虽然是一个脚本语言. 麻雀虽小, 五脏俱全. 在js中也是可以像其他编程语言一样. 声明变量, 条件判断, 流程控制等等. 我们先看一下JS中的数据类型

在js中主要有这么几种数据类型

Snipaste_2024-06-27_09-42-06

在js中声明变量用var来声明

在js中使用// 来表示单行注释. 使用/* */表示多行注释.

var 变量名; // 创建变量, 此时该变量除了被赋值啥也干不了. 
var 变量名 =; // 创建一个变量, 并且有值. 
var 变量名 =1, 变量名2 =2, 变量名3 =3.....; // 一次创建多个变量.并都有值
var 变量名1, 变量名2, 变量名3 =3;  // 创建多个变量. 并且只有变量3有值

数据类型转换:

// string -> number  :  parseInt(字符串)
var a = "10086";
a = parseInt(a);  // 变成整数
console.log(a + 10); // 10096

// number -> string  : 数字.toString() 或者 数字 + ""
var a = 100;
var b = a.toString();
var c = a + "";  
console.log(b);
console.log(c);

// number -> string: 数字转化成16进制的字符串
var m = 122;
var n = m.toString(16);
console.log(n);

// 进制转换:十六进制的AB的十进制是多少
var d = parseInt("AB", 16); // 171

// 自动转换:弱类型中的变量会根据当前代码的需要,进行类型的自动隐式转化
var box1 = 1 + true;
// true 转换成数值,是1, false转换成数值,是0
console.log(box1); // 2

字符串操作:

// split   正则分割,经常用于把字符串转换成数组
var str = "广东-深圳-南山";
var ret = str.split("-");
console.log( ret );

// substr  截取
var str = "hello world";
var ret = str.substr(0,3);
console.log(ret); // hel

// trim    移除字符串首尾空白
var password = "    ge llo   ";
var ret = password.trim();
console.log(password.length); // 13
console.log(ret.length);  // 6

// 切片,当前方法支持使用负数代表倒数下标
// slice(开始下标)   从开始位置切到最后
// slice(开始下标,结束下标)  从开始下标切到指定位置之前
var str = "helloworld";
var ret = str.slice(3,6); // 开区间,不包含结束下标的内容
console.log(ret); // low
var ret = str.slice(5);
console.log(ret); // world
var ret = str.slice(2,-1);
console.log(ret); // lloworl

s.substring(start, end)  //字符串切割, 从start切割到end
s.length  //字符串长度
s.charAt(i) //第i索引位置的字符  s[i]
s.indexOf('xxx')  //返回xxx的索引位置, 如果没有xxx. 则返回-1
s.lastIndexOf("xxx") //返回xxx的最后一次出现的索引位置,如果没有xxx. 则返回-1
s.toUpperCase() //转换成大写字母
s.startsWith("xxx")  //判断是否以xxx开头
s.charCodeAt(i) //某个位置的字符的ascii
String.fromCharCode(ascii) //给出ascii 还原成正常字符

字符串正则:

// match  正则匹配
// js中也存在正则,正则的使用符号和python里面是一样的
// 语法:/正则表达式主体/修饰符(可选)
//修饰符:
	//i:执行对大小写不敏感的匹配。
	//g:执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。

var str = "我的电话是: 13312345678,你的电话: 13512345678";
var ret = str.match(/\d{11}/g); // 匹配,提取数据
console.log(ret);

// replace  正则替换
var str = "我的电话是: 13512345678";
var ret = str.replace(/(\d{3})\d{4}(\d{4})/,"$1****$2"); // 正则的捕获模式  $1$2表示的正则中第一个和第二个小括号捕获的内容
console.log(ret);

// search  正则查找,如果查找不到,则返回-1
var str = "hello";
var ret = str.search(/l/);
console.log(ret);
  • undefined类型

undefined类型只有一个值,即 undefined。

(1) 当声明的变量未初始化时,该变量的默认值是 undefined。

(2)当函数无明确返回值时,返回的也是值 undefined;

  • null类型

另一种只有一个值的类型是 null,它只有一个专用值 null,即它的字面量。值 undefined 实际上是从值 null 派生来的,因此 js 把它们定义为相等的。

尽管这两个值相等,但它们的含义不同。undefined 是声明了变量但未对其初始化时赋予该变量的值,null 则用于表示尚未存在的对象。如果函数或方法要返回的是对象,那么找不到该对象时,返回的通常是 null。

  • 原始值和引用值
// 初始值类型
var a = "bobo";
var b = a;
a = "alvin";
console.log(a);//alvin
console.log(b);//bobo

// 对象类型
var arr1=[1,2];
arr2 = arr1;
arr1.push(3);
console.log(arr1)// [1,2,3]
console.log(arr2);//[1,2,3]

arr1=[4,5];
console.log(arr1);//[4,5]
console.log(arr2);//[1,2,3]
  • 运算符
/*
//算术运算符
   +   数值相加
   -   数值相减
   *   数值相乘
   /   数值相除
   %   数值求余
   **  数值求幂
   a++ 变量被使用后自增1 
     var a = 10
     print(a++) 输出结果为10
     print(a) 就是11
   ++a 变量被使用前自增1  
     var b = 10
     print(++b) 输出的就是11
   b-- 变量被使用后自减1   
   --b 变量被使用前自减1 
   // 在python中是没有++操作的. 但是在js中是有的. 
        a++;  // 翻译一下就是a = a + 1 
        ++a;  // 翻译一下就是a = a + 1
        a--;  // 翻译一下就是a = a - 1
        --a;  // 翻译一下就是a = a - 1
   
//赋值运算符
   =
   +=
   -=
   *=
   /=
   %=
   **=

//比较运算符,比较的结果要么是true, 要么是false
	>   大于
	<   小于
	>=  大于或者等于
	<=  小于或者等于
	!=  不等于[计算数值]
	==  等于[计算]

  !== 不全等[不仅判断数值,还会判断类型是否一致]
	=== 全等[不仅判断数值,还会判断类型是否一致]
	let num1 = 3.14;
    let num2 = "3.14";
    console.log(num1 == num2); // true,因为==运算符会进行类型转换,比较它们的值是否相等
    console.log(num1 === num2); // false,因为===运算符不会进行类型转换,比较它们的值和类型是否都相等

//逻辑运算符
  &&   并且  and    两边的运算结果为true,最终结果才是true
  ||   或者  or     两边的运算结果为false,最终结果才是false
  !    非    not    运算符的结果如果是true,则最终结果是false ,反之亦然.
 

//条件运算符[三目运算符]
	 条件?true:false
	 例如:
	    var age = 12;
        var ret = age>=18?"成年":"未成年"; 
        console.log(ret);
 */

二. 流程控制语句

编程语言的流程控制分为三种:

  • 顺序结构(从上向下顺序执行)
  • 分支结构
  • 循环结构

之前我们学习的方式就是顺序执行,即代码的执行从上到下,一行行分别执行。

例如:

console.log("星期一");
console.log("星期二");
console.log("星期三");

分支结构

  • if 分支语句
 if(条件){
     // 条件为true时,执行的代码
   }
   
   if(条件){
     // 条件为true时,执行的代码
   }else{
     // 条件为false时,执行的代码
   }
   
   if(条件1){
     // 条件1为true时,执行的代码
   }else if(条件2){
     // 条件2为true时,执行的代码
   
   }....
   
   }else{
     // 上述条件都不成立的时候,执行的代码
   }
  • switch语句
switch(条件){
      case 结果1:
           满足条件执行的结果是结果1,执行这里的代码..
           break;
      case 结果2:
      	   满足条件执行的结果是结果2,执行这里的代码..
      	   break;
      .....
      default:
           条件和上述所有结果都不相等时,则执行这里的代码
   }

switch('a'):
  case 1: //只会会执行case 1下面的xxx代码
  	xxx
  	break;
  case 2:
  	xxx
  	break;
  default:
  	xxx
  	break

1、switch比if else更为简洁

2、执行效率更高。switch…case会生成一个跳转表来指示实际的case分支的地址,而这个跳转表的索引号与switch变量的值是相等的。从而,switch…case不用像if…else那样遍历条件分支直到命中条件,而只需访问对应索引号的表项从而到达定位分支的目的。

3、到底使用哪一个选择语句,代码环境有关,如果是范围取值,则使用if else语句更为快捷;如果是确定取值,则使用switch是更优方案。

循环语句

  • while循环
   while(循环的条件){
      // 循环条件为true的时候,会执行这里的代码
   }
  

循环案例:

 var count = 0
 while (count<10){
     console.log(count);
     count++;
 }
  • for循环
   
   // 循环三要素
   for(1.声明循环的开始; 2.条件; 4. 循环的计数){
      // 3. 循环条件为true的时候,会执行这里的代码
   }
   
   for(循环的成员下标 in 被循环的数据){
      // 当被循环的数据一直没有执行到最后下标,都会不断执行这里的代码
   }   

循环案例:

// 方式1
for (var i = 0;i<10;i++){
	console.log(i)
}

// 方式2
var arr = [111,222,333]
for (var i in arr){
    console.log(i,arr[i])
}

  • 退出循环(break和continue)
 for (var i = 0;i<100;i++){
           if (i===88){
               continue  // 退出当次循环
               // break  // 退出当前整个循环
           }
           console.log(i)
       }

三.数组对象

  • 创建数组
创建方式1:
var arrname = [元素0,元素1,.];          // var arr=[1,2,3];

创建方式2:
var arrname = new Array(元素0,元素1,.); // var test=new Array(100,"a",true);
  • 数组方法
var arr = ["A","B","C","D"];
// 内置属性
console.log( arr.length );
// 获取指定下标的成员
console.log( arr[3] ); // D
console.log( arr[arr.length-1] ); // 最后一个成员

// (1) pop()  出栈,删除最后一个成员作为返回值
var arr = [1,2,3,4,5];
var ret = arr.pop();
console.log(arr); // [1, 2, 3, 4]
console.log(ret); // 5


// (2) push() 入栈,给数组后面追加成员
var arr = [1,2,3,4,5];
arr.push("a");
console.log(arr); // [1, 2, 3, 4, 5, "a"]


// (3) shift是将数组的第一个元素删除
var arr = [1,2,3,4,5];
arr.shift()
console.log(arr); // [2, 3, 4, 5]

// (4) unshift是将value值插入到数组的开始
var arr = [1,2,3,4,5];
arr.unshift("bobo")
console.log(arr); // ["bobo",1,2, 3, 4, 5]


// (5) reverse() 反转排列
var arr = [1,2,3,4,5];
arr.reverse();
console.log(arr); // [5, 4, 3, 2, 1]

// (6) slice(开始下标,结束下标)  切片,开区间
var arr = [1,2,3,4,5];
console.log(arr.slice(1,3));


// (7) concat() 把2个或者多个数组合并
var arr1 = [1,2,3];
var arr2 = [4,5,7];
var ret = arr1.concat(arr2);
console.log( ret );


// (8) join()  把数组的每一个成员按照指定的符号进行拼接成字符串
var str = "广东-深圳-南山";
var arr = str.split("-");
console.log( arr ); // ["广东", "深圳", "南山"];

var arr1 = ["广东", "深圳", "南山"];
var str1 = arr1.join("-");
console.log( str1 ); // 广东-深圳-南山
       
  • 遍历数组
 var arr = [12,23,34]
 for (var i in arr){
       console.log(i,arr[i])
 }

四.Object对象

object对象的基本操作

Object 的实例不具备多少功能,但对于在应用程序中存储和传输数据而言,它们确实是非常理想的选择。 创建 Object 实例的方式有两种。

var person = new Object();
person.name = "alvin";
person.age = 18;

另一种方式是使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。下面这个例子就使用了对象字面量语法定义了与前面那个例子中相同的person 对象:

var person = {
                name : "alvin",
                age : 18,
                say: function(){
                      alert(123);
                  }
             }; 
  • object可以通过. 和 []来访问。
console.log(person["age"]);
console.log(person.age)
  • object可以通过for循环遍历
 for (var attr in person){
           console.log(attr,person[attr]);
      }
  • 最后一种创建Object对象的方式:

  • function People(name, age){
        this.name = name; //this表示对象的调用者
        this.age = age;
        this.chi = function(){
            console.log(this.name, "在吃东西")
        }
    }
    p1 = new People("alex", 18);
    p2 = new People("wusir", 20);
    p1.chi();
    p2.chi();
    

json序列化和反序列化

JSON:JavaScript 对象表示法,是一种轻量级的数据交换格式。易于人阅读和编写。

// json是一种数据格式, 语法一般是{}或者[]包含起来
// 内部成员以英文逗号隔开,最后一个成员不能使用逗号!
// 可以是键值对,也可以是列表成员
// json中的成员如果是键值对,则键名必须是字符串.而json中的字符串必须使用双引号圈起来
// json数据也可以保存到文件中,一般以".json"结尾.


{
   "name": "xiaoming",
	"age":12
}

[1,2,3,4]

{
   "name": "xiaoming",
   "age":22,
   "sex": true,
   "son": {
      "name":"xiaohuihui",
      "age": 2
   },
   "lve": ["篮球","唱","跳"]
}

js中也支持序列化和反序列化的方法:

/*
    序列化:可以将对象转换成JSON字符串的形式(可以将对象进行持久化存储)
    反序列化:可以将JSON格式的字符串转换成对象
*/

//创建了一个对象
var data = {
    name: "xiaoming",
    age: 22,
};

//序列化
var json_str = JSON.stringify(data);
console.log(json_str);

//反序列化
obj = JSON.parse(json_str)
console.log(obj);

Date对象

  • 创建Date对象
//方法1:不指定参数
var nowd1 = new Date(); //获取当前时间
console.log(nowd1);
console.log(nowd1.toLocaleString( ));
//方法2:参数为日期字符串
var d2=new Date("2004/3/20 11:12");
console.log(d2.toLocaleString( ));
var d3=new Date("04/03/20 11:12");
console.log(d3.toLocaleString( ));
  • 获取时间信息
获取日期和时间
var date=new Date();
date.getDate();
    getDate()                 获取日
    getDay ()                 获取星期
    getMonth ()               获取月(0-11getFullYear ()            获取完整年份
    getHours ()               获取小时
    getMinutes ()             获取分钟
    getSeconds ()             获取秒
    getMilliseconds ()        获取毫秒
    getTime()  //  时间戳. 表示从1970-1-1 00:00:00 到现在一共经过了多少毫秒

Math对象

// Math对象的内置方法
// abs(x)  返回数值的绝对值
var num = -10;
console.log( Math.abs(num) ); // 10

// ceil(x)  向上取整
var num = 10.3;
console.log( Math.ceil(num) ); // 11

// floor(x) 向下取整
var num = 10.3;
console.log( Math.floor(num) ); // 10

// max(x,y,z,...,n)
console.log( Math.max(3,56,3) ); // 56
// min(x,y,z,...,n)


// random()  生成0-1随机数
console.log( Math.random() );

// 生成0-10之间的数值
console.log( Math.random() * 10 );

// round(x) 四舍五入
// 生成0-10之间的整数
console.log( Math.round( Math.random() * 10 ) );

JS中的函数(重点)

​ 函数在程序中代表的就是一段具有功能性的代码,可以让我们的程序编程更加具有结构性和提升程序的复用性,也能让代码变得更加灵活强大

声明函数

function 函数名 (参数){
    函数体;
    return 返回值;
}
功能说明:

    函数由关键字function定义
    函数名的定义规则与标识符一致,大小写是敏感的
    返回值必须使用return
    

函数调用

    //f(); --->OK
    function f(){
        console.log("hello")

    }
    f() //----->OK

函数参数

(1) 参数基本使用

// 位置参数
function add(a,b){

    console.log(a);
    console.log(b);
}
add(1,2)
add(1,2,3)
add(1)


// 默认参数
function stu_info(name,gender="male"){
    console.log("姓名:"+name+" 性别:"+gender)
}

stu_info("bobo")

函数返回值

在函数体内,使用 return 语句可以设置函数的返回值。一旦执行 return 语句,将停止函数的运行,并运算和返回 return 后面的表达式的值。如果函数不包含 return 语句,则执行完函数体内每条语句后,返回 undefined 值。

function add(x,y) {
          return x,y  //只会返回一个值x
      }

var ret = add(2,5);
console.log(ret)

1、在函数体内可以包含多条 return 语句,但是仅能执行一条 return 语句

2、函数的参数没有限制,但是返回值只能是一个;如果要输出多个值,可以通过数组或对象进行设计。

函数作用域

作用域是JavaScript最重要的概念之一。

JavaScript中,变量的作用域有全局作用域和局部作用域两种。

// 局部变量,是在函数内部声明,它的生命周期在当前函数被调用的时候, 当函数调用完毕以后,则内存中自动销毁当前变量
// 全局变量,是在函数外部声明,它的生命周期在当前文件中被声明以后就保存在内存中,直到当前文件执行完毕以后,才会被内存销毁掉

作用域案例:

var num = 10; // 在函数外部声明的变量, 全局变量
function func(){
  //千万不要再函数内部存在和全局变量同名的变量
  num = 20; // 函数内部直接使用变量,则默认调用了全局的变量,
}
func();
console.log("全局num:",num);

匿名函数

匿名函数,即没有变量名的函数。在实际开发中使用的频率非常高!也是学好JS的重点。

      // 匿名函数赋值变量
       var foo = function () {
           console.log("这是一个匿名函数!")
       };
      foo() //调用匿名函数
			

      // 匿名函数的自执行
      (function (x,y) {
           console.log(x+y);
       })(2,3)


     // 匿名函数作为一个高阶函数使用
     function bar() {

        return function () {
            console.log("inner函数!")
        }
    }
    bar()()
        

6.1~6.4-Javascript进阶(资料)

一. 变量提升(不正常现象)

看以下代码, 或多或少会有些问题的.

function fn(){
    console.log(name);
    var name = '大马猴';
}
fn()

发现问题了么. 这么写代码, 在其他语言里. 绝对是不允许的. 但是在js里. 不但允许, 还能执行. 为什么呢? 因为在js执行的时候. 它会首先检测你的代码. 发现在代码中会有name使用. OK. 运行时就会变成这样的逻辑:

function fn(){
    var name;
    console.log(name);
    name = '大马猴';
}
fn()

看到了么. 实际运行的时候和我们写代码的顺序可能会不一样....这种把变量提前到代码块第一部分运行的逻辑被称为变量提升. 这在其他语言里是绝对没有的. 并且也不是什么好事情. 正常的逻辑不应该是这样的. 那么怎么办? 在新的ES6中. 就明确了, 这样使用变量是不完善的. es6提出. 用let来声明变量. 就不会出现该问题了.

function fn(){
    console.log(name);  // 直接报错, let变量不可以变量提升.
    let name = '大马猴'; 
}
fn()

结论一, 用let声明变量是新版本javascript提倡的一种声明变量的方案

let还有哪些作用呢?

function fn(){
    var name = "周杰伦";
    var name = "王力宏";
    console.log(name);
}
fn()

显然一个变量被声明了两次. 这样也是不合理的. var本意是声明变量. 同一个东西. 被声明两次. 所以ES6规定. let声明的变量. 在同一个作用域内. 只能声明一次.

function fn(){
    let name = "周杰伦";
    console.log(name);
    let name = "王力宏";
    console.log(name);
}
fn()

注意, 报错是发生在代码检查阶段. 所以. 上述代码根本就执行不了.

结论二, 在同一个作用域内. let声明的变量只能声明一次. 其他使用上和var没有差别

**注意:**直接进行变量声明,不使用let和var的含义是什么?

不使用`var`直接赋值的变量,如果没有提前声明,它会成为全局变量。这意味着它可以在整个程序的任何地方访问和修改,这可能导致意外的副作用和难以调试的错误。

function fn(){
    name = '大马猴';
}
fn()
console.log(name)  //正确


function fn(){
    var name = '大马猴';
}
fn()
console.log(name)//报错


function fn(){
    var name = '大马猴';
}
fn()
console.log(name) //报错

二. 闭包函数

我们先看一段代码.

let name = "周杰伦";
function chi(){
    name = "吃掉";
}
chi();
console.log(name);

发现没有, 在函数内部想要修改外部的变量是十分容易的一件事. 尤其是全局变量. 这是非常危险的. 试想, 我写了一个函数. 要用到name, 结果被别人写的某个函数给修改掉了. 多难受.

接下来. 我们来看一个案例:

同时运行下面两组代码:

// 1号工具人.
var name = "alex"

setTimeout(function(){
    console.log("一号工具人:"+name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
}, 5000);

// 2号工具人
var name = "周杰伦"
console.log("二号工具人", name);

两组代码是在同一个空间内执行的. 他们拥有相同的作用域. 此时的变量势必是非常非常不安全的. 那么如何来解决呢? 注意, 在js里. 变量是有作用域的. 也就是说一个变量的声明和使用是有范围的. 不是无限的. 这一点, 很容易验证.

function fn(){
    let love = "爱呀"
}
fn()
console.log(love)

直接就报错了. 也就是说. 在js里是有全局和局部的概念的. 直接声明在最外层的变量就是全局变量. 所有函数, 所有代码块都可以共享的. 但是反过来就不是了. 在函数内和代码块内声明的变量. 它是一个局部变量. 外界是无法进行访问的. 我们就可以利用这一点来给每个工具人创建一个局部空间. 就像这样:

// 1号工具人.
(function(){
    var name = "alex";
    setTimeout(function(){
        console.log("一号工具人:"+name) // 一号工具人还以为是alex呢, 但是该变量是不安全的.
    }, 5000);
})();

// 二号工具人
(function(){
    var name = "周杰伦"
    console.log("二号工具人", name);
})();

这样虽然解决了变量的冲突问题. 但是...我们想想. 如果在函数外面需要函数内部的一些东西来帮我进行相关操作怎么办...比如, 一号工具人要提供一个功能(加密). 外界要调用. 怎么办?

// 1号工具人.
let encrypt_tool = (function(){
    let log_msg = '开始加密......\n'
    // 我是一个加密函数
    let encrypt = function(data){  // 数据
        console.log(log_msg) //打印日志信息(访问外部变量)
        // 返回密文
        return atob(data);//用于将一个Base64编码的字符串解码为原始的二进制数据
    }
    // 外面需要用到这个功能啊. 你得把这个东东返回啊. 返回加密函数
    return encrypt;
})();

//外部调用
console.log(encrypt_tool('i love you'));

注意了. 我们如果封装一个加密js包的时候. 好像还得准备出解密的功能. 并且, 不可能一个js包就一个功能吧. 那怎么办? 我们可以返回一个对象. 对象里面可以存放好多个功能. 而一些不希望外界触碰的功能. 就可以很好的保护起来.

// 1号工具人.
let encrypt_tool = (function(){
    let log_msg_1 = '开始加密......'
    let log_msg_2 = '开始解密......'

    // 我是一个加密函数
    let encrypt = function(data){  // 被加密数据
        console.log(log_msg_1) //打印日志信息(访问外部变量)
        // 返回密文
        return atob(data);
    };
    //解密函数
    let decrypt = function(en_data){  // 加密后的数据
        console.log(log_msg_2) //打印日志信息(访问外部变量)
        // 返回解密后的原数据
        return btoa(en_data);
    };
    // 外面需要用到这个功能啊. 你得把这个东东返回啊. 返回加密函数
    return {'encrypt':encrypt,'decrypt':decrypt};
})();

//外部调用
en_data = encrypt_tool.encrypt('i love you');
de_data = encrypt_tool.decrypt(en_data)
console.log(en_data);
console.log(de_data);

OK. 至此. 何为闭包? 上面这个就是闭包. 相信你百度一下就会知道. 什么内层函数使用外层函数变量. 什么让一个变量常驻内存.等等. 其实你细看. 它之所以称之为闭包~. 它是一个封闭的环境. 在内部. 自己和自己玩儿. 避免了对该模块内部的冲击和改动. 避免的变量之间的冲突问题.

闭包的特点:

  1. 内层函数对外层函数变量的使用.
  2. 会让变量常驻与内存.

三. JS中的各种操作(非交互)

3.1 定时器

在JS中, 有两种设置定时器的方案

// 语法规则
setTimeout(函数, 时间)
// 经过xxx时间后, 执行xxx函数

// 示例:3秒后打印我爱你
setTimeout(function(){
    console.log("我爱你")
}, 3000);

3.2 eval函数(必须会. 隔壁村老太太都会.)

eval本身在js里面正常情况下使用的并不多. 但是很多网站会利用eval的特性来完成反爬操作. 我们来看看eval是个什么鬼?

从功能上讲, eval非常简单. 它和python里面的eval是一样的. 它可以动态的把字符串当成js代码进行运行.

s = "console.log('我爱你')";
eval(s);

也就是说. eval里面传递的应该是即将要执行的代码(字符串). 那么在页面中如果看到了eval加密该如何是好? 其实只要记住了一个事儿. 它里面不论多复杂. 一定是个字符串.

比如,

eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0.1(\'我爱你\')',62,2,'console|log'.split('|'),0,{}))

这一坨看起来, 肯定很不爽. 怎么变成我们看着很舒服的样子呢? 记住. eval()里面是字符串. 记住~!!

那我想看看这个字符串长什么样? 就把eval()里面的东西拷贝出来. 执行一下. 最终一定会得到一个字符串. 要不然eval()执行不了的. 对不...于是就有了下面的操作.

image-20210828160541107

http://tools.jb51.net/password/evalencode, 在赠送你一个在线JS处理eval的网站. 大多数的eval加密. 都可以搞定了.

3.3 prototype和__proto__(重点)

逆向代码示例:

function ct(t) {
    pv['$_BCAO'] = t || [];
}

ct.prototype = {
    '$_CAE':function(f) {
               f();
            }
} 

var H = new ct(t)['$_CAE'](function(t) {
        var e = function(t) {
            for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], r = e['length'],n = 0; n < r; n++){
                var b = 'stuvwxyz~';
            }   
            return b;
        };
    	return e;
    })


  • 原型

    • 概念:原型本身其实是一个function函数,可以将其理解成Python中的class类。

    • 创建一个原型:

      function User(name,pwd){
          this.userName = name;
          this.pwd = pwd;
          this.regist = function(){
              console.log(this.userName+"在注册")
          }
      }
      
  • 实例对象

    • 概念:通过new关键字创建的对象称为实例对象。

    • 创建实例对象:

      let u1 = new User('jay','123');
      let u2 = new User('tom','456');
      
  • 原型对象

    • 概念:原型对象用于存储所有实例对象共享的属性和方法,以减少每个实例对象重复存储相同属性和方法的开销。

    • 原型对象存储所有实例对象共享的属性和方法

      //类似于类属性
      User.prototype.address = "BJ";
      User.prototype.gender = "male";
      //类似于类方法
      User.prototype.login = function login(username, password){
          console.log(`${username}在登录`);
      }
      
      //实例对象共享原型对象存储的内容
      u1.login('jay','123');
      u2.login('tom','456');
      console.log(u1.address,u2.address,u1.gender,u2.gender);
      
    • 获取原型对象:

      User.prototype;   //类似于访问所有的类成员
      u1.__proto__;
      User.prototype === u1.__proto__ //true
      //可以通过原型名访问原型对象或者使用实例名访问原型对象
      
  • 原型链

    • 原型链是JavaScript中对象继承属性和方法的一种方式。具体介绍如下:

      原型链是JavaScript中对象继承属性和方法的一种方式。当访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,它会通过原型链去它的原型对象中查找,如果原型对象也没有,会继续在其原型的原型中查找,这样逐级向上,直到找到属性或方法或者达到原型链的末端。
      

      原型对象本身也是一个对象,它也可以使用__proto__访问它的原型对象,类似于:

      u1.__proto__.__proto__
      
    • 原型链的成员访问:

      • 实例对象可以访问其原型内的成员和其原型链上所有原型对象内的成员
      u1.toString()
      
  • 浏览器环境navigator分析

    Snipaste_2024-07-11_09-24-19

练习测试:理解下属代码

function ct(t) {
    pv['$_BCAO'] = t || [];
}

//[]表示.调用对象成员
var H = new ct(t)['$_CAE'](function(t) {
        var e = function(t) {
            for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], r = e['length'],n = 0; n < r; n++){
                var b = 'stuvwxyz~';
            }   
            return b;
        };
    	return e;
    })

ct.prototype = {
    '$_CAE':function(f) {
               f();
            }
}
function ct(t) {
    pv['$_BCAO'] = t || [];
}

//[]表示.调用对象成员
var H = new ct(t)['$_CAE'](function(t) {
        var e = function(t) {
            for (var e = [[1, 0], [2, 0], [1, -1], [1, 1], [0, 1], [0, -1], [3, 0], [2, -1], [2, 1]], r = e['length'],n = 0; n < r; n++){
                var b = 'stuvwxyz~';
            }   
            return b;
        };
    	return e;
    })

ct.prototype = {
    '$_CAE':function(f) {
               f();
            }
}

3.4 神奇的window(浏览器环境,在console中执行代码)

window对象是一个很神奇的东西. 你可以把这东西理解成javascript的全局. 如果我们默认不用任何东西访问一个标识符. 那么默认认为是在用window对象进行访问该标识符.

例如:

var a = 10; 
a === window.a  // true

window.mm = "爱你"
console.log(mm); //"爱你"

综上, 我们可以得出一个结论. 全局变量可以用window.xxx来表示.

注意:window对象实际上表示的是浏览器的窗口。浏览器的窗口只有浏览器有。我们在浏览器中的开发者工具的Console选项卡中是可以直接使用window对象。

但是:在pycharm中直接使用window对象会报错:ReferenceError: window is not defined。why?就是因为在pycharm中是没有浏览器窗口。

所以切记:window对象是专属于浏览器环境下的一个内置对象。就意味着window只可以在浏览器环境下无需声明直接被使用。

ok. 接下来. 注意看了. 我要搞事情了

//如果想在外部调用下面函数的中的chi函数如何实现?
(function(){
    let chi = function(){
        console.log("我是吃")
    }
})();
chi() //会报错,因为chi是一个局部变量

//正确写法:
(function(){
    let chi = function(){
        console.log("我是吃")
    }
    window.chi = chi //全局的
})();
chi();

//换一种写法. 你还认识么?
(function(w){
    let chi = function(){
        console.log("我是吃")
    }
    w.chi = chi
})(window);

如何在node环境下执行上述代码?

globalThis在浏览器和node中都可以使用。在Pycharm中就可以使用globalThis代替window用于全局变量的声明

//全局变量的声明
globalThis.a = 10;
console.log(a);

window是整个浏览器的全局作用域.

使用 window 对象也可以访问客户端其他对象,这种关系构成浏览器对象模型,window 对象代表根节点,浏览器对象关系的关系如图所示,每个对象说明如下。

  • window:客户端 JavaScript 顶层对象。每当 <body> 或<frameset> 标签出现时,window 对象就会被自动创建。
  • navigator:包含客户端有关浏览器信息。
  • screen:包含客户端屏幕的信息。
  • history:包含浏览器窗口访问过的 URL 信息。
  • location:包含当前网页文档的 URL 信息。
  • document:包含整个 HTML 文档,可被用来访问文档内容及其所有页面元素。
image-20240528194905528-6896946

3.5 call和apply

对于咱们逆向工程师而言. 并不需要深入的理解call和apply的本质作用. 只需要知道这玩意执行起来的逻辑顺序是什么即可(外部函数关联对象的内部成员)

在运行时. 正常的js调用:

function People(name, age){
    this.name = name;
    this.age = age;
    this.chi = function(){
        console.log(this.name, "在吃东西")
    }
}
var p1 = new People("alex", 18);
var p2 = new People("wusir", 20);
p1.chi();
p2.chi();

接下来, 我们可以使用call和apply也完成同样的函数调用

function People(name, age){
    this.name = name;
    this.age = age;
    this.chi = function(){
        console.log(this.name, "在吃东西")
    }
}
p1 = new People("alex", 18);
p2 = new People("wusir", 20);
p1.chi();
p2.chi();

//外部函数关联对象的内部函数:
	//想要让alx吃:"馒头", "大饼",如何调用下面的函数?
function eat(what_1, what_2){
    console.log(this.name, "在吃", what_1, what_2);
}
// call的语法是: 函数.call(对象, 参数1, 参数2, 参数3....)
// 执行逻辑是: 执行函数. 并把对象传递给函数中的this.  其他参数照常传递给函数
eat.call(p1, "馒头", "大饼");

apply和他几乎一模一样. 区别是: apply传递参数要求是一个数组

eat.apply(p1, ["馒头", "大饼"]);

3.6 ES6中的箭头函数

在ES6中简化了函数的声明语法.

var fn = function(){};
// 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var fn = () => {};

var fn = function(name){}
var fn = name => {}
var fn = (name) => {}

var fn = function(name, age){}
var fn = (name, age) => {}

var f = v => v*v
// 等同于
var f = function(v) {
 return v*v
}

3.7 exports

类似Python中的模块导入

// functions.js文件

// 加法函数
function add(a, b) {
  return a + b;
}

// 乘法函数
function multiply(a, b) {
  return a * b;
}

// 导出函数
exports.add = add;
exports.multiply = multiply;
// main.js

// 导入 functions 模块
const functions = require('./functions');

// 使用导入的函数
console.log(functions.add(2, 3));  // 输出: 5
console.log(functions.multiply(4, 5));  // 输出: 20

四. DOM对象(了解)

DOM document Object Model 文档对象模型

// 整个html文档,会保存一个文档对象document
// console.log( document ); // 获取当前文档的对象

查找标签

  • 直接查找标签
document.getElementsByTagName("标签名")
document.getElementById("id值")
document.getElementsByClassName("类名")
//返回dom对象,就是标签对象或者数组
  • CSS选择器查找
document.querySelector("css选择器")  //根据css选择符来获取查找到的第一个元素,返回标签对象(dom对象)
document.querySelectorAll("css选择器"); // 根据css选择符来获取查找到的所有元素,返回数组
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<div id="i1">DIV1</div>

<div class="c1">DIV</div>
<div class="c1">DIV</div>
<div class="c1">DIV</div>


<div class="outer">
    <div class="c1">item</div>
</div>


<div class="c2">
    <div class="c3">
        <ul class="c4">
            <li class="c5" id="i2">111</li>
            <li>222</li>
            <li>333</li>
        </ul>
    </div>
</div>

<script>

   // 直接查找
   var ele = document.getElementById("i1");  // ele就是一个dom对象
   ele.innerText = 'hello bobo'; //将该标签内容修改成了hello bobo
   console.log(ele);

   var eles = document.getElementsByClassName("c1"); // eles是一个数组 [dom1,dom2,...]
   console.log(eles);

   var eles2 = document.getElementsByTagName("div"); // eles2是一个数组 [dom1,dom2,...]
   console.log(eles2);

  //定位outer下的c1对应的标签
   var outer = document.getElementsByClassName("outer")[0];
   var te = outer.getElementsByClassName("c1");
   console.log(te);

    // css选择器
		//层级定位(空格可以表示一个或多个层级)
    var dom = document.querySelector(".c2 .c3 .c5");
    console.log(":::",dom);
		//层级定位
    var doms = document.querySelectorAll("ul li");
    console.log(":::",doms);
    
</script>

</body>
</html>

绑定事件

  • 静态绑定 :直接把事件写在标签元素中
<div id="div" onclick="foo()">click</div>

<script>
    function foo(){           
        console.log("foo函数");   
    }
</script>

  • 动态绑定:在js中通过代码获取元素对象,然后给这个对象进行后续绑定
<p id="i1">试一试!</p>

<script>

    var ele=document.getElementById("i1");

    ele.onclick=function(){
        console.log("ok");
        
    };

</script>

五. jQuery和Ajax(了解)

jQuery是一个曾经火遍大江南北的一个Javascript的第三方库. jQuery的理念: write less do more. 其含义就是让前端程序员从繁琐的js代码中解脱出来. 我们来看看是否真的能解脱出来.

python => 基础语法 => 系统模块 => 第三方的库

jQuery的下载:

​ jquery:

​ https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/1.9.1/jquery.js

https://code.jquery.com/jquery-3.6.0.min.js

只需要把上面这个jquery下载的网址复制到浏览器上, 然后保存(ctrl+s)成js文件就可以了.

一. jQuery初使用

导入jQuery

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

我们用jQuery来完成一个按钮的基本点击效果. 当然, 得和传统的js对比一下

先准备好html. 页面结构. 这里复制粘贴就好

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div class="div-out">
        <input type="button" class="btn" value="我是按钮. 你怕不怕">
        <div class="mydiv">我怕死了...</div>
    </div>
</body>
</html>

需求: 点击按钮. 更改mydiv中的内容.

// 传统js
window.onload = function(){
    document.querySelector(".btn").onclick = function(){
        document.querySelector('.mydiv').innerText = "我好啪啪啊";
    };
}
// jQuery
$(function(){  // $(document).ready(function(){
    $(".btn").click(function(){
        $(".mydiv").text('我要上天');
    })
})

除了$外, 其他东西貌似都挺容易理解的. 而且代码简洁. 异常舒爽.

$是什么鬼, 在jQuery中, $可以认为是jQuery最明显的一个标志了. $()可以把一个普通的js对象转化成jQuery对象. 并且在jquery中 $的含义就是jQuery.

二. jQuery选择器

jQuery的逻辑和css选择器的逻辑是一样的.

// 语法:
$(选择器)

可以使用jQuery选择器快速的对页面结构进行操作.

案例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
	<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>

        $(function(){
            $(".btn").on('click', function(){
                $(".info").text("");
                let username = $("#username").val();
                let password = $("#password").val();
                let gender = $("input:radio[name='gender']:checked").val();  // input标签中radio 并且name是gender的. 并且被选择的.
                let city = $("#city").val();

                let flag = true;
                if(!username){
                    $("#username_info").text('用户名不能为空!');
                    flag = false;
                }

                if(!password){
                    $("#password_info").text('密码不能为空!');
                    flag = false;
                }

                if(!gender){
                    $("#gender_info").text('请选择性别!');
                    flag = false;
                }

                if(!city){
                    $("#city_info").text('请选择城市!');
                    flag = false;
                }

                if(flag){
                    $("#login_form").submit();
                } else {
                    return;
                }
            })
        })

    </script>
</head>
<body>
    <form id="login_form">
        <label for="username">用户名: </label><input type="text" id="username" name="username"><span class="info" id="username_info"></span><br/>
        <label for="password">密码: </label><input type="password" id="password" name="password"><span class="info" id="password_info"></span><br/>
        <label>性别: </label>
            <input type="radio" id="gender_men" name="gender" value="men"><label for="gender_men"></label>
            <input type="radio" id="gender_women" name="gender" value="women"><label for="gender_women"></label>
            <span class="info" id="gender_info"></span>
        <br/>

        <label for="city">城市: </label>
            <select id="city" name="city">
                <option value="">请选择</option>
                <option value="bj">北京</option>
                <option value="sh">上海</option>
                <option value="gz">广州</option>
                <option value="sz">深圳</option>
            </select>
            <span class="info" id="city_info"></span>
        <br/>

        <input type="button" class="btn" value="登录">
    </form>
</body>
</html>

三. 发送ajax请求

  • 什么是ajax?

    • AJAX = 异步的javascript和XML(Asynchronous Javascript and XML)

    • 它不是一门编程语言,而是利用JavaScript在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新部分网页的技术。

    • 对于传统的网页,如果想更新内容,那么必须要刷新整个页面,但有了Ajax,便可以在页面不被全部刷新的情况下更新其内容。在这个过程中,页面实际上是在后台与服务器进行了数据交互,获得数据之后,再利用JavaScript改变页面,这样页面内容就会更新了。

    • 简言之,在不重载整个网页的情况下,AJAX通过后台加载数据,并在网页上进行显示。

    • 通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON - 同时您能够把这些外部数据直接载入网页的被选元素中。

    • //get()方式
        $.ajax({
           url:'./data/index.txt',
           type:'get',
            //当请求成功后,执行下面的匿名函数,函数参数data就是请求到的数据
           success:function(data){
              $('p').html(data);
      
           },
           error:function(error){
              console.log(error)
           }})
      
    • //post()方式
      $.ajax({
         url:'/index',
         type:'post',
         data:{name:'张三',age:12},//post的请求参数
         success:function(dd){
            $('p').html(dd);
         },
         error:function(error){
            console.log(error)
      }
      
    • // 创建一个新的XMLHttpRequest对象
      let xhr = new XMLHttpRequest();
      
      // 设置请求方法和URL
      xhr.open('GET', 'https://xxx.com', true);
      
      // 设置请求完成时的回调函数
      xhr.onload = function() {
        if (xhr.status >= 200 && xhr.status < 400) {
          // 请求成功,处理返回的数据
          let data = JSON.parse(xhr.responseText);
          console.log(data);
        } else {
          // 请求失败,处理错误
          console.error('请求失败,状态码:' + xhr.status);
        }
      };
      
      // 设置请求过程中发生错误的回调函数
      xhr.onerror = function() {
        console.error('请求过程中发生错误');
      };
      
      // 发送请求
      xhr.send();
      
      

7.2-加密解密(资料)

NodeJs

V8引擎

我们知道,js是一种可以直接运行在浏览器中的脚本语言。那么为什么浏览器可以直接运行js脚本程序呢?原因就在于浏览器中内置了“V8”引擎。

什么是V8引擎?

V8引擎是一款专门解释和执行JavaScript代码的虚拟机。任何程序只要集成了V8引擎,就可以执行JavaScript代码。浏览器集成了V8引擎,可以执行JavaScript代码;

Node.js

由于浏览器内部集成了V8引擎,因此js程序可以直接运行在浏览器中。那么,如果想要在浏览器外部运行js代码可以吗?

试想一下,如果我们想在自己电脑上运行Python代码,是不是必须事先要装好Pyhon的运行环境(Python解释器)呢。同样,如果我们想在自己电脑上运行js程序的话,也要事先装好js的运行环境。那么这个执行js程序的运行环境就是“Node.js”。

什么是Node.js?

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境,它使得JavaScript能够在浏览器外部运行。Node.js的出现将JavaScript从浏览器中解放出来。

Node.js环境安装

1、下载Node.js 打开官网下载链接:https://nodejs.org/zh-cn/Snipaste_2024-07-14_18-02-25

2、安装

下载完成后,双击“node-v12.16.1-x64.msi”,开始安装Node.js ①点击【Next】按钮 ②勾选复选框(I accept the…),点击【Next】按钮 Snipaste_2024-07-14_18-05-06

一直【next】就完了,然后【install】安装,安装完后点击【Finish】按钮完成安装

Snipaste_2024-07-14_18-05-40

安装成功后简单一下测试安装是否成功:

在键盘按下【win+R】键,输入cmd,然后回车,打开cmd窗口

Snipaste_2024-07-14_18-07-39

3、环境变量配置

Snipaste_2024-07-14_18-10-32

4、pycharm配置node环境

  1. 打开PyCharm,点击菜单栏中的“File” > “Settings”(对于Mac用户,是“PyCharm” > “Preferences”)。
  2. 在弹出的设置窗口中,选择“Languages & Frameworks” > “Node.js and NPM”。
  3. 在“Node Interpreter”字段中,点击右侧的“…”按钮,然后选择您安装Node.js的路径。这通常是在您的系统路径下的“node”可执行文件。
  4. 确认设置无误后,点击“OK”保存配置。
Snipaste_2024-07-18_19-17-19

base64编码

base64是什么

Base64编码,是由64个字符组成编码集:26个大写字母A~Z,26个小写字母a~z,10个数字0~9,符号“+”与符号“/”。Base64编码的基本思路是将原始数据的三个字节拆分转化为四个字节,然后根据Base64的对应表,得到对应的编码数据。

当原始数据凑不够三个字节时,编码结果中会使用额外的**符号“=”**来表示这种情况。

base64原理

每一个base64的字符会对应有一个索引值(0-63)

image-20230916190327552-4862209

将you进行base64编码过程如下:

image-20230427174804644-4862253-4862254

小于3个字符为一组的编码方式如:

image-20230427174356120-4862276-4862277

ASCII:

Snipaste_2024-07-21_09-13-37

base64测试

base64编码示例:

import base64

# 将原始数据转化为二进制/字节数据
data = "you".encode("utf-8")
print(data)

# 把字节转化成b64
bs = base64.b64encode(data).decode()
print(bs)

bs = "yo".encode("utf-8")
# 把字节转化成b64
print(base64.b64encode(bs).decode())

# 猜测结果
bs = "y".encode("utf-8")
# 把字节转化成b64
print(base64.b64encode(bs).decode())

base64解码示例:

注意, base64编码处理后的字符串长度. 一定是4的倍数(因为Base64编码的基本思路是将原始数据的三个字节拆分转化为四个字节). 如果在网页上看到有些密文的b64长度不是4的倍数. 会报错

import base64

s = "eW91"
ret = base64.b64decode(s)
print(ret) #正确

s = "eW91eQ=="
ret = base64.b64decode(s)
print(ret) #正确

s = "eW91eQ"
ret = base64.b64decode(s)
print(ret) #报错,s不是4的倍数

如果不是4的倍数如何处理呢?解决思路. 使用=填充为4的倍数即可

s = "eW91eQ"
#填充为4的倍数
s += ("=" * (4 - len(s) % 4))
print("填充后", s)
ret = base64.b64decode(s).decode()
print(ret)

base64 编码的优点:

  • 算法是编码,不是压缩,编码后只会增加字节数(一般是比之前的多1/3,比如之前是3, 编码后是4)
  • 算法简单,基本不影响效率
  • 算法可逆,解码很方便,不用于私密传输。

js常见的加密方式

  • 加密在前端开发和爬虫中是经常遇见的。掌握了加密、解密算法也是你从一个编程小白到大神级别质的一个飞跃。且加密算法的熟练和剖析也是很有助于帮助我们实现高效的js逆向。下述只把我们常用的加密方法进行总结。不去深究加密的具体实现方式。

  • 常见的加密算法基本分为这几类,

    • 线性散列算法(签名算法)MD5
    • 对称性加密算法 AES DES
    • 非对称性加密算法 RSA

Md5加密(不可逆)

  • MD5是一种被广泛使用的线性散列算法,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整的一致性。且MD5加密之后产生的是一个固定长度(32位或16位)的数据。

    • 结论:一旦看到了一个长度为32位的密文数据,该数据极有可能是通过md5算法进行的加密!
  • 解密:

    • 常规讲MD5是不存在解密的。但是理论上MD5是可以进行反向暴力破解的。暴力破解的大致原理就是用很多不同的数据进行加密后跟已有的加密数据进行对比,由此来寻找规律。理论上只要数据量足够庞大MD5是可以被破解的。但是要注意,破解MD5是需要考虑破解的成本(时间和机器性能)。假设破解当前的MD5密码需要目前计算能力最优秀的计算机工作100年才能破解完成。那么当前的MD5密码就是安全的。
  • 增加破解成本的方法(方法很多,这里只说我常用的)。

    • 使用一段无意义且随机的私匙进行MD5加密会生成一个加密串,我们暂且称之为串1
    • 将要加密的的数据跟串1拼接,再进行一次MD5,这时会生成串2
    • 将串2再次进行MD5加密,这时生成的串3就是我们加密后的数据。
  • 我们在注册账号时的密码一般都是用的MD5加密。

  • 代码实操:

    • Python版本:

      from hashlib import md5
      
      obj = md5()
      obj.update("bobo".encode("utf-8"))
      
      bs = obj.hexdigest()
      print(bs)
      
    • JS版本:下载安装crypto-js(npm install crypto-js)

      var CryptoJS = require('crypto-js');
      // 原始数据
      var data = '123456';
      // 生成MD5摘要
      var md5Digest = CryptoJS.MD5(data).toString();
      
      console.log(md5Digest);
      
  • 上述代码的核心关键字:md5

DES/AES加密(可逆)

  • DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的算法。该加密算法是一种对称加密方式,其加密运算、解密运算需要使用的是同样的密钥(一组字符串)即可。

  • 注意:

    • 现在用AES这个标准来替代原先的DES。
    • AES和DES的区别:
      • 加密后密文长度的不同:
        • DES加密后密文长度是8的整数倍
        • AES加密后密文长度是16的整数倍
      • 应用场景的不同:
        • 企业级开发使用DES足够安全
        • 如果要求高使用AES
  • DES算法的入口参数有三个:

    • Key、Data、Mode,padding、iv。
      • Key为DES算法的工作密钥;
      • Data为要被加密或被解密的数据;
      • Mode为DES的工作模式。最常用的模式就是 CBC 模式和 ECB模式
        • ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
        • CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或后再加密,这样做的目的是增强破解难度。
      • padding为填充模式,如果加密后密文长度如果达不到指定整数倍(8个字节、16个字节),填充对应字符
      • iv:参数中的iv主要用于CBC模式,确保即使加密相同的明文,每次产生的密文也不相同,增强加密的安全性。iv通常是一个16字节的随机字符串。这个字符串在解密时也需要用到,因此需要妥善保存。
  • Python版本:

    • 环境安装:

      pip install pycryptodome
      
    • 加密代码:

      from Crypto.Cipher import AES
      from Crypto.Util.Padding import pad
      import base64
      
      key = '0123456789abcdef'.encode()  # 秘钥: 必须16字节
      iv = b'abcdabcdabcdabcd'  # 偏移量:16位/字节(字节类型)
      text = 'alex is a monkey!'  # 加密内容
      #设置加密内容的长度填充(位数为16的整数倍)
      text = pad(text.encode(), 16)
      #创建加密对象
      aes = AES.new(key, AES.MODE_CBC, iv)  # 创建一个aes对象
      
      en_text = aes.encrypt(text)  # 加密明文
      print("aes加密数据:::", en_text) #返回二进制类型数据
      
      #二进制密文转换成字符串格式
      en_text = base64.b64encode(en_text).decode()  # 将返回的字节型数据转进行base64编码
      print(en_text) 
      
    • 解密代码:

      from Crypto.Cipher import AES
      import base64
      from Crypto.Util.Padding import unpad
      
      key = '0123456789abcdef'.encode()
      iv = b'abcdabcdabcdabcd'
      aes = AES.new(key, AES.MODE_CBC, iv)
      #需要解密的文本
      text = 'X/A0fy9S7+kUI3HYQRKO46WTlid6T1DBhXutwmPdboY='.encode()  
      #将密文数据转换为二进制类型
      ecrypted_base64 = base64.b64decode(text)  
      
      source = aes.decrypt(ecrypted_base64)  # 解密
      #未填充数据
      print("aes解密数据:::", source.decode())
      #取消填充数据
      print("aes解密数据:::", unpad(source, 16).decode())
      
  • JS版本:

    • 加密:

      const CryptoJS = require("crypto-js")
      
      // 密钥(128位,16字节)
      var key = CryptoJS.enc.Utf8.parse('0123456789abcdef');
      
      // 初始化向量(IV)(128位,16字节)
      var iv = CryptoJS.enc.Utf8.parse('1234567890abcdef');
      
      // 待加密的数据
      var plaintext = 'Hello, bobo!';
      
      // 进行AES-128加密,使用CBC模式和PKCS7填充
      var encrypted = CryptoJS.AES.encrypt(plaintext, key, {
          iv: iv,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
      });
      
      // 获取加密后的密文
      var ciphertext = encrypted.toString();
      
      console.log(ciphertext);
      
    • 解密:

      const CryptoJS = require("crypto-js")
      
      // 密钥(128位,16字节)
      var key = CryptoJS.enc.Utf8.parse('0123456789abcdef');
      
      // 初始化向量(IV)(128位,16字节)
      var iv = CryptoJS.enc.Utf8.parse('1234567890abcdef');
      
      // 密文数据
      var encrypText = 'GYc9oxlZB/PeyfFG3ppK6Q==';
      
      // 进行加密,使用CBC模式和PKCS7填充
      var decrypted = CryptoJS.AES.decrypt(encrypText, key, {
          iv: iv,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
      });
      
      // 解密
      var plaintext = decrypted.toString(CryptoJS.enc.Utf8);
      
      console.log(plaintext);
      
  • 上述代码的关键字:

    • AES,DES
    • encrypt(用于加密的函数名),decrypt(用于解密的函数名)
    • xxxkey:秘钥一般是单独生成的,一定不会傻到明文的写在代码中!

RSA加密(可逆)

  • RSA加密:
    • RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。
  • 非对称加密算法:
    • 非对称加密算法需要两个密钥:
      • 公开密钥(publickey:简称公钥)
      • 私有密钥(privatekey:简称私钥)
      • 公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。
  • 注意:
    • 使用时都是使用公匙加密使用私匙解密。公匙可以公开,私匙自己保留。
    • 算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。
  • 使用流程和场景介绍
    • 通过公匙加密,使用私匙解密。私匙是通过公匙计算生成的。假设ABC三方之间相互要进行加密通信。大家相互之间使用公匙进行信息加密,信息读取时使用各自对应的私匙进行信息解密
    • 用户输入的支付密码会通过RSA加密
  • 公钥私钥生成方式:
    • 公私匙可以在线生成
      • http://web.chacuo.net/netrsakeypair
  • 环境安装:npm install jsencrypt

JS代码示例:

window = globalThis;

const JSEncrypt = require('jsencrypt');


var PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----';
        //私钥
var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----';
//使用公钥加密
var encrypt = new JSEncrypt();//实例化加密对象
encrypt.setPublicKey(PUBLIC_KEY);//设置公钥
var encrypted = encrypt.encrypt('hello bobo!');//对指定数据进行加密
console.log(encrypted);//使用私钥解密

//使用私钥解密
var decrypt = new JSEncrypt();
decrypt.setPrivateKey(PRIVATE_KEY);//设置私钥
var uncrypted = decrypt.decrypt(encrypted);//解密
console.log(uncrypted);

Python代码示例:

1.创建公钥和私钥

from Crypto.PublicKey import RSA

# 通过相关算法生成唯一秘钥
rsakey = RSA.generate(1024)
#将秘钥保存到文件中
with open("rsa.public.pem", mode="wb") as f:
    f.write(rsakey.publickey().exportKey())

with open("rsa.private.pem", mode="wb") as f:
    f.write(rsakey.exportKey())

2.加密算法实现

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64

# 加密
data = "我喜欢你"
with open("rsa.public.pem", mode="r") as f:
    pk = f.read()
    rsa_pk = RSA.importKey(pk)
    #基于公钥创建加密对象
    rsa = PKCS1_v1_5.new(rsa_pk)

    result = rsa.encrypt(data.encode("utf-8"))
    # 处理成b64方便传输
    b64_result = base64.b64encode(result).decode("utf-8")
    print(b64_result)

3.解密算法实现

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64

data = '17wJJtBBujJurHRu3teDC44jSllmqcB9a22U1DQ5/IsI0m3NuShPTxBq8pLTWRdszPapiXfjvOh47P9u5+7U4hvNZL+kKqs3H5m4BKKphORUUfqb9zTowoHb1mM9ji2LuMAYmc1l70ZR+s9XsV3HrHCV/S2FzGWngcWpNVtsTJg='
# 解密
with open("rsa.private.pem", mode="r") as f:
    prikey = f.read()
    rsa_pk = RSA.importKey(prikey)
    #创建解密对象
    rsa = PKCS1_v1_5.new(rsa_pk)
    result = rsa.decrypt(base64.b64decode(data), None)
    print("rsa解密数据:::", result.decode("utf-8"))
  • 上述代码提取关键字:
    • RSA
    • setPublicKey(设置公钥)
    • setPrivateKey(设置私钥)
    • encrypt,decrypt

8.1-加密逆向案例初识

PyExecJS介绍

  • PyExecJS 是一个可以使用 Python 来模拟运行 JavaScript 的库。

    • 使用该模块可以通过python程序调用执行js代码,获取js代码返回的结果!
    • 注意事项:电脑必须安装好了nodejs开发环境上述模块才可以生效!
  • 环境安装:

    • pip install PyExecJS
  • 使用步骤:

    • 导包:
      • import execjs
    • 创建node对象:
      • node = execjs.get()
    • 编译即将被执行的js代码对应的文件,返回上下文对象ctx
      • fp = open(filePath,encoding='utf-8')
      • ctx = node.compile(fp.read())
    • 生成要执行的js函数调用的字符串形式
      • funName = 'getPwd("xxx")'
    • 基于ctx调用eval函数,模拟执行funName表示的js函数
      • result = ctx.eval(funName)
#1.导包
import execjs
#2.创建node对象
node = execjs.get()
#3.编译js文件返回上下文ctx对象(将js文件中的代码读取出来,被compile进行编译)
fp = open('test.js','r',encoding='utf-8')
ctx = node.compile(fp.read())
#4.使用上下文对象ctx调用eval函数执行js文件中的指定函数即可
result = ctx.eval('getPwd("123456")')
print(result)
//test.js
function getPwd(pwd){
    return pwd;
}

微信公众平台案例

https://mp.weixin.qq.com/ :逆向登录密码的加密算法。

分析思路:

  • 先通过抓包工具发现,密码是经过加密,并且发现密码的加密后的数据是32位,大概率是md5加密的!
  • 发现加密后的数据是被pwd这个请求参数使用的。
  • 需要全局搜索pwd或者pwd:,定位加密的环节在哪里
  • 进行断点调试,定位加密算法。

实现方案1(大胆):

  • 直接使用python的md5进行数据加密:

    • from hashlib import md5
      pwd = '123456';
      obj = md5()
      obj.update(pwd.encode("utf-8"))
      
      bs = obj.hexdigest()
      print(bs)
      
  • 逆向网站js代码:

    • 断点调试后,发现return P(t) 就是返回加密的内容。逆向P函数即可。

    • 可以使用【发条】进行js代码调试

    • 调试成功后的js代码:

      • function _(n, u) {
            var i = (n & 65535) + (u & 65535)
                , b = (n >> 16) + (u >> 16) + (i >> 16);
            return b << 16 | i & 65535
        }
        
        function E(n, u) {
            return n << u | n >>> 32 - u
        }
        
        function w(n, u, i, b, S, j) {
            return _(E(_(_(u, n), _(b, j)), S), i)
        }
        
        function C(n, u, i, b, S, j, F) {
            return w(u & i | ~u & b, n, u, S, j, F)
        }
        
        function d(n, u, i, b, S, j, F) {
            return w(u & b | i & ~b, n, u, S, j, F)
        }
        
        function l(n, u, i, b, S, j, F) {
            return w(u ^ i ^ b, n, u, S, j, F)
        }
        
        function p(n, u, i, b, S, j, F) {
            return w(i ^ (u | ~b), n, u, S, j, F)
        }
        
        function f(n, u) {
            n[u >> 5] |= 128 << u % 32;
            n[(u + 64 >>> 9 << 4) + 14] = u;
            var i, b, S, j, F, s = 1732584193, o = -271733879, a = -1732584194, t = 271733878;
            for (i = 0; i < n.length; i += 16) {
                b = s;
                S = o;
                j = a;
                F = t;
                s = C(s, o, a, t, n[i], 7, -680876936);
                t = C(t, s, o, a, n[i + 1], 12, -389564586);
                a = C(a, t, s, o, n[i + 2], 17, 606105819);
                o = C(o, a, t, s, n[i + 3], 22, -1044525330);
                s = C(s, o, a, t, n[i + 4], 7, -176418897);
                t = C(t, s, o, a, n[i + 5], 12, 1200080426);
                a = C(a, t, s, o, n[i + 6], 17, -1473231341);
                o = C(o, a, t, s, n[i + 7], 22, -45705983);
                s = C(s, o, a, t, n[i + 8], 7, 1770035416);
                t = C(t, s, o, a, n[i + 9], 12, -1958414417);
                a = C(a, t, s, o, n[i + 10], 17, -42063);
                o = C(o, a, t, s, n[i + 11], 22, -1990404162);
                s = C(s, o, a, t, n[i + 12], 7, 1804603682);
                t = C(t, s, o, a, n[i + 13], 12, -40341101);
                a = C(a, t, s, o, n[i + 14], 17, -1502002290);
                o = C(o, a, t, s, n[i + 15], 22, 1236535329);
                s = d(s, o, a, t, n[i + 1], 5, -165796510);
                t = d(t, s, o, a, n[i + 6], 9, -1069501632);
                a = d(a, t, s, o, n[i + 11], 14, 643717713);
                o = d(o, a, t, s, n[i], 20, -373897302);
                s = d(s, o, a, t, n[i + 5], 5, -701558691);
                t = d(t, s, o, a, n[i + 10], 9, 38016083);
                a = d(a, t, s, o, n[i + 15], 14, -660478335);
                o = d(o, a, t, s, n[i + 4], 20, -405537848);
                s = d(s, o, a, t, n[i + 9], 5, 568446438);
                t = d(t, s, o, a, n[i + 14], 9, -1019803690);
                a = d(a, t, s, o, n[i + 3], 14, -187363961);
                o = d(o, a, t, s, n[i + 8], 20, 1163531501);
                s = d(s, o, a, t, n[i + 13], 5, -1444681467);
                t = d(t, s, o, a, n[i + 2], 9, -51403784);
                a = d(a, t, s, o, n[i + 7], 14, 1735328473);
                o = d(o, a, t, s, n[i + 12], 20, -1926607734);
                s = l(s, o, a, t, n[i + 5], 4, -378558);
                t = l(t, s, o, a, n[i + 8], 11, -2022574463);
                a = l(a, t, s, o, n[i + 11], 16, 1839030562);
                o = l(o, a, t, s, n[i + 14], 23, -35309556);
                s = l(s, o, a, t, n[i + 1], 4, -1530992060);
                t = l(t, s, o, a, n[i + 4], 11, 1272893353);
                a = l(a, t, s, o, n[i + 7], 16, -155497632);
                o = l(o, a, t, s, n[i + 10], 23, -1094730640);
                s = l(s, o, a, t, n[i + 13], 4, 681279174);
                t = l(t, s, o, a, n[i], 11, -358537222);
                a = l(a, t, s, o, n[i + 3], 16, -722521979);
                o = l(o, a, t, s, n[i + 6], 23, 76029189);
                s = l(s, o, a, t, n[i + 9], 4, -640364487);
                t = l(t, s, o, a, n[i + 12], 11, -421815835);
                a = l(a, t, s, o, n[i + 15], 16, 530742520);
                o = l(o, a, t, s, n[i + 2], 23, -995338651);
                s = p(s, o, a, t, n[i], 6, -198630844);
                t = p(t, s, o, a, n[i + 7], 10, 1126891415);
                a = p(a, t, s, o, n[i + 14], 15, -1416354905);
                o = p(o, a, t, s, n[i + 5], 21, -57434055);
                s = p(s, o, a, t, n[i + 12], 6, 1700485571);
                t = p(t, s, o, a, n[i + 3], 10, -1894986606);
                a = p(a, t, s, o, n[i + 10], 15, -1051523);
                o = p(o, a, t, s, n[i + 1], 21, -2054922799);
                s = p(s, o, a, t, n[i + 8], 6, 1873313359);
                t = p(t, s, o, a, n[i + 15], 10, -30611744);
                a = p(a, t, s, o, n[i + 6], 15, -1560198380);
                o = p(o, a, t, s, n[i + 13], 21, 1309151649);
                s = p(s, o, a, t, n[i + 4], 6, -145523070);
                t = p(t, s, o, a, n[i + 11], 10, -1120210379);
                a = p(a, t, s, o, n[i + 2], 15, 718787259);
                o = p(o, a, t, s, n[i + 9], 21, -343485551);
                s = _(s, b);
                o = _(o, S);
                a = _(a, j);
                t = _(t, F)
            }
            return [s, o, a, t]
        }
        
        function L(n) {
            var u, i = "";
            for (u = 0; u < n.length * 32; u += 8) {
                i += String.fromCharCode(n[u >> 5] >>> u % 32 & 255)
            }
            return i
        }
        
        function y(n) {
            var u, i = [];
            i[(n.length >> 2) - 1] = void 0;
            for (u = 0; u < i.length; u += 1) {
                i[u] = 0
            }
            for (u = 0; u < n.length * 8; u += 8) {
                i[u >> 5] |= (n.charCodeAt(u / 8) & 255) << u % 32
            }
            return i
        }
        
        function $(n) {
            return L(f(y(n), n.length * 8))
        }
        
        function q(n, u) {
            var i, b = y(n), S = [], j = [], F;
            S[15] = j[15] = void 0;
            if (b.length > 16) {
                b = f(b, n.length * 8)
            }
            for (i = 0; i < 16; i += 1) {
                S[i] = b[i] ^ 909522486;
                j[i] = b[i] ^ 1549556828
            }
            F = f(S.concat(y(u)), 512 + u.length * 8);
            return L(f(j.concat(F), 512 + 128))
        }
        
        function z(n) {
            var u = "0123456789abcdef", i = "", b, S;
            for (S = 0; S < n.length; S += 1) {
                b = n.charCodeAt(S);
                i += u.charAt(b >>> 4 & 15) + u.charAt(b & 15)
            }
            return i
        }
        
        function Q(n) {
            return unescape(encodeURIComponent(n))
        }
        
        function B(n) {
            return $(Q(n))
        }
        
        function x(n) {
            return z(B(n))
        }
        
        function M(n, u) {
            return q(Q(n), Q(u))
        }
        
        function R(n, u) {
            return z(M(n, u))
        }
        
        function get_pwd (n, u, i) {
            if (!u) {
                if (!i) {
                    return x(n)
                } else {
                    return B(n)
                }
            }
            if (!i) {
                return R(u, n)
            } else {
                return M(u, n)
            }
        }
        
        
    • python运行代码:

      • import execjs
        node = execjs.get()
        fp = open('day08-8.1.js','r',encoding='utf-8')
        ctx = node.compile(fp.read())
        result = ctx.eval('get_pwd("111111")')
        print(result)
        

8.2- Object对象常用成员(资料)

//判断对象类型typeof和Object.prototype.toString.call(Object原型对象)
console.log('数字1',typeof 1);
console.log("字符串1",typeof "1");
console.log('空对象{}',typeof {});
console.log('布尔true',typeof true);
console.log('空数组[]',typeof []);
console.log('null空',typeof null);
console.log('undefined',typeof undefined);
console.log('函数function (){}',typeof function (){});
//发现null和空数组[]类型都是object类型(无法区分具体类型)
console.log("=================================")
console.log(Object.prototype.toString.call(1));
console.log(Object.prototype.toString.call("1"));
console.log(Object.prototype.toString.call({}));
console.log(Object.prototype.toString.call(true));
console.log(Object.prototype.toString.call([]));
console.log(Object.prototype.toString.call(null));
console.log(Object.prototype.toString.call(undefined));
console.log(Object.prototype.toString.call(function () {}));

//创建新对象,设置其原型对象为window(在node环境下可能需要伪装浏览器环境下的对象)
a = Object.create(window)
a.__proto__ === window //true

//判断对象自身属性中是否具有指定的属性
function func(){
    this.name="bobo";
    this.getAge=function(){}
};
f = new func();
f.hasOwnProperty('name'); //true
f.hasOwnProperty('getAge'); //true
f.hasOwnProperty('toString'); //false

//获取指定对象上一个自有属性对应的属性描述符
Object.getOwnPropertyDescriptor(f,'name');
//获取指定对象上所有属性对应的属性描述符
Object.getOwnPropertyDescriptors(f);
/*
	属性描述符是一组用于精确定义和描述对象属性的特性的集合(属性描述符也是一个对象)。通过属性描述符,开发者可以指定一个属性是否可被修改、删除、枚举或者通过特定的函数来获取和设置其值。
*/


//获取实例对象的原型对象
Object.getPrototypeOf(f);
Object.getPrototypeOf(f) === f.__proto__ ;//true

//设置一个指定的对象的原型(可以对一个已经存在的对象重新设置其原型对象)
Object.setPrototypeOf(f,Object.__proto__) //f对象的原型对象设置成了window的原型对象


//defineProperty直接在一个对象上定义一个新属性,然后可指定新属性的属性描述的,并返回此对象。
let User = {
    "name":"小明",
}//创建一个User对象
//给对象添加两个成员
User.age = 10;
User["age"] = 20;
//给对象定义一个新属性且设置其属性描述符(属性描述符可分为:数据描述符和存取描述符)此时使用数据描述符。
	//参数1:对象。参数2:属性名。参数3:属性描述的
Object.defineProperty(User, "height", {
    enumerable:true, //该属性是否可遍历
    configurable:true,//该属性是否可配置:决定该属性是否可以被删除或修改其属性描述符。
    value:160, //属性的值
    writable:false //该属性的值是否可以通过赋值运算符改变
});
//对象属性遍历,如果某个属性的文件描述符中的enumerable:false则无法遍历出该属性
for (const userKey in User) {
    console.log(userKey);
}
//存取描述符
let Stu = {
    "name":"小红",
}//创建一个Stu对象
let temp = null;//临时变量
//给Stu对象定义一个新属性score,且设置其属性描述符
Object.defineProperty(Stu, "score", {
    enumerable:true,
    configurable:true,
    get:function (){// 当获取属性值是调用
        console.log("正在获取值");
        return temp;
    },
    set:function (value){// 当对属性进行赋值操作时调用
        console.log("正在设置值");
        temp = value;
    }
});
console.log(Stu.score);
Stu.score = 100;
console.log(Stu.score);
/*
	属性描述符注意事项:属性描述符分为两类:数据描述符和存取描述符。数据描述符包含value、writable、enumerable和configurable这些属性。存取描述符包含get、set、enumerable和configurable。两者不能混用,即一个描述符如果是数据描述符就不能包含get或set,反之亦然。
*/

9.1-hook操作

概念:在JavaScript中,hook是一种能够拦截和修改函数或方法行为的技术。通过使用hook,开发者可以在现有的函数执行前、执行后或者替换函数的实现逻辑。hook目的是找到函数入口以及一些参数变化,便于分析js逻辑。

函数hook

function add(a, b) {
    console.log("正在执行");
    return a + b;
}

console.log(add(1, 2))

_add = add

// hook
add = function (a, b) {
    console.log("调用前")
    let result = _add(a, b)
    console.log("调用后")
    return result
}

console.log(add(1, 2))

image-20250613095959660

属性hook:

// 属性hook

let user = {name:'哈哈'}

_name =  user.name
Object.defineProperty(user,'name',{
    get() {
        console.log("获取前")
        return _name
    },

    set(v) {
        console.log("赋值前")
        _name = v
        }
})

console.log(user.name)
user.name = "呵呵"

image-20250613101114330

浏览器环境下atob函数的hook:

开启脚本事件监听

image-20250613104005053

开启后,访问页面,会有断点停留。

image-20250613104422587

在Sources中的Snippets代码段中新增hook代码片段后保存,打上断点,然后运行:

image-20250613104642910

image-20250613105025750

然后取消脚本事件监听,就可以继续进行调试了。会看到代码进入到hook了:

image-20250613105134033

在右侧,可以看到是谁在调用这个函数:

image-20250613105308771

cookie的hook:

同上,只是脚本不一样:

_cookie = document.cookie;
Object.defineProperty(document,'cookie',{
    get(){
        console.log("正在获取cookie:", _cookie);
        return _cookie;
    },
    set(value){
        console.log("正在设置cookie:", value);
        _cookie = value;
    }
});

9.2-hook检测与破解

toString()检测法,函数调用toString()方法,会返回的定义:

image-20250613110728831

image-20250613110749540

上面hook atob之前:

image-20250613110927407

hook后:

image-20250613111047943

两次结果不一样,就可以判断出该函数是否被hook

什么是native?

在js中,一些内置函数如toString或者atob等函数的函数实现会被显示为[native code],而不是显示实现的具体代码。这样的操作对于提高代码的安全性和封装性有一定的作用。

破解:重写被hook函数的toString()方法

image-20250613112417016

但是,通过原型链上的toString()检测法,还是会检测出来:

Function.prototype.toString.call(atob)

image-20250613112856975

破解:在hook中重写原型链上的toString()方法

//重写原型链上的toString方法
Function.prototype.toString = function(){
    return `function ${this.name}() { [native code] }`
}//this.name就是toString的调用者的名字,比如Location.toString,则this.name就是Location,如果将this.name直接换成atob的话,则以后任何调用者调用toString的话,则返回的function后面的名字就都是atob了。也就是如果Location.toString()返回的也是:function atob() { [native code] }

image-20250613113243601

Loading...