跳到主要内容

Docker入门

· 阅读需 4 分钟

公司的项目是用 docker 来部署的。之前看过一个 docker 科普视频,还没有动手实践过。现在将查到的东西做一个梳理(windows 平台)。

docker 把一个应用的代码文件和依赖的环境打包进一个容器,并且这个容器可以极其方便地部署在不同的机器上。docker 有以下三个主要文件:

  • DockerFile 描述了如何创建 image 文件及其基本信息
  • image 文件 环境的完整信息,类似于 windows 安装时的 iso 文件,便携,可以发布到公共空间供他人下载。
  • container 容器,本机系统上的一个进程,将自身与外部操作系统隔离。

docker 使用方式

让我们从 DockerFile 开始。首先编写 dockerignore 文件,忽视某些文件和文件夹,使之不要打包进 image 文件。是不是很类似于 gitignore?是的,image 文件就可类比 git 工作区,可以提交到 dockerhub,一个 image 文件托管平台。然后,编写 DockerFile。它无后缀,可以像下面这样写:

FROM node:8.4
COPY . /app
WORKDIR /app
RUN npm install --register=https://register.npm.taobao.com
EXPOSE 3000

注意,RUN npm install 是在制作 image 文件阶段执行的,说明 npm 包会打包进 image 文件。

有了 DockerFile,就可以创建 image 文件。

docker build -t koa-demo .

-t指定 image 文件的别名,不然就是一串哈希字符。注意最后有一个点,表示 dockerfile 就在当前路径。运行后,新生成的 image 文件存在 windows 下的 C:\Users\${用户名}\AppData\Local\Docker\wsl\data\ext4.vhdx. 但是,要看是否创建成功,指令 docker image ls。

有了 image 文件,可以正式创建容器:

docker run -dp 8000:3000 koa-demo

-d detached, 单独模式,在后台运行。-p建立本机和容器端口之间的映射,即本机的 8000 映射到容器的 3000。koa-demo就是容器的别名。

补充:Docker 中的数据卷(Volume)是用来持久化容器中的数据的一种机制。数据卷可以在容器之间共享数据,并且可以保持数据的持久性,即使容器被删除或重新创建,数据仍然会保留。

docker volume create my_volume
docker run -d --name my_container -v my_volume:/path/to/mount my_image

终止容器,首先运行 docker ps,获得 容器名称或 id,运行 docker stop [name]。删除容器:docker rm [name]

docker compose

compose 是一个 docker 系统的工具,可以一次管理多个容器,并且替代了 bash 脚本,而使用 yaml 来启动容器。在 linux 中,它需要单独安装:

curl -L https://github.com/docker/compose/releases/download/1.3.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

常用命令:

docker-compose up:启动应用,并创建、启动所有定义的服务容器。如果服务不存在,会先构建镜像。可以通过 -d 选项使应用在后台运行。 docker-compose down:停止并删除应用的所有容器、网络、存储卷等相关资源。 docker-compose start:启动应用的所有容器,但不重新创建。 docker-compose stop:停止应用的所有容器,但不删除。 docker-compose restart:重启应用的所有容器。

git、github及其在vscode中的状态表示

· 阅读需 8 分钟

前言

实习过程中,大量使用 git。放假前,同事提出,我提交的版本覆盖了他的。为了能在节后解决问题,顺便对 git 工具体系做一个彻底的复习,写下此文。

初始化

git 是一种分布式管理工具。它能实现多人共同维护一个工程文件。下载好 git,并配置好身份信息后,可以有两种方式开始 git: 一种是下载 git 仓库,一种是在本地执行 git init 。这两种方式执行后,文件夹里都会有一个 .git 文件夹。

git config 相关命令配置仓库,这篇文章介绍得很详细了。总体来说,git 配置分为三个层次:仓库级(local)、用户级(global)、系统级(system)。配置的优先级是仓库->用户->系统。git config -l可以将当前仓库的所有可用配置列举出来,如下:

diff.astextplain.textconv=astextplain
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
http.sslbackend=openssl
http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
core.autocrlf=true
core.fscache=true
core.symlinks=false
pull.rebase=false
credential.helper=manager-core
credential.https://dev.azure.com.usehttppath=true
init.defaultbranch=master
filter.lfs.clean=git-lfs clean -- %f
filter.lfs.smudge=git-lfs smudge -- %f
filter.lfs.process=git-lfs filter-process
filter.lfs.required=true
user.name=li199-code
user.email=
http.sslverify=false
credential.http://git.zhi-shan.cn.provider=generic
credential.https://gitee.com.provider=generic
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
remote.origin.url=http..
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
user.name=
user.email=

我现在有一个需求,要修改提交时的姓名和邮件:

git config --local --add user.name jason
git config --local --add user.email [email protected]

git 提交

可以理解有两种提交:一种提交到本地的 git 区,一种是在本地提交后,提交到远程仓库,即 push。

git status查看本地有哪些修改。git log查看提交日志。git checkout [hashcode]根据某次提交的哈希值将代码回滚。

有一个有用的 git log 命令:git log --graph --oneline. 可以用查看历史提交的记录。

branch 分支(时间线)

git branch [name]创建分支。新分支会继承主分支上的所有历史改动,类似于时间线的概念。

一般来说,其他分支是在主分支基础上的重大更新,如果要合并其他分支,需要在切换到主分支后,执行 git merge [name].

git checkout

git checkout [id] filepath 用于回滚时,最好加上要回滚的文件名,这样只会改变该文件,而不会专门进入 detached head 模式。

"Detached HEAD"是一个 Git 术语,用来描述 HEAD 指针在一个特定的提交版本上,而不是指向任何分支的状态。这种状态下,你处于一个游离的、临时的工作环境,没有与之关联的分支。

当你切换到一个特定的提交版本(通过提交哈希、标签或分支名)而不是一个分支时,Git 会将 HEAD 指针直接指向该提交。这就是所谓的"Detached HEAD"状态。

Detached HEAD 状态通常用于以下情况:

查看历史提交:你可以在 Detached HEAD 状态下查看、比较和分析以前的提交。这对于浏览代码历史、调试问题或查找特定更改非常有用。

临时工作:如果你想在不影响当前分支的情况下进行一些临时的更改或实验,可以切换到 Detached HEAD 状态。你可以在该状态下创建新的提交,但这些提交不会属于任何分支。

git remote

以上说的操作都是在本地完成的。如何将本地改好的代码,传到云端完成多人协同开发呢?

如果是本地初始化 git,在 git push 之前,需要配置文章仓库的 url:

git remote add <name> <url>

<name>是 url 的别名,毕竟 url 很长。默认为 origin。同时,本地 git 文件夹里的 ref 下,也会创建 origin 文件夹,存缓存数据。

git push origin master

其他问题

为什么实习连的公司内部 gitlab 仓库,不用像 github 那样配置 ssh?

因为 git 远程仓库有两种方式,ssh 登录和 https 登录,而我是通过输入账号密码登录的,属于后者。且 windows credential helper 帮我记下了 git 仓库的账号和密码(一台主机可以储存多种仓库的账号,比如 github,gitlab),避免每次提交代码需要重复输入。

vscode git 工作流

首先,正确的工作流是:add -> commit -> pull -> push。在 vscode 中,commit -> pull -> push 简化为一个按钮:commit & sync.

git 状态(更新)

16872235086311687223508501.png

这张图展示了 git 的四种状态:untracked, unmodified, modified, staged. 从指向每个状态的箭头,我解读出一些有用的信息。首先,git init命令执行,创建了一个“容器”,但是所有的文件依旧是 untracked. staged 要么是从 untracked 转来,要么是已经被跟踪的文件被修改后执行git add而来。stage 状态的文件被 commit 后,状态就会回到 unmodified。

在现代的工作环境下,大多数人是通过 vscode 等 IDE 进行 git 操作了。所以,结合 vscode 页面说明对应的 git 状态和操作有助于加深理解。以我的 vscode 界面为例。如果一个项目还没有被 git 管理,这时候初始化,那么在 vscode 的 explorer 里,所有文件都会变成绿色,这种对应的就是上图的 untracked 状态,绿色的 U。同理新建一个文件,也是这种状态。然后,如果修改了一个文件,modified,黄色的 M。删除不在上面的图中,表示为红色的 D。当然了,没有修改的文件就是白色的,unmodified。

17031659366861703165935807.png

继续看,只有处在 staged 状态的文件才能被 commit。所以下图的加号就是把 modified 变为 staged,点了之后,文件会进入 staged changes。这里都是为了精细化控制单个文件。另外,vscode 有自己一套默认规则。正常情况下,点 commit 按钮只会提交 staged changes 内容,如果只有 changes 没有 staged changes,那么 vscode 就会自动将 changes 内容变为 staged 并提交。

17031663916541703166390710.png

17031665366531703166535845.png

Django ORM记录

· 阅读需 3 分钟

记录曾经不懂的问题和踩过的坑

choices

Django 模型中的字段有个 choices 属性,这个属性可以提供被选数据。如果一个字段设置了这个属性,在模版中如果我要显示这个字段,那么 django 模版系统就会将它默认解析为一个下拉菜单。

# models.py
choices = (
(1, '男'), (2, '女'), (3, '其他')
)
gender = models.IntegerField(choices=choices)
# html {{ obj.get_gender_display }}
##在模板中,为了获取元组中id对应值,用左边的语法

外键

#model.py

## 外键要关联的表
class department(models.Model):
name = models.CharField(max_length=32)

## 在结合modelForm时,若for循环输出,下面语句可避免bug
def __str__(self):
return self.name


department = models.ForeignKey(to='department', on_delete=models.CASCADE)
## 外键关联上后,执行迁移,会在本表创建一个名为'department_id'字段。
# html {{ obj.department.name }} ##department字段会返回一个department表的对象

modelForm

添加数据

当想让用户向已有数据库中添加新的数据,应该用 modelForm 来创建一个对象。这个对象可以用 for 循环来在模板中显示字段,而且具有自动对数据进行校验和报错功能。

## views.py

class employModelForm(forms.ModelForm):
## 默认校验非空,可以对数据进行个性化校验,如:
## password = forms.CharField(min_length=3, label='密码')
class Meta:
model = employee
fields = "__all__"

def add(req):
if req.method == 'GET':
# form = employeeForm()
form = employModelForm()
return render(req, 'emp_add.html', {'form': form})

form = employModelForm(data=req.POST)
if form.is_valid():
form.save()
return redirect('/emp/list/')

## 数据校验失败,将已输入的信息和产生的错误提示返回给页面
return render(req, 'emp_add.html', {'form': form})
{{field.label}}: {{ field }} {{ field.errors.0 }} //错误信息输出

修改数据

修改数据时,要告诉 django 修改的对象是哪个。

## views.py
form = employModelForm(data=req.POST, instance=obj)

mysql

· 阅读需 5 分钟

mysql的单表查询很简单,但是单表体积增大时,或者涉及到多表查询时就成了难点。本文主要对这些问题做一些记录。

mysql工作原理

mysql服务启动,监听3306端口。外界的客户端,如cmd,django框架,heidisql都视作客户端,用户名密码验证后可以连接到mysql服务。在操作系统中,数据库视作文件夹,每一张表都是一个特殊后缀的文件。 1668775623718.png

数据库的备份和恢复

备份的本质是创建一个sql文件。命令:

mysql -u -p -B db1 db2 > d:\\xxx.sql  -- 按照数据库来备份
mysql -u -p db1 table1 table2 > d:\\yyy.sql -- 按照表来备份
source d:\\xxx.sql -- 恢复

分页

在数据库规模较大的情况下,每次请求都是请求部分,这样就需要分页。

select * from table limit 每页记录数*(页序号-1, 每页记录数

多子句查询的先后顺序

select * from table
group by ..
hanving ..
order by ..
limit ..

自连接

将一张表当作两张表联合查询,需要给表取别名。

select * from table1 as A, table1 as B where ...

表自复制和去重

insert into table select * from table  -- 自复制,也可以用于制作表的备份
------
-- 表格去重流程:先创建新表,将去重的数据写入新表,然后删掉旧表,将新表改名
create table new like old; --创建字段和旧表一致的新表
insert into new select distinct * from old;
drop table old;
alter table new rename old;

外连接

按照下列模式:

select * from table1, table2 where ...

是在两表的笛卡尔积上进行筛选,这样,不符合筛选条件的数据不会列出。有时候我们需要列出不符合筛选条件的数据。外连接分为左右,以左外连接为例,左表的每一项都进入结果中,对于没有匹配上的字段,则赋为Null。语法:

select * from table1 left join table2 on ... -- 筛选条件是on

外键

为了对新插入的数据进行约束,引入了外键。假设,表A作为主表,也就是约束条件,表B作为从表,只能插入表A中主键存在的数据。一旦主表的主键被引用,就不能删除。

create table tab (
id int primary key,
class_id int,
--下面指定外键
foreign key (class_id) references my_class(id), -- my_class是主表。id必须是主键或者unique
)

check约束

注:mysql 8.0.16后开始全面支持check约束。

CREATE TABLE cc (
id INT,
sex VARCHAR(10) CHECK (sex IN('male', 'female'))
);

索引

索引是对一个字段建立的一个二叉树,提升查找速度。是一种空间换时间的策略。对于查找操作多于修改操作的数据库很有必要。

create index_name on table (ziduan)

事务

为了能将多个sql语句作为一个整体执行,事务提出,将增删改包裹起来。

start transaction; -- 开始事务,此时其他会话看不到事务过程中修改的数据结果
-- 执行一些操作 --
savepoint A; -- 设置保存点A
-- 执行一些操作 --
rollback to A; -- 回退到保存点A
rollback; -- 回退到事务开始时状态
commit -- 提交事务,删除所有保存点,此时修改后的数据向其他会话开放

隔离

为了确保事务获得数据的准确性,提出了隔离。默认隔离级别下,不同事务同时开启造成的问题有:

1668952763819.png

事务隔离级别和各级别会出现的问题:

1668952901628.png

select @@transaction_isolation; --查看当前事务隔离级别(mysql8.0+,低版本为select @@tx_isolation)

Django入门和配置

· 阅读需 6 分钟

Django 是 python 的一个 web 后台框架。他的特点是大而全,因此本身命令和代码构架和原生 python 有较大区别,这篇文章是对 django 从入门到项目运行的学习记录。

django-admin startproject name  //创建项目目录
python manage.py runserver //启动项目
django-admin startapp name //创建app

基本概念

在 django 体系中,一个网站由多个 app 组成,每个 app 都有完整的 MTV 体系:

  • M,model,负责建立和数据库的连接
  • T,Template,负责 html
  • V,view,负责处理请求逻辑,比如是请求页面还是数据,是函数形式

新项目初始化设置

app:类名.apps.类名 Config。app 里面有 models.py, views.py.手动创建一个 urls.py。把模块用到的 url 放进去。把处理请求的函数放在 views.py. 最后,回到‘大哥’文件夹的 urls.py, 用 include 方式引入 app 的 urls.

template 创建

template 不是重点。

首先,在根目录下创建 templates 文件夹,在下面建立 html 文件。之后在 settings.py 里注册一下,使项目认识 templates 文件夹。这样,调用 render 函数时,第二个参数字符串就会寻找 html 模板文件。

模板文件同样需要解耦。在 app 下建立 templates folder,下面子文件夹名为 app 的名字。创建模板文件。在 views.py 里改 render 参数的路径。

extends 和 include 引入的模板要放在根目录下的 tempplates folder.

migration 迁移

迁移似乎不是很直观。可以理解为在 models.py 中进行了相关表的创建和修改字段等操作,并应用到数据库中。django 默认的数据库是 sqlite。具体流程是:

  1. 在 model.py 中建立一个 class
  2. 将 class 在 admin.py 中注册
  3. 运行两条命令: makemigrations(会在 migrations 文件夹下建立一个带序号的 py 文件,相当于提交修改)和 migrate(执行修改)

静态文件的部署

静态文件的部署和 setting 有关。这里用 django 推荐的模板语法写。如果我们要配置整个 project 下的静态文件的话,执行此步骤。

 STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"), # 首选project静态文件搜寻路径
'/var/www/static/', # 第二选project静态文件搜寻路径,还可以有第三选,第四选……
)

访问顺序:会先访问 app 下的 static/文件夹下的 myexample.jpg 文件,若 app 下的 static 文件夹中没有该文件,则访问 project 中的 static/文件夹,查看是否有 myexample.jpg 文件,若有则返回,若没有则去/var/www/static/中寻找。参考链接

python manage.py collectstatic

为什么要使用 STATIC_ROOT 呢,是因为当你设置中 DEBUG 为 True 时,django 会自动为你静态文件代理,不过当 DEBUG 为 False 时,意味着你要进入生产环境,那么,你就必须使用 STATIC_ROOT 来指明你的静态文件在哪里,就像 MEDIA_ROOT 一样。部署过程中,输入上面这条命令就将静态文件集中起来了。

完整的静态文件配置如下:

STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
STATIC_ROOT = os.path.join(BASE_DIR, "static")

ORM django 如何与数据库交互

1668480185606.png

ORM

ORM(object relational mapping),对象关系映射,是将数据库的记录视作程序中的对象。

ORM 可以将用户的 python 语句翻译为 sql 语句交给 mysql 等数据库执行。它的好处是不用写很多的 sql 语句,可以连接多种数据库。除了建立数据库需要手动在 mysql 客户端外,建表、删表、操作表内数据都可以用 ORM 完成。models.py 文件可以视为建表操作,每次更改这个文件,就要执行下列命令,将更改应用到数据库中,同时会在 migrations 文件夹内留下历史记录,类似于 github 提交。

python manage.py makemigrations
python manage.py migrate

建表完毕后,要在 views.py 中对表数据修改。首先要正确引入 models 文件,要写全路径:

from app01.models import UserInfo
UserInfo.objects.create(name=user, password=pwd, age=age)
## objects,对象,在orm中,一行数据视作一个对象
## all():所有数据,filter() 就类似与sql的where
## create:增加,update:改,delete:删除

我对区块链学习过程的记录

· 阅读需 5 分钟

从抽象到具体,从为什么创立区块链,到技术层面。一个掺杂我个人思考的记录。

自由世界人民对自由更进一步的追求,用技术天然的可靠性替代对大机构的信任。

一开始,区块链第一个实现形式是比特币,创始人希望它成为一种货币,交易货币产生的账单,在区块链中存储,谁能获得记账权,谁能获得比特币奖励。区块链的数据存在于每个节点的电脑中(节点可以是你我他这样的普通人)。挖矿就是对记账权的争夺,谁算力强解出难题,谁能获得奖励。整个世界历史上产生的所有数据都存在一台普通的家用电脑也许不可思议,但是区块链只存储数据的摘要,比特币区块链至今总大小只有几百 G。另外,对于个人用户来说,如果不从事挖矿,也不需要下载全部数据。个人用户可以进行加密货币交易,或者使用智能合约,而挖矿的任务交给算力强的节点。而个人只需要付给矿工少量加密货币即可。这并不违背去中心化,因为每个人参与区块链建设的机会是均等的,数据也是透明可查的。

区块链各节点之间每隔一小段时间会进行相互通信,看看是否有人篡改数据,并对异常节点做出惩罚,所以,想篡改区块链中的数据是不可能的。所以,区块链不会跑路,不会毁约。

比特币是区块链在货币上的伟大实践,后来人们发现,不只是货币,其他类型数据也可以上链。互联网经过多年发展,也变得和现实一样,形成了寡头统治的局面。国内的比如美团,滴滴等,收取高额提成。于是,以太坊出现了。以太坊相当于把世界上加入区块链的电脑组合成一台超级计算机,而以太坊就是操作系统。以太坊上可以部署智能合约,取代现在的合同(租房,打车,外卖等线上线下的合约)。以太坊的资金一直在快速增长,它是未来。

技术细节

hash 算法在区块链中的体现形式

(算法是演示网站上的,和实际的以太坊算法不一样,但总体原理类似)

现有一种哈希算法,表示为 hashvalue = f(block, nonce, data).其中 hashvalue 是哈希值,block 指区块,nonce 是随机数,data 是要存储的数据。mine这个动作就是要求解出一个随机数,使生成的哈希值开头四位数为 0.

分布式区块链

区块链是一种类似链表的数据结构。相比于区块,区块链中的区块增加了 prev 数据段,记录了上一个区块的 hashvalue,(第一个区块的 prev 为 000...000).每修改其中一个区块的数据,那么在链上的后续区块都要重新求解,这保证了数据篡改成本极高。同时,分布式使每个用户都保存了区块,使得数据修改成为不可能。

数字签名技术

有算法负责根据私钥(密码)生成公钥。然后,私钥可以对数据进行签名,生成的签名发布到公共空间,别人可以用公钥(公开)来判断数据是否来自于你。

招聘系统面试状态爬虫

· 阅读需 6 分钟

这几天学python爬虫,又买了腾讯云服务器,找个项目练练手。找到了一个需求:对招聘进度做一个定时爬取,如果有变化,微信通知我。任务可以分为两大块,一块是代码实现,一块是项目部署。

代码实现

之前手头上有几个moka的api:

https://app.mokahr.com/personal-center/editApplication/xxxxxx?orgId=zte

后来觉得官网上的会更直观:

https://app.mokahr.com/personal-center/applications?orgId=zte

带上cookie,发起get请求,对返回的json数据肉眼解析,找到面试状态字段的位置。如果状态不是‘面试’,或者脚本请求不到数据,通知我。脚本可能会因为cookie过期失效。所以在外层套try/catch进行异常处理。最后,用一个免费的消息推送网站http://wx.xtuis.cn/提供的接口,将消息推送到手机。

# -*- coding: UTF-8 -*- 
import requests
import json

## 中文乱码着实麻烦。将编码方式改为utf-8.
import sys
defaultencoding = 'utf-8'
if sys.getdefaultencoding() != defaultencoding:
reload(sys)
sys.setdefaultencoding(defaultencoding)

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
'Cookie': ''
}

url = 'https://app.mokahr.com/personal-center/applications?orgId=zte'


def sendMessage(token, mydata):
baseurl = "http://wx.xtuis.cn/"
url = baseurl + token + ".send"

data = {
"text": 'zte notification:'+mydata,
"desp": ''
}
requests.post(url, data=data)


if __name__ == '__main__':
try:
res = requests.get(url=url, headers=headers)
# fp = open('zte.json', 'w', encoding='utf-8')
print(res.json())
# json.dump(res.json(), fp, ensure_ascii=False)
app_list = res.json()[0]['candidateApps'][0]['projectApps'][0]['apps']
app = app_list[0]
status = app['stage']
print(status)
except:
status = 'error!'
print('error!')
if status != '面试':
sendMessage('', status)

项目部署

webshell可以直接拖拽上传本地编写好的py文件,很方便。上传后,设置任务调度。核心命令就是linux的crond。参数:前面五个*设置执行时间间隔,后面跟命令。依次输入:

crontab -e ##进入vim编辑
60 * * * * python ../zte.py ##注意要从根目录开始,写绝对路径。一小时执行一次。
crontab -l ##查看任务是否创建

如果py程序中有print语句用于调试等目的,可以打开mail。任务执行的打印语句会出现在mail中。

mail的翻页命令:z

b系统

b系统的招聘系统是自研的。需要登录验证。由于cookie的有效时间很短,用requests麻烦,就用selenium工具,可以和脚本a做出差距。

代码

selenium模仿人操纵浏览器的行为。所以,主要就是找到想要点击的标签。其中,在登录后,要休眠一定时间,使浏览器能加载网页。

# -*- coding:utf-8 -*-

from selenium import webdriver
from selenium.webdriver.common.by import By
from time import sleep
import sys
import requests
defaultencoding = 'utf-8'
if sys.getdefaultencoding() != defaultencoding:
reload(sys)
sys.setdefaultencoding(defaultencoding)

def sendMessage(token, mydata):
baseurl = "http://wx.xtuis.cn/"
url = baseurl + token + ".send"

data = {
"text": '华为招聘:'+mydata,
"desp": ''
}
requests.post(url, data=data)


options = webdriver.ChromeOptions()
options.add_argument('--headless') # 确保无头
options.add_argument('--disable-gpu') # 无需要gpu加速
options.add_argument('--no-sandbox') # 无沙箱
driver = webdriver.Chrome(executable_path="./chromedriver", chrome_options=options) # 添加软链接后是不需要写路径的

if __name__ == '__main__':
try:
options = webdriver.ChromeOptions()
options.add_argument('--headless') # 确保无头
options.add_argument('--disable-gpu') # 无需要gpu加速
options.add_argument('--no-sandbox') # 无沙箱
driver = webdriver.Chrome(executable_path="./chromedriver", chrome_options=options)
driver.get("https://uniportal.huawei.com/uniportal/?redirect=https%3A%2F%2Fcareer.huawei.com%2Freccampportal%2Flogin_index.html%3Fredirect%3Dhttps%3A%2F%2Fcareer.huawei.com%2Freccampportal%2Fportal5%2Fcampus-recruitment.html%3Fi%3D46238")


driver.find_element(By.ID, 'uid').send_keys('')
driver.find_element(By.ID, 'password').send_keys('')
driver.find_element(By.CLASS_NAME, 'login_submit_pwd_v2').click()

sleep(5)

driver.get('https://career.huawei.com/reccampportal/services/portal/portaluser/pro/getResumeLockSatus')
page = driver.page_source
code = driver.find_element(By.TAG_NAME, 'pre').text
if code != '1':
status = '可以撤回'
else:
status = '不变'
except:
status = '请求失败'

print(status)
if status == '可以撤回' or status == '请求失败':
sendMessage('jDn7yKWZELMb1AAKJnGSle8EU', status)

driver.quit()

部署

我没想到部署是更麻烦的一步。首先需要给云服务器安装chrome和chromedriver。

首先安装chrome:

[root@localhost ~]#  cd /ect/yum.repos.d/
[root@localhost yum.repos.d]# vim google-chrome.repo

vim模式编辑文件:

[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
[root@localhost yum.repos.d]# yum -y install google-chrome-stable --nogpgcheck

然后下载chromedriver,并把文件放到脚本所在文件夹:

wget https://npm.taobao.org/mirrors/chromedriver/83.0.4103.14/chromedriver_linux64.zip
unzip chromedriver_linux64.zip

最后一步,运行。因为selenium模块需要python3,centos默认的是python2.想了几种解决办法,包括把默认版本调至python3,用宝塔创建环境。但是最终选择用conda虚拟环境。主要参考的安装教程:链接

切换到conda环境,运行指令python huawei.py,发现lighthouse用户不支持chrome,故sudo python huawei.py. 报错:chromedriver找不到。首先以为是环境和用户的问题,在crontab中添加相关语句也不管用,然后我知道了,代码中的driver路径要用绝对路径。

总结

学爬虫是有强大的正向激励的。之前秋招时经常守着页面刷新查看状态,很焦虑。现在可以放心了。我还要很多想法,比如b站虚拟主播生日会舰长数爬取,并自动制作表格等。

string/array方法总结

· 阅读需 6 分钟

string、array是非常常用的数据类型。对下面的常用方法进行总结。

string

说明:str表示某一个字符串

字符串长度:str.length

查找子字符串位置:str.indexOf(substr).可以接受第二个参数作为起始检索位置。

str.search()功能和indexOf一致,但是search不能接受第二个参数。

slice(), substring(), substr(): slice(切片),三者都接收索引参数,截取子字符串。substr()第二个参数是要截取的字符串长度

replace(): 替换字符串,只替换首个。如果要全局替换,则要用正则表达式。

str.toUpperCase() 转化为大写

concat():concat(连接),和加号一样的功能。可以多加几个concat实现多个字符串拼接。

返回字符的安全方法:str.charAt()。 用 str[0] //h也能返回正确结果,但不安全。

str.split() split(分割) 根据分隔符返回数组

面试题:slice是干嘛的、splice是否会改变原数组

1. slice是来截取的
参数可以写slice(3)、slice(1,3)、slice(-3)
返回的是一个新的数组
2. splice 功能有:插入、删除、替换
返回:删除的元素
该方法会改变原数组

array.splice(index, howmany, item1, ....., itemX)

参数 描述
index 必需。整数,指定在什么位置添加/删除项目,使用负值指定从数组末尾开始的位置。
howmany 可选。要删除的项目数。如果设置为 0,则不会删除任何项目。
item1, ..., itemX 可选。要添加到数组中的新项目。

补充方法

转为字符串

x.toString():转化为字符串 String(x):全局方法,转化为字符串 num.toFixed():数字转化为指定位数的小数字符串

转为数字

parseInt(x): 全局方法,字符串->整数,直接去掉小数部分,类似于Math.floor(x) parseFloat(x): 全局方法,返回浮点数 Number(x): 全局方法,可以将字符串,日期,boolean转化为数字 +'100':一元+运算符,返回数字

其他

x.valueOf():大多数情况下返回变量自身,日期调用本方法会返回毫秒数

array方法总结

arr是某个数组

如何识别数组:因为typeof arr返回Object,所以用Array.isArray(arr)和arr instanceof Array来判断

数组转为字符串:arr.toString() 结果是逗号分隔的字符串。还可以通过arr.join('分隔符')来指定分隔符。

pop,push,shift,unshift:略

想在任意位置删除元素:arr.splice()

合并数组:arr1.concat(arr2)

返回子数组:arr.slice()

数组排序:自带接口:arr.sort() 注意点:1.默认按字符串顺序升序,也就是说数字排序会错。2.为了解决数字排序,需要传入一个参数函数。

查找最大、最小值:数组没有自带的api,需要借助Math对象。

let max = Math.max(...arr)
let min = Math.min(...arr)
注:...arr为扩展运算符

回调函数:其参数为上一步代码获得的结果

数组迭代:为数组的每一个元素执行操作

Array.forEach(callback) (callback为回调函数) forEach不会更改原数组,也不会返回任何东西,它只负责将元素交给回调函数处理。

var txt = "";
var numbers = [45, 4, 9, 16, 25];
numbers.forEach(myFunction);

function myFunction(value, index, array) { // value, index, array来自数组,其中,index, array可省略
txt = txt + value + "<br>";
}

Array.map(callback): 通过对每个数组元素执行函数来创建新数组。不改变原数组,返回新数组

var numbers1 = [45, 4, 9, 16, 25];
var numbers2 = numbers1.map(myFunction);

function myFunction(value, index, array) {
return value * 2;
}

filter() 方法创建一个包含通过测试的数组元素的新数组。

var numbers = [45, 4, 9, 16, 25];
var over18 = numbers.filter(myFunction);

function myFunction(value, index, array) {
return value > 18;
}

arr.indexOf()

其他数据类型(日期等)

创建日期:new Date(str) str可省,则为当前时间

方法:getTime():时间戳,毫秒值

Math类: Math.random() 0-1的随机数,小数点后17位

注:Array/String/Boolean可以用new的方式创建,但是没有意义且可能会带来错误,因为new出来数据类型都是对象

注:typeof的结果:

NaN 的数据类型是数字 数组的数据类型是对象 日期的数据类型是对象 null 的数据类型是 object 未定义变量的数据类型为 undefined * 未赋值的变量的数据类型也是 undefined *

跨域问题描述和解决方案

· 阅读需 3 分钟

什么是跨域

跨域是浏览器对js脚本和ajax请求做出的限制。不同源的js脚本获取对方的cookie,ajax无法发送跨域请求。

解决方案

CORS

在服务器设置字段Access-Control-Allow-Origin。当浏览器收到服务器返回的资源上,没有Access-Control-Allow-Origin字段,就会拒绝加载

jsonp

jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

问题描述:调试阶段,前端项目请求后端接口返回数据,前端项目和后端的端口不一致,产生了跨域问题。部署阶段,前端打包完毕,项目发起请求,同样有跨域问题,两种场景的解决方法不同。

下面解决问题的场景是,打包的前端文件并不在服务器的文件路径中。

调试阶段

后端可以不用设置,只要前端设置代理服务器即可。在vue根目录下的vue.config.js添加下述代码:

module.exports = ({
devServer: {
proxy: 'http://localhost:3000' //3000为后端接口的端口号
},
publicPath: './' //解决打包后的静态文件路径问题
})

跨域只存在浏览器中,服务器之间不存在跨域,所以可以通过服务器代理

部署阶段

vue根目录下建立两个配置文件:.env.development.env.production

//.env.development
VUE_APP_BASE_API='http://localhost:3000'
VUE_APP_ENV='dev'
//.env.production
VUE_APP_BASE_API='http://localhost:3000'
VUE_APP_ENV='pro'

在请求代码处,设置判定条件,若当前调试环境,则url=/home, 若为打包环境,则url为全文

let api = null;
if (process.env.VUE_APP_ENV === 'dev') {
api = '/home'
} else {
api = process.env.VUE_APP_BASE_API + '/home'
}

最后,express要设置cors允许跨域

router.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});

初学vue2踩坑记

· 阅读需 6 分钟

跟着 b 站黑马程序员的视频入门 vue,跟敲代码过程中,踩到一些坑,在这里记录一下:

  • 定义方法时,是methods而不是method
  • {{}}叫做插值表达式

  • 写计数器案例时用到了 flex,发现下面这种换行不是 flex 认可的换行,align-content无效:

<span>{{num}}</br>{{num}}</span>
  • v-bind bind,绑定的意思,就是给元素绑定属性。首先,用简写形式:[name]="content"。然后绑定类名属性class时,最简单的写法是,:class="{[name]:isActive}",也就是切换类。 v-for 有一个性能优化问题。实时绑定的数据,在数据量大的情况下,计算复杂大,所以,vue 在使用了 v-for 的标签里添加了一个 key 属性,并将其用 v-bind 绑定,属性值应该是一个不重复的值,在数据更新时,判断是否时新的 key, 然后渲染。不宜用索引值,因为在数组中间插入时,很多数据的 key 会更新。

  • v-model 双向绑定表单元素的值。那么常见的表单元素及其值有哪些呢?

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。

官网文档上的举得例子挺多,感觉这里是比较容易犯错的。

  • <input type=''>

顺便复习一下 input 表单元素。看到以下类型能想起他们作用和浏览器中的样式即可。 text/password/radio/checkbox/button


下面是做视频中的项目时遇到问题的记录

  • 如何将输入框的内容双向绑定?

原以为input需要声明一个值作为输入内容,实际上 v-model 自己就可以将输入值和data中的变量绑定。所以应该先创建data中的变量,然后绑定。

  • 悬浮在 item 时,显示删除符合的 css

原本想的是先用行内样式将删除模块设为display:none。后来发现不起效。原因是行内样式的优先级最高,<style>标签里的 css 会被覆盖。所以把上述代码放到了<style>里,成功。

  • 如何设置主体高度随条目数量增大而增大,并在超过某一限度时设置滚动

直接上代码:

.items {
max-height: 420px;
overflow: auto;
}

附上overflow中 scroll 和 auto 属性的区别:

二者都会在内容超出范围时显示滚动条,但在元素没有超出时,auto 会隐藏滚动条,scroll 依旧显示滚动条,但是禁用该滚动条。


一起记录下 vuecli 的入门学习过程。

  • babel:将 es 较新版本的规则编译成较低的,来让浏览器运行

  • npm run serve后,生成的两个地址中,有一个网络地址,可以通过其他在同一局域网下的设备访问

  • package.json中,script下包含了项目的启动方式。除了start外,都要写成npm run ...的形式。

  • 开发环境和生产环境:开发环境包含了很多库,还要.vue后缀文件,不能被浏览器解析。所以,执行 webpack(vuecli 自带)打包,npm run build命令后,变成 html\css\js 的生成环境。

  • .gitignore说明了哪些文件在 git 上传中被忽略

  • .vue文件中的<template>标签下。只能用一个 html 根标签。一个组件就包含了 html\js\css.

  • main.js和 vue 组件里的 js 代码什么关系? index.html直接执行的 js 文件只有main.js,而main.js导入了其他组件编写的 js 代码的接口。

  • 组件注册分为全局(很少用)和局部。局部组件三步走:引入+注册+使用。注册完毕后,就可以将整个组件以 html 标签的形式插入到 app.vue 中。

  • style标签里面添加scoped可以令样式只作用于当前组件,是通过为组件标签添加 hash 值属性实现的。

  • slot是什么?用于组件之间的嵌套。在组件 a 标签内部,插入组件 b 标签,并在组件 b 标签中添加 slot 属性及 id。然后在组件 a 内添加<slot>标签,name 属性是 id。即可实现精确的占位。

package.json 里面的 devDependencies 和 dependencies 分别是什么?

配置项命令描述
devDependencies--save-dev 简写 -D开发环境,管理的依赖包仅在开发阶段有效
dependencies--save 简写 -S生产环境,管理的依赖包在项目上线后依然有效