Python技术

Python技术 's Blog


  • 首页

  • 标签

  • 归档

  • 关于

五分钟带你用python爬取付费歌曲

发表于 2022-12-24 | 分类于 python

有小伙伴说怎么爬取付费的音频

这里分享一个脚本或者说思路,免费下载VIP歌曲。

本人喜欢韩国TARA组合的“lovey dovey”,少女时代的“Gee”,张韶涵的“欧若拉”。

这几首在酷我、酷狗、QQ音乐都只能播放60秒试听片段,就算充了会员,也只有在会员期限内可以听。

所以呢,今天就利用爬虫来免费下载想听的付费音乐。

文章较短,建议看完全文,下载链接怎么找到的,没人跟你说估计难找到。

开始我也摸不着头脑,寻找过程应证了一句话“魔鬼藏在细节中”。

这是用脚本下载的付费歌曲:

顺便说一句,

代码中tkinter这种GUI模块操作起来繁琐,一段小功能需要一长串的代码,很像面向过程,所以我基本不碰它😅

不废话

代码见文末

从URL开始

1
search_url = 'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?'

脚本里的这个地址怎么来的呢?其实就是网页上的搜索框。

打开酷我官网,输入“Lovey Dovey”并回车,出现url 它包括请求参数key,httpsStatus等。

脚本里对url发起请求,并在tkinter中显示搜索结果,就是这一段

输出的歌曲信息,如下图所示

验证歌曲信息

加一行打印item_text,然后左键点击tkinter中的歌曲信息,我们在pycharm的输出中可以直观的看到相关的歌曲内容

魔鬼藏在细节中之“绕过下载按钮”

有了歌曲信息,下一步是获取歌曲的下载链接。这个链接又藏在哪个旮旯里? 如果直接选择“下载歌曲”,会弹框让你下载客户端

这个时候重点来了,点击第一首歌的播放按钮

会提示“该歌曲为付费歌曲”,播放不了,同时下方未出现网络请求: 那么我们换一首免费的,点击第三首的播放按钮。免费的歌曲能播放,并且会自动添加到播放列表中:

然后刷新网页,下载链接出现了,并且能看到响应数据中有个mp3结尾的链接,就是它了! 这个下载链接就是播放列表中,最新播放的歌曲!

前面已经拿到了所有歌曲的信息,包括付费歌曲,按下载链接的格式去发送请求,写入文件就可以了

1
2
3
music_url = 'http://www.kuwo.cn/api/v1/www/music/playUrl?mid={}&type=convert_url3' \
      '&httpsStatus=1&reqId={}' \
  .format(song_rid, songs_req_id)

我们测试一下,脚本下载好的少女时代“Gee”用QQ音乐打开,弹框说“歌曲需付费”

看来这首歌是真的VIP会员歌曲

破解vip音乐 .py

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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import os
import tkinter as tk
import webbrowser
import requests
import tkinter.messagebox as mes_box
import PySimpleGUI as sg
from tkinter import ttk
from retrying import retry


class SetUI(object):
    """
    音乐弹框界面
    """

    def __init__(self, weight=1000, height=600):
        self.ui_weight = weight
        self.ui_height = height
        self.title = " 音乐破解软件"
        self.ui_root = tk.Tk(className=self.title)
        self.ui_url = tk.StringVar()
        self.ui_var = tk.IntVar()
        self.ui_var.set(1)
        self.show_result = None
        self.song_num = None
        self.response_data = None
        self.song_url = None
        self.song_name = None
        self.song_author = None

    def set_ui(self):
        """
        设置简易UI界面
        :return:
        """
        # Frame空间
        frame_1 = tk.Frame(self.ui_root)
        frame_2 = tk.Frame(self.ui_root)
        frame_3 = tk.Frame(self.ui_root)
        frame_4 = tk.Frame(self.ui_root)

        # ui界面中菜单设计
        ui_menu = tk.Menu(self.ui_root)
        self.ui_root.config(menu=ui_menu)
        file_menu = tk.Menu(ui_menu, tearoff=0)
        ui_menu.add_cascade(label='菜单', menu=file_menu)
        file_menu.add_command(label='使用说明', command=lambda: webbrowser.open('www.baidu.com'))
        file_menu.add_command(label='关于作者', command=lambda: webbrowser.open('www.baidu.com'))
        file_menu.add_command(label='退出', command=self.ui_root.quit)

        # 控件内容设置
        choice_passageway = tk.Label(frame_1, text='请选择音乐搜索通道:', padx=10, pady=10)
        passageway_button_1 = tk.Radiobutton(frame_1, text='酷我', variable=self.ui_var, value=1, width=10, height=3)
        passageway_button_2 = tk.Radiobutton(frame_1, text='网易云', variable=self.ui_var, value=2, width=10, height=3)
        passageway_button_3 = tk.Radiobutton(frame_1, text='QQ音乐', variable=self.ui_var, value=3, width=10, height=3)
        passageway_button_4 = tk.Radiobutton(frame_1, text='酷狗', variable=self.ui_var, value=4, width=10, height=3)
        input_link = tk.Label(frame_2, text="请输入歌曲名或歌手:")
        entry_style = tk.Entry(frame_2, textvariable=self.ui_url, highlightcolor='Fuchsia', highlightthickness=1,
                               width=35)
        label2 = tk.Label(frame_2, text=" ")
        play_button = tk.Button(frame_2, text="搜索", font=('楷体', 11), fg='Purple', width=2, height=1,
                                command=self.get_KuWoMusic)
        label3 = tk.Label(frame_2, text=" ")
        # 表格样式
        columns = ("序号", "歌手", "歌曲", "专辑")
        self.show_result = ttk.Treeview(frame_3, height=20, show="headings", columns=columns)
        # 下载
        download_button = tk.Button(frame_4, text="下载", font=('楷体', 11), fg='Purple', width=6, height=1, padx=5,
                                    pady=5, command=self.download_music)

        # 控件布局
        frame_1.pack()
        frame_2.pack()
        frame_3.pack()
        frame_4.pack()
        choice_passageway.grid(row=0, column=0)
        passageway_button_1.grid(row=0, column=1)
        passageway_button_2.grid(row=0, column=2)
        passageway_button_3.grid(row=0, column=3)
        passageway_button_4.grid(row=0, column=4)
        input_link.grid(row=0, column=0)
        entry_style.grid(row=0, column=1)
        label2.grid(row=0, column=2)
        play_button.grid(row=0, column=3, ipadx=10, ipady=10)
        label3.grid(row=0, column=4)
        self.show_result.grid(row=0, column=4)
        download_button.grid(row=0, column=5)

        # 设置表头
        self.show_result.heading("序号", text="序号")
        self.show_result.heading("歌手", text="歌手")
        self.show_result.heading("歌曲", text="歌曲")
        self.show_result.heading("专辑", text="专辑")
        # 设置列
        self.show_result.column("序号", width=100, anchor='center')
        self.show_result.column("歌手", width=200, anchor='center')
        self.show_result.column("歌曲", width=200, anchor='center')
        self.show_result.column("专辑", width=300, anchor='center')

        # 鼠标点击
        self.show_result.bind('<ButtonRelease-1>', self.get_song_url)

    @retry(stop_max_attempt_number=5)
    def get_KuWoMusic(self):
        """
        获取qq音乐
        :return:
        """
        # 清空treeview表格数据
        for item in self.show_result.get_children():
            self.show_result.delete(item)
        headers = {
            'accept': 'application/json, text/plain, */*',
            'accept - encoding': 'gzip, deflate',
            'accept - language': 'zh - CN, zh;q = 0.9',
            'cache - control': 'no - cache',
            'Connection': 'keep-alive',
            'csrf': 'HH3GHIQ0RYM',
            'Referer': 'http://www.kuwo.cn/search/list?key=%E5%91%A8%E6%9D%B0%E4%BC%A6',
            'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/99.0.4844.51 Safari/537.36',
            'Cookie': '_ga=GA1.2.218753071.1648798611; _gid=GA1.2.144187149.1648798611; _gat=1; '
                      'Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1648798611; '
                      'Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1648798611; kw_token=HH3GHIQ0RYM'
        }
        search_input = self.ui_url.get()
        if len(search_input) > 0:
            search_url = 'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?'
            search_data = {
                'key': search_input,
                'pn': '1',
                'rn': '80',
                'httpsStatus': '1',
                'reqId': '858597c1-b18e-11ec-83e4-9d53d2ff08ff'
            }
            try:
                self.response_data = requests.get(search_url, params=search_data, headers=headers, timeout=20).json()
                songs_data = self.response_data['data']['list']
                if int(self.response_data['data']['total']) <= 0:
                    mes_box.showerror(title='错误', message='搜索: {} 不存在.'.format(search_input))
                else:
                    for i in range(len(songs_data)):
                        self.show_result.insert('', i, values=(i + 1, songs_data[i]['artist'], songs_data[i]['name'],
                                                               songs_data[i]['album']))
            except TimeoutError:
                mes_box.showerror(title='错误', message='搜索超时,请重新输入后再搜索!')
        else:
            mes_box.showerror(title='错误', message='未输入需查询的歌曲或歌手,请输入后搜索!')

    def get_song_url(self, event):
        """
        获取下载歌曲的地址
        :return:
        """
        # treeview中的左键单击

        for item in self.show_result.selection():
            item_text = self.show_result.item(item, "values")
            # 获取
            print(1, item_text)
            self.song_num = int(item_text[0])
        # 获取下载歌曲的地址
        if self.song_num is not None:
            songs_data = self.response_data['data']['list']
            songs_req_id = self.response_data['reqId']
            song_rid = songs_data[self.song_num - 1]['rid']
            music_url = 'http://www.kuwo.cn/api/v1/www/music/playUrl?mid={}&type=convert_url3' \
                        '&httpsStatus=1&reqId={}' \
                .format(song_rid, songs_req_id)
            response_data = requests.get(music_url).json()
            self.song_url = response_data['data'].get('url')
            self.song_name = songs_data[self.song_num - 1]['name']
            self.song_author = songs_data[self.song_num - 1]['artist']
        else:
            mes_box.showerror(title='错误', message='未选择要下载的歌曲,请选择')

    def download_music(self):
        """
        下载音乐
        :return:
        """
        if not os.path.exists('./wangYiYun'):
            os.mkdir("./wangYiYun/")
        if self.song_num is not None:
            song_name = self.song_name + '--' + self.song_author + ".mp3"
            try:
                save_path = os.path.join('./wangYiYun/{}'.format(song_name)) \
                    .replace('\', '/')
                true_path = os.path.abspath(save_path)
                resp = requests.get(self.song_url)
                with open(save_path, 'wb') as file:
                    file.write(resp.content)
                    mes_box.showinfo(title='下载成功', message='歌曲:%s,保存地址为%s' % (self.song_name, true_path))
            except Exception:
                mes_box.showerror(title='错误', message='未找到存放歌曲的文件夹')
        else:
            mes_box.showerror(title='错误', message='未选择要下载的歌曲,请选择后下载')

    def progress_bar(self, file_size):
        """
        任务加载进度条
        :return:
        """
        layout = [[sg.Text('任务完成进度')],
                  [sg.ProgressBar(file_size, orientation='h', size=(40, 20), key='progressbar')],
                  [sg.Cancel()]]

        # window只需将自定义的布局加载出来即可 第一个参数是窗口标题。
        window = sg.Window('机器人执行进度', layout)
        # 根据key值获取到进度条
        _progress_bar = window['progressbar']
        for i in range(file_size):  # 循环
            event, values = window.read(timeout=10)
            if event == 'Cancel' or event is None:
                break
            _progress_bar.UpdateBar(i + 1)

    def ui_center(self):
        """
        UI界面窗口设置:居中
        """
        ws = self.ui_root.winfo_screenwidth()
        hs = self.ui_root.winfo_screenheight()
        x = int((ws / 2) - (self.ui_weight / 2))
        y = int((hs / 2) - (self.ui_height / 2))
        self.ui_root.geometry('{}x{}+{}+{}'.format(self.ui_weight, self.ui_height, x, y))

    def loop(self):
        """
        函数说明:loop等待用户事件
        """
        self.ui_root.resizable(False, False)  # 禁止修改窗口大小
        self.ui_center()  # 窗口居中
        self.set_ui()
        self.ui_root.mainloop()


if __name__ == '__main__':
    a = SetUI()
    a.loop()
阅读全文 »

重磅!张文宏最新防治指南来了!

发表于 2022-12-20 | 分类于 python

最近全国各地最热门的话题恐怕都是跟“羊”有关的了。

混乱时期

两周之前,在所有人猝不及防的情况下,国家宣布放开疫情防控。从此我们再也不用每天排队做核酸,上班或者出去完也不用处处设岗查验健康码。

看似大家重归自由了,但是没过几天,大家就觉得有点不对劲了:感染人数激增,感染的症状也不是宣称的90%无症状,而是90%都是有症状的。

于是,大家慌了,疯狂囤积连花清瘟、布洛芬等感冒药和退烧药,以至于一药难求。

又过几天,大家发现做核酸的地方少了,混管结果阳的概率非常大,于是抗原又成了香饽饽,几天时间所有渠道卖断货!

我也不例外,在放开的最开始买了感冒药和退烧药(那几天还没有哄抢,现在还蛮庆幸的)。可是买抗原晚了点,一直没发货,现在手中一个都没有,衰!

羊羊羊

我身处武汉,身边大多数人都羊了,以至于办公的园区没什么人上班,公司楼下的停车场空空荡荡。

这里做个小调查,看看大家羊的比例:

这里,提醒大家还是多关照一下家里的老人,特别是高寿的和有基础病的老人,他们感染之后重症的概率非常高。我身边就有同事朋友家里有老人最近因为感染而离开,全家人笼罩在悲痛中的案例。

第一批杨过都来上班了,我还没有羊,可真是天选打工人。

天选打工人秘诀

不过,我估计我也快了,因为家里领导已经羊了!

从昨天中午到现在,她都一直在发烧,身体酸痛。没办法,我们相依为命,我在家照顾她。

照顾她的同时,我也要防止自己被感染,我也知道迟早是要感染的,只是想尽量少吸入点病毒,减少病毒载体量,期望症状会轻些。毕竟2020过年的时候我在家咳嗽几个月不敢去医院,留下了后遗症,就怕感染症状严重会导致肺部感染,那就麻烦大了,你们可能看不到我写文章了!

我目前的措施是:

  • 和领导分房生活,她在主卧,我在次卧,尽量避免接触,有事视频通话
  • 两人在家里戴口罩,她经过的公共区域及时喷酒精
  • 给她送饭都用一次性碗筷,吃完就喷酒精杀毒
  • 家里时刻开窗通风,房间每天开窗通风几次

防治指南

在搜索家里有羊,如果做防护时,我得到了一份张文宏主任的音频,这是他在一个微信小群里面分享的新冠病毒防治指南,以及就大家关心的问题做了详细的解答。

里面最重要的几点是:

  • 尽量避免重复感染,感染几次可能会要命
  • 感染后躺平休息,尽量别折腾
  • 这是一场持久战,阳过之后还是要戴口罩注意防护

我把他一个小时的录音整理成一份思维导图,供大家参考:

大家需要高清思维导图的话,可以在公众号【Python技术】回复关键词[指南]获取。

总结

这一轮感染我们大多数人都逃不过,躲不掉,我们能做的是不恐慌不害怕,勇敢面对,做好防护,尽量低载量感染,错峰感染。

最后,祝大家身体健康,但愿我们能早日恢复正常生活。

阅读全文 »

省时省力!这些Python高效代码片段必须牢记!

发表于 2022-12-18 | 分类于 python

我的上级领导是一个技术狂人,他在技术上的涉猎非常广泛,而且对技术的自我要求蛮高。

他经常看我们写的代码,挂在嘴边的一句话是:你们的代码像屎一样,都是垃圾!

我们听到后只能默默承受,物理反驳。

他经常劝诫我们要阅读优秀框架的源码,不仅要弄懂框架的原理,更要学习他们写的代码,要将他们写的代码作为奋进的目标!

其实,普通程序员写的代码和大神写的代码的最大区别就是我们是用代码堆砌功能,很少注重代码品质和效率,而大神们能写出优雅精简的代码,实现一个功能我们需要几十上百行代码,而他们可能一行就解决了!并且他们的代码运行效率还比你高!

今天给大家分享一些常用的高效代码片段,希望可以给大家的代码质量带来一点点提高!

单行循环列表

1
2
3
4
mylist = [11,24,56,45,60]
print([i*3 for i in mylist]) #[33, 72, 168, 135, 180]
print([i-10 for i in mylist])
[33, 72, 168, 135, 180] #[1, 14, 46, 35, 50]

这种写法可以减少你的代码行数,而且比较清晰。

获取数据类型

1
2
3
mylist = [123, 'python', 34.23, False, [4,5]]
print([type(i) for i in mylist])
# [<class 'int'>, <class 'str'>, <class 'float'>, <class 'bool'>, <class 'list'>]

我们有时候需要知道数据的类型,做对应的转换或者操作。可以这样判断:

1
print(type(123) == int) # True

获取数字的整数值

1
2
3
4
integer = 8938485
digitz = [int(i) for i in str(integer)]
print(digitz)
# [8, 9, 3, 8, 4, 8, 5]

这里的原理就是先将数字转换为字符串,然后再进行循环获取。

链式比较

1
2
3
a =5 
print(1 == a < 2) # False
print(2 < 4 < 5 == a)  # True

看到第一个,是不是会这样理解:1 == a 为 False,False < 2 为 False,所以最后答案为 False。

答案是对的,但是过程是错的。在 Python 中,比较逻辑是这样的:分别比较 1 == a 和 a < 2,两个都为 True,结果才为 True。

按照这个逻辑,自己领会一下第二个的运行逻辑。

字符串乘法器

如果需要重复生成字符串,通常我们会这样:

1
2
3
4
5
str = ''
for i in range(10):
    str += 'Python'
print(str)
# PythonPythonPythonPythonPythonPythonPythonPythonPythonPython

其实,优雅的方式只需一行:

1
2
print('Python'*10)
# PythonPythonPythonPythonPythonPythonPythonPythonPythonPython

带分隔符打印

1
2
print('123', '234', 100, sep='x')
# 123x234x100

还有另外一种方式:

1
2
print('x'.join(['123', '234', '100']))
# # 123x234x100

变量交换

1
2
3
4
5
d1 = 34
d2 = 23
d1, d2 = d2, d1
print(d1, d2)
# 23 34

字符串变小写

1
2
3
mylist = ['ABCD', 'Python', 'I am Chinese']
print([data.lower() for data in mylist])
# ['abcd', 'python', 'i am chinese']

统计列表中元素出现频率

1
2
3
4
import collections
mylist = [10, 11, 12, 3, 3, 11, 11, 33]
print(dict(collections.Counter(mylist)))
# {10: 1, 11: 3, 12: 1, 3: 2, 33: 1}

更新字典

1
2
3
4
5
6
7
mydict = {1: 'Python', 2: 'Javascript', 3: 'Java', 4: 'C#'}
mydict.update({2: 'C'})
print(mydict)
# {1: 'Python', 2: 'C', 3: 'Java', 4: 'C#'}
mydict.update({5: 'Svelte'})
print(mydict)
# {1: 'Python', 2: 'C', 3: 'Java', 4: 'C#', 5: 'Svelte'}

非 Pandas 方式读取 csv 文件

1
2
3
4
import csv 
with open('Test.csv', 'r') as f:
    read = csv.reader(f)
    print([r for r in read])

总结

好了,这些代码片段需要好好消化,方能事半功倍!大家还有没有其他的简洁高效代码片段,留言区发出来!

阅读全文 »

快看!法国面具男ASCII符号舞来了!

发表于 2022-12-14 | 分类于 python

分享几个处理图片、视频的工具和脚本,

某些时候很容易让MM惊呼“被你装到了”,系不系很猴赛雷呀? 尤其最后一个,法国面具男的曳步舞,将视频转成ASCII符号形式跳舞。

我曾专门学过此视频的跳法,音乐一响,还是会跟着BGM律动! 它的背景杂物、色彩较多,转成ascii后影响人像效果。

怎么办呢?

这次终于使用上了剪映,可以用它的“智能抠像”去除视频背景只留人像,

然后转成ascii人物动作就清晰多了:

废话不多说,上分享

第一个,AnimeGANv2人脸动漫化

AnimeGANv2基于PyTorch(一个深度学习框架),

安装Pytorch比其它麻烦,就不入坑了。

可以电脑打开在线版:https://huggingface.co/spaces/akhaliq/AnimeGANv2 ,无需本地安装框架,直接就可以进行转换。

来看看效果。

洪真英的漫画没本人好看,差评 🚗,效果还行,有点像某音AI绘画

第二个,美女素描图

基于OpenCV库,安装命令pip install opencv-python

python技术公众号介绍过,就不贴链接了

So beautiful !比前面的动漫更具朦胧美,我比较喜欢美女身上这件外套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
img = cv2.imread("h3.jpg")

#转换为灰度图片
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#将灰度图像反转
inverted_image = 255 - gray_img

#将反转的图像进行高斯模糊处理,再将模糊的图像倒置
blurred = cv2.GaussianBlur(inverted_image, (21, 21), 0)
inverted_blurred = 255 - blurred

#将灰度图像除以倒置的模糊图像,创建铅笔草图
pencil_sketch = cv2.divide(gray_img, inverted_blurred, scale=256.0)

#显示图片
cv2.imshow('original',img)
cv2.imshow("i love hongzhenying", pencil_sketch)
cv2.waitKey(0)
cv2.destroyAllWindows()

法国面具男ASCII舞蹈

运行脚本后,导入ascii视频和原视频到剪映中,设置画中画效果。

完工后的画中画视频有点大,100多M,我已上传到linux服务器,you-get可以下载它

1
2
3
you-get http://ssw.fit/free/mask_.mp4

# 买的服务器带宽限制4Mbps,估计下载速度500kB/s左右

或直接观看

完整脚本

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
# -*- coding:utf-8 -*-
from PIL import Image, ImageDraw, ImageFont
import numpy as np
import os
import sys
import shutil
class Video2Ascii:
    def __init__(self, filename):
        # 执行前的一些判断
        if not os.path.isfile(filename):
            print("源文件找不到,或者不存在!")
            exit()
        temp_arr = filename.split('.')
        # 字符列表,从左至右逐渐变得稀疏,对应着颜色由深到浅
        self.ascii_char = list("$@B%8&WM#*oahkbdpqwO0QLCJYXzcvunxrjft/\|()1[]?-_+~<>i!......... ")
        # 传入视频文件名
        self.filename = filename
        # 输出视频文件名
        self.outname = temp_arr[0] + "_out." + temp_arr[1]
        # 存储图片的临时路径、输出路径
        self.pic_path = 'temp_pic'
        self.ascii_path = 'temp_ascii'
        self.outpath = 'temp_out'
        # 设置图片缩小的倍数
        self.resize_times = 6
        # 设置输出文件的名字,声音文件以及带声音的输出文件
        self.mp3ilename = os.path.join(self.outpath, temp_arr[0] + '.mp3')
        self.mp4filename = os.path.join(self.outpath, self.outname)
        # 合并输出的视频文件
        self.mergefilename = os.path.join(self.outpath, temp_arr[0] + '_voice.' + temp_arr[1])
    # [1]、创建存储临时图片的路径
    def createpath(self):
        print("-" * 30)
        print("[1/6]正在创建临时路径...")
        print("-" * 30 + '\r\n')
        # 源视频文件的图片路径
        if not os.path.exists(self.pic_path):
            os.makedirs(self.pic_path)
        else:
            # 清空在创建
            shutil.rmtree(self.pic_path)
            os.makedirs(self.pic_path)
        # 转换之后的图片路径
        if not os.path.exists(self.ascii_path):
            os.makedirs(self.ascii_path)
        else:
            # 清空再创建
            shutil.rmtree(self.ascii_path)
            os.makedirs(self.ascii_path)
        # 存储输出文件的目录
        if not os.path.exists(self.outpath):
            os.makedirs(self.outpath)
    # [2]、将视频分割成图片
    def video2pic(self):
        print("-" * 30)
        print("[2/6]正在切割原始视频为图片...")
        print("-" * 30 + '\r\n')
        # 使用ffmpeg切割图片,命令行如下
        cmd = 'ffmpeg -i {0} -r 24 {1}/%06d.jpeg'.format(self.filename, self.pic_path)
        # 执行命令
        os.system(cmd)
    # [3]、将图片缩放、转成ascii形式
    def pic2ascii(self):
        print("-" * 30)
        print("[3/6]正在处理分析图片,转成ascii形式...")
        print("-" * 30 + '\r\n')
        # 读取原始图片目录
        pic_list = sorted(os.listdir(self.pic_path))
        total_len = len(pic_list)
        count = 1
        # 遍历每张图片
        for pic in pic_list:
            # 图片完整路径
            imgpath = os.path.join(self.pic_path, pic)
            # 1、缩小图片,转成灰度模式,存入数组
            origin_img = Image.open(imgpath)
            # 缩小之后宽高
            resize_width = int(origin_img.size[0] / self.resize_times)
            resize_height = int(origin_img.size[1] / self.resize_times)
            resize_img = origin_img.resize((resize_width, resize_height), Image.ANTIALIAS).convert("L")
            img_arr = np.array(resize_img)
            # 2、新建空白图片(灰度模式、与原始图片等宽高)
            new_img = Image.new("L", origin_img.size, 255)
            draw_obj = ImageDraw.Draw(new_img)
            font = ImageFont.truetype("arial.ttf", 8)
            # 3、将每个字符绘制在 8*8 的区域内
            for i in range(resize_height):
                for j in range(resize_width):
                    x, y = j*self.resize_times, i*self.resize_times
                    index = int(img_arr[i][j]/4)
                    draw_obj.text((x, y), self.ascii_char[index], font=font, fill=0)
            # 4、保存字符图片
            new_img.save(os.path.join('temp_ascii', pic), "JPEG")
            print("已生成ascii图(%d/%d)" % (count, total_len))
            count += 1
            # exit()
    # [4]、合成视频
    def ascii2video(self):
        print("-" * 30)
        print("[4/6]正在合成视频...")
        print("-" * 30 + '\r\n')
        # 输出视频保存路径
        savepath = os.path.join(self.outpath, self.outname)
        cmd = 'ffmpeg -threads 2 -start_number 000001 -r 24 -i {0}/%06d.jpeg -vcodec mpeg4 {1}'.format(self.ascii_path, savepath)
        os.system(cmd)
    # [5]、获取原始视频的mp3文件
    def video2mp3(self):
        print("-" * 30)
        print("[5/6]正在分离音频文件...")
        print("-" * 30 + '\r\n')
        # mp3名字和保存路径
        name = self.filename.split('.')[0] + '.mp3'
        savepath = os.path.join(self.outpath, name)
        cmd = 'ffmpeg -i {0} -f mp3 {1}'.format(self.filename, savepath)
        os.system(cmd)
    # [6]、将视频和音频合并
    def mp4andmp3(self):
        print("-"*30)
        print("[6/6]正在合并视频和音频...")
        print("-" * 30 + '\r\n')
        cmd = 'ffmpeg -i {0} -i {1} -strict -2 -f mp4 {2}'.format(self.mp4filename, self.mp3ilename,  self.mergefilename)
        os.system(cmd)
    # [0]、启动
    def start(self):
        """
            > 程序流程:
                1、创建路径
                2、将原始视频分割成图片
                3、将图片缩放、转成ascii形式
                4、将ascii形式的图片合成视频
                5、获取音频mp3文件
                6、合并视频和音频文件
        :return:
        """
        self.createpath()
        self.video2pic()
        self.pic2ascii()
        self.ascii2video()
        self.video2mp3()
        self.mp4andmp3()
        print("程序执行完成")
if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("参数不匹配,请参考(脚本名 原始视频):xxx.py test.mp4 ")
        exit()
    demo = Video2Ascii(sys.argv[1])
    demo.start()

总结

最近写的二篇文章都跟视频和图像有关,

加上这篇ASCII舞蹈,很容易混淆,这里简要总结下它们用到的库

用python做一个漂亮女生词云舞蹈视频

  • 从视频中提取图片,opencv-python库
  • 分割人像,百度API的“人体分析”

人体分析有一些瑕疵,面具男身后的旗子给识别进去了

用 Python 轻松将懂车帝视频转换为文本

  • 提取音频,moviepy库
  • 分割音频(分割成数个1分钟以内的音频),pydub库
  • 音频转文本,百度API的普通话语音识别

面具男ASCII曳步舞

  • 从视频中提取图片/合成视频/提取音频都用到ffmpeg ```py #将视频分割成图片 ffmpeg -i [输入文件名] -r [fps,帧率] [分割图存储路径]

#提取音频 ffmpeg -i [输入视频文件名] -f mp3 [输出的mp3文件名]

#合成视频 ffmpeg -i [视频文件名] -i [音频文件名] -strict -2 -f mp4 [合并后的文件名] ```

  • 生成ascii图片用到PIL库的ImageDraw

以上就是今天分享的内容,希望你能喜欢!

阅读全文 »

这样print才够劲

发表于 2022-12-12 | 分类于 python

你是不是经常在使用某些库的命令的时候,被它在终端的print装到了。比如java日志 第一印象很重要,要给人一种猴赛雷的感觉。

基于这种理论,

给大家打个招呼,i am ssw

1
2
3
4
.-.     .--.  .-.   .-.    .----. .----..-. . .-.
| |    / {} \ |  `.'  |   { {__  { {__  | |/ \| |
| |   /  /\  \| |\ /| |   .-._} }.-._} }|  .'.  |
`-'   `-'  `-'`-' ` `-'   `----' `----' `-'   `-'

哈,效果居然跟java程序打印出来的一模一样,或者更靓

不小心装到了。

再来一个

1
2
3
4
5
6
7
8
9
 ██▓    ▄▄▄       ███▄ ▄███▓     ██████   ██████  █     █░
▓██▒   ▒████▄    ▓██▒▀█▀ ██▒   ▒██    ▒ ▒██    ▒ ▓█░ █ ░█░
▒██▒   ▒██  ▀█▄  ▓██    ▓██░   ░ ▓██▄   ░ ▓██▄   ▒█░ █ ░█ 
░██░   ░██▄▄▄▄██ ▒██    ▒██      ▒   ██▒  ▒   ██▒░█░ █ ░█ 
░██░    ▓█   ▓██▒▒██▒   ░██▒   ▒██████▒▒▒██████▒▒░░██▒██▓ 
░▓      ▒▒   ▓▒█░░ ▒░   ░  ░   ▒ ▒▓▒ ▒ ░▒ ▒▓▒ ▒ ░░ ▓░▒ ▒  
 ▒ ░     ▒   ▒▒ ░░  ░      ░   ░ ░▒  ░ ░░ ░▒  ░ ░  ▒ ░ ░  
 ▒ ░     ░   ▒   ░      ░      ░  ░  ░  ░  ░  ░    ░   ░  
 ░           ░  ░       ░            ░        ░      ░    

其实这个print很简单,不是我闲的一个字符一个字符敲出来的。

给大家分享一个神器www.patorjk.com/software/taag ,

你可以通过关键词生成各种装逼的字符。点击“Test All”

挑一个喜欢的,然后复制粘贴到代码里就可以了。

也许你会觉得打印到终端,呈现出来是黑白的,b格不够。

那么还可以结合rich,带上颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rich.console import Console
from rich import print
console = Console()
console.rule("[bold blue]另一种风格")
name = '''
 ██▓    ▄▄▄       ███▄ ▄███▓     ██████   ██████  █     █░
▓██▒   ▒████▄    ▓██▒▀█▀ ██▒   ▒██    ▒ ▒██    ▒ ▓█░ █ ░█░
▒██▒   ▒██  ▀█▄  ▓██    ▓██░   ░ ▓██▄   ░ ▓██▄   ▒█░ █ ░█ 
░██░   ░██▄▄▄▄██ ▒██    ▒██      ▒   ██▒  ▒   ██▒░█░ █ ░█ 
░██░    ▓█   ▓██▒▒██▒   ░██▒   ▒██████▒▒▒██████▒▒░░██▒██▓ 
░▓      ▒▒   ▓▒█░░ ▒░   ░  ░   ▒ ▒▓▒ ▒ ░▒ ▒▓▒ ▒ ░░ ▓░▒ ▒  
 ▒ ░     ▒   ▒▒ ░░  ░      ░   ░ ░▒  ░ ░░ ░▒  ░ ░  ▒ ░ ░  
 ▒ ░     ░   ▒   ░      ░      ░  ░  ░  ░  ░  ░    ░   ░  
 ░           ░  ░       ░            ░        ░      ░    
                                                          
'''
print(f'[blue]{name}[/blue]')

是不是有一种黑客的既视感。

戴上面具,你就是黑客里最靓的仔

绿色码雨,矩阵重启,邪魔入侵,

维护世界和平的任务就交给靓仔你了

let’s continue,

这个rich还有一个好处,一般在终端打印分割线

1
2
print('#'*30)
##############################

low了一点,

不符合我们猴赛雷的风格。

我们换一种手法,

使用rich的console.rule(),

将终端输出分成多个部分

1
2
3
from rich.console import Console
console = Console()
console.rule("[bold red]你很猴赛雷呢")

如果觉得文字乏味,这个网站还有字符图片

超级赛亚人,蜘蛛侠,小日本等

甚至还有贪吃🐍

昏昏欲睡的时候来一波,瞬间精神倍长。

上班偷玩忒么很刺激,

可惜总是对不准小红点!

到这里我们的print之旅差不多告一段落了,

你以为这样就结束了?NO

点开“Main Page”

let me see see,每一条支线都是探索宝藏的入口,

比如,

前面提到的字符图片竟然在这个小地方,我随手点进去发现的

呃。。

还有什么好玩的地方,留给大家来探索。

记得分享!

阅读全文 »

好家伙,令女神尖叫的李峋同款爱心代码来了!

发表于 2022-12-07 | 分类于 python

最近有一部虐中带糖的剧,热度直线上升中,这部剧就是《点燃我,温暖你》。陈飞宇饰演的李峋是一个问题少年,但是同时也是一个编程天才,李峋在课程考试中提交的爱心代码着实是戳了一波粉丝的心!

这也太会了!爱心代码还因此浅浅地上了一波热搜。

我不爱刷剧,特别是这种流量明星的剧,所以没看过这电视剧。但奈何不住小女生看啊,看了之后还过来说,你是程序员,你会吗?

这一下就把我问懵了,这种玩意咱们一般也不会去研究,工作上基本也用不着。

而且这种效果用 HTML 前端网页实现比较容易,效果也会比较好!

硬着头皮去问了一下度娘,好家伙,还真有一些人做了出来,真是羡慕这些人,咱们这种加班狗心有余而力不足啊!

但是在他们实现的基础上略微加工加工,变些花样还是可以的。

实现

其实,画这个爱心,主要有几个要素:

  • 基础爱心
  • 爱心周边的散列点
  • 爱心抖动

这几个要素,都有对应的数学算法,套用这些算法就可以实现。

部分代码如下(完整代码文末获取):

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
"""
    爱心函数生成器
    -shrink_ratio: 放大比例
    -t: 参数
"""
def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):
    # 基础函数
    x = 17 * (sin(t) ** 3)
    y = -(16 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(3 * t))
 
    # 放大
    x*=IMAGE_ENLARGE
    y*=IMAGE_ENLARGE
    # 移到画布中央
    x += CANVAS_CENTER_X
    y += CANVAS_CENTER_Y
 
    return int(x), int(y)
 
"""
    随机内部扩散
    -x: 原x
    -y: 原y
    -beta: 强度
""" 
def scatter_inside(x, y, beta=0.15):
    ratio_x = - beta * log(random.random())
    ratio_y = - beta * log(random.random())
 
    dx = ratio_x * (x - CANVAS_CENTER_X)
    dy = ratio_y * (y - CANVAS_CENTER_Y)
 
    return x - dx, y - dy
 
 
"""
    抖动
    -x: 原x
    -y: 原y
    -ratio: 比例
""" 
def shrink(x, y, ratio):
   
    force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6)  # 这个参数...
    dx = ratio * force * (x - CANVAS_CENTER_X)
    dy = ratio * force * (y - CANVAS_CENTER_Y)
    return x - dx, y - dy
 
 
"""
    自定义曲线函数,调整跳动周期
    -p: 参数
"""
def curve(p):
    # 可以尝试换其他的动态函数,达到更有力量的效果(贝塞尔?)
    return 2 * (2 * sin(4 * p)) / (2 * pi)
 

# 爱心类
class Heart:
    def __init__(self, generate_frame=20):
        # 原始爱心坐标集合
        self._points = set()  
        # 边缘扩散效果点坐标集合
        self._edge_diffusion_points = set()  
        # 中心扩散效果点坐标集合
        self._center_diffusion_points = set()  
        # 每帧动态点坐标
        self.all_points = {}  
        self.build(2000)
 
        self.random_halo = 1000
 
        self.generate_frame = generate_frame
        for frame in range(generate_frame):
            self.calc(frame)
 
    def build(self, number):
        # 爱心
        for _ in range(number):
            # 随机不到的地方造成爱心有缺口
            t = random.uniform(0, 2 * pi)  
            x, y = heart_function(t)
            self._points.add((x, y))
 
        # 爱心内扩散
        for _x, _y in list(self._points):
            for _ in range(3):
                x, y = scatter_inside(_x, _y, 0.05)
                self._edge_diffusion_points.add((x, y))
 
        # 爱心内再次扩散
        point_list = list(self._points)
        for _ in range(10000):
            x, y = random.choice(point_list)
            x, y = scatter_inside(x, y, 0.27)
            self._center_diffusion_points.add((x, y))
 
    @staticmethod
    def calc_position(x, y, ratio):
        # 调整缩放比例
        force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.420)  # 魔法参数
 
        dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
        dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)
 
        return x - dx, y - dy
 
    def calc(self, generate_frame):
         # 圆滑的周期的缩放比例
        ratio = 15 * curve(generate_frame / 10 * pi) 
 
        halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * pi)))
        halo_number = int(3000 + 4000 * abs(curve(generate_frame / 10 * pi) ** 2))
 
        all_points = []
 
        # 光环
        heart_halo_point = set()  
        # 光环的点坐标集合
        for _ in range(halo_number):
            # 随机不到的地方造成爱心有缺口
            t = random.uniform(0, 2 * pi)  
            # 魔法参数
            x, y = heart_function(t, shrink_ratio=-15)  
            x, y = shrink(x, y, halo_radius)
            if (x, y) not in heart_halo_point:
                # 处理新的点
                heart_halo_point.add((x, y))
                x += random.randint(-60, 60)
                y += random.randint(-60, 60)
                size = random.choice((1, 1, 2))
                all_points.append((x, y, size))
                all_points.append((x+20, y+20, size))
                all_points.append((x-20, y -20, size))
                all_points.append((x+20, y - 20, size))
                all_points.append((x - 20, y +20, size))
 
        # 轮廓
        for x, y in self._points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 3)
            all_points.append((x, y, size))
 
        # 内容
        for x, y in self._edge_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))
 
        for x, y in self._center_diffusion_points:
            x, y = self.calc_position(x, y, ratio)
            size = random.randint(1, 2)
            all_points.append((x, y, size))
 
        self.all_points[generate_frame] = all_points
 
    def render(self, render_canvas, render_frame):
        for x, y, size in self.all_points[render_frame % self.generate_frame]:
            render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=HEART_COLOR)
 
 
def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_frame=0):
    render_canvas.delete('all')
    render_heart.render(render_canvas, render_frame)
    main.after(1, draw, main, render_canvas, render_heart, render_frame + 1)

效果如下:

当然,你也可以改变爱心的颜色,只需要改变 HEART_COLOR 参数即可。

或者,我们可以在爱心的中心加文字:

1
2
3
4
5
6
7
8
9
10
root = Tk()
    canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
    canvas.pack()
    heart = Heart()
    draw(root, canvas, heart)

    text2 = Label(root, text="爱你",font = ("Helvetica", 18), fg = "#c12bec" ,bg = "black") #
    text2.place(x=395, y=350)

    root.mainloop()

运行效果如下:

总结

待我装模作样噼里啪啦敲过一阵子代码之后,把这个效果展示给那些小MM看时,一个个在那尖呼:太好看了!快教我学 Python 吧!

我心想:你们学啥子 Python ?找个会 Python 的程序员它不香吗?

嘴里却一个劲的说着:好啊!我来给你们开课吧!

阅读全文 »

用python画一只三角猫

发表于 2022-12-01 | 分类于 python

有小伙伴留言 只需十几行代码,轻松爬取公众号文章

我试了一下,2步就能生成一篇400行的文章:

  • 爬取200篇文章,脚本在print时,按markdown格式进行打印
    1
    print('['+标题+']'+'('+url+')','\n')
    
  • 将打印结果ctrl+a复制到博客里,生成“python技术”公众号的文章列表 可以说不费吹灰之力拿到这些文章:

整个操作下来不到一分钟的时间。

如果用复制粘贴的方式工程量就大了,二百篇文章可能需要你点击上千次。

每页5篇文章,200篇的话下滑40页,我点了十来页实在不想继续滚动鼠标了

比较来说,用python批量获取并制作成博文,也便于筛选查找文章。

比如我想查找python技术公众号关于画画的文章,上图的文章列表ctrl+f搜索关键字就能找到相关内容。

python技术公众号的200篇文章,从2021-06-29到现在,看看有没有你要找的内容:

http://ssw.fit/ 阅览室-公众号,可以看到

三角猫

我对画画很感兴趣,按上述操作搜索“画画”,

看到python技术公众号的一篇文章 绝版!没想到 Python 画画这么简单 ,

介绍有一个turtle绘图工具,省的我去找画图工具了

平常画的最多的是三角猫,用turtle来画怎么样呢?

说干就干,在纸上顺手画了它

横眉怒眼,虎虎生威,

我的目标是用turtle把它画出来。

先来点知识储备,介绍几处重要的地方,对画猫有帮助

Turtle库

Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x、纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行的路径上绘制了图形。

部分函数:

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
t.bgcolor("color")  设置背景颜色
t.pensize()         设置画笔尺寸
t.hideturtle()      隐藏画笔
t.speed()           设置画笔的速度,参数范围0~10
t.penup()           起笔,移动时无图,提起笔移动,不绘制图形,用于另起一个地方绘制
t.pendown()         落笔,移动时有图
t.forward(distance) 向当前画笔方向移动distance像素长度
t.backward(distance)向当前画笔相反方向移动distance像素长度
t.goto(x,y)         将画笔移动到坐标为x,y的位置
t.goto(x,y) = t.setpos(x,y) = t.setposition(x,y)
300
t.left(degree)      相对角度,顺时针移动degree°
t.right(degree)     相对角度,逆时针移动degree°
t.hideturtle()      隐藏画笔的turtle形状
t.showturtle()      显示画笔的turtle形状
t.begin_fill()      开始绘制
t.end_fill()        结束绘制
t.fd()              向画笔的朝向移动指定的距离,参数为距离
t.bd()              向画笔的朝向的相反方向移动指定的距离,参数为距离
t.pencolor()        设置画笔的颜色,参数为RGB格式或颜色名称。没有参数传入,返回当前画笔颜色,传入参数设置画笔颜色,可以是字符串如"green","red",也可以是RGB3元组。
t.pensize()         设置画笔的宽度;
t.color()           没有参数则返回当前画笔的颜色和填充的颜色,可以放两个颜色,前一个为画笔颜色,后一个为填充颜色,如t.color("red", "yellow")  
t.fillcolor()       设置填充的颜色,参数为RGB格式或颜色名称
t.begin_fill()      准备开始填充图形
t.end_fill()        填充完成
setx( )             将当前x轴移动到指定位置
sety( )             将当前y轴移动到指定位置
home()              设置当前画笔位置为原点,朝向东。
dot(r,color)        绘制一个指定直径和颜色的圆点,如:t.dot(20,’blue’)
t.bgpic()           设置或获取背景图片,只支持gif图片
t.circle(radius,extent)     设置指定半径radius的圆,参数为半径,半径为正(负),表示圆心在画笔的左边(右边)画圆,extent为角度,若画圆则无须添加。如:t.circle(-20,90),顺时针,半径20画弧,弧度90
t.setup(width=0.5,height=0.75,startx=None,starty=None)width,height:输入宽和高为整数时,表示像素;为小数时,表示占据电脑屏幕的比例,(startx,starty):这一坐标表示矩形窗口左上角顶点的位置,如果为空,则窗口位于屏幕中心

画彩色蟒蛇(可以做猫尾巴)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import turtle
turtle.setup(650, 350,200,200)
turtle.penup()
turtle.fd(-250)
turtle.pendown()
turtle.pensize(25)
colors=['green','blue','yellow','orange','pink','purple']
turtle.seth(-40)
for i in range(4):
    turtle.color(colors[i])#选择索引从0~3的颜色
    turtle.circle(40, 80)#上半弧度
    turtle.circle(-40, 80)#下半弧度
turtle.color(colors[5])
turtle.circle(40, 80/2)
turtle.fd(40)
turtle.circle(16, 180)
turtle.fd(40 * 2/3)

turtle.done()

画猫的身体(三角形)

1
2
3
4
import turtle as t
for i in range(3):
    t.seth(i*120)
    t.fd(200)

turtle的setheading函数(可简写为seth)

重点介绍下这个参数

setheading( to_angle),功能是设置海龟的朝向为 to_angle。setheading可简写为seth。

to_angle 表示角度的数值 (整型或浮点型)。to_angle为正逆时针转向,to_angle为负顺时针转向。每次setheading(to_angle) 小海龟以正东(X轴正方向)为基准转向to_angle角度。 例如:

1
2
3
4
5
import turtle as t
t.seth(60)
t.fd(100)
t.seth(-30)
t.fd(150)

特别提示: right(to_angle)或left(to_angle)与setheading(to_angle)的区别

每次setheading(to_angle) 小海龟以正东(X轴正方向)为基准转向to_angle角度。与之不同,每次right(to_angle)或left(to_angle)小海龟以当前方向为基准向right或left转向to_angle角度。

例如:

1
2
3
4
5
6
7
import turtle as t
t.seth(60)
t.fd(100)
t.seth(-30)
t.fd(150)
t.left(90)
t.fd(50)

画三角猫

经过前述知识的准备,可以动工画🐱了,可以加上颜色、笔画粗细的变化

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
import turtle as t
import time
'''
部分函数及参数说明:
pen_move():画每个部位时,都必须先抬起画笔,移动到指定位置后落下
pen_set():用来设置画笔的颜色尺寸等
t.setup(width,height):入宽和高为整数时,表示像素;为小数时,表示占据电脑屏幕的比例
t.speed():设置画笔速度
t.goto():以左下角为坐标原点,进行画笔的移动
t.circle(radius,extent):设置指定半径radius的圆,参数为半径,半径为正(负),表示圆心在画笔的左边(右边)画圆,extent为角度,若画圆则无须添加。如:t.circle(-20,90),顺时针,半径20画弧,弧度90
t.seth(degree)绝对角度,将画笔的方向设置为一定的度数方向,0-东;90-北;180-西;270-南
'''

wight=800
height=600
t.setup(wight,height)
t.speed(10)

def pen_move(x,y):
    t.penup()
    t.goto(x-wight/2+50,y-height/2+50)
    t.pendown()

def pen_set(size,color):
    t.pensize(size)
    t.color(color)

def draw():
    #第一个眼睛
    pen_move(300,350)
    pen_set(2,'black')
    t.begin_fill()
    t.circle(10)
    t.end_fill()
    # 第一个眼眶
    pen_move(300,350)
    t.circle(15)
    #第二个眼睛
    pen_move(400,350)
    t.begin_fill()
    t.circle(10)
    t.end_fill()
    # 第二个眼眶
    pen_move(400,350)
    t.circle(15)

    # 嘴
    pen_move(340,300)
    t.seth(0)
    t.left(45)
    t.forward(30)
    pen_move(360, 300)
    t.seth(180)
    t.left(45+270)
    t.forward(30)

    # 右边脸框
    t.seth(0)
    pen_move(340,260)
    t.circle(90,150)

    # 左边脸框
    t.seth(180)
    pen_move(340,260)
    t.circle(-90,140)
    # time.sleep(100)

    #耳朵
    # t.seth(0)
    pen_move(260, 400)
    t.left(100)
    # t.forward(100)
    t.circle(-60,70)

    #合上耳朵
    pen_move(285, 430)
    t.left(40)
    t.circle(60,50)

    #右耳朵
    pen_move(380,430)
    t.right(90)
    t.circle(-60,50)

    pen_move(413,410)
    t.left(30)
    t.circle(60,60)

    # 左边身子的线条
    pen_move(320, 270)
    t.seth(180)
    t.left(70)
    t.forward(260)

    # 身子底部线条
    pen_move(230, 30)
    t.seth(0)
    # t.left(60)
    t.forward(240)

    # 右边身子线条
    pen_move(380, 270)
    # t.seth(0)
    t.right(70)
    t.forward(260)

    # 尾巴
    pen_move(380+90, 270-240)
    t.left(60)
    pen_set(6,'black')
    t.circle(130,100)

    t.left(10)
    t.circle(160,40)
    t.left(20)
    t.circle(100,30)
    t.left(50)
    t.circle(80,50)
    t.left(70)
    t.circle(70,40)
    t.left(70)
    t.circle(60,30)
    t.left(70)
    t.circle(60,20)
    t.left(60)
    t.circle(60,10)
    t.left(60)
    t.circle(10,5)
    time.sleep(1)

draw()
阅读全文 »

用Python实现实时显示视频下载进度!

发表于 2022-11-27 | 分类于 python

前天分享过一篇用 Python 轻松将懂车帝视频转换为文本 ,略过了下载过程,现在补上。

在下载懂车帝视频的时候,我们需要实时进度条,这可以帮助我们更直观的看到视频的下载进度 下载好的视频。如下图所示

具体实现步骤,概括起来主要有两点:

  • cookie绕过登录过程

这个方法非常实用,还记得我们如何爬公众号的文章吗 只需十几行代码,轻松爬取公众号文章 ,也是cookie绕过登录

  • tqdm可视化显示下载进度

进度条的文章python技术公众号我记得是发过的,但是半年前的文章了,怎么找方便呢?

这时我们上周爬的200篇公众号文章就发挥作用了:

http://ssw.fit/ 阅览室-公众号,搜索“进度条”

不难发现,《几行代码就能实现漂亮进度条,太赞了!》早已介绍过,有了这篇内容就好办了。

下面开始让python帮我们干活。

cookie绕过登录过程

按F12或通过鼠标右键检查,可以看到cookie

视频页面的唯一标识符,通过cookie获取,就是下面这串数字

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import json
headers = {
    "cookie":"appmsglist_action_3889613222=card; ua_id=Q1Dfu2THA6T9Qr1HAAAAAN_KYa5xTwNmiuqj1Mkl6PY=",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"
}
def parser():
    video_list = []
    rep=requests.get('https://www.dongchedi.com/motor/pc/user/collect/list?count=40&cursor=0&content_type=1',timeout=5,headers=headers)
    rep.encoding='utf-8'

    for item in json.loads(rep.text)['data']['data']:
      video_list.append('https://www.dongchedi.com/video/'+item['gid'])
    print(video_list)

运行上面的脚本,得到收藏的视频url合集

1
2
3
['https://www.dongchedi.com/video/7150286063049802270', 'https://www.dongchedi.com/video/7161306839810998815', 
...]

但它们并不是真正的视频地址,实际地址位于vedio标签的src属性

接下来就要去获取这些视频链接,我们用scrapy对这些url发送请求。

将url合集放到scrapy的start_urls中,通过xpath获取视频地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
class VedioSpider(scrapy.Spider):
    name = 'vedio'
    start_urls = ['https://www.dongchedi.com/video/7150286063049802270', 'https://www.dongchedi.com/video/7161306839810998815',...]

    def parse(self, response):
        global vedio_dict
        html =etree.HTML(response.text)
        #视频标题
        title = html.xpath('//*[@id="__next"]/div[1]/div/div/div/div[2]/div/div[1]/h1/text()')[0].strip()
        #视频地址,@src获取vedio标签的src属性
        x = html.xpath('//*[@id="__next"]/div[1]/div/div/div/div[1]/div[1]/div/div[1]/div/div/div/video/@src')
        vedio_dict[title] = x[0]
        print(vedio_dict)

输出得到标题和视频地址:

到这里我们已经成功了三分之二,接下来用一个炫酷的进度条

可视化显示下载进度

视频文件过大,立即下载会导致内存不足,设置requests的stream参数为True

设置成True时,它不会立即开始下载,使用iter_content或iter_lines遍历内容或访问内容属性时才开始下载。

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
import os
import requests
from tqdm import tqdm

VIDEO_PATH = r'videos'
def download(url,fname):
    # 用流stream的方式获取url的数据
    resp = requests.get(url, stream=True)
    total = int(resp.headers.get('content-length', 0))
    with open(fname, 'wb') as file, tqdm(
        desc=fname,
        total=total,
        unit='iB',
        unit_scale=True,
        unit_divisor=1024,
    ) as bar:
        for data in resp.iter_content(chunk_size=1024):
            size = file.write(data)
            bar.update(size)


if __name__ == "__main__":
    vedio_dict = {'沉浸式试车-2023款大众朗逸': 'https://vxtos', '苑叔试驾新款福特探险者,科技配置有升级,大车也有操控感': 'https://v3-default',}
    for video_name, url in vedio_dict.items():
        video_full_path = os.path.join(VIDEO_PATH,"%s.mp4" % video_name)
        download(url, video_full_path)

我的网速比较快,平均3M/s,5G视频很快就下载好了

阅读全文 »

只需十几行代码,轻松爬取公众号文章!

发表于 2022-11-17 | 分类于 python

最近关注了几个公众号,想收藏有价值的内容。不过文章较多,不停的下滑操作去找文章是一件折磨人的事,试过几次后,面对众多的资源望洋兴叹。

有什么好的方法呢?有人推荐连接手机用fiddler抓包,被坑了2个小时😡,此路不通或者说麻烦。

一个比较好的方法是找到微信公众号平台内部的API,比如“python技术”的的文章这里都有,哈哈:

为了获取文章列表,我特意注册了一个微信公众号。

注册好了,咱们直奔主题,说下操作过程。

进入公众号点击<草稿箱>,再点击右侧<图文模板> ![](http://www.justdopython.com/assets/images/2022/11/scrapywechatmp/2.png) 选择<新建图文模板> ![](http://www.justdopython.com/assets/images/2022/11/scrapywechatmp/3.png) 进入到编辑界面,点<超链接>,选<选择其他公众号>,输入你要爬的公众号名称。 ![](http://www.justdopython.com/assets/images/2022/11/scrapywechatmp/4.png) ![](http://www.justdopython.com/assets/images/2022/11/scrapywechatmp/5.png) 以python技术为例: ![](http://www.justdopython.com/assets/images/2022/11/scrapywechatmp/6.png) 按F12或通过鼠标右键检查,点《Network》后选择《XHR》一般最后一个即是当前页面内容所在,这里看到url和标题分别位于《link》和《title》标签下,如果network没东西刷新试一下

下图最后一个箭头的title标签,可以看到我昨天发表的“用LOL英雄点缀你的博客”

到这里我们已经成功了一半,接下来我们获取用到的《user-agent》《URL》《cooike》《tooken》和《fakeid》,点击Headers

  • cooike帮我们绕过登录过程
  • fakeid是目标公众号的唯一标识符
  • user-agent可以模拟浏览器请求 至此信息获取部分完成,下面开始开始代码部分。

注意: 下面“完整脚本”中的fakeid、token、type等的值在url中可以看到 也可以直接访问这个url,一个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
# -*- coding: utf-8 -*-
import requests
import time

headers = {
    "cookie": "appmsglist_action_3889613222=card; ua_id=Q1Dfu2THA6T9Qr1HAAAAAN_KYa5xTwNmiuqj1Mkl6PY=; wxuin=18828715020059xid=a5c7612f529374b74deb4178e7ff4ca7",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"
}
url = 'https://mp.weixin.qq.com/cgi-bin/appmsg'
fad = 'MjM5ODM3MTUwMA=='                     #爬不同公众号只需要更改 fakeid

def page(num=1):                             #要请求的文章页数
    title = []
    link = []
    for i in range(num):         
        data = {
            'action': 'list_ex',
            'begin': i*5,       #页数
            'count': '5',
            'fakeid': fad,
            'type': '9',
            'query':'' ,
            'token': '1753262244',
            'lang': 'zh_CN',
            'f': 'json',
            'ajax': '1',
        }
        r = requests.get(url,headers = headers,params=data)
        dic = r.json()            
        for i in dic['app_msg_list']:     #遍历dic['app_msg_list']中所有内容
            title.append(i['title'])      #取 key键 为‘title’的 value值
            link.append(i['link'])        #去 key键 为‘link’的 value值
    return title,link

if __name__ == '__main__':
    (tle,lik) = page(5)
    for x,y in zip(tle,lik):
        print(x,y)

成功获取5页url和标题,可以看到我发表过的几篇文章😀,以后查公众号文章就方便了,bingo!

阅读全文 »

自动获取 Bing 壁纸

发表于 2022-11-15 | 分类于 python

不知道大家的windows桌面用的哪个壁纸?

早上来上班,打开电脑,被漂亮的桌面壁纸所吸引,年底将近,这又是哪个地方的节日?

才晓得,原来这是泰国第二大城市清迈的“天灯节”,把🏮送上天空是对神灵的尊敬,代表着摆脱厄运,祈求好运

灯笼通常是由宣纸制成,把点燃的蜡烛固定在中心。火能产生足够的热量使灯笼变得很轻,可以飘向天空。

有些人认为,如果你的灯笼在蜡烛燃尽之前消失在夜空中,那么你将在新的一年里获得好运。

一张小小的桌面壁纸,也可能给你带来惊喜和感动

就像我们囿于办公室或许未能远行,但心底从未停止过对远方的探索,除了诗和远方,还有柴米油盐,甚至还有python帮你做点事。

这么多好看的Bing壁纸怎么手到擒来呢?提供2个方法让你壁纸不愁,赏心悦目

python批量下载

该方法基于一个 Bing 壁纸 API。

用浏览器访问 https://bingw.jasonzeng.dev,你可以看到一张高清图片。

我们可以用 Python 来调用它,然后批量保存下来,比如说下载最近 100 天的图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
from pathlib import Path

def save_pic(path:Path):
    for i in range(100):
        url = f"http://bingw.jasonzeng.dev?resolution=UHD&index={i}"
        with requests.get(url) as r:
            with open(path/f"{i}.jpg","wb") as w:
                w.write(r.content)


if __name__ == "__main__":
    saved_path = Path("./bing_pic")
    saved_path.mkdir(parents = True, exist_ok = True)
    save_pic(saved_path)

执行上面的代码,就可以在目标路径看到下载的高清图片:

接口介绍

1、传入 resolution 参数可以指定壁纸图像的分辨率。默认为1920x1080,可选值如下:

  • UHD
  • 1920x1200
  • 1920x1080
  • 1366x768
  • 1280x768
  • 1024x768
  • 800x600
  • 800x480
  • 768x1280
  • 720x1280
  • 640x480
  • 480x800
  • 400x240
  • 320x240
  • 240x320 UHD 就是高清,图片比较大。

2、传入 index 可以获取哪天的图片,0 表示今天,1 表示昨天,以此类推,index=random 表示随机一天。

3、传入 date 可以获取从某某一天到今天的图片,比如 data=20210401。

4、传入 w 和 h 可以指定图片的宽度和高度。

5、传入 qlt 可以指定图片的质量,取值范围是 0 到 100

方法二:安装bing-wallpaper

浏览器访问

1
https://www.microsoft.com/zh-hk/bing/bing-wallpaper

下载 bing-wallpaper 后,每日都能以全新背景图片来令桌面焕发活力

阅读全文 »

这个神器,让你的代码运行快上100倍!

发表于 2022-11-13 | 分类于 python

Python 已经得到了全球程序员的喜爱,连续多期稳坐编程语言排行榜第一把交椅。但是还是遭到一些人的诟病,原因之一就是认为它运行缓慢。

于是,大家都在想尽各种办法来提高 Python 代码的运行速度,大多数体现在写代码的习惯优化以及代码优化上。但是平时写代码注意太多这些方面可能会有糟糕的体验,甚至会不利于我们的工作效率。

要是有一款能够自动优化我们代码的神器该有多好啊!

今天就给大家带来这样的一款神器——taichi!

taichi 是何方神圣

Taichi 起步于 MIT 的计算机科学与人工智能实验室(CSAIL),设计初衷是便利计算机图形学研究人员的日常工作,帮助他们快速实现适用于 GPU 的视觉计算和物理模拟算法。

说人话就是 Taichi 是一个基于 Python 的领域特定语言,专为高性能能并行计算设计。

本来是服务于学术界的一款 DSL ,但是我们也可以拿来用在我们这些凡夫俗子的代码中(虽然有点大材小用)!

安装

Taichi 是一个 PyPI 包,所以使用 pip 命令即可安装:

pip install taichi

注意 taichi 安装的先决条件是:

  • Python: 3.7/3.8/3.9/3.10 (64-bit)
  • OS: Windows, OS X, and Linux (64-bit)

在使用命令安装的时候,如果遇到错误,可以使用管理员模式命令行进行安装。

一个小栗子

我们先来用一个小栗子,感受一下它的鬼斧神工!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time
 
def is_prime(n):
    result = True
    for k in range(2, int(n**0.5) + 1):
        if n % k == 0:
            result = False
            break
    return result

def count_primes(n: int) -> int:
    count = 0
    for k in range(2, n):
        if is_prime(k):
            count += 1
    
    return count

t0 = time.time()
print(count_primes(100000))
t1 = time.time()

print(t1-t0)

这个是我们以前经常用来做例子的统计质数个数。求100000以内速度比较快,但是到了1000000,运行时间就明显慢了下来,竟然需要3.38秒。

Python 的大型 for 循环或嵌套 for 循环总是导致运行时性能不佳。

我们只需导入 Taichi 或切换到 Taichi 的 GPU 后端,就能看到整体性能的大幅提升:

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 time
import taichi as ti

ti.init()

@ti.func
def is_prime(n):
    result = True
    for k in range(2, int(n**0.5) + 1):
        if n % k == 0:
            result = False
            break
    return result

@ti.kernel
def count_primes(n: int) -> int:
    count = 0
    for k in range(2, n):
        if is_prime(k):
            count += 1
    
    return count

t0 = time.time()
print(count_primes(1000000))
t1 = time.time()

print(t1-t0)

在这里,我们只需要引入 taichi 库,然后加两个注解,速度直接飙到了0.1秒,速度提升了30多倍。如果我们把数字再扩大,速度提升会更明显!

没有使用之前,统计10000000以内质数使用 90 秒,使用之后,并且更改为 GPU 运行,使用 0.1秒。

我们还可以将 Taichi 的后端从 CPU 更改为 GPU 运行:

1
ti.init(arch=ti.gpu)

用 Taichi 进行物理模拟

上面的动图很好地模拟了一块布料落到一个球体上。 动图中的布料建模使用了弹簧质点系统,其中包含 10,000 多个质点和大约 100,000个弹簧。 模拟如此大规模的物理系统并实时渲染绝不是一项容易的任务。

Taichi 让物理模拟程序变得更易读和直观,同时仍然达到与 C++ 或 CUDA 相当的性能。 只需拥有基本 Python 编程技能,就可以使用 Taichi 用更少的代码编写高性能并行程序,从而关注较高层次的算法本身,把诸如性能优化的任务交由 Taichi 处理。

我们直接上源代码:

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
import taichi as ti
ti.init(arch=ti.vulkan)  # Alternatively, ti.init(arch=ti.cpu)

n = 128
quad_size = 1.0 / n
dt = 4e-2 / n
substeps = int(1 / 60 // dt)

gravity = ti.Vector([0, -9.8, 0])
spring_Y = 3e4
dashpot_damping = 1e4
drag_damping = 1

ball_radius = 0.3
ball_center = ti.Vector.field(3, dtype=float, shape=(1, ))
ball_center[0] = [0, 0, 0]

x = ti.Vector.field(3, dtype=float, shape=(n, n))
v = ti.Vector.field(3, dtype=float, shape=(n, n))

num_triangles = (n - 1) * (n - 1) * 2
indices = ti.field(int, shape=num_triangles * 3)
vertices = ti.Vector.field(3, dtype=float, shape=n * n)
colors = ti.Vector.field(3, dtype=float, shape=n * n)

bending_springs = False

@ti.kernel
def initialize_mass_points():
    random_offset = ti.Vector([ti.random() - 0.5, ti.random() - 0.5]) * 0.1

    for i, j in x:
        x[i, j] = [
            i * quad_size - 0.5 + random_offset[0], 0.6,
            j * quad_size - 0.5 + random_offset[1]
        ]
        v[i, j] = [0, 0, 0]


@ti.kernel
def initialize_mesh_indices():
    for i, j in ti.ndrange(n - 1, n - 1):
        quad_id = (i * (n - 1)) + j
        # 1st triangle of the square
        indices[quad_id * 6 + 0] = i * n + j
        indices[quad_id * 6 + 1] = (i + 1) * n + j
        indices[quad_id * 6 + 2] = i * n + (j + 1)
        # 2nd triangle of the square
        indices[quad_id * 6 + 3] = (i + 1) * n + j + 1
        indices[quad_id * 6 + 4] = i * n + (j + 1)
        indices[quad_id * 6 + 5] = (i + 1) * n + j

    for i, j in ti.ndrange(n, n):
        if (i // 4 + j // 4) % 2 == 0:
            colors[i * n + j] = (0.22, 0.72, 0.52)
        else:
            colors[i * n + j] = (1, 0.334, 0.52)

initialize_mesh_indices()

spring_offsets = []
if bending_springs:
    for i in range(-1, 2):
        for j in range(-1, 2):
            if (i, j) != (0, 0):
                spring_offsets.append(ti.Vector([i, j]))

else:
    for i in range(-2, 3):
        for j in range(-2, 3):
            if (i, j) != (0, 0) and abs(i) + abs(j) <= 2:
                spring_offsets.append(ti.Vector([i, j]))

@ti.kernel
def substep():
    for i in ti.grouped(x):
        v[i] += gravity * dt

    for i in ti.grouped(x):
        force = ti.Vector([0.0, 0.0, 0.0])
        for spring_offset in ti.static(spring_offsets):
            j = i + spring_offset
            if 0 <= j[0] < n and 0 <= j[1] < n:
                x_ij = x[i] - x[j]
                v_ij = v[i] - v[j]
                d = x_ij.normalized()
                current_dist = x_ij.norm()
                original_dist = quad_size * float(i - j).norm()
                # Spring force
                force += -spring_Y * d * (current_dist / original_dist - 1)
                # Dashpot damping
                force += -v_ij.dot(d) * d * dashpot_damping * quad_size

        v[i] += force * dt

    for i in ti.grouped(x):
        v[i] *= ti.exp(-drag_damping * dt)
        offset_to_center = x[i] - ball_center[0]
        if offset_to_center.norm() <= ball_radius:
            # Velocity projection
            normal = offset_to_center.normalized()
            v[i] -= min(v[i].dot(normal), 0) * normal
        x[i] += dt * v[i]

@ti.kernel
def update_vertices():
    for i, j in ti.ndrange(n, n):
        vertices[i * n + j] = x[i, j]

window = ti.ui.Window("Taichi Cloth Simulation on GGUI", (1024, 1024),
                      vsync=True)
canvas = window.get_canvas()
canvas.set_background_color((1, 1, 1))
scene = ti.ui.Scene()
camera = ti.ui.make_camera()

current_t = 0.0
initialize_mass_points()

while window.running:
    if current_t > 1.5:
        # Reset
        initialize_mass_points()
        current_t = 0

    for i in range(substeps):
        substep()
        current_t += dt
    update_vertices()

    camera.position(0.0, 0.0, 3)
    camera.lookat(0.0, 0.0, 0)
    scene.set_camera(camera)

    scene.point_light(pos=(0, 1, 2), color=(1, 1, 1))
    scene.ambient_light((0.5, 0.5, 0.5))
    scene.mesh(vertices,
               indices=indices,
               per_vertex_color=colors,
               two_sided=True)

    # Draw a smaller ball to avoid visual penetration
    scene.particles(ball_center, radius=ball_radius * 0.95, color=(0.5, 0.42, 0.8))
    canvas.scene(scene)
    window.show()

感兴趣的可以具体看看代码的实现过程,如果不加 taichi 库,这段代码运行起来会有点吃力,但是上了 taichi 之后,运行效果是如此丝滑!

总结

大家看这个 taichi 是不是会联想到“太极”二字?

没错,这个库是中国人发明的,它就是毕业于清华大学,后来去麻省理工学院进修的胡渊鸣!

他是太极图形创始人,北京太琦图形科技有限公司联合创始人、CEO。总之,他是个天才,让国人也在世界编程舞台上绽放异彩!

阅读全文 »

利用python做一个漂亮女生词云舞蹈视频

发表于 2022-11-03 | 分类于 python

上回我们活捉一只叫“轩逸”的动力蜘蛛,因为它动力拉垮,实在太慢了

这次让它变身给大家跳个舞,看下边的舞蹈,最拉跨的“动力”,这两个字给力吧!

词云来自轩逸车友圈的真实数据,爬取过程可以参考用python来吐槽,真是太会玩啦

gif不够清晰流畅,所以把更清晰的视频文件上传了,http://ssw.fit/file/

前言

利用you-get下载一个B站上跳舞的小姐姐视频,利用懂车帝爬取的“轩逸最不满意”来制作一个漂亮小姐姐词云跳舞视频,一起来看看吧。

1.下载视频

安装 you-get 库

1
pip install you-get -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

利用 you-get 下载 B 站视频到本地

1
you-get https://www.bilibili.com/video/BV1rD4y1Q7jc?from=search&seid=10634574434789745619

2.词云文本

脚本要用到的2个文件:

1
2
轩逸最不满意.txt
stopwords.txt

已上传 http://ssw.fit/file/

3.从视频中提取图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import cv2

# ============================ 视频处理 分割成一帧帧图片 =======================================
cap = cv2.VideoCapture(r"不要心动❤️我要开始表白了【欣小萌】 (P1. 横屏版).mp4")
num = 1
while True:
    # 逐帧读取视频  按顺序保存到本地文件夹
    ret, frame = cap.read()
    if ret:
        if 88 <= num < 888:
            cv2.imwrite(f"./pictures/img_{num}.jpg", frame)   # 保存一帧帧的图片
            print(f'========== 已成功保存第{num}张图片 ==========')
        num += 1
    else:
        break
cap.release()   # 释放资源

结果如下:

4.利用百度AI进行人像分割

准备工作

百度智能云网址

1
https://console.bce.baidu.com/

Python SDK参考文档

安装人体分析 Python SDK

1
pip install baidu-aip

1.领取免费资源

登录进去后,搜索“人体分析” 进入“人体分析”的页面后,领取免费资源 没领取的话,后面脚本的“人像分割”部分会报错,因为使用百度api需要用到这些资源。默认1万次,我已经用了1000多次

2.创建应用

创建一个人像分割的应用,记住你的AppID、API Key、Secret Key,后面会用到。 查看人像分割的 Python SDK 文档,熟悉它的基本使用。

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
# -*- coding: UTF-8 -*-

import cv2
import base64
import numpy as np
import os
from aip import AipBodyAnalysis
import time
import random

# 利用百度AI的人像分割服务 转化为二值图  有小姐姐身影的蒙版
# 百度云中已创建应用的  APP_ID API_KEY SECRET_KEY
APP_ID = '……'
API_KEY = '……'
SECRET_KEY = '……'

client = AipBodyAnalysis(APP_ID, API_KEY, SECRET_KEY)
# 保存图像分割后的路径
path = './mask_img/'

# os.listdir  列出保存到图片名称
img_files = os.listdir('./pictures')
print(img_files)
for num in range(88, len(img_files) + 1):
    # 按顺序构造出图片路径
    img = f'./pictures/img_{num}.jpg'
    img1 = cv2.imread(img)
    height, width, _ = img1.shape
    # print(height, width)
    # 二进制方式读取图片
    with open(img, 'rb') as fp:
        img_info = fp.read()

    # 设置只返回前景   也就是分割出来的人像
    seg_res = client.bodySeg(img_info)
    print(111,seg_res)
    labelmap = base64.b64decode(seg_res['labelmap'])
    nparr = np.frombuffer(labelmap, np.uint8)
    labelimg = cv2.imdecode(nparr, 1)
    labelimg = cv2.resize(labelimg, (width, height), interpolation=cv2.INTER_NEAREST)
    new_img = np.where(labelimg == 1, 255, labelimg)
    mask_name = path + 'mask_{}.png'.format(num)
    # 保存分割出来的人像
    cv2.imwrite(mask_name, new_img)
    print(f'======== 第{num}张图像分割完成 ========')
    time.sleep(random.randint(1,2))

结果如下:

5.小姐姐跳舞词云生成

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
# -*- coding: UTF-8 -*-
from wordcloud import WordCloud
import collections
import jieba
import re
from PIL import Image
import matplotlib.pyplot as plt
import numpy as np


# 读取数据
with open('轩逸最不满意.txt',encoding='utf8') as f:
    data = f.read()

# 文本预处理  去除一些无用的字符   只提取出中文出来
new_data = re.findall('[\u4e00-\u9fa5]+', data, re.S)
new_data = "/".join(new_data)

# 文本分词
seg_list_exact = jieba.cut(new_data, cut_all=True)

result_list = []
with open('stopwords.txt', encoding='utf-8') as f:
    con = f.read().split('\n')
    stop_words = set()
    for i in con:
        stop_words.add(i)

for word in seg_list_exact:
    # 设置停用词并去除单个词
    if word not in stop_words and len(word) > 1:
        result_list.append(word)

# 筛选后统计词频
word_counts = collections.Counter(result_list)
path = './wordcloud/'

for num in range(88, 888):
    img = f'./mask_img/mask_{num}.png'
    # 获取蒙版图片
    mask_ = 255 - np.array(Image.open(img))
    # 绘制词云
    plt.figure(figsize=(8, 5), dpi=200)
    my_cloud = WordCloud(
        background_color='black',  # 设置背景颜色  默认是black
        mask=mask_,      # 自定义蒙版
        mode='RGBA',
        max_words=500,
        font_path='C:/Windows/Fonts/simhei.TTF',   # 设置字体  显示中文
    ).generate_from_frequencies(word_counts)

    # 显示生成的词云图片
    plt.imshow(my_cloud)
    # 显示设置词云图中无坐标轴
    plt.axis('off')
    word_cloud_name = path + 'wordcloud_{}.png'.format(num)
    my_cloud.to_file(word_cloud_name)    # 保存词云图片
    plt.close()
    print(f'======== 第{num}张词云图生成 ========')

结果如下:

6.合成跳舞视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# -*- coding: UTF-8 -*-
import cv2
import os

# 输出视频的保存路径
video_dir = '轩逸最不满意.mp4'
# 帧率
fps = 30
# 图片尺寸
img_size = (1920, 1080)

fourcc = cv2.VideoWriter_fourcc('M', 'P', '4', 'V')  # opencv3.0 mp4会有警告但可以播放
videoWriter = cv2.VideoWriter(video_dir, fourcc, fps, img_size)
img_files = os.listdir('./wordcloud')

for i in range(88, 801):
    img_path = './wordcloud/' + 'wordcloud_{}.png'.format(i)
    frame = cv2.imread(img_path)
    frame = cv2.resize(frame, img_size)   # 生成视频   图片尺寸和设定尺寸相同
    videoWriter.write(frame)      # 写进视频里
    print(f'======== 按照视频顺序第{i}张图片合进视频 ========')

videoWriter.release()   # 释放资源

本文参考 利用Python做一个漂亮小姐姐词云跳舞视频

阅读全文 »

sql查询后台可以自己做吗?

发表于 2022-11-02 | 分类于 python

Img

公司主营短信,工作中数据库查询占了很大比例。

我们的操作是,通过navicat连接十多台机器的库,然后连接数据库-输入sql-修改查询条件(如一长串的日期)进行各种查询,

遇上高峰期,客服频频转发问题给我们,例如查下客户为什么没收到短信啦,查询发送记录啦,某个短信通道的发送量,签名统计等等 Img 最让我郁闷的地方,每次手机号、日期、短信通道等条件挨个修改一遍,键盘敲得噼里啪啦,鼠标点的不亦乐乎,别人还以为你有多忙,结果一顿操作猛于虎就查了条数据 Img

都说打工人苦打工人累 Img

我们就不要把工作耗费在这无意义的机械重复中了,是的,即使快一点也好 Img

思路

我想到总结常用的sql,写入配置文件 Img 通过网页点击执行这些语句。

在前端,好不容易靠试错发现,可以通过JavaScript的splice函数来对接红框内的查询条件: Img

利用splice实现搜索条件动态添加,这个用法是我蒙的,不知道专业的前端MM怎么做的。

上述内容就是我最初的想法。

虽然研发已经给客服MM做了查询后台,不过不适合我们这种“查询工程师”。

手工DIY一个? Img

想法挺好,怎么实现呢。

这个不急,骑着毛驴看唱本——走着瞧

摸到一条鱼

日月轮转,上班闲敲棋子,忙改文字,敲敲打打的,在没有其它技术人员支持的情况下,前后端居然调通了。

忽然间,大部分的查询工作,我都可以通过自己的笔记本轻描淡写的“指点江山”了: Img

不管多少个数据库,多少条语句,我都可以写到配置文件中。

前端呢,可以用element-ui的“Cascader 级联选择器”分门别类来添加要执行的sql Img

下图红框内就是“Cascader 级联选择器”效果,选中后会动态出现相关的搜索选项: Img

我们看下展示效果: Img

大体情况就是这样。

怎么实现的,主要介绍2点:

  • 如何连接不同的数据库
  • 如何添加一条sql查询

    文末上传了所有源码到gitee,有兴趣的同学可以看看

如何连接不同的数据库

1. 怎么在前端看到数据库

如图,怎么让数据库信息在下拉框中显示呢? Img 首先,每个数据库取个英文名字,写在配置文件里,通过configparser模块读取 Img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from rest_framework.views import APIView
import configparser

def read_cfg(name):
    cfg = configparser.RawConfigParser()
    cfg.read(settings.CONFIG_PATH, encoding='utf-8')
    return cfg.items(name)

class get_dbs(APIView):
    def get(self, request, *args, **kwargs):
        dbs = read_cfg('db')
        dbs_list = []
        d = {}
        for k,v in dbs:
            d['label'] = k
            d['value'] = v
            dbs_list.append(d)
            d = {}
        return JsonResponse({'code': 1, 'data': dbs_list})

然后配置前端,一打开页面就获取数据库信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
  created() {
    this.get_dbs()
  },
}

async get_dbs() {
    //this.$http.get('dbs'),dbs为获取数据库信息的接口地址
    const { data: res } = await this.$http.get('dbs')
    if (res.code !== 1) {
    return this.$message.error('获取dbs失败')
    }
    this.operateFormLabel[0].children = res.data
},

2. 让django处理前端传来的数据库信息

公司数据库为oracle,并且经过堡垒机,所以用到cx_Oracle和sshtunnel模块

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
from cx_Oracle import Connection
from sshtunnel import SSHTunnelForwarder
from rest_framework.views import APIView

class get_messages(APIView):
    def post(self, request, *args, **kwargs):
        data = json.loads(request.body.decode('utf-8'))
        db = data['db']
        #根据下拉框中选择的数据库名字,配置要连接的数据库
        if not db:
            return JsonResponse({'code':0,'msg':"请选择数据库"})
        if db == 'lt1':
            conn = ('192.168.2.1','ms','sgate;Normal;sms')
        elif db == 'lt2':
            conn = ('192.168.2.6','ms2','sgate;Normal;sms2')
        
        #sshtunnel建立客户端与堡垒机的隧道
        with SSHTunnelForwarder(
                ('堡垒机地址', port),
                ssh_username="ssw",
                ssh_password="1223456",
                remote_bind_address=(conn, 1521)) as server:
            conn = (conn[1], '123456', '127.0.0.1:%d/%s' % (server.local_bind_port,conn[2]))
            xconn = Connection(*conn)
            cursor = xconn.cursor()  # 新建游标
            cursor.execute(sql)  # 执行sql语句
            ret = cursor.fetchall()
            cursor.close()
            xconn.close()

这样就可以在页面下拉框中,自由的进行数据库的连接啦。接下来就是如何添加sql了,请看下面的例子。

如何添加一条sql查询

前端操作

1. 怎么在前端看到它

如图,怎么让这条sql在前端显示呢? Img 只需一步, 在messages.vue中添加级联菜单的一级菜单巡检和二级菜单服务器即可

1
2
3
4
5
6
7
8
9
10
11
12
      casecadeFormLabel: [
        {
          model: 'weekly_check',
          label: '巡检',
          children: [
            {
              label: '服务器',
              value: 'inspect'
            },
          ]
        }
      ],

只是显示还不够,每条语句要显示的字段不一样,我们需要单独定义它们 Img

2. 单独定义要显示的表头字段

如上所述,在messages.vue中添加inspect的表头字段,并且设置this.tableLabel = this.inspectLabel。为每条sql语句设置不同的表头字段,赋值给this.tableLabel,这样可以让不同的sql显示不同的字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      inspectLabel: [
        { prop: 'ip', label: '服务器', width: 120},
        { prop: 'cpu', label: 'CPU占用率', width: 70},
        { prop: 'mem', label: '内存占用率', width: 70},
        { prop: 'disk', label: '磁盘使用情况', width: 230 },
        { prop: 'vda1', label: '/dev/vda1使用率', width: 100},
        { prop: 'vdb1', label: '/dev/vdb1使用率', width: 100},
        { prop: 'network', label: '网络连接', width: 60},
        { prop: 'service', label: '服务检查', width: 165 },  
        { prop: 'url', label: '站点检查', width: 165 },       
        { prop: 'create_time', label: '日期', width: 70}      
      ], 

      ...中间略
      else if (this.operateForm.sql === 'inspect') {
        this.tableLabel = this.inspectLabel
      }        
      
3. 动态添加搜索条件

如图,框内的3个搜索条件是通过JavaScript的splice函数生成的。通过它,我们可以为每条语句定义不同的搜索条件。 Img 在commonForm.vue中编辑:

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
// 如果点击的的sql是“inspect”,就在页面上添加3个搜索条件,分别是网络连接、服务检查、时间范围
export default {
  data() {
    return {
      network: {
        model: 'network',
        label: '网络连接'
      },
      service: {
        model: 'service',
        label: '服务检查'
      },
      timerange: {
        model: 'timerange',
        label: '时段',
        type: 'date'
      },
    }
  },

    methods: {
    handleChange() {

        if (this.selectedKeys === 'inspect') {
            this.formLabel.splice(1, 1, this.network)
            this.formLabel.splice(2, 1, this.service)
            this.formLabel.splice(3, 1, this.timerange)
        }
    }
    }
}

接下来是后端操作

后端操作

1. 读取sql

首先,sql取名为“inspect”。 config.cfg-[sql] 填写要执行的sql语句 Img

old_views.py-read_cfg函数,get_sql函数 读取config.cfg中的sql语句,比如读到名为“inspect”的语句,返回的结果是这样的 [‘select project,ip,cpu,mem,disk,vda1,vdb1,network,service,url,create_time\nfrom weekly_check\nwhere create_time BETWEEN {1} AND {2}’]

1
2
3
4
5
6
7
8
9
10
11
12
import configparser
def read_cfg(name):
    cfg = configparser.RawConfigParser()
    cfg.read(settings.CONFIG_PATH, encoding='utf-8')
    return cfg.items(name)

#读取config.cfg中的sql语句,比如读到名为“inspect”的语句,返回的结果是这样的
#['select project,ip,cpu,mem,disk,vda1,vdb1,network,service,url,create_time\nfrom weekly_check\nwhere create_time BETWEEN {1} AND {2}']
def get_sql(sqlname):
    data = read_cfg('sql')
    sql = [item[1] for item in data if sqlname == item[0]]
    return sql[0]
2. 对页面提交过来的sql语句进行处理

然后,对页面提交过来的sql语句进行处理: 如修改日期,修改where条件,加上搜索条件等

old_views.py-get_messages()

1
2
3
4
5
6
7
8
9
10
11
12
13
if sqlname in ('inspect'):
    inputLabel = []
    field = []
    condition = []
    field_dict = {'network': network, 'service': service}
    for key, value in field_dict.items():
        print('len(value)',len(value))
        field.append(key)
        condition.append("{0} like '%{1}%' and".format(key, value))
    if sqlname == 'inspect':
        print('get_sql(sqlname)',get_sql(sqlname).format(','.join(field), start, end))
        print('condition',condition)
        sql = get_sql(sqlname).format(','.join(field), start, end).replace('where','where {0}').format(' '.join(condition))
3. 数据转成字典

(('农林牧渔', '172.16.1.6', '2.05', '24.59', '/dev/vda1 used: 7.9G nouse: 30G', '19.75', '74.11', '异常', 'Bootlog: OK'),)

这是从数据库返回的数据,类型为元组,需要转成字典并给value加上key,方便前端识别。如{‘项目名称’: ‘农林牧渔’, ‘ip’: ‘172.16.1.6’}

old_views.py-foo函数,bar函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#把参数转成字典
def foo(**kwargs):
    #对字典中的datetime时间格式数据转为字符串
    if isinstance(kwargs['create_time'],datetime.datetime):
        kwargs['create_time'] = kwargs['create_time'].strftime(('%Y-%m-%d %X'))
    return kwargs

#field是选取哪些字段的数据返回给前端
def bar(ret,sqlname,field=None,user_id=None):
    for item in ret:
        if sqlname == 'inspect':
            obj = foo(project=item[0], ip=item[1], cpu=item[2],mem=item[3],disk=item[4],
                      vda1=item[5],
                      vdb1=item[6],
                      network=item[7],
                      service=item[8],
                      url=item[9],
                      create_time=item[10])
        yield obj        

上一步bar()函数主要作用是把从数据库查到的数据转成字典,并给value加上一个key,大概过程:

1
2
3
4
5
6
7
8
9
10
11
import datetime
ret = (('农林牧渔', '172.16.1.6', '2.05', '24.59', '/dev/vda1 used: 7.9G nouse: 30G', '19.75', '74.11', '异常', 'Bootlog: OK', '', datetime.datetime(2022, 8, 19, 16, 17, 12)),)
obj = {}
field = ['network', 'service']
for item in ret:
    for i, v in enumerate(field):
        obj[v] = item[i]

print(obj)
输出为:
{'network': '农林牧渔', 'service': '172.16.1.6'}

到这里,一条sql查询就添加完了。

源码下载和安装

vue

1
2
3
4
git clone https://gitee.com/sswfit/vue-morning-shift.git
cd vue-morning-shift
npm install --registry=https://registry.npm.taobao.org
npm run serve

django

1
2
3
4
git clone https://gitee.com/sswfit/morning_shift.git
cd morning_shift
pip install -r requirements.txt
python manage.py runserver localhost:8887
阅读全文 »

几个有趣且有用的Python自动化脚本

发表于 2022-10-19 | 分类于 python

最近好多人都在吐槽打工人好难,最近尤其难!

谁说不是呢?

以前大家自己买热水壶烧水泡茶喝,几排工位共用一个,方便快捷。最近公司发通知说会有用电危险不让用了,发现使用直接没收。现在只能去公共区域接热水。工作这么忙,跑那么远去实在是懒得动!

公司是担心大家泡茶喝耽误工作吗?

打工已是如此的艰难,有些事情就不要拆穿!

在这难熬的日子里,给大家搜集几个有用的脚本,希望给大家带来一点乐趣,或者给大家的工作生活提升效率。

自动生成素描草图

在注册一些网站时,经常发愁头像怎么选?放真人照上去怕吓跑别人,放风景图片自己又不喜欢。

是时候用素描草图了,妈妈再也不用担心我的头像吓跑人了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import cv2
  img = cv2.imread("elon.jpg")

  ## Image to Gray Image
  gray_image = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

  ## Gray Image to Inverted Gray Image
  inverted_gray_image = 255-gray_image

  ## Blurring The Inverted Gray Image
  blurred_inverted_gray_image = cv2.GaussianBlur(inverted_gray_image, (19,19),0)

  ## Inverting the blurred image
  inverted_blurred_image = 255-blurred_inverted_gray_image

  ### Preparing Photo sketching
  sketck = cv2.divide(gray_image, inverted_blurred_image,scale= 256.0)

  cv2.imshow("Original Image",img)
  cv2.imshow("Pencil Sketch", sketck)
  cv2.waitKey(0)

运行效果如下:

自动发邮件

有时候我们工作中需要给领导或者客户发邮件,这可是一门技术活。如果很快发过去,要么是效率高,要么是工作不饱和。所以我们可能需要定时发邮件,比如凌晨一点。

这里以QQ邮箱为例,简单演示怎么自动发邮件。

在运行脚本之前,需要先在邮箱中设置开启 smtp 服务。

设置完成后,会生成一个授权码,这个授权码在下面的程序中会用到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import smtplib 
from email.message import EmailMessage
import pandas as pd

def send_email(remail, rsubject, rcontent):
    email = EmailMessage()      
    # 发件人邮箱                    
    email['from'] = '发件人邮箱'    
    # 收件人邮箱        
    email['to'] = remail            
    # 主题               
    email['subject'] = rsubject   
    # 内容                  
    email.set_content(rcontent)                     
    with smtplib.SMTP(host='smtp.qq.com',port=25)as smtp:     
        smtp.ehlo()                                 
        smtp.starttls()       
        # 授权码登录                     
        smtp.login("发件人邮箱","授权码") 
        smtp.send_message(email)                    
        print("email send to ",remail)              

if __name__ == '__main__':
    send_email('目标邮箱','test','test')

解压文件

解压文件的软件多如牛毛,但是如果一次性需求比较旺盛,需要批量解压的时候,就可以考虑用 python 实现了。 python 解压文件就几行代码:

1
2
3
4
from zipfile import ZipFile

unzip = ZipFile("file.zip", "r")
unzip.extractall("outputdir")

写个代码读取某个目录下的压缩文件,然后应用这两行就可以了。

PDF 加解密

对于一些重要 PDF 文件,我们可以对其设置密码,只有拿到文件和密码才可以查看内容。PDF 软件可以帮助我们做这个事情,但是如果有好多份文件呢?

使用Python的pikepdf模块,即可对文件进行加密,写一个循环就能进行批量加密文档。

1
2
3
4
5
import pikepdf

pdf = pikepdf.open("test.pdf")
pdf.save('encrypt.pdf', encryption=pikepdf.Encryption(owner="your_password", user="your_password", R=4))
pdf.close()

总结

今天的内容就介绍到这里,赶紧收藏和点赞吧!

阅读全文 »

为买车,我要爬懂车帝了

发表于 2022-09-26 | 分类于 python

上班摸鱼看了2个星期车评,还是一头雾水,选合资还是国产?发动机cvt好还是双离合好?艾瑞泽5 GT动力足,但腰线和前脸让人吐槽,真的可靠吗。国产选长安逸动还是吉利帝豪?标志408出来了,还有艾瑞泽8很漂亮。看会视频吧,同质化严重,讲来将去就是车内车外介绍一遍。

热门视频下通常有几百条评论,我一般会翻看一遍,七个八个视频就是几千条评论,信息芜杂,没个定准,所以干脆不看了!我决定从评分入手,爬下懂车帝看到底有多少个汽车品牌。长安、吉利、奇瑞它们有多少车型,高分段的车型多不,每家热销的车子分数排在哪个段位。所以用scrapy爬了3655条数据。

正好前段时间在python技术分享过一篇《不止高效,原来pandas表格可以更美的!》,https://mp.weixin.qq.com/s/KpKF6tvdcfG7d6rf8Xbv2w ,结合里面介绍过的排序分组配色,对我们的数据进行分析。通过这些数据,可以看出厂商的产品布局和销售优势。

比如马路上很多别克牌子,我又对它没啥印象,除了知道威朗在紧凑型轿车中排名靠前,还有其它热销车型吗?所以特意看了下别克的数据:

好家伙,居然有排名第一的! 再比如韩国车企在中国没落了,落寞成什么样子呢,

可以看到,现代和起亚在小型轿车和中型MPV是有销量的,悦纳曾经也是月销过万的主流合资轿车,这里它虽然位于小型桥车销量NO.10,但8月仅销售303辆。 确实有点惨淡啊,除了伊兰特,8月销量超过1千辆的只有一个库斯途,其它最多的的月销也只有1到3百辆,它们曾经非常受欢迎,不过随着汽车行业的更新换代,逐渐淡出了消费者们的视野。

评分最高的车型

再举个🌰,我想知道486个品牌中,每个品牌评分最高的车型

486个品牌的车型评分,已上传,http://ssw.fit/file/ 。由于部分品牌没有车型,如“众泰”没一款车型,所以爬的时候不会把这种写入csv文件中。一共3655条数据,也就是3655个车型。

对car.csv进行处理:

1
2
3
4
5
6
7
8
9
10
import pandas as pd
df2 = pd.read_csv("D:/桌面/car.csv",encoding='gb18030')
#取出评分大于0的(也就是去掉懂车帝上显示“暂无评分”的)
x = df2.groupby('评分').filter(lambda x:x['评分'].mean() > 0)

#取各组品牌中评分最高的
t = x.sort_values('评分',ascending=False).groupby('品牌', as_index=False).first()

t.sort_values('评分',ascending=False,inplace=True)
t.to_csv('评分榜.csv', index = True)

输出如下:

可以看到,所有品牌中,评分最高的车型是劳斯莱斯的“幻影”,4.74分,这个分数很高了。要知道国产长安最高3.88(长安UNI-K),吉利最高3.9(星越L),日产最高也没超过4分。

说明几个地方

下面开始整活。先说明几个地方:

  • 因为品牌较多,对应不同的url,可以利用multiprocessing.dummy多线程加快速度(这是个好工具,咱们的另一篇文章《3个python小工具,Linux服务器性能直线飞起!!!》,https://mp.weixin.qq.com/s/x4jZN4TisfTzdzVGSwZlWg 也用到过它)

  • 使用的爬虫框架是scrapy,它结构清晰,使用起来方便,比所有内容写到一个文件里好些。

  • 有些页面是动态加载的,需要使用selenium模拟页面向下滚动加载,把滚动条拉到最下面。

  • 大概运行过程是这样的:

好了,先从观察页面开始。

猜了个猜

观察它的url。首先来个十八连猜,猜下它尾部的18个“x”分别代表什么意思?

1
https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x

我先猜一个,倒数第3个x代表汽车品牌,不信吗,修改数字的话,第二个红框会出现不同的品牌:

所以url的倒数第三位,数字3代表奔驰,2为奥迪,4是宝马。其它你可以试一下,能搜索到结果的数字大概在600以内。我猜汽车品牌数也在600内吧。当然不能这么一个个猜, 还是赶紧干正事,找出每个品牌对应的数字,这样才好向相关品牌的页面发送请求。

找了个找

一共有6个属性为layout_label__1qfS8的span标签,我们要找的是第6个,即找到“已知条件”这个span标签。

这个时候下面脚本中的条件condition[5]才成立,BeautifulSoup才找得到:

1
2
3
4
5
6
7
soup = BeautifulSoup(rep.text,'html.parser')
condition = soup.find_all('span', class_='layout_label__1qfS8')
# 当num大于500时,有可能没这个品牌,condition[5]会报错
try:
    condition[5].next_sibling.a.text
except Exception as e:
    pass

因为数字大于500时,很可能没这个品牌,页面上不会出现“已知条件”,而是提示“0车系符合条件”

上面的next_sibling属性用来查询兄弟节点,也就是“已选条件”那个span的下一个span;next_sibling.a.text,下一个span的a标签里的文字就是品牌的名字

找出品牌对应的id

BeautifulSoup找到数据后,因为品牌对应的url较多,使用multiprocessing.dummy多线程加快速度。另外,数字大于500时,很可能找不到品牌,所以循环1000以内的数字基本能覆盖到所有品牌

1
2
pool = ThreadPool(10)
pool.map(get_brand_id,[i for i in range(1,1000)])

综上,获取品牌对应id的完整脚本:

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
# -*- coding: utf-8 -*-
import json,re,requests,ssl
from bs4 import BeautifulSoup
from multiprocessing.dummy import Pool as ThreadPool

num_list = []
brand_dict = {}
def get_brand_id(num):

    x = 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-%s-x-x' % num
    rep = requests.get(x,timeout=10)
    soup = BeautifulSoup(rep.text,'html.parser')
    condition = soup.find_all('span', class_='layout_label__1qfS8')
    # 当num大于500时,有可能没这个品牌,condition[5]会报错
    try:
        s = condition[5].next_sibling.a.text
        print('s111', s)
    except Exception as e:
        pass

    for span in condition:
        if span.string == '已选条件':
            print('ok')
            brand_dict[s] = num
            num_list.append(num)
            
pool = ThreadPool(10)
pool.map(get_brand_id,[i for i in range(1,1000)])
print(num_list)
print(brand_dict)

输出结果如下,总共486个汽车品牌:

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
{
前面略...
 '雪佛兰': 6,
 '雪铁龙': 21,
 '零跑汽车': 207,
 '雷丁': 282,
 '雷克萨斯': 22,
 '雷诺': 46,
 '雷诺三星': 301,
 '雷达汽车': 514,
 '霍顿': 278,
 '领克': 174,
 '领志': 309,
 '领途汽车': 247,
 '飞凡汽车': 401,
 '飞碟汽车': 404,
 '首望': 340,
 '马自达': 15,
 '驭胜': 167,
 '骐铃汽车': 104,
 '高合': 249,
 '魏牌': 66,
 '黄海': 132,
 '龙程汽车': 415
 }

品牌对应的url如下,我们可以挑选自己感兴趣的品牌url发送请求:

1
2
3
4
5
6
7
[
 前面略...
 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-101-x-x',
 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-76-x-x',
 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-1-x-x',
 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-126-x-x',
]

下面会用scrapy爬虫框架对这些url发起请求,数据写入csv

scrapy设置

整个目录结构如下:

  1. 创建scrapy项目
    1
    2
    3
    4
    5
    #创建scrapy项目
    scrapy startproject dcd
    cd dcd
    #生成一个爬虫
    scrapy genspider car "https://www.dongchedi.com/"
    
  2. 修改settings.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 是否遵守协议,设置false
    ROBOTSTXT_OBEY = False
    #设置请求头
    DEFAULT_REQUEST_HEADERS = {
     'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
     'Accept-Language': 'en',
     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0'
    }
    #下载中间件
    DOWNLOADER_MIDDLEWARES = {
    'dcd.chrom_middlewares.DcdDownloaderMiddleware': 543,
    }
    ITEM_PIPELINES = {
    'dcd.pipelines.DcdPipeline': 300,
    }
    
  3. 新建一个chrom_middlewares.py文件

第2步DOWNLOADER_MIDDLEWARES设置的下载中间件,我们自己编写: chrom_middlewares.py

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
import time
from selenium import webdriver
from scrapy.http.response.html import HtmlResponse

class DcdDownloaderMiddleware(object):

    def __init__(self):
        # selenium加载浏览器
        options = webdriver.ChromeOptions()
        options.add_argument('--no-sandbox')
        options.add_argument('--disable-gpu')
        options.add_argument('--ignore-certificate-errors')
        options.add_argument('--ignore-ssl-errors')

        self.driver = webdriver.Chrome(executable_path=r"C:\drf2\drf2\chromedriver.exe",options=options)
        self.driver.maximize_window()

    #重写process_request方法
    def process_request(self, request, spider):
        print('request.url',request.url)
        self.driver.get(request.url)
        js = 'return document.body.scrollHeight;'
        height = 0
        #selenium模拟页面向下滚动加载全部页面
        if request.url != 'https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x':
            while True:
                new_height = self.driver.execute_script(js)
                if new_height > height:
                    self.driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
                    height = new_height
                    time.sleep(1)
                else:
                    print("滚动条已经处于页面最下方!")
                    break
        source = self.driver.page_source
        # 创建一个response对象,把页面信息都封装在reponse对象中
        response = HtmlResponse(url=self.driver.current_url,body=source,request = request,encoding="utf-8")
        return response

对process_request说明一点:

如果车型多,需要滚动鼠标分一次或多次才能加载完毕,这个时候需要selenium模拟页面向下滚动加载全部车型,否则取到的车型是不全的。

1
2
3
4
5
6
7
8
9
while True:
    new_height = self.driver.execute_script(js)
    if new_height > height:
        self.driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
        height = new_height
        time.sleep(1)
    else:
        print("滚动条已经处于页面最下方!")
        break

  1. item.py ```py import scrapy

class DcdItem(scrapy.Item): #品牌 brand = scrapy.Field() #车型 name = scrapy.Field() #评分 score = scrapy.Field() #特点 title = scrapy.Field()

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
这几个字段的意思用箭头标明了:

![](http://www.justdopython.com/assets/images/2022/09/scrapydcd/12.png)

5. `car.py`
```py
import scrapy
from lxml import etree
from dcd.items import DcdItem
import os,csv

if os.path.exists('D:/桌面/car.csv'):
    print('delete?')
    os.remove('D:/桌面/car.csv')
f = open('D:/桌面/car.csv', 'a+', newline='', encoding='gb18030')
f_csv = csv.writer(f)
f_csv.writerow(['品牌','车型', '评分', '特点'])
class RainSpider(scrapy.Spider):
    name = 'car'
    allowed_domains = ['https://www.dongchedi.com/']
    start_urls = ['https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-11-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-13-x-x']

    def parse(self, response):
        print('html111')
        html =etree.HTML(response.text)

        item = DcdItem()
        brand = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/div[4]/span[2]/div/div/a/text()')[0]
        lis = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/ul/li[position()>= 1]')
        print('111 lis',lis)
        for li in lis:
            name = li.xpath('./div/a[1]/text()')[0]
            try:
                #有评分
                score = li.xpath('./div/a[2]/text()')[0].split('分')[0]
            except Exception as e:
                #无评分
                score = 0
            try:
                #有标题
                title = li.xpath('./div/span/text()')[0]
                # print('title111',title)
            except Exception as e:
                #无标题
                title = '无'
            print(name,score,title)
            f_csv.writerow([brand,name,score,title])

        item['name'] = name
        item['score'] = score
        item['title'] = title
        yield item

下面对car.py的2个地方进行说明

5.1 获取品牌

1
brand = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/div[4]/span[2]/div/div/a/text()')[0]

xpath路径,在edge浏览器中可以通过右键“检查”找到元素,再右键选择“复制”->”复制 Xpath”

按crtl+F键可以粘贴刚才复制的xpath,按回车键,页面上会突出显示对应的元素。

5.2 获取所有的li标签,代表一辆辆汽车信息

然后循坏这些li标签,获取到车型、评分、左上角的蓝色说明文字,写入csv文件

1
lis = html.xpath('//*[@id="__next"]/div[1]/div[2]/div/ul/li[position()>= 1]')

  1. 新增一个启动爬虫的文件start.py
    1
    2
    from scrapy.cmdline import execute
    execute('scrapy crawl car'.split(' '))
    

    文件位置:

在pycharm中右击即可运行爬虫:

排序分组配色

参考上文提到的《不止高效,原来pandas表格可以更美的!》

假如你想了解长安吉利奇瑞这3个品牌,那么在car.py中填写对应的url

1
2
#url中的73代表吉利,18是奇瑞,35是长安,对这3个品牌发起请求
start_urls = ['https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-73-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-18-x-x','https://www.dongchedi.com/auto/library/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-35-x-x']

这样car.csv中就只有这3个品牌的数据了,方便我们配色和对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd
from datetime import datetime,timedelta
df2 = pd.read_csv("D:/桌面/car.csv",encoding='gb18030')

#取出评分大于0的(也就是去掉懂车帝上显示“暂无评分”的)
x = df2.groupby('评分').filter(lambda x:x['评分'].mean() > 0)
x.sort_values('评分',ascending=True,inplace=True)
new = x.groupby(['品牌','评分','车型','特点'],as_index=False)
new3 = new.all()

#给每种品牌加上颜色
#评分大于3.8的,用黄色标注
new3.style.highlight_between(left='吉利汽车',right='吉利汽车',subset=['品牌'],props='background:#ffa5a5')\
.highlight_between(left='奇瑞',right='奇瑞',subset=['品牌'],props='background:#a1eafb')\
.highlight_between(left='长安',right='长安',subset=['品牌'],props='background:#71c9ce')\
.highlight_between(left=3.8,right=5,subset=['评分'],props='background:#f9ed69')

输出结果如下:

从数据可以看出,国内一线品牌产品线丰富,吉利在小型SUV、紧凑型轿车、紧凑型SUV都有热销产品,奇瑞仅瑞虎3x和瑞虎5x位于销量榜前10(怎么没有出口汽车?),看来理工男从产品受欢迎程度上来说离一线品牌还有差距

可以根据自己的喜好,给想看的品牌配上它们的logo色,看看它们的数据是否有惊喜

代码下载地址

已上传到 linux服务器上,http://ssw.fit/file/

总结

爬完了

阅读全文 »

我用django偷偷绑定员工MAC信息

发表于 2022-09-22 | 分类于 python

新人入职,我们经常需要登记他的ip和MAC地址,为什么呢? 因为行政MM经常来找我“打印机又出问题了”,作为一个桌面维护工程师,我知道打印机的ip是固定的,但员工修改自己的跟它一样引起冲突。这样的话,一不小心涉及到网络安全了,emm..

影响网络安全的因素很多,IP地址盗用或地址欺骗就是其中一个常见的因素。为了防止内部人员进行非法IP盗用(例如盗用权限更高人员的IP地址), 可以在交换机的每个端口上做IP地址的限定,如果有人修改了自己的IP地址,那么他的网络就不通了。他就会来找我“怎么我上不了网?”,好好的上不了网,可能你干了啥坏事。

作为一个网络管理人员,如果对MAC地址和IP进行绑定,就会创建一个十分有利的环境,可以大大减小安全隐患。

同时,这些信息可以和姓名一起,在入职的时候统一登记,绑定mac的同时顺便给他分配邮箱。不少公司分配邮箱的任务是行政MM做的,我们帮她做了,可以减少她们的日常工作,从而建立起良好的关系。

听起来非常不错,那怎么实行呢?且听下回分解

思路

  1. 我们需要的信息有ip、MAC、姓名(用于生成公司邮箱)

这些内容可以通过django网页获取,谁访问就会显示谁的IP地址,用户只需输入姓名。跟行政商量,让它作为入职的一个流程,登记这些信息。

  1. 查看录入情况

为方便查看,需要准备一个后台页面。员工提交一条数据,后台就能看到新用户。 以新员工邢道荣为例,看看他的录入, 整个流程是这样的:

具体操作

MAC地址怎么获取?

linux执行arping命令,会返回对方的MAC。

我们需要找一台内网linux机器,用paramiko模块登录上去,让它替我们arping员工网页提交过来的ip

1
2
[root@vm3 ~]# arping -f 192.168.14.6 -I ens33|grep reply
Unicast reply from 192.168.14.6 [00:0E:C6:83:3B:F9]  1.007ms

翻译成python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_mac(ip):
    import paramiko
    client = paramiko.SSHClient()
    private_key = paramiko.RSAKey.from_private_key_file('C:/Users/0717/Documents/id_rsa')
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect(
        hostname='192.168.14.173',
        username='root',
        port=22,
        pkey=private_key,)
    stdin, stdout, stderr = client.exec_command('arping -f %s -I ens33|grep reply' % ip)
    msg = stdout.read().decode('utf-8')
    client.close()
    return msg

检测ip

办公网络分有线和无线,两者的ip网段不一样。员工如果通过无线访问这个页面,要提示他仅有线网络需要提交IP信息,无线网络无需提交,请不要使用代理访问本页面

换成python表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def check_ip(addr):
    #只匹配有线网络的网段
    v = re.compile('(192.168.14).(\d+)')
    return v.match(addr)

def record(request):
    ip = request.META.get('REMOTE_ADDR')

    if check_ip(ip):
        return render(request, 'ipinfo.html', {'ip':ip})
        #check_mac根据re.compile('.*(\w{2}:\w+:\w+:\w+:\w+:\w+).*')进行正则匹配
        if not check_mac(ip):
            ip = ip + '检测到MAC地址异常,请联系管理员'
            return render(request, 'ipinfo.html', {'ip': ip})
    else:
        ip = ip + '仅有线网络需要提交IP信息,无线网络无需提交,请不要使用代理访问本页面'
        return render(request, 'ipinfo.html', {'ip': ip})

根据姓名分配邮箱

根据员工网页提交的姓名,自动分配公司邮箱,格式为“名字拼音的简写+公司邮箱后缀”。

这里用到pypinyin模块

1
2
3
4
5
#安装命令,pip install pypinyin
from pypinyin import lazy_pinyin

print(lazy_pinyin('上将潘凤'))
['shang', 'jiang', 'pan', 'feng']

名字长度一般为2~4个汉字,解析成拼音后进行拼接,如’邢道荣’分配的邮箱为xingdr@163.com,’潘凤’为panfeng@163.com

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def test1(name_list):
    name_list = lazy_pinyin(name_list)
    if len(name_list) == 2:
        email_name = name_list[0] + name_list[1]
    elif len(name_list) == 3:
        email_name = name_list[0] + name_list[1][0] + name_list[2][0]
    elif len(name_list) == 4:
        email_name = name_list[0] + name_list[1] + name_list[2][0] + name_list[3][0]
    email = email_name + '@163.com'
    print(email)

test1('邢道荣')
xingdr@163.com
test1('潘凤')
panfeng@163.com

提交数据

用requests提交员工信息到后台

1
2
3
user_info = {'username': name, 'password': user_id, 'email': email, 'ip': ip, 'MAC': mac}
conn = requests.session()
ret = conn.post('http://127.0.0.1:8887/api/v1/users/', data=json.dumps(user_info),

交换机绑定MAC

登录思科交换机用到python的第三方模块ciscolib

1
2
3
4
switch = ciscolib.Device('192.168.14.10', '123456')
switch.connect()
switch.enable(password='BxAdmin')
switch.cmd("write")

这个可以在管理后台增加一个”保存”按钮,让它执行相关交换机命令。

绑定的相关命令如下:

  • 查看整个端口的ip-mac表
    1
    cisco(config)#show mac-address-table
    
  • ip与mac地址的绑定
    1
    cisco(config)#arp 192.168.14.6 0000.e268.9980 ARPA
    
  • ip和交换机端口的绑定,绑定后的端口只有此ip能用,改为别的ip后立即断网
    1
    2
    3
    cisco(config)#interface FastEthernet0/17
    cisco(config-if)#ip access-group 6 in        
    cisco(config)#access-list 6 permit 192.168.14.6
    

    这样就将交换机的FastEthernet0/17与192.168.14.6绑定了

代码下载地址

包括html文件,已上传到 linux服务器上,http://ssw.fit/file/

小结

通过设计这样的网页办事窗口,员工入职从mac绑定到邮箱分配,再到其它信息登记,一条流水线服务,是不是规范和便捷呢,我的工作量也减少了,行政部的入职流程也更清晰了。

阅读全文 »

羊了个羊,无限通关!(9.17亲测可用)

发表于 2022-09-17 | 分类于 python

最近羊了个羊这个小游戏真的爆火,好多朋友玩得不亦乐乎,各大平台也是激起了广泛的讨论。

不过随着讨论度的上升,玩家们逐渐发现这个游戏,可能并没有表面看上去的那么“纯良”:随机生成的关卡并非必然有解,有的网友甚至晒出了消除到最后却无法通关的截图:

很多玩家直呼被骗,为自己浪费掉的时间感到可惜。不过没有关系,虽然游戏中无法通关,但我们可以通过直接向服务器发送通关信息的方式,增加排行榜中的通关次数,让你轻松成为朋友圈中的那只领头羊。

发送请求前,我们首先需要获取自己的token值,所以需要安装fiddler抓包工具。这个工具配置起来没有什么坑,网上教程有很多,大家可以自行查找。

其次由于PC端最新版微信无法抓取到来自小程序的请求,因此如果你的PC端微信是最新版本(3.7.6或以上)的话,需要会退回旧版本。(公众号后台回复“羊了个羊”,获取旧版微信安装包)

点击微信左下角更多->设置->关于微信,查看当前的版本号,这里由于我已经回退了,显示的是旧版。

旧版本的安装过程与正常流程完全相同,亲测没有遇到问题,有顾虑的朋友可以在安装前先备份相关文件。

回退版本后,首先打开fiddler,然后正常登录微信,打开羊了个羊小程序,在fiddler中就能抓取到host为cat-match.easygame2021.com的请求了。

如果到这一步没有抓取到请求,可以尝试先打开任务管理器,找到小程序的进程,右键打开文件所在位置。 (如果已经抓取到请求可以跳过下一步骤,直接进行值的获取token值)

打开文件位置后,向上找到WMPFRuntime目录,这时关闭PC微信进程,删除WMPFRuntime文件夹里面的所有文件(如果不退出会报正在占用,无法删除)

然后重新打开微信,将羊了个羊小程序移除,重新搜索进入,就能抓取到了。

抓取到请求后就可以开始token值的获取了,首先打开游戏正常玩到第二关,然后随便点几下,进入到通关失败的页面。

这时候切换到fiddler查看cat-match.easygame2021.com最新的请求,查看请求头,就能在里面找到自己的t值了。

然后我们要做的事情就很简单了,创建请求头,然后多次发送通关的信息,就能让排行榜的通关次数不断+1啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from time import sleep
import requests
 
header = {
    "t": "此处填入前面获取的token值"
}
 
def game():
    for i in range(10):
        requests.get("http://cat-match.easygame2021.com/sheep/v1/game/game_over?rank_score=1&rank_state=1&rank_time=15&rank_role=1&skin=1", headers=header)
        print(f"已通关{i+1}次,等待5秒...")
        sleep(5)
 
if __name__ == '__main__':
    print("执行开始...")
    game()
    print("执行完毕!")

替换掉请求头后,直接执行上述代码即可,简单粗暴。

代码没有什么需要解释的地方,这里要注意的首先是要设置一定的时间间隔,其次不要执行太多次,亲测请求频率过高会导致IP被禁一段时间。

以上方法亲测在9月17日是有效的,游戏后续不确定是否会升级后端的逻辑,因此不能保证此方法永久有效,所以有兴趣的朋友请尽快尝试啦。

(文中提到的安装包和代码已经整理好了,公众号后台回复“羊了个羊”获取)

阅读全文 »

跟女朋友旅游三天,Python治好了我的精神内耗

发表于 2022-09-16 | 分类于 python

前阵子请了年假,陪女朋友出了趟远门,一路心情愉悦景色宜人,不过累也是真的累,尤其在几天都没休息好还要一路颠簸回到家之后。

谁想到前脚刚踏回家门的我,刚准备休息,就收到了这样的消息:

把图片逐一保存,没想到她四天功夫竟然拍了小两百张照片…

这就有点让我为难了,首先我现在的工作不需要切图,PS也早就卸载了,其次就算有PS,光是给几百张图套上预设也得好一会儿才能搞定。

我有点一筹莫展,一边琢磨一边端详起这些照片来:

由于原图质量还可以,所以如果修的话其实不需要太多操作:因为当天云彩很多光线不是很好,照片颜色有点平淡,可以适当加一下饱和度;同时由于画面上好似覆盖着一层薄雾,可以考虑降低一下亮度,能做好这两点,就是一张不错的照片了。

这是我突然想起前阵子用OpenCV时看到的一个api,借助python,我们说不定能快速把这几百张图搞定。

首先介绍一下HSV颜色空间,HSV是一种颜色空间,与RGB通过红绿蓝的组合来描述颜色不同,HSV把颜色拆分为色调(H)、饱和度(S)和明度(V)三个维度,这样能够更直接的表达色彩的明暗以及鲜艳程度,因此广泛应用于图像识别领域。

借助opencv的split()函数,我们可以将图片的HSV变量分离出来,然后再用merge()函数合成一张新的图片,达到批量修改饱和度和明度的效果。

split也可以将图片的RGB三颜色通道分离出来,然后单独对某个通道进行修改。

话不多说,开始操作:

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
import cv2
import numpy as np
import os

def modify_image(img_path, target_dir):
    # 读取全部图片
    pic = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
    # 将图片修改为HSV
    pichsv = cv2.cvtColor(pic, cv2.COLOR_BGR2HSV)
    # 提取饱和度和明度
    H,S,V = cv2.split(pichsv)
    # S为饱和度,V为明度
    new_pic = cv2.merge([np.uint8(H), np.uint8(S*1.4), np.uint8(V*0.9)])
    # 将合并后的图片重置为RGB
    pictar = cv2.cvtColor(new_pic, cv2.COLOR_HSV2BGR)
    # 获取原文件名
    file_name = img_path.split("/")[-1]
    # 将图片写入目录
    cv2.imwrite(os.path.join(target_dir, file_name), pictar)

root, dirs, files = next(os.walk("./test/"))

for item in files:
    img_path = os.path.join(root,item)
    process_image(img_path, "./target/")

看眼手机的功夫,几百张图片就处理完毕了。左边修改前右边修改后,可以看到效果还是很明显的。

几分钟就搞定了所有的图片,女朋友喜笑颜开,我当然不会告诉她我是怎么做到的啦。

以上就是今天的全部内容,我们下次再见~

阅读全文 »

PyAutoGUI,轻松搞定图片上传!

发表于 2022-09-15 | 分类于 python

最近用vuepress建了个博客,音乐的背景图片需要网络地址。 还有博客自动复用的摘要图片也需地址 开始用的阿里云的免费对象存储oss,但又是登录又是设置读写权限的,稀碎的操作令人疲惫。 能不能简单点,自动上传,并且马上能得到文件的网络地址。

于是开始探索轻量级的方案,手动给自己搞一个,一键复制开箱即用的的上传页面: 把文件传到自己的云服务器上,传完后自带文件地址和copy按钮,流程顿时清爽了许多。

接下来的问题是,图片准备好了,十几张,怎么传更轻松点?这时背景音乐响起“王牌飞行员pyautogui请求出战”。优秀,机械重复的操作(如刷新网页、抢票、某些小游戏等)无疑是你的拿手好戏。

实时获取鼠标的当前坐标

这点很重要,因为所有的点击操作都基于坐标,就像selenium的操作基于xpath路径一样:

1
2
3
4
5
6
7
8
9
10
11
12
import time, os
import pyautogui as pag
try:
    while True:
        #print('Press Ctrl-C to end')
        x, y = pag.position() #返回鼠标的坐标
        print('Position : (%s, %s)' % (x, y)) # 打印坐标
        time.sleep(1) #每个1s中打印一次 , 并执行清屏
        os.system('cls') #执行系统清屏指令

except KeyboardInterrupt:
    print('end')

输出如下,鼠标放在哪就会显示哪个地方的x,y坐标:

1
2
3
4
Position : (937, 668)
Position : (1320, 689)
Position : (836, 579)
Position : (669, 585)

开始上传了

上传的操作页面我已建好:

http://ssw.fit/upload

获取到鼠标坐标就好办了,一路点击,疯狂输出。selenium还有验证码、反爬虫等限制,这个你自己的电脑,还不是想点哪就点哪。 先点击“选择文件”按钮,

1
2
3
4
5
6
7
import pyautogui
pyautogui.click(307, 227)
time.sleep(2.5)

# 弹出对话框后,点击路径那一栏,目的是为下一步粘贴路径
pyautogui.click(993, 332)
time.sleep(1.5)

typerwrite键入文件路径

每台电脑的文件路径不一样,让pyautogui的typerwrite输入图片文件夹的路径,不加时间参数,输入速度飞快

1
2
3
4
5
# 键入图片路径
pyautogui.typewrite('C:/Users/0717/Pictures/blog/upload')
# 按回车键
pyautogui.hotkey('enter')
time.sleep(1)

双击选中图片

图片的位置是固定的,所以可以取前5或前10张图片的坐标。下次再上传多张图的话,先清空文件夹,把图片拷贝到文件夹就好了

1
pyautogui.doubleClick(x,y)

点击“上传”按钮

上传需要时间,sleep一会

1
2
3
4
5
6
7
pyautogui.click(304, 278)
#上传需要时间,预算等多久
if x == 847:
	#847是第一张图片的x坐标,因为我上传的第一张是gif动图,文件大,上传多等几秒
	time.sleep(11)
else:
	time.sleep(2.5)

点击”copy”按钮

图片上传完后会自动生成copy按钮,点击它直接复制文件的网络地址

1
pyautogui.click(304, 278)

热键ctrl+v,回车,网页访问图片

1
2
3
pyautogui.hotkey('ctrl','v')
time.sleep(0.5)
pyautogui.hotkey('enter')

每张照片传完再自动粘贴地址和网页浏览,看看刚上传的图片多漂亮: 所以,脚本结束前,让我们欣赏美图3秒:

1
time.sleep(3)

点击浏览器的返回按钮

最后一步,欣赏完,返回主页面进入下一张图片的上传流程!

1
2
pyautogui.click(32, 67)
time.sleep(2)

完整脚本

总结一下,这里用到的pyautogui操作:

  • 单击click
  • 双击doubleClick
  • 输入文字typewrite
  • 热键组合hotkey('ctrl','v'),热键回车hotkey('enter')
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
import time
import pyautogui

def auto_upload(x,y,file_path):
    # 点击”选择文件“按钮
    pyautogui.click(307, 227)
    time.sleep(2.5)

    # 弹出对话框后,点击路径那一栏,目的是为下一步粘贴路径
    pyautogui.click(993, 332)
    time.sleep(1.5)

    # 键入图片路径
    pyautogui.typewrite(file_path)
    # 按回车键
    pyautogui.hotkey('enter')
    time.sleep(1)

    # 双击图片
    pyautogui.doubleClick(x,y)
    # 等文件出现
    time.sleep(6)

    # 点击“上传”按钮
    pyautogui.click(304, 278)
    #等几秒传完
    if x == 847:
        #847是第一张图片的x坐标,因为我上传的第一张是gif动图,文件大,上传多等几秒
        time.sleep(11)
    else:
        time.sleep(2.5)

    # 点击“copy”按钮
    pyautogui.click(297, 545)
    time.sleep(1)

    # 点击浏览器的地址栏
    pyautogui.click(410, 66)

    # 模拟键盘点击ctrl+v,然后按回车键
    pyautogui.hotkey('ctrl','v')
    time.sleep(0.5)
    pyautogui.hotkey('enter')

    #欣赏美女3秒
    time.sleep(3)

    # 点击浏览器的返回按钮
    pyautogui.click(32, 67)
    time.sleep(2)

#文件的x,y坐标
file_list = [(847, 489),(965, 490),(1136, 493),(1271, 504),(1391, 498)]
[ auto_upload(f[0],f[1],'C:/Users/0717/Pictures/blog/upload') for f in file_list]

运行过程

上传2张图片,整个脚本运行起来是这样的:

最后一个问题

上传了好几张,都到哪去了?这里可以看到啦: http://ssw.fit/free/

好了,这就是今天分享的全部内容,我们下次再见~

阅读全文 »

别再听中介忽悠了,用python找到最合适你的房子!

发表于 2022-09-13 | 分类于 python

书接上回,上篇文章()中,我们用python获取到了上海地区某平台的2w多条房源数据。

但上篇文章中只做了简单的筛选,这只是第一步,接下来,我们应当如何从这些数据中挑选出符合自己要求的房子呢?

为了确保换房后我和女友前往各自上班地点的通勤时间都在可接受范围内,我需要知道从各处房源位置前往两家公司所需的时间。为了获取这些信息,我们需要借助高德地图api这个工具。

使用高德api,我们能够轻松地根据地址或名称获取到地址对应的坐标位置,进而获取到对应地点的通勤和周边信息,十分的方便。

在使用api之前,我们首先需要获取到自己的Key值。进入高德开放平台网站,完成个人开发者注册和zfb实名认证后,点击控制台→应用管理→我的应用→创建新应用,来完成应用的创建。

之后点击右上角的添加,来为自己创建一个Key,注意这里服务平台要选择Web服务,不同选项对应的服务范围是不同的。

创建key值之后,就可以开始使用api获取数据了。

首先我们要根据地点名称得到对应的坐标值,然后用出发地和目的地的坐标调用接口,得到两个位置之间的通勤时间。

思路理清之后,就到了操作时间了。

获取房源坐标

因为总的房源数量太大,所以我们可以用小区的坐标位置代替房源的具体位置进行调用,这样需要进行的处理量就大大减小了,可以节省一些不必要的成本。

因此首先我们对上篇文章中获取到的数据做一个简单的处理,利用set对小区名做一个去重。

1
2
3
csv_read=pd.read_csv('../document/sh.csv',header=None)
village_set = set(csv_read[2])
village_list = list(village_set)

获取到小区列表后,我们尝试调用一下获取坐标的API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 高德API的URL
geourl = 'https://restapi.amap.com/v3/geocode/geo'  

# 地址前要加地区名,否则可能定位到其他城市
params = {'key':'在这里填入个人的Key值',
        'address': '上海市国金中心'}
# 发送请求                
res = requests.get(geourl, params)
jd = json.loads(res.text)
# 返回值的具体格式可以在API文档中查看
geopoint_1 = jd['geocodes'][0]['location']

print(geopoint_1)
# 121.502021,31.236814

调用成功之后,我们就可以用相同的方法,获取到列表中所有小区的坐标。

获取路程时间

在得到各个小区的坐标位置之后,我们就可以调用api获取两个坐标之间的路程时间了。

举个例子,如果我需要获取两个坐标之间的公交地铁通勤时间,可以用如下的方法:

1
2
3
4
5
6
7
8
9
10
11
# 高德API的URL
puburl = 'https://restapi.amap.com/v3/direction/transit/integrated?origin={}&destination={}&key={}&time=9:00&city=上海'  

# 发送请求  
r=requests.get(puburl.format(geopoint_1, geopoint_vill, '在这里填入个人的Key值'))  
r=r.text  
jsonData=json.loads(r)
# 获取步行距离
publength = round(int(jsonData['route']['transits'][0]['walking_distance'])/1000, 2)
# 获取总时间
pubtime = round(int(jsonData['route']['transits'][0]['duration'])/60)

这里一般会获取到多条路线,不过因为第一条路线通常是用时最短的,所以这里就以第一条路线的数据为代表。

用类似的方法,通过使用不同的url,就能获取到驾车、步行等方式的路程时间。不过要注意不同的这些api的输入和输出参数是有一定区别的,具体的要参照文档。

完整代码

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
import pandas as pd
import requests
import json
import csv
import codecs

# 创建导出文件
with open(r'..\document\village.csv', 'wb+')as fp:
    fp.write(codecs.BOM_UTF8)
f = open(r'..\document\village.csv','w+',newline='', encoding='utf-8')
writer = csv.writer(f)
writer.writerow(("小区名", "坐标", "步行距离-地点1","通勤时间-地点1", "步行距离-地点2","通勤时间-地点2"))

geourl = 'https://restapi.amap.com/v3/geocode/geo'  
puburl = 'https://restapi.amap.com/v3/direction/transit/integrated?origin={}&destination={}&key={}&time=9:00&city=上海'  

# 读取文件
csv_read=pd.read_csv('../document/sh.csv',header=None)
village_set = set(csv_read[2])
village_list = list(village_set)

# 获取第一个坐标
geourl = 'https://restapi.amap.com/v3/geocode/geo'  
# 地址前要加地区名,否则可能定位到其他城市
params = {'key':'在这里填入个人的Key值',
        'address': '上海市国金中心'}
# 发送请求                
res = requests.get(geourl, params)
jd = json.loads(res.text)
# 返回值的具体格式可以在API文档中查看
geopoint_1 = jd['geocodes'][0]['location']

# 获取第二个坐标
params = {'key':'在这里填入个人的Key值',
        'address': '上海市国正中心'}               
res = requests.get(geourl, params)
jd = json.loads(res.text)
geopoint_2 = jd['geocodes'][0]['location']

for adr in village_list:
    # 获取小区坐标
    params = {'key':'在这里填入个人的Key值',
        'address': '上海市'+adr}                
    res = requests.get(geourl, params)
    jd = json.loads(res.text)
    geopoint = jd['geocodes'][0]['location']

    # 获取第一个位置的信息
    r=requests.get(puburl.format(geopoint_1, geopoint, '在这里填入个人的Key值'))  
    r=r.text  
    jsonData=json.loads(r)
    publength_1 = round(int(jsonData['route']['transits'][0]['walking_distance'])/1000, 2)
    pubtime_1 = round(int(jsonData['route']['transits'][0]['duration'])/60)  

    # 获取第二个位置的信息
    r=requests.get(puburl.format(geopoint_2, geopoint, '在这里填入个人的Key值'))  
    r=r.text  
    jsonData=json.loads(r)
    publength_2 = round(int(jsonData['route']['transits'][0]['walking_distance'])/1000, 2)
    pubtime_2 = round(int(jsonData['route']['transits'][0]['duration'])/60)  

    writer.writerow((adr, geopoint, publength_1, pubtime_1, publength_2, pubtime_2))

f.close()

将脚本执行后,就能获得各个小区距离目标地点的路程时间。后面再经过一些简单的筛选,就能大大缩小找房的选择范围了。

高德API还有很多其他的功能,比如POI周边搜索可以查询小区周边指定范围内(比如方圆1公里)是否有便利店,健身房等设施,结合前端组件还可以在地图中显示出指定的位置,合理运用这些功能,能够实现更多的个性化需求,文中只用了很小一部分,大伙可以参照API文档自行尝试。

不过也要注意一点,对于个人开发者而言,高德API每日的调用次数是有限制的,为了避免超额,大家在爬取数据的时候可以根据实际情况适度缩小范围,减少处理的数据量。

以上就是这次找房故事的全部内容了,希望能对大家有所帮助,感谢阅读,也祝愿每个打工人都能住的舒心,早日有房。

阅读全文 »
1 2 … 28
Python Geek Tech

Python Geek Tech

一群热爱 Python 的技术人

554 日志
56 分类
159 标签
RSS
GitHub 知乎
Links
  • 纯洁的微笑
© 2019 - 2023 Python Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist