本地想要传输文件到 ARM 板卡中,可以使用 scp 命令,但是需要手动填写scp指令,例如:

1
scp filepath root@192.168.137.89:/home/root/

执行时还需要手动输入IP、路径、密码等,所以写了一个脚本可以自动完成指令和密码的填充。同时处理了第一次传输文件时的密钥处理,可以在系统提示时自动填入 Y 进行确认。

其中使用了 pexpect 库,可以通过 pip install pexpect 指令安装。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python3
import sys
import pexpect
import os

'''本脚本用于实现scp指令将上位机文件传输给板卡 并自动填充目标ip地址和用户名密码'''

class LogWrapper:
def __init__(self, file):
self.file = file

def write(self, data):
self.file.write(data.decode('utf-8'))
self.file.flush()

def flush(self):
self.file.flush()


if __name__ == '__main__':
filepath = sys.argv[1]
if len(sys.argv) > 2:
dst_path = ' root@192.168.137.89:/home/root/' + sys.argv[2]
else:
dst_path = ' root@192.168.137.89:/home/root/'
command = "scp " + filepath + ' ' + dst_path
password = "root"
print(command)

# Remove old host key if it exists
known_hosts_file = "~/.ssh/known_hosts"
ip_address = "192.168.137.89"
remove_command = f"ssh-keygen -f \"{known_hosts_file}\" -R \"{ip_address}\""
os.system(remove_command)

# Execute SCP command
child = pexpect.spawn(command, logfile=LogWrapper(sys.stdout))
index = child.expect(["Are you sure you want to continue connecting (yes/no)?", "password:", pexpect.EOF])

if index == 0:
child.sendline("yes")
child.expect("password:")

if index in [0, 1]:
child.sendline(password)
child.expect(pexpect.EOF)

print(child.before.decode())

执行时通过以下命令即可:

1
./scp.py filename

创建 githubpages

1. 创建仓库

先在github网页上面创建仓库,名称是个人用户名 + “.github.io“,例如 quzhanghao.github.io

2. 安装 git

本地安装 git,并在本地使用 git clone 下载创建的仓库。

3. 安装 Node.js

本地安装 Node.js,可以通过命令行检查是否安装成功:

1
node -v

4. 安装 Hexo

使用 npm 安装 Hexo

1
npm install -g hexo-cli

5. 初始化博客

在下载的 git 仓库目录中初始化博客内容:(将 my-blog 替换为实际的博客名称)

1
2
3
4
hexo init my-blog
cd my-blog
npm install
npm install hexo-deployer-git --save

6. 配置博客

可以通过编辑博客配置文件 _config.yml,设置博客的基本信息,包括标题、描述、作者等。
_config.yml 文件中,添加 GitHub Pages 的部署信息:

1
2
3
4
deploy:
type: git
repo: https://github.com/yourusername/your-repo.git
branch: gh-pages

其中 yourusernameyour-repo 替换为实际的 GitHub 用户名和仓库名称。

创建和发布文章

1. 创建新文章

使用以下命令创建新文章:

1
hexo new "文章标题"

2. 生成静态文件

运行以下命令以生成静态文件:

1
hexo generate

3. 部署到 GitHub Pages

运行部署命令将生成的静态文件推送到 GitHub Pages:

1
hexo deploy

githubpages 跳转外部网站

如果需要访问 githubpages 时自动跳转到外部的网站,可以修改 hexo 的配置,并更新发布。
来到博客的源码文件夹,修改的相对文件路径:

1
.\xxx.github.io\node_modules\hexo-theme-landscape\layout\_partial\head.ejs

只需要在 <head> 标签内部增加一行即可:(把域名修改为需要跳转的域名)

1
<meta http-equiv="refresh" content="0; url=https://quzhanghao.me">

修改完之后,通过下面的代码生成博客并发布:

1
2
hexo generate
hexo deploy

一、前言

由于Keil软件在代码开发编辑开发方面比较难用、界面丑、功能落后,很多时候我们都会使用其他的编辑器进行代码的编辑开发,只有当需要烧录程序或者仿真调试的时候才使用Keil软件。

其实我们完全可以选择使用VSCode,用这一款软件同时进行编辑开发和烧录、仿真调试工作,只需要提前通过Keil创建好工程即可,后续的所有工作都可以通过VSCode完成。

二、需要的插件和第三方工具

VSCode插件可以通过VSCode软件安装即可,在插件中搜索安装。

2.1 VSCode插件:C/C++

C/C++插件用来实现代码的智能识别和补全、代码浏览等基本功能。

C/C++

2.2 VSCode插件:Cortex-Debug

Cortex-Debug插件用来实现代码仿真和调试。

Cortex-Debug

2.3 VSCode插件:Embedded IDE

Embedded IDE简称EIDE,用来导入Keil工程,支持Cortex-M内核芯片的编译和烧录工作。

Embedded IDE

2.4 GDB工具

GDB工具用来配合Cortex-Debug插件实现仿真调试功能。
需要单独下载并安装,安装的时候可以选择把执行文件路径加入系统的环境变量中,避免找不到”gcc.exe“文件路径等问题。

下载url:https://developer.arm.com/downloads/-/gnu-rm

下载win32.exe用于windows系统的版本安装即可。

gcc-arm-none-eabi....win32.exe

2.5 OPENOCD

OPENOCD用来配合Cortex-Debug插件实现仿真调试功能,下载之后放到任意的路径均可。

下载路径: https://gnutoolchains.com/arm-eabi/openocd/

三、编译和烧录功能实现

编译和烧录通过Embedded IDE插件实现,Embedded IDE配置好了就可以进行Keil工程的编译和烧录了。

更详细的介绍可以参见此插件的具体文档:
https://em-ide.com/#/README

3.1 路径和ELF功能设置

3.1.1 路径设置:

Keil路径设置

其实就是设置Keil软件的MDK TOOLS.INI文件路径,这个文件就在Keil软件的安装目录下面可以找到。如下:

TOOLS.INI

3.1.2 ELF文件设定

选择将 .axf 文件转换为 .elf 文件,这个是为了下一步的仿真调试功能做准备。

ELF转换

3.2 导入项目

这里也就是导入现有的Keil工程。在VSCode的侧边栏,点击EIDE插件按钮,在左下角选择导入。

导入

在弹出的类型选择中选择MDK,然后选择对应的Keil工程文件即可,后缀是.uvprojx。

MDK

打开工程以后可以看到工程的文件和很多项目相关的配置项,这些配置项其实和Keil软件打开工程的配置内容类似,下面逐个来介绍。

项目参数

3.3 芯片支持包

芯片支持包

点击右边的加号可以添加,可以从本地目录选择文件添加(From Disk),所谓的支持包,就是Keil5安装的pack包,选择此工程使用的芯片对应的包即可,例如:

pack包

3.4 构建配置

构建配置就是和Keil工程中的编译器设置、RAM设置等完全对应的,介绍如下:

3.4.1 编译器类型选择

点击构建配置右侧的符号可以进行切换。

编译器类型

在keil中的位置:

3.4.2 CPU类型和浮点数设置

不需要手动选择,前面选择了芯片支持包,芯片就已经确定了,这里会自动识别。

3.4.3 RAM/FLASH布局

就是Keil软件中的IRAM和IROM选项,从Keil导入工程时就自动识别了,一般不会出错。

Keil中设置

对应VSCode设置

3.4.4 构建器选项

类似Keil中设置参数的C/C++选项卡的内容,以及User选项卡

3.4.4.1 用户任务

就是编译完成之后Keil中执行的User Command

Keil中设置位置

对应VSCode

3.4.4.2 其他项目

按照Keil工程中的设置即可。

C/C++编译器

MicroLIB

还可以修改输出的目录名称,这里修改为了obj。这个路径是和.uvprojx工程文件同级目录的。

输出目录名修改

3.4.5 烧录配置

根据实际使用的烧录器设定即可。

烧录配置

接口类型和下载速度都可以设定修改。

以上的参数设定完成之后就可以进行编译和烧录了。如下:

编译按钮

红框中的几个按钮分别是编译、清除、烧录、从头重新编译。编译完成之后的结果,可以看到和Keil编译结果类似,可视性更好一些:

编译结果

四、仿真调试

仿真调试通过Cortex-Debug插件来实现。Cortex-debug 使用 gdb 来完成调试;下图介绍了 gdb 调试的组成和它们之间的关系,在开始之前有必要理解清楚它们的关系。

gdb 调试

这张图片来源于:https://blog.csdn.net/qq_40833810/article/details/106713462

4.1 配置GNU编译器路径

gcc编译器路径设置

GNU编译器下载url:https://developer.arm.com/downloads/-/gnu-rm

先下载安装编译器,再设定编译器的路径即可。

4.2 设置openocd路径

openocd路径

4.3 设定项目配置

项目配置文件修改:launcth.json

这里的executable就是上一步EIDE扩展中编译得到的ELF文件的路径(相对路径即可)。

项目配置

4.4 配置完成

以上配置完成之后,可以进行调试,打断点、监视数据等。

仿真调试

以下就是正常的调试界面。

正常调试界面

〇、安装Python3

如果不需要安装多个不同版本python的话,在官网下载就可以了,需要注意安装时最好勾选把路径添加到环境变量中。

安装时添加环境变量

安装完成后,在系统cmd命令行输入python,按下回车,如果出现正常的Python版本信息和>>>符号,说明安装成功并加入了环境变量。

安装成功

一、安装SublimeText3

SublimeText3是一款具有代码高亮、语法提示、自动完成等功能,跨平台,且反应快速的编辑器软件,还支持非常丰富的插件扩展机制。
下载地址:http://www.sublimetext.com/3

二、安装PackageControl

SublimeText3使用PackageControl进行插件的管理和控制。安装方法在这里:
https://packagecontrol.io/installation

安装步骤

1、按下Ctrl + Shift + p组合键
2、手动输入Install Package Control,然后按下回车

三、安装SublimeREPL插件

使用SublimeREPL插件,可以一键运行python脚本
按下Ctrl + Shift + p组合键,输入install,选择

安装插件

稍等之后会弹出所有可安装的插件列表,手动输入REPL即可找到。

插件列表

安装成功后,可设置快捷键,如F5,按下即可执行。
设置快捷键方法:
安装完成后,在菜单点击Preferences ---> Key Bindings,添加自定义按键行为。
配置如下文件:(修改用户定义快捷键,不需要改变软件原来的默认快捷键)

设置用户快捷键

1
2
3
4
5
6
7
8
9
10
11
[
{
"keys": ["f5"],//这是自己设的快捷键
"command": "run_existing_window_command",
"args":
{
"id": "repl_python_run",
"file": "config/Python/Main.sublime-menu"
}
}
]

到了这里,简单的Python脚本已经可以通过按下F5按键来执行了。(如果不能执行说明Python路径不在环境变量中)
例如这个文件只有一个打印语句:

hello world示例

按下F5,会在另外一个窗口显示执行结果:

执行结果

四、另外的方式执行

1、不用SublimeREPL插件的话, 也可以使用sublimetext3build system执行。
按照如下的图片选择编译类型为Python,然后按下Ctrl + B组合键,即可运行python脚本。(前提是安装python时将python的安装路径添加到了环境变量中)
如果Python安装路径没有在环境变量,可以手动添加环境变量,或者新建build system

选择编译系统

2、新建build system的方法如下:
按下ctrl + shift +p组合键,输入build,

新建

将弹出的文件内容修改为:

1
2
3
4
5
{
"cmd": ["C:/xxxxxx/Python/python.exe","-u","$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python",
}

其中cmd内容修改为python.exe可执行文件的路径。
然后保存,例如保存为Python3.sublime-build,这样在上面选择编译系统时就会有新增的Python3。

五、常用插件安装

  1. 中文插件 localization:
    默认界面显示均为英文,安装插件时输入localization即可变为中文显示。

  2. Anaconda:
    一款很好用的代码补全、代码提示、代码跳转的插件。
    SublimeCodeIntel也可以,但是不如Anaconda好用。

  3. SublimeTmpl:
    配置文件模板,当新建xxx.py时,直接依据模板生成文件内容,提高效率。文件模板支持使用变量,如当前的年月日时间等。

  4. AutoPEP8
    可以自动根据PEP8标准格式化代码,或者提示不满足PEP8的代码格式。

  5. SideBarEnhancements
    支持打开侧边栏显示文件夹的所有文件。

  6. Git
    支持git的大部分功能。

  7. `Bracket Highlighter
    高亮显示标签:将光标放入开始/结束标签,会高亮显示对应的标签。

六、SublimeText3配置

包括设置字体格式,字体大小,空格替换TAB等。
打开Preference-->settings,我的设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"ensure_newline_at_eof_on_save": true, //确保保存时插入换行符
"font_face": "Consolas", //字体
"font_size": 14, //字体大小
"ignored_packages":
[
"Vintage"
],
"rulers":
[
80,
100
],
"tab_size": 4, //tab键长度
"highlight_line": true,//高亮当前行
"translate_tabs_to_spaces": true, //tab转换为空格
"trim_trailing_white_space_on_save": true, //保存时自动删除行尾空格
"show_encoding": true, //显示当前文件的编码
"show_full_path": true
}

公司年会,需要一个非常简单的抽奖工具,因为我的电脑是Windows系统,年会要在同事的Mac上面使用,考虑到开发的便捷性和跨平台特性,用Python是最好的选择。

1、获取抽奖名单

我拿到的人员名单在一个excel表格中,使用openpyxl或者xlrd库可以很容易读到所有的人名,放入一个集合set或者列表list即可。

名单.png

2、抽奖逻辑

每次点击开始抽奖时,都会把抽奖名单打乱。使用定时器,每隔一段时间,例如50ms,依据打乱的名单列表,按照索引不断修改文本框中的人名,点击停止时停止定时器,文本框中的人名就不会再变化,这时显示的人名即为中奖者。然后,将已经中奖的人名从列表中删掉,确保下次抽奖时不会重复中奖。

显示界面:

抽奖效果

3、主要代码

显示的界面可以用QtDesigner画出来,再用pyuic自动生成界面代码。代码在windows系统苹果Mac上面都可以直接运行,只是字体显示效果稍有不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python3
from xlrd import open_workbook
from lottery_draw import Ui_MainForm
import sys
import random
import time
import os.path
from PyQt5.QtCore import QThread, QTimer
from PyQt5 import QtCore, QtGui, QtWidgets

NAME_SET = set()

def get_all_names():
p_path = os.path.split(os.path.realpath(__file__))[0]
table = open_workbook(p_path + '/抽奖名单.xlsx').sheet_by_index(1)
for cell in table.col_values(0):
if not cell:
break
NAME_SET.add(cell.strip())

class Py_Select(Ui_MainForm, QtWidgets.QWidget):
def __init__(self):
super(Py_Select, self).__init__()
self.setupUi(self)
self.init()
self.setWindowTitle("抽奖工具")

def init(self):
get_all_names()
self.name_list = list(NAME_SET)
self.boxNames.setText('')
self.btnStart.clicked.connect(self._start)
self.btnStop.clicked.connect(self._stop)
self.index = 0
self.timer = None

def _start(self):
self.btnStart.setHidden(True)
self.btnStop.setHidden(False)
self.name_list = list(NAME_SET)
random.seed(int(time.time()))
random.shuffle(self.name_list)

if not self.timer:
self.timer = QTimer()
self.timer.timeout.connect(self._range_name)
self.timer.start(50)

def _range_name(self):
self.index += 1
if self.index >= len(self.name_list):
self.index = 0
self.boxNames.setText(self.name_list[self.index])

def _stop(self):
if self.timer:
self.timer.stop()
NAME_SET.discard(self.boxNames.text().strip())
self.btnStart.setHidden(False)

if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
myshow = Py_Select()
myshow.show()
sys.exit(app.exec_())

一、需求

当我们浏览视频网页时,有可能想要下载下来某个视频,或者想要 跳过臭长的广告(慎),但是现在的网站往往不提供直接下载mp4或其他格式的视频文件的功能。我们通过使用Python配合Chrome浏览器,可以获取到腾讯视频的视频真实地址,直接下载视频文件。

二、实现

以下以小猪佩奇某一集为例,看看如何得到视频地址。

1
https://v.qq.com/x/cover/bzfkv5se8qaqel2/b0020buglwx.html

有一种思路是:使用Chrome浏览器的开发者工具,监控网页加载过程中发出的每个请求和服务器的回复,从中查找关于视频信息的蛛丝马迹。当发现可疑的request时,查看该请求发出的所有参数内容,使用Python拼装相应的参数,并构造虚假的http请求头模拟成用户通过网页的点击,向服务器发出请求,获取服务器回复内容,然后从回复内容中解析出视频地址。

根据以前的经验,这些网站的开发者,在移动端的功能实现会相对简单一些,我们更容易识别和抓取,因此我们解析的时候使用浏览器模拟成移动端,如图:

获取视频地址的关键请求

我们用Python,拼接出图中方框Request的URL地址,填入参数,发出GET请求,就可以得到一个包含视频信息的json字符串。
GET请求的URL是:

1
http://h5vv.video.qq.com/getinfo?

问号后面由很多包含参数名称和值的组合构成,例如

otype=json&platform=11&defnpayver=1&appver=3.4.40

其中一个重要的参数vid,就是视频地址本身提供的:

vid参数

当我们发出get请求,得到的json数据类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
{
"dltype": 1,
"exem": 0,
"fl": {
"cnt": 4,
"fi": [
{
"id": 10203,
"name": "sd",
"lmt": 0,
"sb": 1,
"cname": "标清;(270P)",
"br": 37,
"profile": 1,
"drm": 0,
"video": 1,
"audio": 1,
"fs": 5705248,
"super": 0,
"hdr10enh": 0,
"sname": "标清",
"resolution": "270P",
"sl": 0
},
{
"id": 10212,
"name": "hd",
"lmt": 0,
"sb": 1,
"cname": "高清;(480P)",
"br": 42,
"profile": 1,
"drm": 0,
"video": 1,
"audio": 1,
"fs": 10092707,
"super": 0,
"hdr10enh": 0,
"sname": "高清",
"resolution": "480P",
"sl": 0
},
{
"id": 10201,
"name": "shd",
"lmt": 0,
"sb": 1,
"cname": "超清;(720P)",
"br": 62,
"profile": 1,
"drm": 0,
"video": 1,
"audio": 1,
"fs": 19246460,
"super": 0,
"hdr10enh": 0,
"sname": "超清",
"resolution": "720P",
"sl": 1
},
{
"id": 10209,
"name": "fhd",
"lmt": 3,
"sb": 1,
"cname": "蓝光;(1080P)",
"br": 67,
"profile": 1,
"drm": 0,
"video": 1,
"audio": 1,
"fs": 40730393,
"super": 0,
"hdr10enh": 0,
"sname": "蓝光",
"resolution": "1080P",
"sl": 0
}
]
},
"fp2p": 2,
"hs": 0,
"ip": "113.110.230.111",
"ls": 0,
"preview": 299,
"s": "o",
"sfl": {
"cnt": 0
},
"tstid": 3,
"tm": 1574871391,
"vl": {
"cnt": 1,
"vi": [
{
"br": 62,
"ch": 0,
"cl": {
"fc": 1,
"ci": [
{
"idx": 1,
"cs": 19246460,
"cd": "299.96",
"cmd5": "f579ecf14d0aa58c0bc846f40ebe0c7c",
"vkey": "",
"urllist": [],
"keyid": "q0020tfo8j7.10201.1"
}
]
},
"ct": 21600,
"drm": 0,
"dsb": 0,
"fclip": 1,
"fmd5": "f579ecf14d0aa58c0bc846f40ebe0c7c",
"fn": "q0020tfo8j7.p201.mp4",
"fs": 19246460,
"fst": 5,
"fvkey": "6D7FDA1832CC88940F6F20E281EE9727639DF6B0D70FFF73083818AB45289A0507A0FD280B370536D0918C1A3564AA34F9698B83C61A88962F765BBF7EA67010F5B4D1D11737658D783A86ED5A5EA22933C2838506DEC3B16C1223E7727442E18A1AA2630567BFD436C4353F31F5A0E5",
"head": 0,
"hevc": 0,
"iflag": 0,
"level": 0,
"lnk": "q0020tfo8j7",
"logo": 1,
"mst": 8,
"pl": [
{
"cnt": 3,
"pd": [
{
"cd": 2,
"h": 45,
"w": 80,
"r": 10,
"c": 10,
"fmt": 40001,
"fn": "q1",
"url": "http: //puui.qpic.cn/video_caps/0/"
},
{
"cd": 2,
"h": 90,
"w": 160,
"r": 5,
"c": 5,
"fmt": 40002,
"fn": "q2",
"url": "http://puui.qpic.cn/video_caps/0/"
},
{
"cd": 2,
"h": 135,
"w": 240,
"r": 5,
"c": 5,
"fmt": 40003,
"fn": "q3",
"url": "http://puui.qpic.cn/video_caps/0/"
}
]
}
],
"share": 1,
"sp": 0,
"st": 2,
"tail": 0,
"td": "299.96",
"ti": "恐龙先生弄丢了",
"tie": 0,
"type": 1036,
"ul": {
"ui": [
{
"url":"http://113.105.141.22/vlive.qqvideo.tc.qq.com/AF094anUFAellJsZIYnUozoSnZLLcgP480IDBq7WleyE/uwMROfz2r5zCIaQXGdGnC2dfKb2-hlZWyQT_tzD-Vsr
eqSpl/",
"vt": 203,
"dtc": 0,
"dt": 2
},
{
"url": "http: //lmsjy.qq.com/uwMROfz2r5zCIaQXGdGnCmdfKb0i2sHXl3M2Wy9RmDZEeplY/",
"vt": 170,
"dtc": 0,
"dt": 2
},
{
"url": "http://lmbsy.qq.com/uwMROfz2r5zCIaQXGdGmm2dfKb0Pk2-yYlV7ZrIrO9TJ-LqW/",
"vt": 130,
"dtc": 0,
"dt": 2
},
{
"url": "http://video.dispatch.tc.qq.com/uwMROfz2r5zCIaQXGdGmlWdfKb2svKK_VNuAbg616jtBjIn0/",
"vt": 0,
"dtc": 0,
"dt": 2
}
]
},
"vh": 720,
"vid": "b0020buglwx",
"videotype": 106,
"vr": 0,
"vst": 2,
"vw": 1280,
"wh": 1.7777778,
"wl": {
"wi": []
},
"uptime": 1465198419,
"fvideo": 0,
"cached": 1,
"fvpint": 0,
"swhdcp": 0
}
]
}
}

看到这其中包含很多的url地址,可以确定视频地址的信息就在其中,不过还需要分析一下,怎么样根据这些数据把视频地址拼接出来。
其实浏览器也是根据这个json数据,通过调用js脚本获取到视频地址,但是如果要通过解析js脚本获取地址就会很麻烦,尤其是考虑到js脚本可能经过了压缩和加密。所以还是通过浏览器开发者工具,查看浏览器实际get请求中的数据格式,以供参考。

视频地址格式

经测试其中有些参数可以不传入。最终代码如下,只需传入一个网址,就可以获取到mp4格式视频的一个url(默认就是最高清晰度)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
import re
import json

headers = {
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'}

def getTencentVideoUrl(url):
vid = url.split('/')[-1][:-5]
url = 'http://h5vv.video.qq.com/getinfo?otype=json&platform=11&defnpayver=1&appver=3.4.40&defn=fhd&vid=' + vid
html = requests.get(url, headers=headers).text
# 获取json数据
p = re.compile(r'({.*})', re.S)
jsonstr = re.findall(p, html)[0]
json_data = json.loads(jsonstr)
print(jsonstr)

# 解析json数据获取url
fvkey = json_data['vl']['vi'][0]['fvkey']
keyid = json_data['vl']['vi'][0]['cl']['ci'][0]['keyid'].split('.')
filename = keyid[0] + '.p' + keyid[1][2:] + '.' + keyid[2] + '.mp4'
baseUrl = json_data['vl']['vi'][0]['ul']['ui'][3]['url'] # 实际数据中有多个cdn可供选择
result = baseUrl + filename + '?vkey=' + fvkey
return result

if __name__ == '__main__':
url = input('请输入腾讯视频网址:')
print('视频下载地址:\n', getTencentVideoUrl(url))

三、问题:

实际上有些视频是分成了多个小部分,这时就会需要获取每个视频的地址,下载下来然后再拼接成一个完整的视频文件,如果想要做的话也是可以的,自动下载多段视频,然后自动合并成1个完整的文件
这里可能就不能只发送一次get请求了,而是根据视频分割成了多少个,发送多次请求。

一、预期结果:

空气质量历史数据查询网站 上可以查询到每个城市的空气质量历史数据。我们的计划是用Python获取并打印某个城市某个月当中每一天的空气质量数据,包括pm2.5,pm10,SO2等数据。如下图效果:

执行结果

二、爬取数据过程:

1、需要爬取页面

html页面

很显然由网页的url看到,只需要两个参数,一个city一个month,就可以获取这个网页内容。但是由于这些数据是动态加载的,而不是直接出现在原始的html源文件中,所以直接用requests库的get方法是拿不到数据内容的。

2、查找js代码

既然直接get看不到实际显示的数据,那么就很容易想到数据加载是通过js完成的。我们在该页面包含的js代码中去找。通过用chrome浏览器的调试,我发现这些空气质量数据实际是用js代码向下面这个 url 发送post请求获得的。
所以我们的任务就变成用python模拟执行这个post请求。

3、解析js代码

在js代码里面找到下面的部分,根据getServerData函数名很容易判断数据就是通过这里获取的。

源代码js函数

我们用Chrome浏览器调试,在这里设置断点,刷新页面,中断后逐步调试,就找到了实际获取数据使用的代码,居然隐藏在jquery.min.js文件里面一个很隐蔽的地方,并且明显经过了混淆,代码显示成了一大堆看不懂的数字,在网上找一个js的反混淆工具,解析之后部分代码如下:

反混淆前,chrome调试内容

反混淆后部分js代码

这里已经可以很明显看出post请求的参数:url和data了,我们只要按照这个格式发送一个post请求就解决问题了。

4、用python模拟执行js

第3步已经很接近实现了,但是这里post请求的参数是经过加密的。如果我们要用python来直接获取数据,就需要研究清楚它加密和解密的方法,再用python把这个js代码加密解密的过程重写一下,但是这样很花时间还容易错,完全没有必要。我们用Python的PyExecJs库,只要在Python中直接调用这部分js代码就行了。还是使用原封不动的js代码,使用getParam()将参数加密并上传,获取到服务器的返回数据后,再使用decodeData()将数据解析出来。

注:需要安装第三方库

1
pip install pyexecjs

三、代码

代码:因为js文件太大就不贴了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import requests
import execjs
import json


def createParams(city, month, ctx):
'''由城市名、年月得出经js加密后的post参数,ctx由js代码解析得到'''
method = 'GETDAYDATA'
js = 'getEncryptedData("{0}", "{1}", "{2}")'.format(method, city, month)
params = ctx.eval(js)
return {'hd': params}


def getResponseData(city, month, ctx):
'''由城市名、年月向服务器发送post请求并解密返回数据,ctx由js代码解析得到'''
apiUrl = 'https://www.aqistudy.cn/historydata/api/historyapi.php'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'
}

response = requests.post(apiUrl, data=createParams(
city, month, ctx), headers=headers, timeout=10)
if response.status_code != 200:
return None
# 解析数据
js = 'decodeData("{0}")'.format(response.text)
decrypted_data = ctx.eval(js)
data = json.loads(decrypted_data)
return data['result']['data']['items']


if __name__ == '__main__':
# js环境,这里用nodeJS
node = execjs.get()
# compile javascript
ctx = node.compile(open('encryption.js', encoding='utf-8').read())

city = input('请输入城市名(如: 西安):')
year = input('请输入年份(如: 2018):')
month = input('请输入月份(如: 5):').zfill(2)
items = getResponseData(city, year + month, ctx)

if items is not None:
print('\n')
print('日期\tAQI\tPM2.5\tPM10\tSO2\tNO2\tCO\tO3\t质量等级')
for item in items:
print(item['time_point'], end='\t')
print(item['aqi'], end='\t')
print(item['pm2_5'], end='\t')
print(item['pm10'], end='\t')
print(item['so2'], end='\t')
print(item['no2'], end='\t')
print(item['co'], end='\t')
print(item['o3'], end='\t')
print(item['quality'])
else:
print('数据获取失败!')

本文只做微信公众号测试用

一、功能

关注微信公众号 西安气象爱好者,发送通过高德地图分享的链接地址,格式为 http://surl.amap.com/xxxxxxxxxxxxx ,公众号将自动回复对应地点的经度和纬度,可获取自己定位位置的经纬度,也可搜索地图任意位置获取经纬度。

二、操作方法:

1、在高德地图APP上定位或者搜索地址,选择分享,按住下图红圈即可复制链接。

获取链接地址

2、将复制的链接发送给微信公众号 西安气象爱好者,获取地址数据。

如:

三、python代码:

1、高德地图链接解析

原理就是这个链接地址会经过302跳转跳转的中间地址包括了经度和纬度,只要通过requests库获取到,再由正则表达式提取就可以。
(实际上,如果用浏览器打开链接,经纬度也会直接出现在最终的200状态的地址中,但浏览器地址栏的地址最终会被js代码替换掉,禁用js就不会被替换)

禁用js后浏览器打开链接地址

不禁用js时打开同一链接地址

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import re
import requests

def dd2dms(deg):
d = int(float(deg))
md = abs(float(deg) - d) * 60
m = int(md)
sd = (md - m) * 60
return '{:d}° {:d}′ {:.7f}″'.format(d, m, sd)

headers = {
"User-Agent": "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
"Referer": "https://www.amap.com"
}

def Locate(url_string):
amapUrl = r'^http://surl.amap.com/\w{13}$'
if re.match(amapUrl, url_string) is None:
return 'NOT FOUND'

try:
url_string = 'https://' + url_string[7:]
r = requests.head(url_string, stream=True, headers=headers)
realUrl = r.headers['Location']

posPattern = r'(\d{1,3}\.\d{6,20}),(\d{1,3}\.\d{6,20})'
jingdu = re.search(posPattern, realUrl).group(2)
# print(jingdu)

weidu = re.search(posPattern, realUrl).group(1)
# print(weidu)
result = '({0},{1})\r\n({2},{3})'.format(weidu, jingdu, dd2dms(weidu), dd2dms(jingdu))

return result

except BaseException as e:
print(e)
return 'NOT FOUND'

if __name__ == '__main__':
url_string = 'http://surl.amap.com/3U9wY_0957DEP'
print(Locate(url_string))

2、微信公众号自动回复

也是用python来做微信公众号的后台接入,web.py模块比较简单,写两个方法,一个接收一个回复。

GET:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def GET(self):
try:
data = web.input()
if len(data) == 0:
return "hello, this is handle view"
signature = data.signature
timestamp = data.timestamp
nonce = data.nonce
echostr = data.echostr
token = "xxxxxxxxxxx"
list1 = [token,timestamp,nonce]
list1.sort()
str_list1 = ''.join(list1)
print(str_list1)
sha1 = hashlib.sha1()
sha1.update(str_list1.encode('utf-8'))
hashcode = sha1.hexdigest()
print("handle/GET func: hashcode, signature: ", hashcode, signature)
if hashcode == signature:
return echostr
else:
return ""
except Exception as Argument:
return Argument

POST:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def POST(self):
str_xml = web.data()
xml = etree.fromstring(str_xml)
msgType = xml.find("MsgType").text
fromUser = xml.find("FromUserName").text
toUser = xml.find("ToUserName").text
#如果符合高德链接格式则返回经纬度,否则返回test+原字符串
if msgType == 'text':
getMsg=xml.find("Content").text.strip()
amapUrl = r'^http://surl.amap.com/\w{13}$'
if re.match(amapUrl, getMsg) is None:
content = "test"+ getMsg
else:
content=getLocate.Locate(getText)
render = web.template.render('templates/')
return render.reply_text(fromUser, toUser, int(time.time()), content)
elif msgType == 'image':
pass
else:
pass

labview2018中新增了python接口,可以直接调用python模块,方便传入参数。
目前限制很多,支持的python版本只有2.7和3.6,而且python必须和labview版本一致,同为32位或者同为64位。

labview2018自带python接口

调用的过程是,先设置版本并打开python会话,再传入python模块路径,调用函数名称,返回数据类型,还可以传入多个参数。

python代码示例:不需要其它内容,只要这个函数可以执行就好。

1
2
def main():
return 'Hello World!'

在以前的labview版本中调用python其实也比较方便,调用执行系统命令就可以,只不过指令需要自己通过连接字符串去完成拼接,包括参数的添加。

另一种方法调用python

其中的bool真用来设定系统命令最小化执行,也就是不会弹出来一个cmd窗口。python代码略有不同:

1
2
3
4
5
def main():
return 'Hello World!'

if __name__ == '__main__':
print(main())

上面两种方法获取的结果同样是Hello World!,区别在于python程序中,第一个是只要写一个函数,return正常返回,在labview中指定返回数据类型即可。而第二种中,要把想在labview中获得的数据print打印出来,labview接收到的必定是字符串,然后再解析实际数据类型。

今天需要查看以前labview项目的一个文件,发现文件 加密 了看不到labview的源码,密码又忘记了,试了很多个都不对,无奈只好想着破解。在网上找到了一种思路,试验之后发现是可以的。

原来,vi文件密码的md5值是以二进制形式存储在文件里面的。用二进制方式打开vi文件,查找hex00 00 00 30,这段之后的16个字节就是密码的md5值。按照这种方法可能找到好几个不同的md5值,只有一个是正确的。实际操作中,用notepad++或者sublime text这样的编辑器,很容易就找到了这个值。

查找到md5值

76f3154b83e937edc04996af54848040 这个字符串就可以找到密码。
虽然说md5不能直接逆向破解,但是只要有一个足够大的数据库,把很多字母数字字符串能够求到的md5全部保存下来,那么要反向查询到密码也就很容易了。
https://www.cmd5.com/ 这就是一个很好用的在线解密网站。

上面是手动查询,如果要用python找到md5的话,可以像下面这样:(其实这里对bytes数据的处理并不好,不过考虑到我这里vi文件一般都很小,也无所谓了,因为bytes不能直接用正则表达式,而find只返回第一个匹配的,所以用这种办法是可行的,比全部转成字符串再找要好一些)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def getMd5(path):
dest=bytes.fromhex('00000030')
with open(path,'rb') as fs:
content = fs.read()
md5Pos = 0
while content:
md5Pos = content.find(dest, md5Pos)
if md5Pos==-1:
break
print(content[md5Pos+4:md5Pos+20].hex())
md5Pos += 20


if __name__=='__main__':
getMd5(r'C:\Users\upython\Desktop\xxx.vi')

执行结果有两个符合条件的:

md5查找结果

拿到这两个字符串,再手动上网去解密就好,当然也可以再写爬虫自动去网上查到密码。

0%