2022年3月20日——公告存网站页面到数据库
文件存储
- 文件夹不允许出现
/\:*?|<>"
- a标签中href不能有, 因为通过etree.tostring会被转义成%5C===>进行了str.replace("\", “/”)
- windows下路径分隔符是
\\
, 所以save_path中会有\\
, 因此使用save_path.replace("\\", "/")
可以解决 - 多级创建文件夹
os.makedirs()
、单层创建os.mkdir()
5文件名称问题: 乱码太长报错, 将其取a标签中内容解决
1 | # 乱码太长报错 |
数据库表
varchar能存多少汉字、数字?
具体还是要看版本的,一个字符占用3个字节 ,一个汉字(包括数字)占用3个字节=一个字符
- 4.0版本以下,varchar(100),指的是100字节,如果存放UTF8汉字时,只能存33个(每个汉字3字节)
- 5.0版本以上**,varchar(100),指的是100字符,⭐️无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放100个。
- UTF8编码中一个汉字(包括数字)占用3个字节
- GBK编码中一个汉字(包括数字)占用2个字节*
varchar的最大长度是多少呢?
mysql的vachar字段的类型虽然最大长度是65535,但是并不是能存这么多数据,最大可以到65533,其中需要1到2个字节来存储数据长度(如果列声明的长度超过255,则使用两个字节来存储长度,否则1个)字节,当不允许非空字段的时候(因为要用一个字节来存储不可为空的标识),当允许非空字段的时候只能到65532(省下了存储非空的那个字节)。
mysql字段类型存储需要多少字节?
数字类型
列类型 | 需要的存储量 |
---|---|
TINYINT | 1 字节 |
SMALLINT | 2 个字节 |
MEDIUMINT | 3 个字节 |
INT | 4 个字节 |
INTEGER | 4 个字节 |
BIGINT | 8 个字节 |
FLOAT(X) | 4 如果 X < = 24 或 8 如果 25 < = X < = 53 |
FLOAT | 4 个字节 |
DOUBLE | 8 个字节 |
DOUBLE PRECISION | 8 个字节 |
REAL | 8 个字节 |
DECIMAL(M,D) | M字节(D+2 , 如果M < D) |
NUMERIC(M,D) | M字节(D+2 , 如果M < D) |
日期和时间类型
列类型 | 需要的存储量 |
---|---|
DATE | 3 个字节 |
DATETIME | 8 个字节 |
TIMESTAMP | 4 个字节 |
TIME | 3 个字节 |
YEAR | 1 字节 |
串类型
列类型 | 需要的存储量 |
---|---|
CHAR(M) | M字节,1 <= M <= 255 |
VARCHAR(M) | L+1 字节, 在此L <= M和1 <= M <= 255 |
TINYBLOB, TINYTEXT | L+1 字节, 在此L< 2 ^ 8 |
BLOB, TEXT | L+2 字节, 在此L< 2 ^ 16 |
MEDIUMBLOB, MEDIUMTEXT | L+3 字节, 在此L< 2 ^ 24 |
LONGBLOB, LONGTEXT | L+4 字节, 在此L< 2 ^ 32 |
ENUM(‘value1’,‘value2’,…) | 1 或 2 个字节, 取决于枚举值的数目(最大值65535) |
SET(‘value1’,‘value2’,…) | 1,2,3,4或8个字节, 取决于集合成员的数量(最多64个成员) |
MySQL中类型后面的数字含义
形式:类型(m)
- 整数型的数值类型已经限制了取值范围,有符号整型和无符号整型都有,而M值并不代表可以存储的数值字符长度,它代表的是数据在显示时显示的最小长度,当存储的字符长度超过M值时,没有任何的影响,只要不超过数值类型限制的范围。当存储的字符长度小于M值时,只有在设置了zerofill用0来填充,才能够看到效果,换句话就是说,没有zerofill,M值就是无用的。
- 字符型如varchar(50) 可以储存50个字符,表示的是可变不定长的。
mySQL默认字符集
MySQL对于字符集的指定可以细化到一个数据库,一张表,一列,应该用什么字符集。 但是,传统的程序在创建数据库和数据表时并没有使用那么复杂的配置,它们用的是默认的配置,那么,默认的配置从何而来呢?
- 编译MySQL 时,指定了一个默认的字符集,这个字符集是 latin1;
- 安装MySQL 时,可以在配置文件 (my.ini) 中指定一个默认的的字符集,如果没指定,这个值继承自编译时指定的;
- 启动mysqld 时,可以在命令行参数中指定一个默认的的字符集,如果没指定,这个值继承自配置文件中的配置,此时 character_set_server 被设定为这个默认的字符集;
- 当创建一个新的数据库时,除非明确指定,这个数据库的字符集被缺省设定为character_set_server;
- 当选定了一个数据库时,character_set_database被设定为这个数据库默认的字符集;
- 在这个数据库里创建一张表时,表默认的字符集被设定为 character_set_database,也就是这个数据库默认的字符集;
- 当在表内设置一栏时,除非明确指定,否则此栏缺省的字符集就是表默认的字符集;
最终create.sql文件
1 | SET NAMES utf8mb4; |
2022年3月20日-护士题目下载存Word
索引报错
- MySQL 添加索引报错:
BLOB/TEXT column used in key specification without a key length
- 当我们对一个名称为platform的字段,类型为 text 添加unique唯一性约束和索引约束时,会报错。 原因:MySQL只能将BLOB/TEXT类型字段设置索引数据的前N个字符,因此,只需要通过sql在增加索引时指定对应字段的长度即可,如:
ALTER TABLE hello_world ADD INDEX key1(platform(250), platform2(250), type);
, 其中,platform 和 platform2 就是 text 类型的数据 - 根本原因: 错误发生的原因是因为MySQL只能将BLOB/TEXT类型字段设置索引为BLOB/TEXT数据的前N个字符,因此错误常常发生在字段被定义为TEXT/BLOB类型或者和TEXT/BLOB同质的数据类型,如TINYTEXT,MEDIUMTEXT,LONGTEXT ,TINYBLOB,MEDIUMBLOB 和LONGBLOB,并且当前操作是将这个字段设置成主键或者是索引的操作。在未指定TEXT/BLOB‘键长’的情况下,字段是变动的并且是动态的大小所以MySQL不能够保证字段的唯一性。因此当使用TEXT/BLOB类型字段做为索引时,N的值必须提供出来才可以让MySQL决定键长,但是MySQL不支持在TEXT/BLOB限制,TEXT(88)是不行的。
- 解决方案是将unique限制和索引从TEXT/BLOB字段中移除,或者是设置另一个字段为主键,如果你不愿意这样做并且想在TEXT/BLOB上加限制,那么你可以尝试将这个字段更改为VARCHAR类型,同时给他一个限制长度,默认VARCHAR最多可以限定在255个字符,并且限制要在声明类型的右边指明,如VARCHAR(200)将会限制仅仅200个字符.(注: 但是mysql不支持对TEXT/BLOB长度的限制。)
- https://blog.csdn.net/u012069924/article/details/28858337
▲. 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。Java 开发手册 33/44 说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来确定。
- 当我们对一个名称为platform的字段,类型为 text 添加unique唯一性约束和索引约束时,会报错。 原因:MySQL只能将BLOB/TEXT类型字段设置索引数据的前N个字符,因此,只需要通过sql在增加索引时指定对应字段的长度即可,如:
数据库插入数据
1 | -- \\表示一个\ |
操作word
使用
python-docx
:python -m pip install python-docx
, https://python-docx.readthedocs.io/en/latest/
- 添加1-9级标题
1 | from datetime import datetime |
- 添加段落
1 | from datetime import datetime |
- 设置字体大小和样式
1 | from datetime import datetime |
1 | Doc = Document() |
from : https://www.pythonheidong.com/blog/article/692569/99875f167810b45f17e8/
- 有序(无序)列表和引用
1 | # 增加引用 |
- 表格和分页
1 | # 增加图片(此处使用相对位置) |
提取文字
1 | from docx import Document |
from :
- https://www.cnblogs.com/rainbow-tan/p/14981998.html
- https://www.cnblogs.com/wenshi-jj/p/15388808.html
- https://www.cnblogs.com/rencm/p/6285304.html
- https://blog.csdn.net/weixin_44576802/article/details/86552550——颜色
- 参考:https://www.cnblogs.com/wenshi-jj/p/15388808.html——页边距、页眉和页脚、分栏
共同点处理
文件保存
由于文件夹、文件名不允许有
/\:*?|<>"
,因此需要保存时如果出现这些字符得特别处理。
- 项目一:
xxxx阻垢剂\反渗透阻垢剂\25KG询价书询价公告\zxc.doc
==>xxx阻垢剂、反渗透阻垢剂、25KG询价书询价公告\zxc.doc
,存储在attaches\xxx阻垢剂、反渗透阻垢剂、25KG询价书询价公告\zxc.doc
1 |
|
- 项目二:
'.\\docx/专科题库\\儿科\\[书籍]\\S-《实用临床护理三基-"应知应会"》—名词解释、简答(规02)\\something.docx'
===>'.\\docx/专科题库\\儿科\\[书籍]\\S-《实用临床护理三基-'应知应会'》—名词解释、简答(规02)\\something.docx'
1 | def format_file_path(path: str) -> [bool, str]: |
附录
lxml库的使用
-
提取Element中最近标签中的文本(不包含标签本身):
ele.xpath('//*[@id="xxx"]/text()')
-
提取Element中所有标签(标签嵌套)中的文本(不包含标签本身):
ele.xpath('//*[@id="xxx"]/string(.)')
- 注:跟
//text()
区别在于,string(.)
的结果为合并后的str,//text()
为未合并的list
- 注:跟
-
提取Element中所有内容(标签本身):
etree.tostring(ele, method="html")
-
修改Element某一属性:
1
2
3
4
5
6appendixs = html_content.xpath('/html/body/div[4]/div/div[4]/div/div/div[1]/div[4]/p/a')
for a in appendixs:
href = a.xpath("@href")[0]
save_path = self.download_file(ReUtil.extra_announcement_id(detail_article_url), href, title)
# html中会对\进行转义
a.attrib["href"] = save_path注: 修改子element的某属性后,root节点tostring的结果中element也会被修改。(root抱有element的引用)
-
Element的XML标记名通过对象的属性访问
1
2
3
4
5
6
7
8
9
10a = '<a href="http://baidu.com">hh<span>gg</span></a>'
res = {_Element: 1} <Element html at 0x1de3a6b9800>
attrib = {_Attrib: 0} {}
base = {NoneType} None
nsmap = {dict: 0} {}
prefix = {NoneType} None
sourceline = {int} 1
tag = {str} 'html'
tail = {NoneType} None
text = {NoneType} None -
Element被组织在XML树结构中。增加子Element并指定它们的父Element,使用append方法。
1
2
3
4
5# 法一
root = etree.Element("root")
root.append(etree.Element("child1"))
# 法二
childNode = etree.SubElement(root, "child") -
删除Element下某一节点内容,与添加相同,也有实例方法和工厂方法两种
-
parentnode.remove(node)
-
etree.strip_elements(html, 'element_name', with_tag=True/False)
-
timeit
模块使用
1 | spider = HuaNengSpider(ifend="in") |
1 | run_time = timeit.timeit('HuaNengSpider(ifend="notin", is_in_one=True).run_with_concurrent()', |
Python中操作SQLAlchemy,SQLAlchemy中文技术文档
参考:https://www.jianshu.com/p/0ad18fdd7eed、python3 SQLAlchemy模块使用
ArgumentParser
使用
-
parser.add_argument("-o", "--is_in_one", action="store_true", help="数据存放在一张表中")
, 其中action的store_true
表示,如果出现–is_in_one(action)则设置未true, 所以默认为False- 参数设置为True、False的最好不使用choice参数:因为
parser.add_argument("-o", "--is_in_one", choices=[True, False], help="数据存放在一张表中")
, choice中为True、False时, 可以选择不填,此时is_in_one为None, 如果必须要这两个则选一个则加上required=True
选项- 加上required效果===>
爬虫脚本: error: argument -o/--is_in_one: invalid choice: 'q' (choose from True, False)
, 但是如果输入True同样会报错: 爬虫脚本: error: argument -o/–is_in_one: invalid choice: ‘True’ (choose from True, False)
所以只有
choices=["True", "False"]
才能生效, 好在的是Python中对true判断比较宽容,下面三种都可以,因此使得choice传str类型的True也可以成功判定,即能实现相同效果,但是还是存在些歧义的,得谨慎使用1
2
3
4
5
6
7
8
9a = "true"
b = True
c = "True"
if a:
print("a yes")
if b:
print("b yes")
if c:
print("c yes")
其他SQL文件
1 | CREATE TABLE `auto_bilibili` ( |
2022年3月26日——小鸡词典
抓取指定词条信息
需求:将包含英文的词条信息保存到excel文件中
难点:
- 由于不断下滑都会产生数据, 因此难以评估数据量
思路:通过搜索a-z英文字母,从而找出所有包含英文字母的单词,由于搜索结果的数量是有限的,因此可以确定是否完成。 随着而来的问题是,如果词条为apple,那么搜a/p/l/e都会重复抓取==>防重思路: 通过词条id来判断是否已经爬取过
Lookup:
-
数据库中create_time既可以指定为DATETIME也可以设置为TIMESTAMP,而对应的在SQL中位db.DateTime、TIMESTAMP
▲. 由于不需要对excel中样式进行修改,所以为了省事,最终直接采用了入库导出Excel的方式,而没用使用xlwings库
日志输出两次
原因是多个Py文件中引入了LOG
, 但引入方式不一样,如main.py中是from logger import LOG
, 而spider.py中是from logger import LOG
,
saver.oy中是from jikipedia.logger import LOG
▲ 问题就出在main中导入了spider.py, 而spider又引用了saver.py,但是saver中引用的LOG由于导入方式不同,使得跟前两个的使用的LOG不是同一个,但LOG配置都是一样的,所以导致了输出多次。
pytest库使用
pip install pytest
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有 init 方法
- 测试函数以test_开头
- 断言使用基本的assert即可
pytest 夹具(fixture)
测试需要在一组已知对象的背景下进行。 这组对象称为测试夹具(fixture)。
1 |
|
综合测试
接下来,我们展示如何在 Python 包中运行测试。
- 首先把将所有的测试文件全部放在
./tests/
文件夹下
1 | setup.py |
1 |
|
pytest tests/
执行文件、文件夹
- pytest test_mod.py 执行该模块下的测试类测试方法
- pytest testing 执行该文件夹下的所有模块
- pytest test_cgi.py::test_answer,执行test_answer方法
- pytest test_cgi.py::TestMyClass::test_one,执行TestMyClass类下的test_one方法
more: 在 PyCharm 里使用 Pytest 测试框架
requests异常
ReadTimeout: 意思是已经建立连接,并开始读取服务端资源。如果到了指定的时间,没有可能的数据被客户端读取,则报异常。
出现的情况是在设置了timeout设置为3秒,服务器在3秒内未给出响应,出现报错requests.exceptions.ReadTimeout
。 ConnectTimeout:
意思是用来建立连接的时间。如果到了指定的时间,还没建立连接,则报异常。
附录
导入问题:
在tests/中如果要引工程根目录的文件比较麻烦,得通过相对路径from ..utils import xxx
的方式, 但是这样必须把再加上sys.path.append("..")
,针对这种情况,一个解决方案是所有模块都从根工程为绝对路径开始引,比如main.py中引settings为from jikipedia.settings import xxx
,
而tests中引也是同样from jikipedia.settings import xxx
==>pycharm中使用alt+enter提示后使用的import也是从根目录导入from jikipedia.xxx import yyy
注: 这样需要把工程根目录作为包,所以得在根目录加上__init__.py
文件
脚本中相对路径问题
由于settings.py会直接在同级的根目录下创建attaches文件夹,但是如果直接是mkdir("./attaches")
的话,tests/中只要间接导入了settings.py都会在tests/下创建attaches,因为此时的脚本路径是在tests/下,所以settings中的可执行mkdir(.)
就变成了在tests/.
所以这边应该指定绝对路径, 下面是一个通过__file__
获得脚本绝对路径,然后再获得其所在文件夹的做法:
DOWNLOAD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "attaches")
DOWNLOAD_PATH的值被赋值成了F:\aDevelopment\Python\ShowYourCode\jikipedia
从而解决了这个问题
redis使用
特点:
1.基于内存的key-value数据库
2.基于c语言编写的,可以支持多种语言的api //set每秒11万次,取get 81000次
3.支持数据持久化
4.value可以是string,hash, list, set, sorted set
连接redis服务器
- 本地命令行进入redis:
redis-cli
- 远程连接:
redis-cli -h host -p port -a password
命令参数:
key
- keys * 获取所有的key
- select 0 选择第一个库
- move myString 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动
- flush db 清除指定库
- randomkey 随机key
- type key 类型
- set key1 value1 设置key
- get key1 获取key
- mset key1 value1 key2 value2 key3 value3
- mget key1 key2 key3
- del key1 删除key
- exists key 判断是否存在key
- expire key 10 10过期
- pexpire key 1000 毫秒
- persist key 删除过期时间
string
- set name cxx
- get name
- getrange name 0 -1 字符串分段
- getset name new_cxx 设置值,返回旧值
- mset key1 key2 批量设置
- mget key1 key2 批量获取
- setnx key value 不存在就插入(not exists)
- setex key time value 过期时间(expire)
- setrange key index value 从index开始替换value
- incr age 递增
- incrby age 10 递增
- decr age 递减
- decrby age 10 递减
- incrbyfloat 增减浮点数
- append 追加
- strlen 长度
- getbit/setbit/bitcount/bitop 位操作
hash
- hset myhash name cxx
- hget myhash name
- hmset myhash name cxx age 25 note “i am notes”
- hmget myhash name age note
- hgetall myhash 获取所有的
- hexists myhash name 是否存在
- hsetnx myhash score 100 设置不存在的
- hincrby myhash id 1 递增
- hdel myhash name 删除
- hkeys myhash 只取key
- hvals myhash 只取value
- hlen myhash 长度
list
- lpush mylist a b c 左插入
- rpush mylist x y z 右插入
- lrange mylist 0 -1 数据集合
- lpop mylist 弹出元素
- rpop mylist 弹出元素
- llen mylist 长度
- lrem mylist count value 删除
- lindex mylist 2 指定索引的值
- lset mylist 2 n 索引设值
- ltrim mylist 0 4 删除key
- linsert mylist before a 插入
- linsert mylist after a 插入
- rpoplpush list list2 转移列表的数据
set
- sadd myset redis
- smembers myset 数据集合
- srem myset set1 删除
- sismember myset set1 判断元素是否在集合中
- scard key_name 个数
- sdiff | sinter | sunion 操作:集合间运算:差集 | 交集 | 并集
- srandmember 随机获取集合中的元素
- spop 从集合中弹出一个元素
zset
- zadd zset 1 one
- zadd zset 2 two
- zadd zset 3 three
- zincrby zset 1 one 增长分数
- zscore zset two 获取分数
- zrange zset 0 -1 withscores 范围值
- zrangebyscore zset 10 25 withscores 指定范围的值
- zrangebyscore zset 10 25 withscores limit 1 2 分页
- Zrevrangebyscore zset 10 25 withscores 指定范围的值
- zcard zset 元素数量
- Zcount zset 获得指定分数范围内的元素个数
- Zrem zset one two 删除一个或多个元素
- Zremrangebyrank zset 0 1 按照排名范围删除元素
- Zremrangebyscore zset 0 1 按照分数范围删除元素
- Zrank zset 0 -1 分数最小的元素排名为0
- Zrevrank zset 0 -1 分数最大的元素排名为0
- Zinterstore
- zunionstore rank:last_week 7 rank:20150323 rank:20150324 rank:20150325 weights 1 1 1 1 1 1 1
排序:
- sort mylist 排序
- sort mylist alpha desc limit 0 2 字母排序
- sort list by it:* desc by命令
- sort list by it:* desc get it:* get参数
- sort list by it:* desc get it:* store sorc:result sort命令之store参数:表示把sort查询的结果集保存起来
订阅与发布:
- 订阅频道:subscribe chat1
- 发布消息:publish chat1 “hell0 ni hao”
- 查看频道:pubsub channels
- 查看某个频道的订阅者数量: pubsub numsub chat1
- 退订指定频道: unsubscrible chat1 , punsubscribe java.*
- 订阅一组频道: psubscribe java.*
redis事物:
- 隔离性,原子性,
- 步骤: 开始事务,执行命令,提交事务
-
multi //开启事务
-
sadd myset a b c
-
sadd myset e f g
-
lpush mylist aa bb cc
-
lpush mylist dd ff gg
服务器管理
- dump.rdb
- appendonly.aof
- //BgRewriteAof 异步执行一个aop(appendOnly file)文件重写, 会创建当前一个AOF文件体积的优化版本
- //BgSave 后台异步保存数据到磁盘,会在当前目录下创建文件dump.rdb
- //save同步保存数据到磁盘,会阻塞主进程,别的客户端无法连接
- //client kill 关闭客户端连接
- //client list 列出所有的客户端
- client setname myclient1 //给客户端设置一个名称
- client getname
- config get port
- //configRewrite 对redis的配置文件进行改写
rdb
- save 900 1
- save 300 10
- save 60 10000
aop备份处理
- appendonly yes 开启持久化
- appendfsync everysec 每秒备份一次
命令:
- bgsave异步保存数据到磁盘(快照保存)
- lastsave返回上次成功保存到磁盘的unix的时间戳
- shutdown同步保存到服务器并关闭redis服务器
- bgrewriteaof文件压缩处理(命令)
使用场景
- 去最新n个数据的操作
- 排行榜,取top n个数据 //最佳人气前10条
- 精确的设置过期时间
- 计数器
- 实时系统, 反垃圾系统
- pub, sub发布订阅构建实时消息系统
- 构建消息队列
- 缓存
Redis在互联网公司一般有以下应用:
- String:缓存、限流、计数器、分布式锁、分布式Session
- Hash:存储用户信息、用户主页访问量、组合查询
- List:微博关注人时间轴列表、简单队列
- Set:赞、踩、标签、好友关系
- Zset:排行榜
MYSQL中时间类型设置
- create_time:设置数据类型为:TIMESTAMP,默认值为:
CURRENT_TIMESTAMP()
- modify_time:设置数据类型为:TIMESTAMP,默认值为:
CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()
1 | CREATE TABLE `base_name`.`table_name` ( |
除了TIMESTAMP以外,也可以设置为DATETIME(0),对于在SQLAlchemy中为TIMESTAMP和DateTime类型
IP代理池
爬虫和反爬虫是一对矛和盾,反爬虫很常见的一个方法就是封IP,一个IP短时间内频繁访问,可以做限流或者是加入黑名单。针对这种情况,我们可以采用IP代理池+随机UA的方式来规避。
此外爬虫是一种IO密集型程序,如果全程单线程执行那会很慢,因此可以用多线程来提高数据采集效率,不过自己管理多线程太麻烦,所以可以选择线程池。
代理池
一个完善的代理池,应该可以实现以下功能
- 批量采集代理(或者通过接口导入我们购买的代理,不过偶尔用一用还是免费的就好)
- 采集到之后自动验证代理有效性
- 将有效代理存储起来
- 提供获取随机代理的接口、提供管理(删除、增加)代理的接口
使用代理池
之前在就看到过崔庆才书中介绍的开源代理池,但没仔细研究,本次搜索过后又发现了一个在GitHub上有14k+ Stars的代理池来用,名字叫ProxyPool
。
官方文档提供了两种部署方式,包括下载代码运行和docker,既然有docker那肯定选最方便的docker啦!
不过官方的docker命令还不够方便,因为这个代理池还需要依赖Redis服务,因此可以用docker-compose配置来用:
1 | version: "3" |
接口信息:
1 | { |
获取随机代理
封装获取随机代理和删除代理的操作后就可以使用了
1 | import requests |
from: https://www.cnblogs.com/deali/p/15890678.html
添加代理装饰器
1 | # @author: Mrli |
Author: Mrli
Link: https://nymrli.top/2022/03/19/2022年3月19日-20日爬虫项目记录/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.