豪翔天下

Change My World by Program

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
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
# 字符判断
string.isalpha() # 是否为字母
string.isdigit() # 是否为数字
string.isalnum() # 是否为数字或字幕
string.islower() # 是否都是小写
string.isupper() # 是否都是大写
string.istitle() # 是否都首字母大写
string.isspace() # 是否都是空白字符

# 格式化字符串
'abcdef %s' % (123) # 特别适合长字符串不用加号来拼接字符串的情况
"my name is {name}".format(name=name) # 这种方式好处是可以直接定义名字
"my name is {} at {}".format(name, address) # 这种方式可以在少数变量的情况下偷一下懒
"{1} {0} {1}".format("hello", "world") # 可以指定位置
f'my name is {变量名}' # Python3.6里面新增的特性,可用这种方式直接格式化字符串
f'{name=}' # python3.8开始直接这样可以输出name=value形式的字符串
f'{{}}' # 直接插入大括号
my_dict = {'name': '...', 'addr': '...'}
'my name is {name} at {}'.format(**my_dict) # 直接解析字典的参数
my_list = {'name', 'addr'}
'my name is {0[0]} at {0[1]}'.format(my_list) # 直接解析列表的参数
''.join(['a','b']) # 合并字符串列表并增加分隔符,需要注意的是列表中必须为字符串,如果是数字需要强制转换一下

# 格式化数字
'{:.2f}'.format(3.1415926) # 3.14, 保留两位小数

# 指定列宽格式化字符串
import textwrap
print(textwrap,fill(s, 70)) # 将s字符串已70列显示,多的换行
os.get_terminal_size().columns # 可以使用这个方法获得终端的大小和尺寸

# 字符串填充,数字前补零
a = 'abc'
print(a.zfill(5)) # 输出'00abc'

# 去掉空格
s.strip() # 去掉两端空白
s.lstrip() # 去掉左边空白,加参数可以
s.rstrip() # 去掉右边空白
s.replace(' ', '') # 去掉所有空白
s.removeprefix('.png') # py3.9,删除后缀
s.removesuffix('abc') # py3.9,删除前缀

# 大小写转换
s.upper() # 全部转为大写
s.lower() # 全部转为小写

# json格式去掉冒号后的空格
json.dumps(string, separators=(',', ':')) # 默认的分隔符是(', ', ': ')
# json格式输出中文而不是unicode字符串
json.dumps(string, ensure_ascii=False)

# url编码与解码
from urllib import parse
parse.unquote(url) # url解码
result = parse.urlparse(url) # url解析
query_dict = parse.parse_qs(request.query) # 获取查询参数
query_dict.get('field', []) # 获取指定参数

# 进制转换
binascii.b2a_hex(string.encode('utf-8')) # 字符串转16进制

''.join(random.sample(string.ascii_letters + string.digits, 10)) # 生成随机字符串

查找与替换

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
# startswith
str.startswith(str, beg=0, end=len(string))
str.endswith(str)

# 统计子字符串出现次数
str.count('sub_str')

# 字符串替换
import re
text = 'Today is 03/16/2023'
pat = re.compile(r'(\d+)/(\d+)/(\d+)')
pat.sub(r'\3-\1-\2', text)
'Today is 2016-11-27'

>>> text = 'Today is 03/16/2023, tomorrow is 03/17/2023'
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text)
'Today is 2023-03-16, tomorrow is 2023-03-17'

# 带命名组的替换
re.sub(r'<a.*?>(.*?)</a>','\g<1>', text) # 替换a标签,但保留a标签里面的内容,需要注意的是.*表示最长匹配,而.*?表示最短匹配。添加参数flag=re.IGNORECASE表示不区分大小写

# 对替换做特殊处理
print(re.sub('(?P<value>\d+)', lambda matched: str(int(matched.group('value')) * 2), s))

# 使用正则方式查找
import re
url = 'http://haofly.net/note.html'
match = re.search('(.*)/(.*?).html', a)
match = re.search('(.*)/(.*?).html', a, re.IGNORECASE) # 不区分大小写的正则匹配
print(match.group(1), match.group(2))

# 基本查找,返回第一个出现该字符串的位置
text.find(',')

# 查找某字符串出现的所有位置的一个列表
[m.start() for m in re.finditer(',', text)] # 输出[4, 11]

# 忽略大小写的查找
re.findall('python', 'text, flags=re.IGNORECASE)

# 查找所有匹配的
matches = re.findall('pattern', string) # 返回所有匹配的列表

# 最短匹配
str_pat = re.compile(r'\\"(.*?)\\"')
str_pat.findall(text2) # 输出['no.', 'yes.']

# 查找时中文编码问题
re.search('中文(.*?)呵呵'.decode('utf8'), string)

# 字符串分割-正则方式
line = 'asdf fjdk; afed, fjek, asdf, foo'
import re
re.split(r'[;,\\s]\\s_', line) # ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
re.split(r'(;|,|\\s)\\s_', line) # 这样连分隔符都能分割出来
re.split('(==|>=|<=|>|<)', 'requests>1.2.3') # 多个字符的解析

时间处理

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
# 简单的获取时间:
import datetime
a = str(datetime.date.today())
print(a) # 格式为2015-07-17
a = time.strftime('%H:%M:%S') # 格式为11:20:00
time.strftime('%Y-%m-%d %H:%M:%S')

# 获取当前时间时间戳
time.time()

# 字符串转时间:
time_str='Tue, 11 Nov 2014 06:37:20 +0000'
date = datetime.datetime.strptime(time_str, '\%a, \%d \%b \%Y \%H:\%M:\%S \%z')
print(date) # 输出'2014-11-11 06:37:20+00:00'
print(date.timestamp()) # 输出时间戳'1415687840.0'
# 或者
date = datetime.datetime(2006, 12, 12, 12, 12, 12)

# 获取当天开始和结束的时间(即00:00:00到23:59:59)
today = datetime.date.today()
datetime.datetime.combine(today, datetime.time.min)
# 得到datetime.datetime(2015, 7, 24, 0, 0)
datetime.datetime.combine(today, datetime.time.max)
# 得到datetime.datetime(2015, 7, 24, 23, 59, 59, 999999)

# 时间加一天,加一分钟,昨天,明天,前面几天,后面几天
now = datetime.datetime.now()
date = now + datetime.timedelta(days = 1)
date = now + datetime.timedelta(seconds = 3)

# 关于时间占位符总结:
%d:日
%b:简写的月份,如Oct
%Y:年份
%H:小时
%m:月
%M:分钟
%S:秒
%z:与时区相关,在标准时间上加时间,例如'+00:00'

# 各种格式举例
time.strftime('%Y-%m-%dT%H:%M:%S%z') # 2015-11-11T02:49:03+00:00

# 转换时间为UTC/GMT时间
time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(time.mktime(time.strptime("2008-09-17 14:04:00", "%Y-%m-%d %H:%M:%S"))))

# 时间戳的转换:
ltime=time.localtime(1395025933)
timeStr=time.strftime("\%Y-\%m-\%d \%H:\%M:\%S", ltime)

string = '2015年09月18日 00:01:00'
date = time.strptime(string, '\%Y年\%m月\%d日 \%H:\%M:\%S')
b = time.mktime(date) # 获取时间戳

# datetime转时间戳
time.mktime(the_date.timetuple())

# 时间戳转datetime
datetime.datetime.fromtimestamp(1234567890)

# 获取本月有多少天,以及最后一天的计算方法
import calendar
today = datetime.date.today()
_, last_day_num = calendar.monthrange(today.year, today.month)
last_day = datetime.date(today.year, today.month, last_day_num)

# 计算间隔时间
begin = datetime.datetime(2015, 3, 14, 23, 59, 59)
today = datetime.datetime.today()
interval = today - begin
interval.seconds() # 时间差多少秒
interval.days # 相差多少天,对应的.seconds表示相差多少秒,小时等同理

编码问题

  • 2中打印str显示前面加了个u且中文类似\u8be5\u9879:这是十六进制的Unicode编码,使用string.encode('utf-8')进行转换

  • 2中类似\uiahd\u9483这样的字符串:需要注意的是,该字符串本来就是这样,而不是编码成这样的,这时候需要反编码:string.decode('unicode_escape'))

  • 2中无法输出中文: 无论是str还是unicode还是encode('utf-8')都无法输出中文,可以试试在print的时候不带括号: print a,但是print一个对象依然不行,可以单独打印某个字段

  • 无法解析\u2c这样的unicode字符,出现错误UnicodeDecodeError: 'unicodeescape' codec can't decode bytes in position 0-3:truncated \uXXXX escape: 原因是unicode默认是\uxxxx这样的形式来解析字符串的,但是如果出现\u2c这种,是解析不了的,应该写成\u002c这种形式,前面需要补全

  • UnicodeDecodeError: ‘ascii’ codec can’t decode byte: 可以试试unicode(string, 'utf-8)

  • url编码 Python3中,url编码放在了url lib.parse中了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # python3
    from urllib import parse
    parse.quote(str) # urlencode
    parse.quote_plus(str)
    parse.unquote(str) # urldecode
    parse.encode() # 把字典转换为query的方式

    # python2
    urllib.urlencode(dict)
    urllib.quote(str)
    urllib.unquote(str) # urldecode
  • bytes to string/字节转字符串

    1
    b"abcde".decode('utf-8')
  • string to bytes/字符串转字节

    1
    str.encode("abcfc")
  • 将字符串输出为16进制字节:

    1
    2
    3
    4
    ":".join("\{:02x\}".format(ord(x) for x in 字符串))
    # 或
    ":".join("\{0:x\}".format(ord(x) for x in 字符串))
    # 输出类似于: 12:45:45
  • 16进制转换为utf-8 :类似 \xe5\x94\xae\这种,使用如下方式进行转换

    1
    2
    3
    4
    5
    6
    7
    # 方法一
    unicode(string, 'utf-8')

    # 方法二
    a = u'xb3\xe5'
    b = array('u', a).tostring()[::2].decode('gbk')
    print(b)
  • base64编码和解码

    1
    2
    3
    import base64
    a = base64.b64encode(s)
    b = base64.b64decode(a)
  • gb2312字符串转换为utf-8

    1
    data.encode('latin1').decode('gb2312')
  • 大端/小端

    Python使用struct.pack和struct.unpack来将数据封装成大端/小端的字节流,例如struct.pack('>h',14)表示将14封装成大端模式

  • 查看字符编码

    1
    2
    import chardet
    chardet.detect(string)

TroubleShooting

  • “TypeError: Unicode-objects must be encoded before hashing”

    原因是在3.x中,md5方法仅接受unicode编码过后的字符串:

    1
    hashlib.md5(string.encode('utf-8')).hexdigest()

Github Tools

rclone

云存储命令行工具,支持Google Drive, Amazon Drive, S3, Dropbox, Backblaze B2, One Drive, Swift Hubic, Cloudfiles, Google Cloud Storage, Yandex FIles

Python

如果要使用pip安装最新版本可以在后面加上版本号

backoff

一个支持代码重试机制的装饰器

BeautifulSoup4

XML/HTML解析组件

better-exceptions

能够将异常打印得非常直观好看,并且能显示某些具体的值

coverage

代码覆盖率检测工具

django.contrib.syndication.views

Django自带的输出feed的工具

django.core.paginator

这是Django自带的分页工具,非常实用

django-avatar

Django头像插件

django-extensions

Django的扩展包的包,带有非常方便的一些工具,比如自动打印sql语句等。

django-debug-toolbar

Django的调试工具集,包含了很多的调试及性能优化工具,应该非常好用,未使用过

django-haystack

Django的全文搜索功能

django-redis

在Django中使用Redis必备。需要注意的是,它对value做了序列化,而且在key前面加入了版本号,类似_:1:key,_而且,默认生存时间是300秒,需要加入参数_cache.set(“key”, “value”, timeout=None)_。Redis密码的格式应该是 “LOCATION”: “redis://:密码内容@104.236.170.169:6379/1″,真的服了官网那不明不白的表述了

django-rest-framework

Django的Restful框架

django-social-auth

Django社会化认证工具

django-socketio

Django的WebSockets ,好爽

django-wysiwyg

Django使用wysiwyg作为富文本编辑器

dh-virtualenv

Python部署工具,弃用pip,而是将package打包成Debian packages的形式,自动解决各种依赖问题

difflib

Python自带模块,比较文本之间的差异,且支持输出可读性强的HTML文档

dpart

Spark的Python实现,分布式任务处理

fuzzywuzzy

计算字符串相似率

hashids:

将整数转换为hash值,并且支持反解,这不仅仅是Pythond的一个库,而且支持几十种语言。可用于将后台生成的唯一ID转换成混淆的hash值。

httpstat:

在命令行打印CURL请求的详细信息

IPy

IP地址处理模块,可用于计算大量的IP地址,包括IPv4、IPv6网段、网络掩码、广播地址、子网数、IP类型等。参考文章

jieba(官方文档)

结巴中文分词,未使用过

lunardate

获取农历

memory_profiler

能够分析每行代码每个变量的内存使用量,用于优化效率

MkDocs

项目文档工具,以markdown的方式攥写spinx烈性的文档

**MoviePy**:Python处理视频文件

MRQ: Python的分布式worker任务队列,使用Redis和gevent。既有RQ那样简单,又有Celery的性能。,具有强大的用户面板,可以控制队列中的任务、当前任务、workder的状态,并且能按任务区分日志。

mysqlclient: Python3链接MySQL/Mariadb数据库的库,相比于官方的库以及众多其他第三方库,这个库虽然只有一个人在维护开发,但是Pypi的权重值有9,而且Github一直有更新。在安装的时候需要先安装依赖:sudo apt-get install python-dev libmysqlclient-dev,Python3要加3,windows下可以直接安装,如果是OS X,那么可能是没有将mysql添加到环境变量,在_.profile_做如下修改

1
2
3
4
PATH="/Library/Frameworks/Python.framework/Versions/3.5/bin:/usr/local/bin/python3:/usr/local/mysql/bin:${PATH}"
export PATH
DYLD_LIBRARY_PATH="/usr/local/mysql/lib/${DYLD_LIBRARY_PATH}"
export DYLD_LIBRARY_PATH
paramiko(官方文档)

基于Python2/3实现的SSH2的库,支持认证及密钥方式,可以实现远程命令执行、文件传输、中间SSH代理等功能。windows安装的时候会有依赖问题,可见这个issue

pep8

PEP8规范检测工具,使用时直接pep8 ./

Pillow

Python图像处理库,与PyLab互斥,只能安装一个哟

progressBar2

在终端显示进度条

psutil

跨平台的获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息的库,主要用于系统监控,分析和限制系统资源及进程的管理。实现了一些命令行的工具(如:ps、top、lsof、netstat、ifconfig、who、df、kill、free、nice、ionice、iostat、iotop、uptime、pidof、tty、taskset、pmap等)

pyautogui

跨平台的python自动化模拟输入模块,能够模拟鼠标和键盘

pyspider

有图形界面的爬虫程序

python-nmap

使用Python实现的端口扫描工具

random-avatar

直接生成指定大小的随机头像,是按照你的IP来计算的

requests

(官方文档),比SSL和HttpResponse更加高级,更方便,一句话就可以搞定人家几十句的功能,非常方便

SaltStack

基于Python开发的一套C/S架构配置管理工具,底层使用ZeroMQ消息队列pub/sub方式通信,使用SSL整数签发的方式进行认证管理。而Ansible基于ssh协议传输数据,所以SaltStack普遍被认为比Puppet快,缺点是需要安装客户端。

SciPy

Python科学计算库

stackoverflow

直接通过关键字从stackoverflow上面抓去来作为一个工具函数,黑科技

xpinyin

汉字拼音

Python-GUI

Camelot
Cocoa
GTk
Kivy

跨平台,完全免费

PyObjC

仅仅OS X可用,但是也非常方便

PyQT

跨平台,但商业使用需要商业许可证

PHP

clockwork

可以直接在浏览器里面查看性能的性能调试工具(有个坑是如果你用的是其他会修改route规则的插件,那么必须保证能访问/__clockwork才能使用)

config

轻量级的配置文件读取工具,支持PHP/INI/XML/JSON/YAML文件

guzzle

requests更好用的请求库,已经放弃requests库了,更新很慢,无法上传文件,目测作者也已经放弃这个库了,已经没有回复PR了。。。

jsonmapper

自动将JSON对象转换为相应的类对象,相当于Java里面的bean

PhpSms

可能是目前最聪明、优雅的php短信发送哭了。从此不再为各种原因造成的个别发送失败而烦忧。。。。

Go

logrus

比自带的log好用得多的日志库

Java

retrofit

Java里面非常好用的HTTP client,用起来显得十分简洁,简化了HTTP请求

JS/Jquery

Awesomplete:jQuery的联想次插件,必须异步加载哟,例如:
1
2
3
4
5
6
7
8
<script type="text/javascript">
$(function(){
var input = document.getElementById("myinput");
var awesomplete = new Awesomplete(input);
awesomplete.list = ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE","Node.js" , "Ruby on Rails"];

});
</script>
BootSideMenu

Bootstrap隐藏滑动侧边栏jQuery插件,虽然不大好看,依赖还有点多,不过好用

bootstrap-select

基于Bootstrap和jQuery的下拉选择输入列表插件

clipboard.js

纯HTML5实现的复制到粘贴板的插件

DataTables

表格插件,几乎涵盖了所有想要的功能,定制化非常强

editor

一个十分漂亮的markdown编辑器

fingerprintjs

浏览器唯一性解决方案

fullpage.js

全屏插件

lightslider

图片平滑滚动插件

hotkeys

无任何依赖的键盘事件捕获插件

jquery-notebook

简洁的网页编辑器

simditor

彩程设计的wysiwyg类型的编辑器

Smoothzoom

简单的图片点击放大组件

three.js

有太多酷炫的效果了(webgl)

unslider

用过最好用的图片轮播插件,而且用起来也特简单

wysihtml

十分强大的网页编辑器,但是文档几乎没有,上面有Django版本

PHP

Carbon

各种时间处理

laravel-5-markdown-editor

Laravel5 Markdown编辑器

PHP Debug Bar

方便调试,可以直接在浏览器里面看到变量信息,而不用var_dump()了

python requests模块

一直以来我都看不惯python自带的urllib包的繁琐的使用方法,所以我都使用的requests包来代替原生方法。它能方便的发送GET和POST请求,支持HTTPS,基本上能模拟人类真实的访问。

发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 动态method
requests.request(method, url, ...)

# GET请求
response = requests.get(url, params={}, timeout=5, allow_redirects=False) # allow_redirects表示是否允许重定向,默认为True
r = requests

# POST请求
r = requests.post(url, data=参数字典)

# json请求,需要注意的是,如果data不显示设置为json数据,那么服务端收到的是application/x-www-form-urlencoded格式,无论你是否设置header头
r = requests.post(url, json=data) # 这样可以直接将字典作为参数
r = requests.post(url, data=json.dumps(data), headers={'content-type': 'application/json'})
r = json() # 获取json响应

r = requests.post(url, verify=False) # 禁用https的验证

设置请求

1
2
requests.get(url, timeout=30)	# 设置连接超时时间为多少秒,不是响应时间
requests.get(url, timeout=(10, 30)) # 第一个是连接时间,第二个是等待响应的时间

自定义HTTP头

1
2
3
4
headers = {
'User-Agent': '注意名称'
}
requests.get(url, headers=headers)

会话对象

以这种方式可以跨请求保持某些参数,不用再自己提取上一次请求的信息了,比如cookie等,但是需要注意的是,即使使用了会话,方法级别的参数并不会跨请求保持,如果要跨方法,可以使用with

1
2
3
4
5
6
s = requests.Session()
s.get(url)
r = s.get(next_url)

with requests.Session() as s:
s.get(url)

设置代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# HTTP代理
proxies = {
"http": "http://127.0.0.1:8118",
"https": "http://127.0.0.1:8118",
}
# socks代理
## 安装依赖pip install requests[socks]
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}

requests.get("http://google.com", proxies=proxies)


上传文件

类似这样的post请求,就是把文件和要发送的字段一起发送上去的

1
2
3
4
5
6
7
8
9
10
11
12
13
-----------------------------241652170216373
Content-Disposition: form-data; name="value_1"

12345
-----------------------------241652170216373
Content-Disposition: form-data; name="value_2"

67890

--273f13699c02429db4eb95c97f757d38
Content-Disposition: form-data; name="value_2"; filename="value_2"

67890

上传方式

1
2
3
4
5
r = S.post(url, files={
'file_0': open('filename', 'rb'), # 这里的字段名一般都取file_0,然后顺序下去
'file_1': open('filename2', 'rb'),
'其他字段': (None, '字段值')
})

获取响应

1
2
3
4
5
6
7
8
response.text	# 获取编码后的响应文本信息
response.json() # 获取json响应
response.content # 直接获取响应的二进制信息
response.cookies # 获取响应cookie对象
response.status_code # HTTP status,http状态码
response.raise_for_status() # 当响应出错,即为4xx或者5xx的时候直接抛出requests.RequestException错误
response.encoding # 获取网页的编码方式,需要注意的是,requests参照的严格http协议标准写的,如果响应中的Content-Type字段没有设置charset,那么即使网页标签中有明确是utf-8编码也是会自动设置成ISO-8859-1编码的。此时只需要下面这样修改编码即可
response.encoding = 'utf-8' # 直接将response设置为指定的编码,最好只针对ISO-8859-1进行这样转换,因为其他有声明编码的用这种方式可能反而获取不到正确的值

重定向

1
2
# 如果直接请求,如果发生重定向,那么response.status_code = 200, response.history = 301
print([x for x,y in A.__dict__.items() if type(y) == FunctionType])

下载文件

1
2
3
4
5
6
7
8
# 小文件,直接吸入
open('filename', 'wb').write(r.content)

# 大文件,分片写入
r = requests.get('https://haofly.net/test.mp4', stream=True)
with open('filename.mp4', 'wb') as fd:
for chunk in r.iter_content(1024):
fd.write(chunk)

TroubleShooting

  • 设置最大重试次数
    之前发现设置了timeout时间却没反应,原来是因为查询不到ip地址,导致在timeout时间内就已经默认在重试了,要设置就得先执行语句requests.adapters.DEFAULT_RETRIES=5

  • 无法获取httponly的cookie信息
    通过headers.get('Set-Cookie')获取cookies字符串,然后通过正则匹配来查找

  • SSLError(SSLEOFError(8, ‘EOF occurred in violation of protocol (_ssl.c:1038): 检查代理ssl的代理设置是否正确,可以取消全局代理,直接用proxies参数设置代理试试

  • 发送https请求出现警告: Suppress InsecureRequestWarning: Unverified HTTPS request is being made

    这是因为在发送https的时候未使用证书进行认证,如果一定要关闭这个警告添加这样的语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # ways 1
    import requests
    from requests.packages.urllib3.exceptions import InsecureRequestWarning

    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

    # ways 2
    import urllib3
    from urllib3.exceptions import InsecureRequestWarning
    urllib3.disable_warnings(InsecureRequestWarning)
  • **user-agent列表**

    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
    user_agent_list = [  
        'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19',
        'Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
        'Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
        'Mozilla/5.0 (Android; Mobile; rv:14.0) Gecko/14.0 Firefox/14.0',
        'Mozilla/5.0 (Android; Tablet; rv:14.0) Gecko/14.0 Firefox/14.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0',
        'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0',
        'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0',
        'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
        'Mozilla/5.0 (Linux; Android 4.1.2; Nexus 7 Build/JZ054K) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36',
        'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) CriOS/27.0.1453.10 Mobile/10B350 Safari/8536.25',
        'Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)',
        'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
        'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
        'Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)',
        'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.9.168 Version/11.52',
        'Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.229 Version/11.62',
        'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+',
        'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
        'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
        'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
        'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
    ]

Scrapy

Scrapy可以说是Python爬虫界最著名的一个框架了,在我看来也是最全的一个框架,当然优缺点也是很明显的:

  • 文档太多,有很多几分钟就能上手的例子
  • Scrapy默认将已经抓取过的和队列中的请求都存储在内存中,不过可以使用JOBDIR将进度持久化
  • Scrapy本身不支持分布式,要支持分布式,需要依靠scrapy-redis,但是该库很久没人维护了,不过逻辑比较简单,可以自己造轮子
  • Scrapy要抓取Js生成的页面,需要使用其他的工具来辅助,比如Splash,Selenium等,现在(2023)我更推荐scrapy-playwright
  • Scrapy可以通过设置CONCURRENT_REQUESTS设置并发的线程数量,默认是16,另外一个控制是CONCURRENT_REQUESTS_PER_DOMAIN默认是8。两个变量都是有作用的,并发有多大,程序就会开多少个子线程。当然,具体怎么执行还是得看CPU,例如,在4核8线程上面,同时仅有8个线程在运行(对于Python来说,其实仅有一个线程),超过的线程,基本上属于等待唤醒的状态,等那8个线程执行完毕或者遇到IO阻塞的时候才会被唤醒。这一点,对于网络延迟很大的任务非常有用,不用再所有线程去等待了。

在学习scrapy的过程中,如果有看源码的兴趣,建议顺便看看scrapy-redis,虽然该项目很少维护,但是却非常有利于搞懂scrapy框架。

限制

  • Cloudflare我现在没找到什么更好的方法,但是有一个ZenRows提供了付费服务,小贵,但是确实有用,并且API很简单

基本框架

新建项目,scrapy startproject test目录结构如下(mac里面没有把scrapy命令放到bin里面去,直接搜索命令所在地吧):

1
2
3
4
5
6
7
8
9
10
11
.  
├── scrapy.cfg
└── ProjectName
  ├── __init__.py
  ├── items.py   # 定义items
  ├── pipelines.py # 定义items的处理器
  ├── settings.py   # 全局社会自
  └── spiders     # 爬虫文件
      ├── __init__.py
    ├── 一个爬虫
    └── 又一个爬虫

常用命令

1
2
3
4
5
6
7
8
9
scrapy startproject test    # 创建项目
scrapy genspider haofly haofly.net # 新建爬虫
scrapy list # 列出当前project所有的spider
scrapy bench # 基准测试,测试当前硬件的情况下最大的抓取速度
scrapy check # scrapy自身的单元测试,很多人都不建议用,很难用,而且没没什么作用

scrapy crawl haofly --loglevel=critical # 开始一个爬虫,设置日志级别
scrapy crawl haofly --output=output.json # 开始某个爬虫,output参数会把抓取到的item保留到文件中去
scrapy crawl zhaopin -s JOBDIR=crawls/zhaopni-1 # -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
BOT_NAME   # 定义项目名称
ITEM_PIPELINES = {
'project.pipelines.PostPipeline': 300, # 处理获取到的Items的各种方法,会按照后面数值从小到大的顺序依次处理
}
EXTENSIONS = {
'scrapy.extensions.throttle.AutoThrottle': 0, # 定义扩展程序,AutoThrottle表示自动限制频率的扩展,还要在下面继续配置其参数
}
SPIDER_MIDDLEWARES = { # 中间件
'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50, # 处理非200状态码的中间件
# 'youtube.middlewares.MyCustomSpiderMiddleware': 543,
}

# 配置AutoThrottle插件,会根据爬取的网站的负载自动限制爬取速度,不开的话默认的下载延迟就是0。通过自动调节,可以自动调节并发数和下载延迟。
AUTOTHROTTLE_ENABLED=True  # 是否开启自动限制频率,不开简直太恐怖了
AUTOTHROTTLE_START_DELAY=1 # 以秒为单位,默认为5
AUTOTHROTTLE_MAX_DELAY=10 # 默认为60
AUTOTHROTTLE_DEBUG=False

CONCURRENT_REQUESTS=16 # 全局并发线程的数量
CONCURRENT_REQUESTS_PER_DOMAIN=8 # 针对某一个域名的最大并发量
CONCURRENT_ITEMS=100 # 同时处理的Items的最大值
REACTOR_THREADPOOL_MAXSIZE=20 # 线程池,主要是为了减少创建销毁线程的开销

COOKIES_ENABLED=False # 是否开起cookie
LOG_LEVEL='DEBUG' # LOG级别,在下面介绍了log的几种级别,默认级别是INFO

REDIRECT_ENABLED=False # 禁止重定向
RETRY_ENABLED=False # 关闭重试

DUPEFILTER_DEBUG = True # 打开dupefilter的debug

爬虫主体

当一个请求被yield以后,会立马添加到队列中去;当线程空闲的时候则会从队列中取出;然后经过middleware中间件对request进行处理,最后发起真正的请求。

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
import scrapy
from scrapy import Request

class TestSpider(scrapy.Spider):
name = 'test'
allowed_domains = ['test.com']
start_urls = ['https://www.test.com/']
handle_httpstatus_list = [301] # 捕获非200的响应,301表示允许重定向。HTTPERROR_ALLOW_ALL = True表示捕获所有

def start_requests(self):
"""生成初始请求"""
self.crawler.stats.set_value('key', 'value') # 可以自定义给爬虫设置全局的状态字段
self.crawler.stats.inc_value('my_custom_count') # 还能设置自增字段
self.crawler.stats.max_value('my_custom_count') # 还能设置最大值或最小值min_value(多线程依然能实现)
yield Request(url, self.parse, meta={})

def parse(self, respone):
yield item

def parse_another(self, response):
# 需要注意的是,所有的回调函数,要么返回item list,要么返回request list,如果什么都不返回,例如,直接写了个self.another_func(...),如果后面没有yield方法,那么该函数并不会执行
self.parse_page(response) # 即使你在parse_page里面返回了yield,该函数也不会执行,最好这样
for item in self.parse_page(response):
yield item

def closed(self, reason): # 爬虫结束的hook,正常结束reason都是"finished"
print(self.crawler.stats.get_stats()) # 获取爬虫的状态信息,就是最后的总结

请求与响应

1
2
3
4
5
6
7
8
request.meta['proxy'] = 'http://xxx.xxx.xxx.xxx:2333'	# 给meta设置proxy字段则会添加代理

response.body # 获取响应的body
response.body_as_unicode() # 获取响应编码后的内容
response.requests # 获取相应的请求
response.status # 获取HTTP状态码
response.url # 获取请求的URL
response.headers # 获取请求头

网页解析

1
respons.css('li')	# css选择器 

Items

Item只是对爬取结果对象的一个简单封装,提供了简洁的语法。需要注意的是,在pipeline里面处理item的时候必须return item否则,其他的item会接受不到,而且会打印一个莫名其妙的None,终端也不会显示抓取到的item,也就不将讲item输出到文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# items.py中对item进行定义
import scrapy
class ResultItem(scrapy.Item):
name = scrapy.Field()
url = scrapy.Field()

# 在爬虫里面可以这样使用
def parse(self, response):
item = scrapy.Items()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').extract()
item['description'] = response.xpath('//td[@id="item_description"]/text()').extract()
yield item

# item的常用方法
post['name']
post.get('name')
post.get('name', 'no value')
post.keys()
post.items()
dict(post) # 将Item转换为字典
Post(dist) # 从字典创建Item

Pipeline

处理item的管道,用来处理爬虫抽取到的数据,可以在这里面进行数据的验证和持久化等操作。要使用pipeline,必须在设置里面将ITEM_PIPELINES的注释取消掉

1
2
3
4
5
6
7
8
class TestPipeline(object):
def open_spider(self, spider): # 爬虫开始时候执行
print('open')
self.session = DB_Session()

def close_spider(self, spider): # 爬虫结束时候执行
print('close')
self.session.close()

Middleware中间件

可以直接在middlewares.py中进行定义

DownloadMiddleware下载中间件

所有的请求在yield Request的时候被加入队列,当调度器空闲的时候则取出来,然后以此被下载中间件进行处理(即process_request),最后发起真正的请求。需要注意的是,process_request是每个中间件顺序执行的,但是process_response则是每个中间件倒序执行的。

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
class MyMiddleware(object):
process_request(self, request, spider):
"""
重试的时候并不会进入这里面
:return None: 返回None或者无返回,则会继续处理该请求,继续执行被接下来的中间件处理
: Response: 返回Response,则会继续处理该请求,但是不会被其他中间件处理,相当于不请求了直接给一个响应
Request: 返回Request,则不会继续处理该请求,相当于新建了一个请求,重新来
:raise IgnoreRequest: 将会调用process_exception
"""
pass

process_response(self, request, response, spider):
"""
所有的有http状态码的响应都会到这里来
:return Response: 将会继续往下执行
Request: 该链条会终端,重新来一个请求
:raise IgnoreRequest:
"""
pass

process_exception(self, exception, spider):
"""
所有的没有http状态码的异常
:return None: 将会继续处理
Response: 返回一个正常的Response
Request: 重新来一个请求
"""
pass

Signal信号

scrapy内部类似于事件触发的机制,通知某件事情发生了。

1
2
3
4
5
6
7
8
9
10
11
engine_started()	# 当Scrapy引擎启动爬取时发送该信号
engine_stopped() # 当Scrapy引擎停止时发送该信号
item_scraped() # 当item被爬取,并通过所有Pipeline后(没有被丢弃(dropped),发送该信号
item_dropped()
spider_closed()
spider_opened()
spider_idle(spider) # spider处于空闲时候的信号
spider_error
request_scheduled(request, spider) # 当引擎调度一个Request对象用于下载时,该信号被发送
response_received(resopnse, request, spider)
response_downloaded(response, request, spider)

日志用法

日志有几种级别,默认是DEBUG,会打印所有的信息,可以在配置文件中进行配置,也可在抓取命令上指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 日志打印,日志的几种级别,默认是DEBUG,抓取的时候可指定log级别,也可在配置文件中配置
CRITICAL: 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)

# 如果在spider里面使用,可以直接
self.logger.critical('发生严重错误')

# 如果在其他地方,可以这样用
import logging
logger = logging.getLogger()
logger.warning("This is a warning")

###暂停与继续

scrapy提供了简单的方法以便于程序意外终止或者主动停止,仅需要在运行爬虫的时候添加参数,用于指定队列状态存储目录,scrapy会将序列化后的队列状态存储在该目录中

1
2
3
4
scrapy crawl somespider -s JOBDIR=crawls/somespider-1

# 如果想让某个url允许出现重复,那么可以给Request这个参数dont_filter。如果返回200,但是还是想重新请求,想让该url不会被filter掉,那么可以直接生成一个相同的request将dont_filter设置为True就行了
yield Request(url=url, dont_filter=True)

Telnet

scrapy运行的时候会打开一个6023端口,用于实时查看爬虫当前的进度。直接telnet 127.0.0.1 6023即即可进入

1
2
3
4
5
6
7
8
9
10
# est()命令
time()-engine.start_time: 总的执行时间
len(engine.downloader.active): 正在下载的请求数量
len(engine.slot.inprogress): 当前处理进程数量
len(engine.slot.scheduler.mqs): 当前还在排队的请求数量,被yield以后就被放到这里面
len(engine.scraper.slot.queue):
len(engine.scraper.slot.active): 当前等待处理的响应的书俩昂
engine.scraper.slot.active_size: 所有的响应总的大小
engine.scraper.slot.itemproc_size: 有多少个item等待被处理
engine.scraper.slot.needs_backout()

其他语法

1
2
3
4
5
6
7
8
9
10
11
12
# 元素选择可使用xpath和css方式,但一般都用css方式方便直观点,比如
response.css('div.no-txt-box p.tit) # 获取符合条件的元素的列表 response.css('p.class::text)[0].extract() # 获取p元素的内容

# 将item传递到下一级请求中去
yield Request(url, callback=..., meta={'item': item})
item = response.meta['item']

# 处理最开始的请求,生成初始url列表
def start_requests(self):
"""产生种子url"""
for url in start_urls:
yield Request(url, self.parse)

##TroubleShooting

  • 安装出错libffi

    1
    2
    3
    No package 'libffi' found
    c/_cffi_backend.c:13:17: fatal error: ffi.h: No such file or directory
    #include <ffi.h>

    需要安装这个sudo apt-get install libffi-dev

  • ImportError: No module named twisted.internet或者No module named twisted
    执行pip3 install twisted,如果出现错误No matching distribution found for Twisted那么就是系统存在多个python版本导致找不到解压twisted包的库,这时候需要先安装sudo apt-get install bzip2 libbz2-dev,然后重新安装python3即可

  • 无法捕获除200以外的错误
    首先,像上面的配置文件中添加HttpErrorMiddleware中间件,然后在spider里面定义需要捕获哪些错误

    1
    2
    class MySpider(CrawlSpider):
    handle_httpstatus_list = [404]
  • 安装出错no module named w3lib.http
    pip install w3lib

# 打印日志方法
from scrapy import log
log.msg("lalalala", level=log.INFO)

暂停与继续

# 要使用暂停与继续功能,必须编写通用爬虫才能,也就是说Spider继承自CrawlSpider而不是BaseSpider,BaseSpider仅仅会抓取start_urls里面的
  • 处理不同的item
# pipeline处理不同的items
if isinstance(item, FeedItem)  # 这种判断方式感觉好鸡肋

# 在pipeline中丢弃items不再处理


raise DropItem("Duplicate item found.")
1
2
3
4
# 指定需要捕获的html状态
handle_httpstatus_list = [404, 502]

# no module

/tmp/xmlXPathInitipwvpamp.c:1:26: 错误:libxml/xpath.h:没有那个文件或目录

User-Agent列表

上次爬一个代理网站发现返回521错误,排查了好久居然发现是User-Agent错误,可我明明之前也是选择了不同的User-Agent的呀,难道服务器会记录一个IP对应一个User-Agent.这里罗列一下常用的User-Agent。更全的列表可以参考user-agent-list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'Mozilla/5.0 (Android; Tablet; rv:14.0) Gecko/14.0 Firefox/14.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:21.0) Gecko/20130331 Firefox/21.0',
'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 6_1_4 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) CriOS/27.0.1453.10 Mobile/10B350 Safari/8536.25',
'Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 5.2)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
'Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)',
'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.9.168 Version/11.52',
'Opera/9.80 (Windows NT 6.1; WOW64; U; en) Presto/2.10.229 Version/11.62',
'Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+',
'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13',
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27',
'Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
'Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3',
'Mozilla/5.0

安装方法

CentOS使用包的方式安装最新MariaDB,CentOS安装client直接yum install mysql而不是client,而安装mysql则直接用yum install -y mysql mysql-server mysql-dev mysql-devel,CentOS7上已经用mariadb代替了mysql,这样子使用:

1
2
3
4
5
6
7
8
9
10
yum install mariadb-server mariadb-client mariadb-devel -y
systemctl start mariadb.service # 启动服务
systemctl enable mariadb.service # 开机启动

# 彻底删除mysql
sudo systemctl stop mysql
sudo apt-get purge mysql-server mysql-client mysql-common mysql-server-core-* mysql-client-core-*
sudo rm -rf /var/lib/mysql
sudo rm -rf /etc/mysql
sudo deluser mysql && sudo delgroup mysql

另外,更新方式可以参考这篇文章: 如何更新到MariaDB 10.4

Ubuntu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装最新版本mariadb,需要先导入对应的镜像库https://downloads.mariadb.org/mariadb/repositories
sudo apt-get install mariadb-server mariadb-client libmariadbd-dev

## 安装mysql,可以使用https://dev.mysql.com/downloads/repo/apt/的方式
wget https://dev.mysql.com/downloads/repo/apt/mysql-apt-config_0.8.15-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.15-1_all.deb # 会进入版本选择界面,选择正确的版本,然后ok
sudo apt-get update && sudo apt-get install mysql-server即可

# 如果是开发,还需要安装
sudo apt-get install libmariadb-client-lgpl-dev
sudo ln -s /usr/bin/mariadb_config /usr/bin/mysql_config

# 第一次登录使用
sudo mysql # mysql8可以直接这样进入然后设置密码
sudo mysql -u root
阅读全文 »

安全测试

测试清单

对于用户可以控制的数据进行过滤或者转义处理

  • 包括get、post、cookie、header、数组下标等内容
  • 字符串必须对单引号进行转移处理,至少有”<”, “>”, “‘“, “””

    身份认证和会话管理

  • 加密的会话ID需要具有时效性,避免永不过期
  • 使用复杂的加密方式避免伪造
  • 避免每次生成同样的会话ID

    跨站脚本(XSS)

    功能级访问控制缺失

  • 对于每个功能的访问,需要明确授予特定角色的访问权限(在展示、修改、删除的时候必须检查信息的所有者和操作中是否一致,展示不同用户数据的时候尽量避免url参数可被遍历)

    跨站请求伪造(CSRF)

  • 关键请求必须增加来路判断
  • 关键请求尽量增加token校验(Token要足够随机,Token是一次性的)

    服务安全规范

  • 禁止外网服务使用默认配置
  • 禁止web应用通过IP方式访问
  • 禁止公网IP开放80和443以外的端口
  • 禁止.svn/.git/log等敏感文件发布到服务器,所有代码上传必须配置好过滤器
  • 禁止所有web应用目录写权限,如业务需要写权限必须禁止直行权限
  • 禁止数据库及非关系数据库服务器开放外网端口访问

代码审查清单

自己的代码审查清单,可以根据此来写测试

code方面

  • 代码能否正常工作,各个功能是否能得到正确的结果
  • 代码是否符合当前语言的编码规范,Python使用PEP8
  • 是否存在多余的代码和注释
  • 代码是否尽可能模块化了
  • 循环是否设置了终止条件
  • 是否有可删除的日志或调试代码

安全

所有的数据输入是否都进行了检查(检测正确的类型,长度,格式和范围)并且进行了编码?
在哪里使用了第三方工具,返回的错误是否被捕获?
输出的值是否进行了检查并且编码?
无效的参数值是否能够处理?

文档

是否有注释,并且描述了代码的意图?
所有的函数都有注释吗?
对非常规行为和边界情况处理是否有描述?
第三方库的使用和函数是否有文档?
数据结构和计量单位是否进行了解释?
是否有未完成的代码?如果是的话,是不是应该移除,或者用合适的标记进行标记比如‘TODO’?

测试

代码是否可以测试?比如,不要添加太多的或是隐藏的依赖关系,不能够初始化对象,测试框架可以使用方法等。
是否存在测试,它们是否可以被理解?比如,至少达到你满意的代码覆盖(code coverage)。
单元测试是否真正的测试了代码是否可以完成预期的功能?
是否检查了数组的“越界“错误?
是否有可以被已经存在的API所替代的测试代码?

全平台通用软件

  • Android Stuido:Android开发专用IDE
  • Bitwarden: 全平台还免费的密码管理工具
  • 百度云:把平时产生的垃圾文件都扔上面,重要信息以及其他文件加密上传
  • Docker:虚拟容器
  • FileZilla:有家庭免费版,FTP工具
  • Google Chrome: 最好的网页浏览器,没有之一
  • QQ
  • intelliJ IDEA(付费版本): 全栈开发必备,还能用来管理数据库
  • Postman: 最好用的http测试工具
  • SourceTree: Git项目管理工具
  • Sublime Text:编辑器,现在基本只用于快速打开大文本文件以及快速格式化文本文件用
  • VirtualBox:虚拟机应用
  • 微信
  • Wireshark:免费的抓包应用
阅读全文 »

正则表达式这个东西确实有点复杂,要是每次都从头写挺麻烦的,所以就把平时会用到的都记录到这儿。

  • 在线正则表达式测试以及性能分析工具:https://regex101.com/

  • 有一个搜索正则非常棒的方法,直接在google输入类似regex email等内容,出来的第一条会是谷歌提供的格式化的搜索结果。

  • 正则表达式引擎的实现方式:

    DFA自动机(Deterministic Final Automata确定型有穷自动机): 时间复杂度是线性的,更加稳定,但是功能有限

    NFA自动机(Non deterministic Finite Automaton不确定型有穷自动机): 时间复杂度不稳定,有时很好有时很差,大多数编程语言使用的是NFA,由于不稳定,所以对于复杂的语句我们有时候需要优化一下。

  • 正则匹配是一个CPU密集型的任务,如果出现CPU被占满性能依然不够的情况,可以用上述性能分析网站看是否有灾难性回溯的出现,如果没有,可以考虑换一种更适合业务场景的算法,例如敏感词过滤推荐使用DFA算法,最后,实在不行,只有提高CPU了。

字符 说明
^ 匹配字符串开始的位置
$ 匹配字符串结束的位置
* 零次或多次匹配前面的字符或子表达式,例如,zo*匹配z和zoo
+ 一次或多次匹配前面的字符或子表达式,例如zo+匹配zo和zoo但不匹配z
? 零次或一次匹配前面的字符或子表达式,例如zo?匹配z和zo但不匹配zoo
\w 匹配所有非特殊字符
\W 匹配所有特殊字符
[] 包括里面的字符
[^] 排除里面的字符
[^\W] 排除所有的非特殊字符
[\w]+或\w+ 匹配数字、字母以及下划线(即非特殊字符)
[\w-+]+ 匹配数字、字母、下划线以及-、+
{n} 刚好匹配n次,例如 o{2}与”Bob”中的”o”不匹配,但与”food”中的两个”o”匹配
{n,} 至少匹配n次
{n,m} 匹配至少n次,至多m次。不填m表示不限最大数量
. 匹配除”\n”之外的任何单个字符,要匹配任意字符应该使用([\s\S]*),这种方法包括了换行符的
.* 匹配任意字符,最长匹配(贪婪型的,比如(.)/就表示遇到最后一个/就结束)
.*? 匹配任意字符,最段匹配(贪婪型的,比如(.)/就表示遇到第一个/就结束),包括换行符就是([\s\S]*?)
?= 零宽断言,先从要匹配的字符串中的最右端找到第一个匹配项,然后再匹配前面的表达式,例如[a-z](?=ing)可以匹配cooking singing中的cook与sing,.(?=ing)可以匹配cooking singing中的cooking sing而不是cook,同样,这样的表达式可以匹配多次,适合找前缀和后缀的情况
\d 匹配一个数字
\n 匹配一个换行符
\f 匹配一个换页
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\s+ 匹配一个或多个空白
| 或者,最好把两个条件都用小括号括起来,例如(a|b)
() 在匹配的时候没什么作用,主要用于程序提取匹配到的字符串
(?i) 后面的内容忽略大小写
阅读全文 »

Python装饰器

装饰器就是一个函数,它接受其它函数为参数返回一个装饰过的函数,装饰器可用于静态方法、属性(@staticmethod),在函数前后执行特定代码(比如,验证参数、给函数调用做缓存、注册回调函数、给函数打日志)

最简单的装饰器:

1
2
3
4
5
6
7
8
9
10
11
from functools import wraps
def b(func):
@wraps(func)
def decorate(func):
print('b')
return func(canshu) # 如果要传递参数可以在这里进行传递
return decorate

@b
def a(canshu):
print('a')

执行a()的时候会分别输出ba,需要注意的是,这里不加wraps也是可以的,但是如果不加wraps,那么函数就真的相当于一个新的函数了,通过内省方法获取函数的元信息等都会变成新的,而如果wraps则会消除这样的影响。在flask中如果对views函数进行了装饰,不加wraps会出现这样的错误:

AssertionError: View function mapping is overwriting an existing endpoint function: decorate

使用类作为装饰器

被装饰的函数会作为实例化参数,得到一个类实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class LoginCheck:
def __init__(self, f):
self._f = f

def __call__(self, *args):
Status = check_function()
if Status is 1:
return self._f(*args)
else:
return alt_function()

def check_function():
return test

def alt_function():
return 'Sorry - this is the forced behaviour'

@LoginCheck
def display_members_page():
print 'This is the members page'

应用场景

  1. 日志记录与分析
  2. 数据验证(用户合法性校验、数据合法性校验)
  3. 重复使用代码