未末 发布的文章

上个月发邮件说8月就不提供ovz了,要我换服务器。
2020-07-11T04:21:39.png
这个服务器是之前一个特价套餐,配置很烂,唯一的优点是硬盘大,所以也就一直续费到了明年。
对大硬盘的需求来自于下载/上传一些大文件,另外对网络稍微有一点要求,最终定的这个。

服务器面板如下,常年加载极慢:
2020-07-11T04:05:20.png
基础配置如下:
2020-07-11T04:13:43.png
是不是看起来还不错,实际上平时使用完全达不到这个速度。
I/O常年10-20的样子,至于这次测出来这么高,估计是因为我邻居都迁移了。

最后我选了个SSD,NVEM空间实在是太小了,截至下次付费前不用补差价,后面续费才需要。
如果需要,也就比之前多10刀。
2020-07-11T04:22:54.png

备份记录

嗯,乱七八糟。。
2020-07-11T04:25:46.png

貌似有些大文件
2020-07-11T04:26:34.png
2020-07-11T04:27:55.png

emm,这都两年多了,是时候说再见了!
2020-07-11T04:31:22.png

差不多了,备份下剩下的脚本,网站配置什么的就可以了。
2020-07-11T04:33:18.png

这个应该是慕课网的免费课程下载脚本,都忘了什么怎么搞的了。
2020-07-11T04:36:11.png
2020-07-11T04:37:37.png

整理之后压缩就这么点了
2020-07-11T04:44:26.png

最终备份结束
2020-07-11T04:48:20.png

数据清除+重置系统
2020-07-11T04:52:02.png
2020-07-11T04:51:34.png

结束

好像面板重装功不能用了,rm -rf *
2020-07-11T04:56:46.png

关机拜拜!

无聊,瞎写点

算是一个练习

之前都是用的BeautifulSoup来提取需要的信息,相对来说操作简单,但是需要这个额外的库有时候感觉还是比较慢。

之前也知道python内置了HTMLParser,可以用来解析html,但是一直没用过,这次用这个来做。

关键目标

  • 尽量保持原有对齐
  • mediainfo分离
  • 图片

实现代码

直接上代码了。

from html.parser import HTMLParser

class THTMLParser(HTMLParser):

    def __init__(self):
        super(THTMLParser, self).__init__()
        self.brflag = 0
        self.qflag = None
        self.imgtag = None
        self.xtag = False
        self.recording = False
        self.record_step = 0 # 抽取进入的深度
        self.record_data = []

    def handle_starttag(self, tag, attrs):
        if tag == "legend":
            # 跳过 legend
            self.recording = False
        if tag == "x":
            self.xtag = True
        if tag == "div":
            if self.recording is True:
                self.record_step += 1
            for key, value in attrs:
                if key == "id" and value == "kdescr":
                    self.recording = True # 开始记录
                    self.record_step += 1 # 步进步数
                    break
        if tag == "fieldset":
            if self.recording is True:
                self.record_step += 1
            if self.qflag is None:
                self.qflag = "quote_start"

    def handle_endtag(self, tag):
        if tag == "legend":
            self.recording = True
            return
        if self.recording is True:
            self.record_step -= 1
            # 回退到起始tag 则说明要抽取的部分遍历结束了
            if self.record_step == 0:
                self.recording = False
        # if self.xtag is True:
        #     self.xtag = False
        if tag == "fieldset" and self.qflag == "find_next":
            self.record_data[-1] += "[/quote]"
            self.qflag = None

    def handle_startendtag(self, tag, attrs):
        if self.recording is False:
            return
        # 处理 <tagname /> 这种形式的tag
        if tag == "br":
            if self.brflag != 0:
                if len(self.record_data) > 0:
                    if len(self.record_data[-1]) > 0 and self.record_data[-1][-1] != "\n":
                        self.record_data[-1] += "\n"
                self.brflag = 0
            self.brflag += 1
        if tag == "hr":
            if self.qflag is None:
                self.qflag = "quote_start"
            if self.qflag == "find_next":
                self.record_data[-1] += "[/quote]"
                self.qflag = None
        if tag == "img":
            for key, value in attrs:
                if key == "src":
                    text = f"[img]{value}[/img]"
                    self.record_data.append(text)

    def handle_data(self, data: str):
        if self.recording is True:
            # 这里没有处理 \u3000 即全角空白 因为全角空白可以对齐
            text = data.strip("\n\t").replace("\xa0", " ")
            if text != "":
                if self.brflag == 1:
                    self.brflag = 0
                if self.xtag is True:
                    self.record_data[-1] += text
                    self.xtag = False
                else:
                    if self.qflag == "quote_start":
                        text = "[quote]" + text
                        self.qflag = "find_next"
                    self.record_data.append(text)
                if self.record_data[-1] in ["Video", "Audio", "Other"]:
                    self.record_data[-1] = "\n" + self.record_data[-1]

    def handle_comment(self, data):
        pass
        # print('<!--', data, '-->')

    def handle_entityref(self, name):
        pass
        # print('&%s;' % name)

    def handle_charref(self, name):
        pass
        # print('&#%s;' % name)

if __name__ == "__main__":
    with open(r"torrent.html", "r", encoding="utf-8") as f:
        content = f.read()
    parser = THTMLParser()
    parser.feed(content)
    with open(r"torrent.md", "w", encoding="utf-8") as f:
        content = f.write("\n".join(parser.record_data))

提取效果预览

2020-07-05T10:24:53.png

起因

工作需要,为了简化一些数据的处理流程,于是乎我决定用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