起因

工作需要,为了简化一些数据的处理流程,于是乎我决定用VBA来做,没办法,谁让那些处理数据的模板都是xlsm呢!

原来的流程

  • 登录某个内网网站,其实就是个网盘,目录很多
  • 一级一级展开,找到要下载的数据文件
  • 更改模板文件名字与数据文件同名
  • 打开模板,执行处理相关宏
  • 检查处理结果

现在的流程

  • 打开模板文件,点击绑定宏的按钮,等待完成,目视检查即可。

简化关键点

  • 自动下载文件
  • 下载文件后自动执行处理数据的宏

相关库

  1. 为了现代化一点,登录网站和下载选择了WinHttpRequest
  2. 返回数据是字典形式的,选用了VBA-JSON/JsonConverter
  3. 写入文件选择的是Adodb.Stream这个库,写入的内容是IWinHttpRequest::ResponseBody

注意点

  1. WinHttpRequest默认保持cookie,不需要操心。一开始因为犯了一个小错误(以为登录失败,实际上登陆成功),我还一直在找原因,以为是要手动处理cookie,然后就把IWinHttpRequest.Option(WinHttpRequestOption_EnableRedirects) = False了,最后发现不是,算是踩了坑。
  2. 通过Open写入二进制数据(ResponseBody),文件的开头始终会多一些数据,没有找到原因,最后使用Adodb.Stream搞定。
  3. Function如果返回的是Object,那么形式应该是这样的,即赋值的时候要用Set,调用的地方也要用Set,如果是常规的类型则两个地方都不用Set,用了反而会错:
Function BeCallFunc(Param1 As String) As Object
    Dim res As Object
    ' ...
    Set BeCallFunc = res
End Function
Sub MainSub()
    Dim MainRes As Object
    Dim MainParam1 As Object
    Set MainRes = BeCallFunc(MainParam1)
End Sub
  1. As的大小写机制没有搞明白,似乎是在同一个函数里面同是一个模式,即都是As或都是as即可。
  2. 代码放到VBA是编辑器里面后,相关的代码大小写会自动调整,中文粘贴进去会正常,但是从VBA里复制出来会乱码,暂时没有查怎么避免。
  3. 有些函数的库需要提前在工具-引用里面选上,同系列(不同版本)的库只能用一个。
  4. base64的转换用的是Microsoft XML 3.0,这个网上一搜就能找到。
  5. 最好不要再VBA的编辑器里面敲代码,因为这是真的不好写,虽然有些代码提示是有帮助的。令人难受的点有什么呢,比如你回车选择它的提示,然后会给你多个换行出来,我也不知道这是怎么想的,可能需要系统性学习下才知道怎么操作可以避免这个。另外比如你选中剪切了一部分代码,会直接报错,说你代码格式/参数什么的不对,我知道不对呀!我这不是在改吗。。
  6. 运行时报错你找不到在哪里,它就是说你错了,貌似只有明显的语法错误才会标红,其他的BUG个人经验是先运行到指定的光标位置,然后F8逐语句比较好定位。
  7. 如果预先通过Dim定义了类型,那么赋值时,值会被转换到被赋值变量的类型。
  8. 如果想执行宏,但文件又不能是xlsm,那么请通过Open的方式,激活要操作的文件,然后跑VBA代码。

碎碎念

  • Mid函数用于截取字符串,值得注意的是,截取开始位置是1,而非编程中常常使用0作为开始,也许这是为了方便非编程人员吧,不过我是很难受。。
  • Shell command可以执行command命令,注意这里的command可以是其他exe,执行cmd的话一般就是 Shell "cmd /c ...",没错这里是一个字符串。
  • VBA中如果你要输出一个引号("),那么请把它写成两个即可,即两个引号代表一个引号,例如"""0"""实际上是"0"
  • 连接字符串可以用+也可以用&。
  • Object对象的函数传递参数,根据我目前的经验,如果要返回值,那么用res = obj.callfunc(param1, param2),不需要的话就是obj.callfunc param1, param2,否则报错是语法错误,没错就是这么神奇。。
  • WinHttpRequest要把请求头删掉的话SetRequestHeader传入header的值和NULL即可,不过由于抓包失败,没有验证到底是Null,还是”null“,还是"",网上也没搜到例子。。
  • 调试的时候,看变量相关属性和值,是在本地窗口,立即窗口是console,Debug.Print之类的输出会在这里显示,不过本地窗口的信息量有限。
  • 代码提示很渣。。

其他的想起来再补充。
有些数据方面的爬虫倒是可以基于Excel来做,就是写成VB代码不太方便。

Q:为什么要对python脚本保护?
A:虽然自己代码写的菜,但是确实不想暴露源代码。

方案一 删除内置函数

dir可以用来查看参数的属性、方法列表,即使你用pyinstaller打包了脚本,但其他人依然可以在import后用它查看你的函数方法等。
如果把这个函数删除则可以避免这个问题。

脚本1:

import builtins
delattr(builtins, "dir")
class T:
    def a(self):
        print("is a.")

脚本2:

from script1 import T
t = T()
t.a()
print(dir(t))

脚本2输出:

is a.
Traceback (most recent call last):
  File "script1.py", line 10, in <module>
    print(dir(t))
NameError: name 'dir' is not defined

方案二 判断调用来源函数名

主要用到inspect这个库

脚本1:

from inspect import getouterframes, currentframe

class T:
    def a(self):
        callname = getouterframes(currentframe(), 2)[1][3]
        if callname != "callreal":
            print("You are idiot!")
        else:
            print("is a.")

脚本2:

from script1 import T

t = T()

def calltest():
    t.a()

def callreal():
    t.a()

if __name__ == "__main__":
    calltest()
    callreal()

脚本2输出:

You are idiot!
is a.

方案三 修改内置函数

继承——修改——覆盖

oldList = list
class NewList(list):
    def __len__(self):
        raise AttributeError("禁止使用!")
list = NewList
q = list()
print(q.__len__())

输出:

Traceback (most recent call last):
  File "script2.py", line 13, in <module>
    print(q.__len__())
  File "script2.py", line 10, in __len__
    raise AttributeError("禁止使用!")
AttributeError: 禁止使用!

方案四 对类中的函数重写为自己的逻辑

示例:

class B(type):
    def __getattribute__(self, name):
        if name == "__dict__" or name == "__bases__":
            return {"禁偷窥":""}
        return super().__getattribute__(name)

class A(metaclass=B):
    def a(self):
        print("a")

    def b(self):
        print("b")

t = A()
t.a()
t.b()

print(dir(A))
print("a" in dir(A))
print("b" in dir(A))

输出:

a
b
['禁偷窥']
False
False

写在前面

前两天win10自己更新了(1909,系统版本18363.836),然后发现远程又不能用了(被远程也用不了,可能是之前不知道改了什么造成的)。
于是又想起rdpwrap这个项目,结果这个项目不知道发生了什么凉了。
2020-05-17T08:51:18.png

好在文件什么的我都还有,另外上一次系统更新,远程服务挂掉的时候,rdpwrap.ini这个配置文件也找了GitHub上较新的(2020/02/13)。
中文网站能搜到的基本都是通过这个项目的脚本搞定,不过这个项目有个问题,到了后面要手动替换termsrv.dll这个文件(不知道是什么时候开始的)。
也就是本文重点,替换termsrv.dll文件,会谷歌的同学应该很利索能找到教程,在此写个图文版的。

下载RDPWrap-v1.6.2相关文件。

Tips! 该压缩包为个人打包,不喜勿下。
2020-05-17T08:57:55.png

替换termsrv.dll

该文件位于C:\Windows\System32\,替换时你会发现先要求管理员权限(System32下的文件,正常),确定之后会提示需要更改当前用户的权限。

然后我依稀记得是要先停止远程服务,结果停止了也没有,然后谷歌了下才知道是管理员的权限也不够hhh。
2020-05-17T09:03:47.png

默认打开上面图里红框部分是灰色,不可操作的(不要以为点那个编辑可以直接修改权限)。
要怎么修改呢?
termsrv.dll -> 属性 -> 安全 -> 高级 -> 更改 ->

  • 可直接输入要选择的对象名称处填入用户名(大写)\Administrators
  • 也可在高级 -> 立即查找 -> 选择Administrators -> 双击/确定

-> 确定 -> 应用+确定
-> 编辑 -> 选择Administrators ->修改权限为完全控制 -> 应用+确定 -> 确定

图示如下:
2020-05-17T09:14:22.png
2020-05-17T09:20:33.png

现在你可以替换termsrv.dll了。

最后

在替换termsrv.dll后,打开powershell(管理员)

执行net stop termservice
替换C:\Program Files\RDP Wrapper下的rdpwrap.ini文件
PS执行net start termservice

至此运行RDPConf.exe可以看到全绿,又可以愉快地使用远程桌面了。
2020-05-17T09:26:39.png

其他

rfxvmt.dll忘了要不要替换。如果前面没成可以试试替换,我反正之前替换过,系统更新后也还是之前替换的版本。
2020-05-17T09:30:37.png

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

思路

  1. 数字转字符列表依次计算平方的和
  2. 检查是不是在已知的非快乐数中,是则继续计算;不是且不为1,那就是新的非快乐数,记录并返回False;都不是则它是未知的快乐数,记录并返回True
  3. 平方和部分用字典预存储0-9的平方结果
class Solution:
    def isHappy(self, n: int) -> bool:
        stock = {
            "0" : 0,
            "1" : 1,
            "2" : 4,
            "3" : 9,
            "4" : 16,
            "5" : 25,
            "6" : 36,
            "7" : 49,
            "8" : 64,
            "9" : 81,
        }
        self.happy_stack = [0, 1]
        self.non_happy_stack = []

        def check(n):
            if n in self.happy_stack:
                return True
            _sum = sum([stock[num] for num in str(n)])
            if _sum in self.non_happy_stack:
                return False
            elif _sum != 1:
                self.non_happy_stack.append(_sum)
                return check(_sum)
            else:
                self.happy_stack.append(_sum)
                return True
        return check(n)

数字各位平方和

不知道这样会不会快一点

def get_sum(n):
    _sum = 0
    while n > 0:
        _sum += (n % 10) ** 2
        n = int(n / 10)
    return _sum