python爬虫
2024-05-05 09:53:01 1 举报
AI智能生成
Python笔记,主要为爬虫方向,包括基础语法,并发编程,数据库操作,前端知识,爬虫,scrapy框架,JS逆向。因为是学习笔记,所以并不是很专业,边学边记,随时查漏补缺,JS逆向部分还未完善。
作者其他创作
大纲/内容
基础语法
python是动态语言
给变量赋值的时候,不需要指定变量的数据类型
尽管会警告,但还是照样运行
告诉程序员看:我这个函数规定了数据类型,你传错类型后续出了什么事不负责
告诉程序员看:我这个函数规定了数据类型,你传错类型后续出了什么事不负责
print
print("{0}+{1}={2}".format(1, 2, 3))
1+2=3
这个在下面字符串格式化里会详细讲
print("balabalabala", end=" ")
不写end,默认换行(end="\n")
"这个是%s常用的%s格式化" % ("我", "字符串")
%s
字符串
%10s,表示10个字符的宽度
%i or %d
整数
同上
%f
浮点数
%.3f,保留三位小数
%10.3f,宽10并保留三位小数
f'我叫{"你猜"},今年{88}岁'
保留字
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
转义字符
\n
换行,一般在末尾,strip对其也有效
\0
空字符
\t
tab,四个空格
\'
‘
\''
“
\\
\
以下不常用
\a
发出系统响铃声
\b
退格符,覆盖删除前一个
\r
换行,并将当前字符串之前的所有字符删除
\v
不常用
\f
不常用
数据类型
内置的 type() 函数可以用来查询变量所指的对象类型
Number(数字)
String(字符串)
List(列表)
Tuple(元组)
Set(集合)
Dictionary(字典)
String(字符串)
List(列表)
Tuple(元组)
Set(集合)
Dictionary(字典)
不可变数据(3 个):Number(数字<包括int(整型)、float(浮点型:小数)、bool(布尔型)、complex(复数)>)
String(字符串)、Tuple(元组);
不可以增删改
做改变之后 id() 查看内存地址,改变
String(字符串)、Tuple(元组);
不可以增删改
做改变之后 id() 查看内存地址,改变
可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。
可以增删改
增删改后用内置函数 id() 查看内存地址,不变
可以增删改
增删改后用内置函数 id() 查看内存地址,不变
作用:
number:用来存储数字类型的数据,多用于运算,布尔型也属于number,用于逻辑运算,true为真,false为假
string:用来存储字符串,如中文、英文等,一般用于一些描述性的文字输出
list:用来存储列表,即多个数据类型任意的元素
tuple:用来存储元组,一种有序的且不可更改的集合
set:用来存储集合,一种无序的,且可以更改的集合
dictionary:用来存储字典,一种无序的,可变的有索引的集合
number:用来存储数字类型的数据,多用于运算,布尔型也属于number,用于逻辑运算,true为真,false为假
string:用来存储字符串,如中文、英文等,一般用于一些描述性的文字输出
list:用来存储列表,即多个数据类型任意的元素
tuple:用来存储元组,一种有序的且不可更改的集合
set:用来存储集合,一种无序的,且可以更改的集合
dictionary:用来存储字典,一种无序的,可变的有索引的集合
字符类型转化
str()
int()
float()
注释
单行:#
多行: ''' 或者"""
input
运算符
算数
+
-
*
/
%取余
**幂
//取整除,向下取整
赋值
=
+=
-=
*=
/=
%=
**=
/=
:=,不常用
比较
==
!=
<
>
<=
>=
逻辑(布尔)
and,与
or,或
not,非
位运算
将数字转成二进制进行计算
python中可用bin(4).replace('0b','')得到对应二进制数
python中可用bin(4).replace('0b','')得到对应二进制数
&
按位与,都是1才是1,其他全是0
|
按位或,有1就是1
^
按位异或,不同就是1
~
按位取反,0变1,1变0
<<
左移动相应位数,高位丢弃,低位补0
x<<n==x*(2**n)
>>
右移动相应位数,低位丢弃,高位补0
x>>n==x/(2**n)
int除完会变成float
所以只是数值上相等,type上还是不同的
所以只是数值上相等,type上还是不同的
优先级
**
*,/,//,%
+,-
<<,>>
&
|
>,<,>=,<=,==,!=
and
or
=
三大结构(顺序,选择,循环)
顺序结构
选择结构
对象的布尔值
python一切皆对象,所有对象都有一个布尔值,
bool()
bool()
以下都为false
false
数值0
None
空字符串
"", ''
空列表
[], list()
空元组
(), tuple()
空字典
{}, dict()
空集合
set()
其他对象布尔值都为true
判断方法
==
比较值
is
是否是同一个地址
isinstance
判断当前对象是否是指定对象的子类
if isinstance (当前对象,指定对象)
单分支
if
双分支
if else
多分支
if elif elif (else)
嵌套
if 套 if
插一嘴
pass语句
一个占位符:没想好if判断后执行啥,先pass占个位
三元运算
语句1 if 判断语句 else 语句2
判断语句成立执行语句1,否则执行语句2
循环结构
range()
range(stop)
range(start, stop)
range(start, stop, step)
while循环
一般while true,也可以while+条件
for_in 循环
break跳出
if...break
结束当前循环,本层
或者直接break
continue
if...continue
结束当前循环,直接进入下次循环,本层
或者直接continue
else
for...else
当循环中不执行break,则执行else
while...else
break会让else中的代码不执行,continue就没这个功能
嵌套循环
字符串
驻留
在内存中保存一份且不可变字符串的方法。(相同的字符串只保留一份)
交互模式(cmd)
字符串长度为0或者1
符合标识符的字符串(只包含字符 数字 下划线)
字符串只在编译时进行驻留,而非运行时
a = 'abc'
b = 'ab' + 'c'
c = ''.join(['ab','c'])
print(a is b)>>True
print(a is c)>>False
b = 'ab' + 'c'
c = ''.join(['ab','c'])
print(a is b)>>True
print(a is c)>>False
a b c值一样,type也都是str
b的值在运行之前就连接完毕了
c的值是程序运行的时候通过join的方法和列表连接的,
运行的时候会开辟新的存储空间,所以和a b的存储空间不一样
b的值在运行之前就连接完毕了
c的值是程序运行的时候通过join的方法和列表连接的,
运行的时候会开辟新的存储空间,所以和a b的存储空间不一样
[-5,256]之间的整数数字
通过a == b,查看值是否相等,通过a is b,查看存储位置是否相等,用id( )也可以查看存储位置
相同的值可以强制驻留
import sys
a = sys.intern(b)
a = sys.intern(b)
pycharm对字符串进行了优化,有些不驻留的进行了强制驻留处理
优点
避免频繁重复创建销毁,提升效率节约内存
因为字符串的拼接和修改比较影响性能
用str类型的join来拼接字符串,而非+
join()是先计算所有字符串的长度,再拷贝,只new一次对象,效率比+高
查
index()
查询substr(子串)第一次出现的位置
找不到报valueerror
rindex()
查询substr(子串)最后一次出现的位置
找不到报valueerror
find()
查询substr(子串)第一次出现的位置
找不到返回-1
rfind()
查询substr(子串)最后一次出现的位置
找不到返回-1
count()
返回str在start和end之间,在my_str里出现的次数
my_str.count(str, start=0, end=len(my_str))
startswith()
检查字符串是否以指定字符串开头,返回True或False
endswith()
检查字符串是否以指定字符串结束,返回True或False
大小写转换
a.upper()
所有都大写
a.lower()
所有都小写
a.swapcase()
大转小,小转大
a.capitalize()
第一个大写,其余小写
a.title()
每个单词的,第一大写,其余小写
对齐
居中
a.center(20,'*')
参数第一个是宽度,第二个是填充符(默认空格)
如果宽度参数小于字符串长度,则返回原字符串
左对齐
a.ljust(20, '*')
同上
右对齐
a.rjust(20, '*')
同上
右对齐,左0补齐
a.zfill(20)
一个参数,如果是-87654,在 - 后面填0
分割
split()
从左开始分割,默认从空格开始分割
放回一个列表
a.split(spe='分隔符参数', maxsplit=1最大分割次数)
maxsplit是可选参数
rsplit()
从右侧,同上
partition(str)
字符串分割为str前+str+str后三部分
splitlines()
若字符串中有换行,则按行分割,返回各行为元素的列表
判断
a.isidentifier()
是否为合法标识符字符串(只包含字母(中文) 数字 下划线)
a.isspace()
是否全是空白字符(空格 换行 tab)
a.isalpha()
是否全是字母(中文)
a.isdecimal()
是否全是十进制数字
a.isnumeric()
是否全是数字
只要是数字就行,中文数字,中文大写数字,罗马数字
a.isdigit()
是否只含数字
这个就只是阿拉伯数字
a.isalnum()
是否全是字母(中文)和数字
替换
a.replace('原字符串',‘替换成的字符串’,2)
第三个参数是最大替换次数,可选
合并
join()
'*'.join(list)
将列表中的元素用 * 合并成一个字符串
元组同上
字符串序列
单个字符当一个元素
a = '*'.join('hello world')
print(a)
>>h*e*l*l*o* *w*o*r*l*d
print(a)
>>h*e*l*l*o* *w*o*r*l*d
比较
>, >=, <, <=, ==, !=
比较规则:从第一个字符开始比较,到第一个不相同的字符
比较原理:比较的是字符的原始值order value
ord('a')
查看a的原始值
chr(97)
查看原始值为97的字符
切片
a[start : end : step]
a[:5]
从0到4
a[6:]
从6到最后
step正从前开始,step负从后开始
其他方法
删除两端空白字符
strip()
格式化(按固定格式输出)
在字符串中加入变量
在字符串中加入变量
"这个是%s常用的%s格式化" % ("我", "字符串")
%s
字符串
%10s,表示10个字符的宽度
%i or %d
整数
同上
%f
浮点数
%.3f,保留三位小数
%10.3f,宽10并保留三位小数
"这个是{0}比较常用的{1}格式化".format("我", "字符串")
{0:.3}
一共三位
{0:.3f}
三位小数
{0:10.3}
宽10位,共三位小数
f'我叫{"你猜"},今年{88}岁'
编码转换
编码
将字符串转换成二进制数据(bytes)
word = "赞美愚者"
encoding1 = word.encode(encoding='GBK') # GBK编码格式 一个中文两个字节
encoding2 = word.encode(encoding='utf-8') # utf-8 一个中文三个字节
print(encoding1)
print(encoding2)
encoding1 = word.encode(encoding='GBK') # GBK编码格式 一个中文两个字节
encoding2 = word.encode(encoding='utf-8') # utf-8 一个中文三个字节
print(encoding1)
print(encoding2)
>>b'\xd4\xde\xc3\xc0\xd3\xde\xd5\xdf'
>>b'\xe8\xb5\x9e\xe7\xbe\x8e\xe6\x84\x9a\xe8\x80\x85'
>>b'\xe8\xb5\x9e\xe7\xbe\x8e\xe6\x84\x9a\xe8\x80\x85'
解码
将bytes数据转换成字符串
decoding1 = encoding1.decode(encoding='GBK')
decoding2 = encoding2.decode(encoding='utf-8')
print(decoding1)
print(decoding2)
decoding2 = encoding2.decode(encoding='utf-8')
print(decoding1)
print(decoding2)
>>赞美愚者
>>赞美愚者
>>赞美愚者
列表
1. 列表对象有序排序
2. 索引映射唯一数据
3. 列表可以存储重复数据
4. 任意数据类型混存
5. 根据需要动态分配和回收内存
2. 索引映射唯一数据
3. 列表可以存储重复数据
4. 任意数据类型混存
5. 根据需要动态分配和回收内存
创建
lst=["a", "b", 98]
lst=list(["a", "b", 98])
列表生成式
lst = [i for i in range(1, 10)]
lst = [2*i for i in range(1, 6)]
>>[2, 4, 6, 8, 10]
>>[2, 4, 6, 8, 10]
获取
lst.index("a")
获取制定元素的索引(第一个)
lst[1]
获取列表中index为1的元素
切片
lstChild = lst[start : stop : step]
step为正
从前往后start
step为负
从后往前start
增删改查
查
判断元素是否存在
in
not in
遍历
首选
for item in lst:
item是可迭代对象
while
>>1
>>2
>>3
>>2
>>3
统计元素个数
L.count('a')
查询元素的索引
list.index('元素')
增
append()
列表末尾添加一个元素
extend()
列表末尾添加至少一个元素,增加的是另一个序列的多个值,但用的时候()里面是一个参数
这样不行list_ouput.extend(user_input1, user_input2, user_input3)
得这样list_ouput.extend([user_input1, user_input2, user_input3])
insert()
任意位置插入一个元素
insert(1,90):在index 1 位置上加入90
删
remove()
一次删除一个,有重复的删除第一个
lst.remove(元素值)
pop()
删除指定索引的元素,不写索引默认最后一个元素
lst.pop(索引号)
clear()
清空列表
del
根据下标删除
del list[2]
删除列表
改
lst[index]=修改后的元素
lst[start:stop]=[........]
切片修改
排序
lst.sort()
lst.sort(reverser=True),表示降序
通过id(lst)可查看还是同一个列表
lst.sort(key=function( ))
key参数主要用于自主选择排序的方式,一般=某函数
stus = [
{'name': 'yucas', 'age': 18},
{'name': 'wayne', 'age': 20}
]
stus.sort(key=lambda x: x['age'])
print(stus)
{'name': 'yucas', 'age': 18},
{'name': 'wayne', 'age': 20}
]
stus.sort(key=lambda x: x['age'])
print(stus)
sorted(lst)
sorted(lst, reverser=True), 降序
创建了一个新列表
reverse()
将列表倒叙
给程序传递参数
import sys
print(sys.argv)
print(sys.argv)
sys.argv就是一个列表,这个列表中存储着运行是传递的参数,注意全部是字符串
python test.py 卢卡斯
>>卢卡斯
>>卢卡斯
元组
创建
("元素",“有一个元素”, 8 )
()可以省略
tuple(......)
tuple(("元素",“有一个元素”, 8 ))
("元素",)
一个元素的元组,一定要加逗号
()
元组不可变,但元组中的可变数据可以改变,比如有个list元素
遍历
t = tuple(("python", "good", 98))
for item in t:
print(item)
for item in t:
print(item)
>>python
>>good
>>98
>>good
>>98
集合
没有value的字典
也有hash表计算存储位置
元素不允许重复,无序
也有hash表计算存储位置
元素不允许重复,无序
创建
{......}
set(......)
set([列表])
将列表转成集合
set((元组))
将元组转成集合
set("字符串")
将字符串转成集合
s1 = set("python")
print(s1)
>>{'o', 'p', 'n', 'y', 'h', 't'}
print(s1)
>>{'o', 'p', 'n', 'y', 'h', 't'}
set() 空集合
用{}创建的是空字典
增删改查
查
in
not in
增
s.add(一个元素)
s.update(至少一个元素)
删
s.remove(元素)
元素不存在报keyerror
s.discard(元素)
元素不存在不报错
s.pop()
不能加参数,随意删一个
s2 = s1.pop(),s2是s1中拿出的那个元素
s.clear()
清空元素
del s
删除集合
改
集合的元素为不可变类型,所以无法修改
集合之间的关系
相等
==
!=
子集
s1.issubset(s2)
s1是否是s2的子集
超集
s2.issuperset(s1)
s2是否是s1的超集
不相交
s1.isdisjoint(s2)
s1和s2是否不相交
交并差
交集
s1.intersection(s2)
s1 & s2
并集
s1.union(s2)
s1 | s2
差集
s1.difference(s2)
s1 - s2
对称差集(s1并s2 - s1交s2)
s1.symmetric_difference(s2)
s1 ^ s2
集合生成式(和列表差不多)
s1 = {i for i in range(1, 10)}
s4 = {2*i for i in range(1, 6)}
>>{2, 4, 6, 8, 10}
>>{2, 4, 6, 8, 10}
序列类型的类型转换(不包括字典)
tuple()
列表,集合转换为元组
set()
列表,元组转换为集合
可完成对list和tuple的快速去重
list()
元组,集合转换为列表
判断数据是否可迭代
from collections.abc import Iterable
print(isinstance([], Iterable))
>>True
print(isinstance([], Iterable))
>>True
zip()
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
字典
1.键值对,键不可以重复,值可以重复
2.元素是无序的
3.key必须是不可变对象
4.动态调整内存
5.消耗较大内存
6.hash表计算存储位置
2.元素是无序的
3.key必须是不可变对象
4.动态调整内存
5.消耗较大内存
6.hash表计算存储位置
创建
a = {"name": 'Lucas', "age": 30}
b = dict(name='LucasLu', age=32),内置函数
c={},空字典
字典生成式
items = ["a", "b", "c"]
values = [1, 2, 3]
values = [1, 2, 3]
d = {item.upper() : value for item, value in zip(items, values)}
>>{'A': 1, 'B': 2, 'C': 3}
增删改查
查(键对应的值)
[键]
a["name"]
>>Lucas
>>Lucas
如果键不存在,报错,keyerror
get(键)
a.get("name")
如果键不存在,返回None
a.get("gaga", 99)
如果键不存在,返回默认值
key的判断
in
not in
遍历
所有键
for key in dict.keys():
print(key)
print(key)
所有值
for val in dict.values():
print(val)
print(val)
所有元素
for item in dict.items():
print(item)
print(item)
每对键值输出为一个元组tuple
删
del a["name"]
删除指定键值对
del a
删除整个字典a
a.clear
清空字典a
增
a["新键"]=对应的值
改
a["要修改的键"]=新值
视图操作
dict.keys()
获取字典中所有的键(key)
dict.values()
获取字典中所有的值(value)
dict.items()
获取字典中所有的键值对(key.value)
遍历元素
for item in a:
print(item)
获取键keys
print(a[item])
获取值values
print(a.get(item))
获取值values
推导式
快速生成数据
列表(List Comprehension列表解析)
[变量 for 变量 in 可迭代对象]
[x for x in range(4)]
加条件
[x for x in range(4) if x % 2 ==0]
for嵌套循环
a = [(x, y) for x in range(1, 3) for y in range(5, 10)]
自动x和y在取值范围内排列组合
列子[1, ..., 100]变成[[1,2,3], [4,5,6],...]
a = [x for x in range(1, 101)]
print(a)
b = [a[x:x+3] for x in range(0, len(a), 3)]
print(b)
print(a)
b = [a[x:x+3] for x in range(0, len(a), 3)]
print(b)
集合
同上
字典
name = ['张三', '李四', '王五', '赵六']
sign = ['水瓶', '射手', '摩羯', '双鱼']
dict = {x+'爷':y+'座' for x,y in zip(name, sign)}
print(dict)
sign = ['水瓶', '射手', '摩羯', '双鱼']
dict = {x+'爷':y+'座' for x,y in zip(name, sign)}
print(dict)
拆包
快速提取
元组
a, b = [11, 22]
列表
同上
集合
同上
字典
同上,默认取到的是key
# 这样可以去键值对
for k,v in dict.items():
print(k, v)
for k,v in dict.items():
print(k, v)
经典python交换变量值
a, b = b, a
=右边b, a 会被当做一个元组(b, a)
函数
创建
def 函数名([函数参数]):
函数体
[return xxx]
函数体
[return xxx]
def calc(a, b):
result = a+b
return result
result = a+b
return result
部分内置函数
len
返回长度
max
返回最大值
del
del a或者del(a)
time包
稍后再说
random包
用到再说
自定义函数记得加说明文档
参数传递
形参实参
形参(定义处)
a和b
实参(调用处)
a和b的实际值
参数调用
print(calc(10, 20))
print(calc(b=20, a=10))
print(calc(b=20, a=10))
形参实参名称可以不同
参数传递时的内存
如果是不可变对象,在函数体内的修改不影响实参的值
记住python赋值不是改变对象的值而是将对象指向存储值的内存就行
如果是可变对象,在函数体内的修改影响实参的值
参数定义
函数定义时,给形参定义默认值,调用时只有和默认值不符时传递实参
def fun(a, b=10):
print(a, b)
fun(100) # a=100, b=10
fun(20, 30) # a=20, b=30
print(a, b)
fun(100) # a=100, b=10
fun(20, 30) # a=20, b=30
*args(未命名参数)
个数可变的位置参数
结果是元组
def fun(*args):
print(args)
fun(10)
fun(10, 20, 30)
print(args)
fun(10)
fun(10, 20, 30)
>>(10)
>>(10, 20, 30)
>>(10, 20, 30)
这时args是一个元组,可以进行元组的各种操作
表示调用函数时多余的未命名参数都会以元组的方式存储到args里
一个函数只能定义一个位置参数
**args(**kwagrs)(命名参数)
个数可变的关键字参数
结果是字典
def fun(**args):
print(args)
fun(a=10)
fun(a=10, b=20, c=30)
print(args)
fun(a=10)
fun(a=10, b=20, c=30)
>>{'a': 10}
>>{'a': 10, 'b': 20, 'c': 30}
>>{'a': 10, 'b': 20, 'c': 30}
表示调用函数时多余的命名参数都会以键值对的方式存储到kwagrs
def fun(a, b, *, c, d)
*之后的参数只能用关键字参数传递
一个函数只能定义一个关键字参数,且要放在位置参数后面
不过这样也可以def fun(a, b, *, c, **arges)
因为本质上它还是只有一个关键字参数,*只不过是个条件
因为本质上它还是只有一个关键字参数,*只不过是个条件
总结
def fun(a, b, c):
print('a=', a)
print('b=', b)
print('c=', c)
print('a=', a)
print('b=', b)
print('c=', c)
fun(1, 2, 3)
a= 1
b= 2
c= 3
b= 2
c= 3
lst = [10, 20, 30]
fun(*lst)
fun(*lst)
a= 10
b= 20
c= 30
b= 20
c= 30
dic = {'a': 11, 'b': 12, 'c': 13}
fun(**dic)
fun(**dic)
a= 11
b= 12
c= 13
b= 12
c= 13
返回值return
返回值和结束函数2个作用
return后面的语句不会被执行,只执行第一个return
返回多个值时,结果为元组(所以实际上返回的是一个值)
是元组就能拆包
通过星号拆包
*对列表、元组、集合可以拆包,但一般都是在调用函数时用
**可以对字典进行拆包,拆包的结果是命名参数
4种函数
无参,无返回
一般情况下用来打印提示等类似的功能
无参,有返回
一般情况下像采集数据等功能会用到
有参,无返回
一般情况下对某些变量设置数据而不需结果时用此类函数
有参,有返回
大多数
函数嵌套
# 例1 画线
def line():
print('---------------'*3)
def print_lines(num):
for i in range(num):
line()
i += 1
print_lines(5)
def line():
print('---------------'*3)
def print_lines(num):
for i in range(num):
line()
i += 1
print_lines(5)
# 算平均数
def sum_up(n1, n2, n3):
return n1 + n2 + n3
def average(n1, n2, n3):
return sum_up(n1, n2, n3)/3
print(average(1, 5, 9))
def sum_up(n1, n2, n3):
return n1 + n2 + n3
def average(n1, n2, n3):
return sum_up(n1, n2, n3)/3
print(average(1, 5, 9))
变量的作用域
局部变量
函数内的
全局变量
函数外的
要在函数内修改全局变量,先用global声明
匿名函数(lambda)
add_up = lambda 形参1,形参2:表达式
用add_up(1, 2)调用
接收任何数量的参数但只能返回一个表达式的值,
其默认就是返回的,不用写return
其默认就是返回的,不用写return
使用方式
1.像普通函数一样,变量名( )
2.在其他函数实参的位置,直接定义一个lambda
def fun(a, b, opt):
print(opt(a, b))
fun(1, 4, lambda x, y: x+y)
print(opt(a, b))
fun(1, 4, lambda x, y: x+y)
递归函数
就是函数内调用自己
占内存效率低,但简单易懂啊
阶乘函数
def fac(n):
if n == 1:
return 1
else:
res = n*fac(n-1)
return res
def fac(n):
if n == 1:
return 1
else:
res = n*fac(n-1)
return res
斐波拉契数列
F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)
F[n]=F[n-1]+F[n-2] (n>=2,F[0]=0,F[1]=1)
def fib(n):
if n == 1:
return 0
elif n == 2:
return 1
else:
return fib(n-1)+fib(n-2)
for i in range(1, 8):
print(fib(i))
if n == 1:
return 0
elif n == 2:
return 1
else:
return fib(n-1)+fib(n-2)
for i in range(1, 8):
print(fib(i))
有调用有终止
其实递归是一种算法,一般用来解决“树”、“图”等操作,还能解决像“汉罗塔问题”等
面向对象 特殊属性和方法 类的赋值和浅拷贝
面向过程 VS 面向对象
类
类 VS 对象
不同的数据类型属于不同的类
900就是int数据类型下的一个对象
python中一切皆对象
有id(内存空间),有type(数据类型),有值
有id(内存空间),有type(数据类型),有值
类的创建
class Student:
pass
pass
一般首字母大写,其余小写
组成
类属性
可以在外面通过Student.gender = None,加一个类属性
self
当通过实例对象调用方法的时候,self能够自动指向实例对象,从而拥有了操作这个对象中的属性或者方法的可能。
静态方法
静态方法之所以不需要self,是因为方法内不需要实例对象的引用
比如上面的例子中,eat方法,并没有指向name或者age,所以其实属于静态方法
能获取构造函数定义的变量,也不可以获取类的属性。
类方法
不能获取构造函数定义的变量,可以获取类的属性。
对象的创建
stu = Student(初始化方法的参数)
stu.eat()
对象名.方法名
Student.eat(stu)
类名.方法名(类的对象)----方法定义处的self
stu.name
调用属性
删除属性
调用
类属性
类中方法外的变量,被该类所有对象共享
# 直接写在类里面的变量
native_pace = '江苏'
native_pace = '江苏'
print(Student.native_pace)
print(stu.native_pace)
print(stu.native_pace)
类方法
使用类名直接访问的方法
@classmethod
def cm(cls):
print('类方法写cls')
def cm(cls):
print('类方法写cls')
Student.cm()
静态方法
使用类名直接访问的方法
@staticmethod
def method():
print('静态方法不加self')
def method():
print('静态方法不加self')
Student.method()
对象关联
类A,实例对象a=A()
类B,实例对象b=B()
类B,实例对象b=B()
B.新属性aa=a
再用B.aa就可以调用A的属性和方法了
三大特征(封装,继承,多态)
封装
将数据(属性)和行为(方法)包装到类对象中。(转为私有属性or隐藏属性)
如果属性不希望被外部调用,可以前面加2个_
self.__age = age
再用stu.__age是访问不到的,
但用stu._Student__age还是能访问,纯靠自觉
再用stu.__age是访问不到的,
但用stu._Student__age还是能访问,纯靠自觉
print(dir(cat)),可以看到cat的所有属性和方法
继承
基本概念
没写父类的默认继承object
定义子类时,要在构造函数中调用父类的构造函数
super().__init__( )
支持多继承
子类有多个父类
关于多继承的__mro__顺序
用类名.__mro__,这个魔术方法,可以查看这个类的继承顺序
用类名.__mro__,这个魔术方法,可以查看这个类的继承顺序
如果2个子类中都继承了同个父类,当在子类中通过父类名调用时,parent被执行了2次
子主题
如果2个子类中都继承了同个父类,当在子类中通过super调用时,parent被执行了1次
子主题
子主题
单继承
子类只有一个父类
子主题
子主题
1.super().__init__相对于类名.__init__,在单继承上用法基本无差
2.但在多继承上有区别,super方法能保证每个父类方法只会执行一次,而类名.方法()会导致方法可能被执行多次
3.多继承时,用super方法,对父类传参,由于super的算法,必须把参数全部传递,否则报错,所以就要加上*args和**kwargs
4.单继承时,用super方法,不用全部传参,只能传父类方法所需的参数,否则报错
注:在多继承时,尽量不用super,用父类名.方法()
用super虽然对于同个父类的调用可以避免重复执行,但代码阅读难度增加,不利于debug
2.但在多继承上有区别,super方法能保证每个父类方法只会执行一次,而类名.方法()会导致方法可能被执行多次
3.多继承时,用super方法,对父类传参,由于super的算法,必须把参数全部传递,否则报错,所以就要加上*args和**kwargs
4.单继承时,用super方法,不用全部传参,只能传父类方法所需的参数,否则报错
注:在多继承时,尽量不用super,用父类名.方法()
用super虽然对于同个父类的调用可以避免重复执行,但代码阅读难度增加,不利于debug
object类
所有类的父类
__str__()方法
返回对象的描述
https://blog.csdn.net/m0_57133702/article/details/120568504
方法重写
子类想输出自己独有的东西,父类没有
就是在子类中重新定义父类的方法
先加个super().xx()
这样可以继承父类的xx()方法
然后再加新的功能
先加个super().xx()
这样可以继承父类的xx()方法
然后再加新的功能
如果不加super(),就继承不了父类方法
多态
有别于如Java的静态语言,python这种动态语言的多态就相当灵活了
如果一个变量存储了某一个实例对象的引用,且通过这个变量调用指向的对象中的某个方法,
此时如果变量指向的对象是子类创建的那么就调用子类中的方法,如果是父类创建的对象那么就调用父类的方法
如果一个变量存储了某一个实例对象的引用,且通过这个变量调用指向的对象中的某个方法,
此时如果变量指向的对象是子类创建的那么就调用子类中的方法,如果是父类创建的对象那么就调用父类的方法
反正就是调用类的方法很方便
class Dog():
def bark(self):
print("普通狗汪汪叫")
class LangDog(Dog):
def bark(self):
print("狼狗狂吠")
class ZangAo(Dog):
pass
class Person():
def pk_dog(self, dog):
print('人向狗进行了攻击')
dog.bark()
Anna = Person()
dog1 = Dog()
dog2 = LangDog()
dog3 = ZangAo()
Anna.pk_dog(dog1)
Anna.pk_dog(dog2)
Anna.pk_dog(dog3)
def bark(self):
print("普通狗汪汪叫")
class LangDog(Dog):
def bark(self):
print("狼狗狂吠")
class ZangAo(Dog):
pass
class Person():
def pk_dog(self, dog):
print('人向狗进行了攻击')
dog.bark()
Anna = Person()
dog1 = Dog()
dog2 = LangDog()
dog3 = ZangAo()
Anna.pk_dog(dog1)
Anna.pk_dog(dog2)
Anna.pk_dog(dog3)
>>人向狗进行了攻击
>>普通狗汪汪叫
>>人向狗进行了攻击
>>狼狗狂吠
>>人向狗进行了攻击
>>普通狗汪汪叫
>>普通狗汪汪叫
>>人向狗进行了攻击
>>狼狗狂吠
>>人向狗进行了攻击
>>普通狗汪汪叫
内建属性
常用内建属性
__new__(cls)
帮助类在内存中只开辟一个空间
储存属性和方法
在__init__之前
储存属性和方法
在__init__之前
如果需要当前类做多个实例化,则只需要创建一个空间来存储实例对象
__init__
__class__
实例对象p是通过__class__找到Person这个类的,然后才能调用类中的方法
__getattribute__
实例对象要访问实例属性,要通过这个方法
方法中的item参数是实例属性的属性名称
能够完成属性访问时进行拦截
用户访问属性,传入一个参数,可以返回提前设定好的值
如果访问的是name1,会被拦截,传回指定值,如果访问name2,则可以返回传入的值
object.__getattribute__(self, item)是为了保证能返回属性的值,没有的话返回None
注意:不要在__getattribute__方法中调用self.xxxx,会进入死递归,用object.__getattribute__(self, item)
__doc__
类名.__doc__
输出类的说明描述。类的说明写在类名下一行,__doc__只调用那段注释。
__module__和__class__
module打印所在的模块(所在的py文件)
class打印所在类
class打印所在类
__del__
当对象在内存中被释放之前,自动触发执行
一般不去改
__call__
可以在__call__中添加一些功能,用Foo()()直接调用
把实例对象当成一个函数去使用
当成函数去用,就可以传参了
__dict__
返回类的属性和方法,以及对应的值
返回实例对象的属性,名称和值
返回实例对象的属性,名称和值
__str__
如果在类中定义了__str__方法,打印方法时,默认输出这个方法的返回值
返回值只接受string
__getitem__, __setitem__, __delitem__
对容器类型进行操作,获取,设置,删除
对于列表,元组,字符串。foo[index],索引
对于字典。foo[key],键
对于列表,元组,字符串。foo[index],索引
对于字典。foo[key],键
字典
一定要用foo['hahaha']操作才能调用这三个方法
一定要用foo['hahaha']操作才能调用这三个方法
动态绑定属性和方法
创建完对象后,为个别对象动态绑定一个属性(方法),该属性只有这个对象有,其他对象没有
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(self.name + '在吃饭')
stu1 = Student('张三', 20)
stu2 = Student('李四', 30)
stu1.gender = '女' # 这一步就是动态绑定新属性
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
print(self.name + '在吃饭')
stu1 = Student('张三', 20)
stu2 = Student('李四', 30)
stu1.gender = '女' # 这一步就是动态绑定新属性
gender属性只有stu1有
def show(self):
print('stu1专属方法', self.name)
import types
# MethodType给类动态绑定实例方法
stu1.show = types.MethodType(show, stu1) # 将show方法帮给stu1对象
stu1.show()
print('stu1专属方法', self.name)
import types
# MethodType给类动态绑定实例方法
stu1.show = types.MethodType(show, stu1) # 将show方法帮给stu1对象
stu1.show()
show()只有stu1能调用
@staticmethod
def static_func():
print('这是一个静态方法')
Student.address = '深圳' # 先创建一个类属性
@classmethod
def class_func(cls):
print(f'这是类方法,类属性值为:{cls.address}')
Student.static_func = static_func
Student.class_func = class_func
stu2.static_func()
stu1.class_func()
def static_func():
print('这是一个静态方法')
Student.address = '深圳' # 先创建一个类属性
@classmethod
def class_func(cls):
print(f'这是类方法,类属性值为:{cls.address}')
Student.static_func = static_func
Student.class_func = class_func
stu2.static_func()
stu1.class_func()
__slots__特殊属性
限制属性的创建
1.为了限制随意给对象添加属性或者方法
2.对子类不起作用,只限制当前类
3.不可以卸载__init__中
1.为了限制随意给对象添加属性或者方法
2.对子类不起作用,只限制当前类
3.不可以卸载__init__中
property属性
1.装饰器方式
当前类中,装饰在某个方法上,使得这个方法可以像属性那样被调用
用于拿到当前方法中计算的返回值
被property装饰的方法不能创建除了self之外的形参
用于拿到当前方法中计算的返回值
被property装饰的方法不能创建除了self之外的形参
@property
def func(self):
return '调用func'
foo = Foo()
res = foo.func
def func(self):
return '调用func'
foo = Foo()
res = foo.func
简单例子:
'''
对于京东商城中显示电脑主机的列表页面,
每次请求不可能把数据库中的所有内容都显示到页面上,
而是通过分页的功能局部显示,
所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
1.根据用户请求的当前页和总数据条数,计算出m和n
2.根据m和n去数据库中请求数据
'''
对于京东商城中显示电脑主机的列表页面,
每次请求不可能把数据库中的所有内容都显示到页面上,
而是通过分页的功能局部显示,
所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:
1.根据用户请求的当前页和总数据条数,计算出m和n
2.根据m和n去数据库中请求数据
'''
>>第一页显示第0到第9条数据
>>第一页显示第10到第19条数据
>>第一页显示第10到第19条数据
用property进行商品价格控制
>>100
>>200
>>... AttributeError: 'Goods' object has no attribute 'price'. Did you mean: '_price'?
>>200
>>... AttributeError: 'Goods' object has no attribute 'price'. Did you mean: '_price'?
在商城开发中设置打折价格
>>80.0
>>setter被调用
>>160.0
>>deleter被调用
>>.... AttributeError: 'Goods' object has no attribute 'original_price'
>>setter被调用
>>160.0
>>deleter被调用
>>.... AttributeError: 'Goods' object has no attribute 'original_price'
2.类属性方式,创建值为property对象的类属性
类属性 = property(需要装饰的方法)
>>100
案例
第一个参数是方法名,调用 对象.属性 时自动触发执行方法
第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
第四个参数是字符串,调用 类名.属性.__doc__ ,此参数是该属性的描述信息
注:property中的参数顺序一定要按照get set del排序
第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
第四个参数是字符串,调用 类名.属性.__doc__ ,此参数是该属性的描述信息
注:property中的参数顺序一定要按照get set del排序
>>当前get bar被执行
>>a
>>当前set bar被执行 d
>>当前del bar被执行
>>当前字符串是对property的一个解释说明
>>a
>>当前set bar被执行 d
>>当前del bar被执行
>>当前字符串是对property的一个解释说明
python2.7中常这么用,3之后很少用
if__name__=='__main__':
函数入口:写在入口下的内容只能在当前文件内运行,被引用时,入口下的内容引用不到
test_1
test_2
>>num1是可引用内容
>>100
>>AttributeError:不拉不拉,test_1没num2这个属性
>>100
>>AttributeError:不拉不拉,test_1没num2这个属性
用到再查
特殊属性
特殊方法
new和init的传参过程
三器一闭
迭代器
可以记住遍历的位置的对象
只能往前不能后退
只能往前不能后退
iter()和next()
for
while
# 一般写except Exception:当前python所有错误的基类
Iterable和Iterator
如果对象只有__iter__方法,是可迭代对象
如果对象有__iter__和__next__方法,则是迭代器
如果对象有__iter__和__next__方法,则是迭代器
自定义迭代器
例
1. 先定义主要类
2. 给这个类定义一个迭代器
3. MyList的实例对象my_list这时就可以用for循环遍历了
>>11
>>22
>>33
>>22
>>33
例:学生系统
1.初始化和add方法
2.iter和next
3.实例化,调用3次add,for循环遍历
请输入姓名: lucas
请输入年龄: 18
请输入地址: nanjing
请输入姓名: wayne
请输入年龄: 20
请输入地址: shenzhen
请输入姓名: bill
请输入年龄: 19
请输入地址: shanghai
>>{'name': 'lucas', 'age': 18, 'address': 'nanjing'}
>>{'name': 'wayne', 'age': 20, 'address': 'shenzhen'}
>>{'name': 'bill', 'age': 19, 'address': 'shanghai'}
请输入年龄: 18
请输入地址: nanjing
请输入姓名: wayne
请输入年龄: 20
请输入地址: shenzhen
请输入姓名: bill
请输入年龄: 19
请输入地址: shanghai
>>{'name': 'lucas', 'age': 18, 'address': 'nanjing'}
>>{'name': 'wayne', 'age': 20, 'address': 'shenzhen'}
>>{'name': 'bill', 'age': 19, 'address': 'shanghai'}
注释:
如果有4个类,分别在4个py文件中
这4个类的迭代的方法是一模一样的
写一个迭代器直接导入引用
如果有4个类,分别在4个py文件中
这4个类的迭代的方法是一模一样的
写一个迭代器直接导入引用
多继承也可以,但项目开发中,尽量少继承,继承的执行速度比对象关联慢
生成器
概念:含yield的函数
例
定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。
生成器函数返回生成器的迭代器。 "生成器的迭代器"这个术语通常被称作"生成器"。
要注意的是生成器就是一类特殊的迭代器。
作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__()。如同迭代器一样,我们可以使用__next__()函数来获取下一个值。
要注意的是生成器就是一类特殊的迭代器。
作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__()。如同迭代器一样,我们可以使用__next__()函数来获取下一个值。
执行流程
会在yield处暂停
生成器表达式
创建长序列时使用
(x for x in range(100))
(x for x in range(100))
对比
列表解析(列表推导式)
>>列表推导式时间为3.2063777446746826
生成器表达式
>>生成器表达式时间为0.0
原因
列表解析:
1.开辟一个内存
2.开完内存创建所有值
生成器表达式:
1.返回的不是具体序列数据,是生成器对象
2.使用next()取生成器中的值,内存中只有一个值。调用一次,产生一个值
1.开辟一个内存
2.开完内存创建所有值
生成器表达式:
1.返回的不是具体序列数据,是生成器对象
2.使用next()取生成器中的值,内存中只有一个值。调用一次,产生一个值
例
>>True
>>True
>>25000000
>>[]
>>True
>>25000000
>>[]
内置方法
send()
1.send方法与next方法类似
2.可以向生成器传递一个参数,并在生成器中进行接受
3.一般不在第一次获取值时使用,第一次获取时必须传递None
print(my_iter.send(None))
4.可以在程序运行的过程中对当前的函数运行过程进行操作
2.可以向生成器传递一个参数,并在生成器中进行接受
3.一般不在第一次获取值时使用,第一次获取时必须传递None
print(my_iter.send(None))
4.可以在程序运行的过程中对当前的函数运行过程进行操作
>>0
>>接收到参数,将此时的i(即0)+10输出=10
>>1
>>接收到参数,将此时的i(即0)+10输出=10
>>1
注意 i 的值,第一次取值时在yield处暂停,第二次取值继续时,先读if判断,此时 i 还未 +1
close()
关闭当前生成器(相当于删除或销毁)
my_iter.close()
print(next(my_iter))
print(next(my_iter))
关闭之后再取值会抛出异常
总结
当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有执行。
当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield处暂停
next()方法返回值就是yield处的参数
next()方法返回值就是yield处的参数
继续调用next()方法时,函数直接接着上一次暂停的yield处继续执行,到下一个yield出暂停
如果后面没有yield就抛出StopIteration异常
如果后面没有yield就抛出StopIteration异常
和return的区别
return:代表整个函数结束,结束前返回值,函数中只能有一个,生成器中不可以有return
yield:不代表函数结束(只是暂停),返回一个生成器对象(可用next取到其值),函数中可以写多个yield
yield:不代表函数结束(只是暂停),返回一个生成器对象(可用next取到其值),函数中可以写多个yield
生成器内部声明了迭代器协议:__iter__ __next__,不需要自己定义
闭包(优雅的编程技巧)
1.有函数嵌套,一般只嵌套一层
2.内部函数引用了外部函数的参数
就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。
可以这样理解,闭包就是能够读取其他函数内部变量的函数。
2.内部函数引用了外部函数的参数
就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。
可以这样理解,闭包就是能够读取其他函数内部变量的函数。
如果在一个函数中有内层函数
如果内层函数没有释放,则外层函数会永远占着内存
如果内层函数没有释放,则外层函数会永远占着内存
def person():
def info():
xxxxxx
p = person() # 这里只引用了info(),并没有调用,所以info没有运行,此时person()这个函数还是占着内存
p() # 这样就运行了info()这个函数,运行完就会释放内存
def info():
xxxxxx
p = person() # 这里只引用了info(),并没有调用,所以info没有运行,此时person()这个函数还是占着内存
p() # 这样就运行了info()这个函数,运行完就会释放内存
案例
>>内部函数的参数为: 10
>>30
>>30
函数引用
定义一个函数可以理解为:
定义了一个全部变量,变量名就是函数名(test)
这个test变量指向一个代码块,就是函数
就是说test保存了一个代码块的地址,即引用
定义了一个全部变量,变量名就是函数名(test)
这个test变量指向一个代码块,就是函数
就是说test保存了一个代码块的地址,即引用
>>1
>>2
>>True None None
>>True
>>2217856965072
>>140714178485464
>>2
>>True None None
>>True
>>2217856965072
>>140714178485464
None
使用闭包修改函数中的变量nonlocal
>>6
>>7
>>51
>>52
>>7
>>51
>>52
装饰器(依赖闭包)
概念
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
简单来讲:装饰器的作用就是为已经存在的函数或者对象添加额外的功能。
它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
简单来讲:装饰器的作用就是为已经存在的函数或者对象添加额外的功能。
例
初体验(现在不这么写)
>>[DEBUG]: enter say_hello()
>>hello
>>hello
这段代码的运行流程
1.将say_hello这个函数的引用,传递给debug函数(引用:给内存地址,不运行)
2.预读wrapper代码。预读开始
3.发现wrapper函数的返回值是say_hello函数的调用
4.返回wrapper函数的引用。预读结束
5.执行debug(),并传递一个say_hello的函数引用。开始运行
6.debug函数返回的是一个内部函数的引用
7.sayHello(),调用内部函数wrapper
print(f'********')
return func(),就是say_hello()
print(‘hello’)
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。
因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖。
在不修改say_hello函数原代码的基础上,加一个debug功能。
因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖。
在不修改say_hello函数原代码的基础上,加一个debug功能。
效果一样,这是最简单的装饰器,被装饰函数无法传参
流程
1.say_hello()处,@debug,运行debug
2.将@debug下的函数传给func
3.预读wrapper,返回wrapper的引用
4.say_hello加(),实际上就是调用wrapper函数。>>打印[DEBUG]这段话
5.wrapper返回func(),就是say_hello()。>>打印hello
被装饰函数可以传参
>>[DEBUG]: enter say()
>>hello! lucas
>>hello! lucas
万能装饰器(被装饰函数可以有多个参数)
终极装饰器(带参数的装饰器)
>>info: enter function say()
>>hello lucas
>>hello lucas
类装饰器
装饰器要求接收一个callable对象,并返回一个callable对象。
所以类装饰器中,__init__接收函数,重载__call__()并返回一个函数
一个装饰器可以装饰多个函数
一个函数可以有多个装饰器(很少用)
所以类装饰器中,__init__接收函数,重载__call__()并返回一个函数
一个装饰器可以装饰多个函数
一个函数可以有多个装饰器(很少用)
简单的类装饰器
带参数的类装饰器
>>INFO: enter function say()
>>say lucas
>>INFO: enter function say()
>>say lll
>>None
>>say lucas
>>INFO: enter function say()
>>say lll
>>None
元类
开发中用不到,用于阅读源代码
类的本质是个对象,
所以可以对它做一下操作
将它赋值给一个变量
拷贝它
为它增加属性
将它作为函数参数进行传递
代码解释
创建一个类
类是可以动态创建的
type
所有类都继承于object,所有类都是由type创建。
object也是由type创建的,type也是由type创建
object也是由type创建的,type也是由type创建
实例对象由类对象创建,所以type(实例对象),出来的是对应的类
而类对象是由type创建,所以type(类对象),出来的是type。
object也是类,所有类都继承于object,但其创建者是type
而类对象是由type创建,所以type(类对象),出来的是type。
object也是类,所有类都继承于object,但其创建者是type
使用type创建一个类
type函数的第二种用法(自己写函数不要让一个函数有两种完全不同的功能)
例
定义一个父类
定义一堆方法
将方法和父类放进子类中去
实例化后检查子类功能
>>住在深圳的lucas同学
>>lucas 深圳
>>这是一个静态方法
>>这是类方法 深圳
>>lucas 深圳
>>这是一个静态方法
>>这是类方法 深圳
元类本身的探究
元类创建类,类创建实例对象
函数type实际上是一个元类。type是python在背后用来创建所有类的元类。
str是用来创建字符串对象的类
int是用来创建整数对象的类
type是用来创建类对象的类
可以通过__class__属性来看到这一点
str是用来创建字符串对象的类
int是用来创建整数对象的类
type是用来创建类对象的类
可以通过__class__属性来看到这一点
__metaclass__
class Foo(object, metaclass=something):
# __metaclass__ = something, python2这么写
# 这个方法不用加def
# __metaclass__ = something, python2这么写
# 这个方法不用加def
自定义类
元类的主要目的是为了创建类时能够自动地改变类
例
异常处理
例,什么是异常
一个简单的登录系统
输入两个整数算商
try:
n1 = int(input('输入一个整数:'))
n2 = int(input('输入另一个整数:'))
res = n1/n2
# except ZeroDivisionError:
# print('除数不能为0哦')
# except ValueError:
# print('懂什么叫整数吗')
except BaseException as B:
print('出错了,请学会自己看报错:', B)
else:
print('结果为:', res)
finally:
print('-----------------')
print('感谢使用,本程序结束')
n1 = int(input('输入一个整数:'))
n2 = int(input('输入另一个整数:'))
res = n1/n2
# except ZeroDivisionError:
# print('除数不能为0哦')
# except ValueError:
# print('懂什么叫整数吗')
except BaseException as B:
print('出错了,请学会自己看报错:', B)
else:
print('结果为:', res)
finally:
print('-----------------')
print('感谢使用,本程序结束')
处理异常
try--except
try正常运行,出对应异常走except
多个异常
try--except--except(多个异常)
只执行一个分支
except(NameError, TypeError):
多个异常一般这么写
多个异常一般这么写
except后面跟一个元组
try--except--else
用的不多
用的不多
try内没有异常走else
try--except--else--finally
报不报错都要跑
捕获异常对象(异常本身信息)
as e
全捕捉
用Exception
不写默认Exception
自定义异常(用的不多)
例1
自定义的异常要继承Exception
例2
先定义一个异常类
在函数中主动抛出异常
调用函数
通过异常捕获获得以下字符串里面的字母字符
str_1='d52a733i2327ha244i982d23s553b245'
抛出异常
raise
不加as e
打印出自定义信息
一般不这么用,在except下面加raise
一般使用场景
网络请求
文件读写
常见异常类型
BaseException
所有异常的基类
https://docs.python.org/zh-cn/3.9/library/exceptions.html#exception-hierarchy
Exception
常见异常的基类(父类)
不知道会有什么异常就用这个
ZeroDivisionError
除(or取模)0,所有数据类型
IndexError
序列中没索引
KeyError
映射中没这个键
NameError
未声明/初始化对象(没有属性)
SyntaxError
语法错误
ValueError
传入无效参数
traceback模块
pycharm程序调试
debug shift+F9
文件操作
打开文件
内置函数open
open(file_name [, access_mode][, buffering])
file_name 变量:是一个包含要访问的文件名称的字符串值。
access_mode 变量:指打开文件的模式,对应有只读、写入、追加等。
access_mode变量值不是必需的(不带access_mode变量时,要求file_name存在,否则报异常),默认的文件访问模式为只读(r)。
access_mode变量值不是必需的(不带access_mode变量时,要求file_name存在,否则报异常),默认的文件访问模式为只读(r)。
'r'
- 读取 - 默认值。只能读取,如果文件不存在则报错。
'a'
- 追加 - 在原有内容基础上追加内容,在末尾写入,如果不存在则创建该文件。
'w'
- 写入 - 只能写入,如果文件不存在则创建该文件。
‘w+’
可读可写
'x'
- 创建 - 创建指定的文件,如果文件存在则返回错误。
't'
- 文本 - 默认值。文本模式。
'b'
- 二进制 - 二进制模式(例如图像)。
'rb'
二进制格式打开一个文件,只读
'wb'
同上,只写
'ab'
同上,追加
'wb+'
同上,读写
注意:尽量一次只用一种模式,+模式好像不太好用
buffering:(一般很少用)
如果buffering的值被设为0,就不会有寄存;
如果buffering的值取1,访问文件时就会寄存行;
如果将buffering的值设为大于1的整数,表示这就是寄存区的缓冲大小;
如果取负值,寄存区的缓冲大小就是系统默认的值。
如果buffering的值被设为0,就不会有寄存;
如果buffering的值取1,访问文件时就会寄存行;
如果将buffering的值设为大于1的整数,表示这就是寄存区的缓冲大小;
如果取负值,寄存区的缓冲大小就是系统默认的值。
路径
绝对路径
从根文件夹开始
E:/zoom/palegechong/图灵/基础语法/文件操作/文档/test.txt
相对路径
相对于当前工作目录的路径
./文档/test.txt
例
encoding='utf-8',设置编码
基本文件操作
read()
write()
readline()
读一行
readlines()
放进列表,每行一个元素
writelines()
文件流操作
如果对文件进行了读写之后,要关闭文件流(close())
因为文件读写需要使用计算机资源
如果不关容易程序异常,还占系统资源
因为文件读写需要使用计算机资源
如果不关容易程序异常,还占系统资源
with open as f:
with open可以自动调用close
重命名
os模块
os.rename('原文件名', '新文件名')
文件名这里写路径,不论是绝对路径还是相对路径
删除
os模块
也是填路径
迭代读取
普通版
fileinput模块
fileinput读完一行释放一行内存
StringIO函数
序列化/反序列化
JSON
前端的字典
json模块
并发编程
网络编程
TCP/IP
ip: port
ip定位服务器地址,端口定位在操作系统上的具体位置
知名端口0-1023
80端口 HTTP服务
21端口 FTP服务 上传下载
22端口 SSH服务 远程链接
443端口 HTTPS服务 加密的HTTP协议
21端口 FTP服务 上传下载
22端口 SSH服务 远程链接
443端口 HTTPS服务 加密的HTTP协议
1024 < 可用端口 < 10000
协议
udp
文本、二进制文件【图片音频视频】
传输快,结构简单
广播:可以完成聊天室开发(在微信群里一个人发消息所有人都看见)
不安全,容易丢包,只负责发送,不保证收不收得到
不严格区分客户端和服务端
发送方也可使接收方
一对多
网络直播
游戏
部分物联网
游戏
部分物联网
tcp
严格区分客户端和服务端
发送数据前会先链接服务器,如果检查服务器不在线,就抛出异常
一对一
socket包/套接字
socket.socket(family, type)
family -> 网络类型
ipv4 --> pc .AF_INET
ipv6 --> 移动端 .AF_INET6
ipv4 --> pc .AF_INET
ipv6 --> 移动端 .AF_INET6
type -> 协议类型
tcp .SOCK_STEAM
udp .SOCK_DGRAM
ftp
ssh
...
tcp .SOCK_STEAM
udp .SOCK_DGRAM
ftp
ssh
...
UDP
C/S架构的网络流程
例
发送信息
0.创建套接字对象
1.接收方地址(‘ip’, 端口)
2.套接字对象.sendto(消息.encode(), 地址)
3.关闭套接字对象
1.接收方地址(‘ip’, 端口)
2.套接字对象.sendto(消息.encode(), 地址)
3.关闭套接字对象
循环发送
0.创建套接字对象
1.创建循环,循环发送
2.接收方地址(‘ip’, 端口)
3.用户输入数据
4.套接字对象.sendto(消息.encode(), 地址)
5.判断,输入exit则break
6.循环外,关闭套接字
1.创建循环,循环发送
2.接收方地址(‘ip’, 端口)
3.用户输入数据
4.套接字对象.sendto(消息.encode(), 地址)
5.判断,输入exit则break
6.循环外,关闭套接字
接收消息
0.创建套接字对象
1.绑定本地地址(ip,端口),如果不绑定,每次都会随机分配一个端口
2.接收数据,一次最大1024
3.打印接收到的数据,接收到的是(二进制信息,发送方地址)的一个元组,取index[0]且decode
4.关闭套接字对象
1.绑定本地地址(ip,端口),如果不绑定,每次都会随机分配一个端口
2.接收数据,一次最大1024
3.打印接收到的数据,接收到的是(二进制信息,发送方地址)的一个元组,取index[0]且decode
4.关闭套接字对象
循环接收
0.创建套接字对象
1.绑定本地地址
2.创建循环,循环接收
3.接收数据,一次最大1024
4.打印接收到的数据[0]
6.判断,收到exit就break
7.循环外,关闭套接字
1.绑定本地地址
2.创建循环,循环接收
3.接收数据,一次最大1024
4.打印接收到的数据[0]
6.判断,收到exit就break
7.循环外,关闭套接字
可选择聊天器
发送函数和接收函数
主函数
TCP
网络流程
例
客户端
0.创建套接字
1.链接tcp服务器.connect(ip和端口)# 服务器不在线则抛出异常
2.向tcp发送消息.send(.encode('utf-8))
3.关闭套接字
# 要先让服务端连网
1.链接tcp服务器.connect(ip和端口)# 服务器不在线则抛出异常
2.向tcp发送消息.send(.encode('utf-8))
3.关闭套接字
# 要先让服务端连网
服务器
0.创建套接字对象
1.绑定端口,随机端口别人可不好连接.bind
2.使服务器转为监听(等待),tcp默认状态是连接别人.listen(128)默认最大连接数 多线程
3.如果客户度连接成功,生成新套接字.accept()
堵塞方法,如果没有接收到消息,卡在这
进行数据接收和发送,获取客户端ip port
返回值是一个元组(套接字对象,(ip,port)),解包获取
4.接收数据.recv(1024)
5.打印信息
6.收到消息后,发回执.send()(别了,还得改客户端)
7.关闭套接字(2个)
1.绑定端口,随机端口别人可不好连接.bind
2.使服务器转为监听(等待),tcp默认状态是连接别人.listen(128)默认最大连接数 多线程
3.如果客户度连接成功,生成新套接字.accept()
堵塞方法,如果没有接收到消息,卡在这
进行数据接收和发送,获取客户端ip port
返回值是一个元组(套接字对象,(ip,port)),解包获取
4.接收数据.recv(1024)
5.打印信息
6.收到消息后,发回执.send()(别了,还得改客户端)
7.关闭套接字(2个)
注意事项
文件下载服务器
服务器
文件函数
主函数
if __name__ == '__main__':
main()
main()
客户端
前import socket
后
if __name__ == '__main__':
main()
后
if __name__ == '__main__':
main()
多任务
多线程
概念
线程
线程有开始、顺序执行和结束3部分,有一个自己的指令指针,记录运行到什么地方。
线程的运行可能被抢占(中断)或暂时被挂起(睡眠),从而让其他线程运行,这叫作让步。
线程的运行可能被抢占(中断)或暂时被挂起(睡眠),从而让其他线程运行,这叫作让步。
线程是运行代码的一个最基本的资源
运行代码
是有线程去执行的
是有线程去执行的
CPU读取内存中,代码的二进制数据。
由进程分配内存(进程是操作系统资源的一个分配的单位)
由进程分配内存(进程是操作系统资源的一个分配的单位)
进程创建一个线程对象去运行代码
代码运行起来都是单线程程序
操作系统创建一个进程来分配系统资源
进程创建一个线程来运行代码
操作系统创建一个进程来分配系统资源
进程创建一个线程来运行代码
多任务
让进程创建多个线程来执行文件
多核CPU
时间片轮训
任务切换机制
理论上,一个双核CPU,同时只能执行2个任务。(全局解释器锁GIL)
但时间片轮训,是让任务执行几纳秒的时间,然后切出去,让其他程序进来运行。快速切换,(就如两个智子以光速运动,感觉上是可以同时控制地球上的所有粒子加速器。)
理论上,一个双核CPU,同时只能执行2个任务。(全局解释器锁GIL)
但时间片轮训,是让任务执行几纳秒的时间,然后切出去,让其他程序进来运行。快速切换,(就如两个智子以光速运动,感觉上是可以同时控制地球上的所有粒子加速器。)
并行
真正的用多个核心同时执行任务
并发
在一段时间中,多个任务进行切换执行
python同一时刻只能运行一个线程,但切换速度快也能称之为多任务
案例
一个简单的多线程任务
单线程
hello是1秒打印一个
多线程
5个hello是同时打印
主线程和子线程
每段代码都有一个线程来运行
如果主线程代码执行完毕
但子线程还在任务中
主线程会等子线程完毕后
退出程序
但子线程还在任务中
主线程会等子线程完毕后
退出程序
主线程结束后进入等待,等待子线程结束后退出
主线程主要负责执行.py文件
如果.py文件中发现threading
则自动创建一个子线程
如果.py文件中发现threading
则自动创建一个子线程
对线程执行顺序不加干扰
导入包
定义要运行的2个函数
隔1秒打印一次
放入线程
输出结果
在线程执行过程中,执行线程的顺序是随机的
运行代码时,只有一个线程被执行
运行代码时,只有一个线程被执行
干扰线程执行顺序
前面都一样
加个ctime方便看时间戳
关键是调用join()
输出
比如下载10部电影
首先开10个线程,获取电影数据
join()等待执行完成
再开10个线程,下载电影数据
首先开10个线程,获取电影数据
join()等待执行完成
再开10个线程,下载电影数据
获取无顺序,下载无顺序,但要获取完才能下载
查看当前线程数量
其他都一样,就是在线程start后print调用enumerate方法
>[<_MainThread(MainThread, started 10900)>, <Thread(Thread-1, started 8528)>, <Thread(Thread-2, started 14552)>]
创建的2个+当前程序,共3个
这是个列表,所有可以直接用len(threading.enumerate())查看当前有几个线程
创建的2个+当前程序,共3个
这是个列表,所有可以直接用len(threading.enumerate())查看当前有几个线程
共享全局变量
如果多个线程同时对同一个全局变量操作,
会出现资源竞争问题,
从而数据结果会不正确。
会出现资源竞争问题,
从而数据结果会不正确。
>>线程创建之前g_num为:0
>>线程创建之后g_num为:690959
>>线程1计算结果为:1000000
>>线程2计算结果为:1264604
>>线程创建之后g_num为:690959
>>线程1计算结果为:1000000
>>线程2计算结果为:1264604
修改全局变量步骤:
1.获取
2.修改
3.赋值回去
多线程特点:(有可能是这样的)
t1线程走完步骤1,2 挂起
t2线开始走步骤1,2,3 挂起
t1线程走步骤3
循环次数多了之后,结果就乱了
2个线程各+num次,最终结果应该是2*num
主线程是提前获取g_num,然后等待的,所以主线程获取的结果也不对
1.获取
2.修改
3.赋值回去
多线程特点:(有可能是这样的)
t1线程走完步骤1,2 挂起
t2线开始走步骤1,2,3 挂起
t1线程走步骤3
循环次数多了之后,结果就乱了
2个线程各+num次,最终结果应该是2*num
主线程是提前获取g_num,然后等待的,所以主线程获取的结果也不对
互斥锁(避免资源竞争)
t1获取全局变量时,上锁,
t1解锁后,其他线程才能操作全局变量
t1解锁后,其他线程才能操作全局变量
例
锁对象是全局变量
上了锁的2个函数
主线程要阻塞
>>add_1: 1000000
>>add_1: 2000000
>>主线程:2000000
>>add_1: 2000000
>>主线程:2000000
死锁
先看代码
声明2把锁
run_1
用锁A锁全部,用锁B锁中间
A上锁后,等待1秒,
再往下B锁已经被线程2上锁了,
只有等线程2给锁B解锁后,
线程1才能在锁B锁
再往下B锁已经被线程2上锁了,
只有等线程2给锁B解锁后,
线程1才能在锁B锁
run_2
用锁B锁全部,用锁A锁中间
B上锁后,等待1秒,
再往下A锁已经被线程1上锁了,
只有等线程1给锁A解锁后,
线程2才能在锁A锁
再往下A锁已经被线程1上锁了,
只有等线程1给锁A解锁后,
线程2才能在锁A锁
主线程
2个线程都在等对方解锁,
所以会卡死
所以会卡死
用面向对象写线程
创建类,继承threading.Thread
注意
1. 如果要加实例属性
新的构造函数要继承父类的构造函数
Thread中的__init__有大量参数
新的构造函数要继承父类的构造函数
Thread中的__init__有大量参数
2.重写方法run
这个就不用继承了
父类中的run方法只是单纯的调用了target引用的函数
这个就不用继承了
父类中的run方法只是单纯的调用了target引用的函数
创建实例对象,
因为有继承父类,
所以实例对象可以调用父类的start方法
因为有继承父类,
所以实例对象可以调用父类的start方法
tcp并发服务器(再次强调,python没有并行)
先导包
消息控制类
用于处理连上服务器后
接收传过来的消息和服务器信息
用于处理连上服务器后
接收传过来的消息和服务器信息
TCP套接字
在initi里建套接字,绑本地信息,改listen
在run里accept,调用Message(接收信息),start
在initi里建套接字,绑本地信息,改listen
在run里accept,调用Message(接收信息),start
主函数
启动服务器
启动服务器
启动后可以同时连多个客户端
如果想让子线程随主线程一起结束
threading.Thread(target=子线程函数,daemon=True)
线程池(有待完善)
概念
启动新线程成本高,涉及与操作系统交互。
尤其当需要创建大量生存期很短的线程时,线程池可极大提高性能.
且线程过多系统会卡,池可以限制运行的线程数量
尤其当需要创建大量生存期很短的线程时,线程池可极大提高性能.
且线程过多系统会卡,池可以限制运行的线程数量
创建大量空闲线程(指定数量)
有任务则启动执行,任务结束后返回空闲状态
有任务则启动执行,任务结束后返回空闲状态
用法
包
from concurrent.futures import ThreadPoolExecutor
创建对象
pool = ThreadPoolExecutor(max_workers=2)
# 池内2条线程
# 池内2条线程
提交线程给池
future_1 = pool.submit(函数, 函数的参数)
判断线程是否结束
print(future_1.done())
查看任务返回结果(阻塞,没算出结果阻塞当前线程)
print(future_1.result())
关池
pool.shutdown()
整体
案例
用add_done_callback(),不阻塞
map(),同个函数不同参数
线程VS进程
功能
定义
区别
优缺点
多进程
概念
一个程序运行起来后,代码与使用到的系统资源被称之为进程,它是操作系统分配资源的基本单元。
进程状态
就绪态
运行条件都已经具备,正在等待CPU执行
执行态
CPU正在执行其功能
等待态
等待某些条件满足,例如一个程序sleep了,此时就处于等待态
注意
创建进程,一定要加if __name__=='__main__':函数入口
具体原因解释起来挺麻烦的,先这么记着
具体原因解释起来挺麻烦的,先这么记着
Process操作
总结
属性 name group
target
运行哪个
args
target引用的函数的参数,元组
kwargs
传命名参数,字典
给进程命名,一般不用
指定进程组,一般不用
方法
start()
启动
is_alive()
判断是否存活
join([timeout])
等待该进程结束,或者等几秒
terminate()
终止进程(可用于关闭僵尸进程)
案例
不加p.join(),最后一个返回还是True,
因为系统回收进程需要时间,主程序直接运行等不到
因为系统回收进程需要时间,主程序直接运行等不到
案例
1.创建、启动进程
Process(target=要运行的对象)
子进程和主进程也是并发的
2.查看进程pid
os.getpid()获取当前进程pid
os.getppid()获取当前进程父进程pid
os.getppid()获取当前进程父进程pid
3.给进程传参
关于Process的参数
4.进程不共享全局变量
子进程是复制了主进程的代码重新开辟的一块新内存
线程是同一个进程内的操作,就是同一块内存空间
进程是重新开辟新内存空间
线程是同一个进程内的操作,就是同一块内存空间
进程是重新开辟新内存空间
代码说明
通过id()可以发现,2个子进程和主进程中的lst不是同一个列表
5.进程间的通信(Queue)
队列:先进先出列表(栈是先进后出)
put添加元素
put_nowait不等待
get提取元素
get_nowait不等待
full是否满了
empty是否为空
put_nowait不等待
get提取元素
get_nowait不等待
full是否满了
empty是否为空
通过队列实现进程通信
#一个进程抓网站数据
#一个进程数据读写
#一个进程数据读写
模拟抓取下来的数据
模拟数据读写
创建一个列表,把queue里的数据拿出来、塞进去
开启进程
记得加join,保证都抓下来再读写
6.进程池
概念
创建进程池后,会自动创建设置好的进程个数
进程池(10个进程),重复利用
比如有一百万个任务,放入池中
十个十个执行,其他等待池中有任务执行完毕再进池运行
比如有一百万个任务,放入池中
十个十个执行,其他等待池中有任务执行完毕再进池运行
知识点
如果任务数大于进程数,多出来的任务会等待被执行
close要在join之前
关闭进程池,关的是池这个容器,不是进程
关了之后就不能动态创建进程了,但原本已启动的进程照常
关了之后就不能动态创建进程了,但原本已启动的进程照常
案例
先导包
worker一个记录随机休眠的函数
主程序创建一个3的池,循环10个创建worker进程
主程序创建一个3的池,循环10个创建worker进程
异步执行要比同步执行 快很多
同步执行要十几秒
Queue
一般情况下,代码编写要统一风格
就是说如果使用线程,则整个程序统一使用线程
就是说如果使用线程,则整个程序统一使用线程
协程
铺垫
概念
线程和进程都是计算机提供的
都是os提供的api,就是系统层面的功能接口,
python只是负责调度。
但协程是完全python创建的,语言层面的创造。
多线程其实也只有一个线程在运行,其他线程等在那也会浪费一些性能
所以不如就开一个线程。
都是os提供的api,就是系统层面的功能接口,
python只是负责调度。
但协程是完全python创建的,语言层面的创造。
多线程其实也只有一个线程在运行,其他线程等在那也会浪费一些性能
所以不如就开一个线程。
协程性能大于多线程
协程的大致运行状态:
在一个线程中运行多个任务,
任务和任务之间来回切换,
并在同一时间内只能运行一个任务
在一个线程中运行多个任务,
任务和任务之间来回切换,
并在同一时间内只能运行一个任务
基本思路
greenlet(现在不用)
这样就能输出1 3 2 4
或者用yield(也很牵强,因为yield只能从上往下)
这个是1 3 4 2
从1到2后,回不去1了,
强行查个yield from func1()在2里,会报错
从1到2后,回不去1了,
强行查个yield from func1()在2里,会报错
@asyncio.coroutine
task对象和事件循环
主流用法
async
1.函数被async声明,则函数为一个协程函数对象
2. 函数对象不能被直接调用
3.要运行协程函数,需要借助事件循环
4.await是协程关键字,
用于等待一些耗时的任务并拿到这些任务的返回值之后才解堵塞
5.在协程中,func是引用,func()不是调用,是返回协程对象
6.事件循环只能执行可被等待对象:协程对象,asyncio对象,task对象
2. 函数对象不能被直接调用
3.要运行协程函数,需要借助事件循环
4.await是协程关键字,
用于等待一些耗时的任务并拿到这些任务的返回值之后才解堵塞
5.在协程中,func是引用,func()不是调用,是返回协程对象
6.事件循环只能执行可被等待对象:协程对象,asyncio对象,task对象
异步执行两个函数
1.同时输出1 3
2.第一个await开始等待2s,第二个await也开始等待2s
3.2s后,输出2和4
如果下面的sleep改成3,那就是1 3,2s后2,3-2=1s后4
1.同时输出1 3
2.第一个await开始等待2s,第二个await也开始等待2s
3.2s后,输出2和4
如果下面的sleep改成3,那就是1 3,2s后2,3-2=1s后4
案例
单线程爬虫
第一个链接拿到数据,没写入完成,第二是链接不会开始读的
协程爬虫
异步编程
事件循环
原理
事件循环做了什么:
1.循环整个任务列表
2.将任务分为可以执行和执行完毕两种
3.将完成的任务从列表中剔除
4.当列表为空,中断循环
1.循环整个任务列表
2.将任务分为可以执行和执行完毕两种
3.将完成的任务从列表中剔除
4.当列表为空,中断循环
创建
基本应用
await关键字
等待某些IO任务
await + 可等待对象
await + 可等待对象
await探究
可以通过await执行其他协程函数
await本质就是等待
await本质就是等待
同步单线程,红线后等待2秒
协程对象1执行完成后,才会执行协程对象2
事件循环中放入的是一个普通的协程对象,不是task对象
协程对象1执行完成后,才会执行协程对象2
事件循环中放入的是一个普通的协程对象,不是task对象
task对象
普通型(代码重复)
如果await后面是协程对象,则会同步切换
task放入列表
done内容
子主题
pending内容
set( )
事件循环/task对象
asyncio.run
中封装了loop和creat_task
中封装了loop和creat_task
创建task对象时,要保证事件循环已经存在
比如像这样,就是先创建task对象,再run运行(自动创建事件循环)
其实没必要,run方法会自动创建task对象
列表里放入func()就没问题了
其实没必要,run方法会自动创建task对象
列表里放入func()就没问题了
如果创建一个全局列表
1.列表中放入协程函数对象,
2.再用asyncio.wait()将列表转为可等待对象
3.然后再用run
1.列表中放入协程函数对象,
2.再用asyncio.wait()将列表转为可等待对象
3.然后再用run
Future对象(比较底层不太用得到)
task是继承future的
在future对象中会保存当前执行的这个协程任务的状态
如果当前任务状态为finished,则await不再等待
在future对象中会保存当前执行的这个协程任务的状态
如果当前任务状态为finished,则await不再等待
示例
交叉编程(一般不这么用)
有些库不支持协程,这时就可以用线程\进程池
run_in_executor
先导包
None
>>当前方式会自动创建一个线程池去执行普通函数: test
协程函数中可以运行一个线程池,但这个池必须由事件循环处理
协程函数中可以运行一个线程池,但这个池必须由事件循环处理
自建线程池
如果协程函数中自己手动创建了一个线程池,则必须用run_in_executor调用
自建进程池
同上,但如果要创建进程池,一定要用if __name__ == '__main__':
异步迭代器(一般不这么用)
# 迭代器执行的代码一般是IO耗时操作
异步上下文管理器(更用不到)
uvloop(了解一下)
数据库
纯数据库
MySQL(存在磁盘)
基本概念
创建数据库,相当于文件夹
在数据库中创建表,相当于文件中创建文件
表中可以存储相关数据
数据表必须有表名
表中有字段
数据有类型
一些字段可能存在约束
关系型数据库
表与表之间有关系
数据类型
整型
int
bit二进制整型(0false,1true)
tinyint
-128~127
小数
decimal(精度高)别名:货币类型
交易,金额
float
字符串
创建字符串要规定长度
varchar(常用)
不固定长度
创建时候设定30,存hello,占用的是5
char
固定长度
创建时候设定30,存hello,占用的也是30
text(超过255字符)
长文本
日期时间
date
time
datetime
枚举类型
enum
选择,选项
gender性别:男/女
约束
主键
primary key
不为空,不重复
非空
not null
唯一
unique
不重复
默认
default
存数据的时候没写这个字段,系统会填入建表时设置的默认值
外键
foreign key
无符号(不能为负数)
子主题
自动增长
auto_increment
库的基本操作
注释
--这是一个注释
/*
多行
*/
多行
*/
连接
mysql -uroot -p
查看
show databases;
进入数据库
use 数据库名;
查看当前数据库
select database();
创建
create database 数据库名 charset=utf8;
查看数据库创建过程
show create database 数据库名;
删除库
drop database 数据库名;
退出mysql
\q,exit
表的基本操作
查看表
show tables;
创建表
create TABLE classes(
id int UNSIGNED auto_increment PRIMARY KEY not null,
name VARCHAR(10) not NULL
);
id int UNSIGNED auto_increment PRIMARY KEY not null,
name VARCHAR(10) not NULL
);
查看表结构
desc 表名;
添加字段
表添加新列
alter table 表名 add 列名 类型;
修改字段
重命名
alter table 表名 change 字段原名 字段新名 类型 [约束];
不重命名
alter table 表名 modify 原字段名 类型 [约束];
删除字段
alter table 表名 drop 字段名;
查看表的创建过程
show create table 表名;
删除表
drop table 表名;
表中数据的基本操作
查 子查询(可替代)
select * from 表名;
select 字段,字段 from 表名;
设别名
表
select s.name from students as s;
字段(只在查询结果中)
select id as 序号, name as 姓名, gender as 性别 from students;
去重
select distinct 字段名 from 表名;
只是字段去重查询
条件
where
select * from 表名 where 条件;
enum类型相当于一个列表,条件查询的时候尽量用下标
sql语言里尽量不要用中文
sql语言里尽量不要用中文
比较运算
=
>
<
>=
<=
!= or <>
>
<
>=
<=
!= or <>
逻辑运算
and
or
not
(select * from 表名 where not 条件;)
or
not
(select * from 表名 where not 条件;)
模糊查询
like
%,任意多个字符
_,任一字符
_,任一字符
范围查询
in(非连续)
select * from 表名 where 字段 in (选项);
between(连续)
select * from 表名 where 字段 between 下限 and 上限;(包括上下限)
判断是否为空
is null
null和‘ ’不一样
is not null
排序
order by
asc升序
order by默认升序
order by默认升序
前面都一样
order by 要排序的列名或列index(从1开始计数)
order by 要排序的列名或列index(从1开始计数)
desc降序
列1 asc,列2 desc;
聚合函数
count(*)
总行数
max(字段)
字段的最大值
min(字段)
字段的最小值
sum(字段)
求和
avg(字段)
平均值
分组
group by
+group_concat
select 字段1,group_concat(字段2) as 字段2 from 表名 group by 字段1
对字段1分组,并显示对应组里的字段2
+聚合函数
+having
having一定要在group by后面
加条件
+ with rollup(很少用)
分页
select * from 表名 limit start(起始位,如果是首页0可以不写),count(显示数);
查第5-10这六条数据
limit要在最后,不能写公式进去要写具体值
连接查询
join on
inner join on
2表一一对应,不显示对应不上的
left(right)join on
主表 left join on 子表
主表中的数据对应不上子表数据,则显示null
子表中数据对应不上主表的不显示
子表中数据对应不上主表的不显示
自关联
就是表内数据间有关联
自己 inner join on 自己
可以同张表as不同的名字
可以同张表as不同的名字
例
aid是自身id,pid是所属id
通过where后面的条件找到所属上级,
通过join on后面的条件找到结果
通过where后面的条件找到所属上级,
通过join on后面的条件找到结果
嵌套查询(巨慢,别用,会被认为是外行)
selec结果相当于一个临时表,嵌套就是在临时表中查询
子查询和外键查询,如果是公司的数据库,不要用,数据量稍微大点,CPU就100%
增
全字段
insert into 表名(字段名1,字段名2.....不写也行,写就要写全)values(数据1,数据2....)
如果有默认值,也要写value
如果有默认值,也要写value
部分字段
insert into 表名(字段名1,字段名2)values(数据1,数据2)
多行插入
全字段插入
insert into 表名 values(....),(......);
改
update 表名 set 字段1=值1,字段2=值2.... where 条件;
删
delete from 表名 where 条件;
is_delete=0 False,在页面中显示数据
is_delete=1 True,隐藏数据,数据在库里,但看不懂
is_delete=1 True,隐藏数据,数据在库里,但看不懂
视图
相当于把语句分装到函数里,语句结果建立一个临时表
调用时当表去select
命名v_开头
调用时当表去select
命名v_开头
create view 视图名 as select语句
drop view 视图名
事务
表引擎必须是innodb类型才支持事务
特性ACID
原子性
事务不可分割,全成功or全失败
一致性
事务将数据库从一种一致性状态转换到另一种一致性状态。
一致性状态:
1.系统的状态满足数据的完整性约束
2.系统的状态反应数据库本应描述的现实世界真实状态,比如转账前后2个账户的金额总和应保持不变
( 所有操作全部成功之后也不会立即生效,要提交)
一致性状态:
1.系统的状态满足数据的完整性约束
2.系统的状态反应数据库本应描述的现实世界真实状态,比如转账前后2个账户的金额总和应保持不变
( 所有操作全部成功之后也不会立即生效,要提交)
隔离性
并发执行的事物不会相互影响,其对数据库的影响和它们串联执行时一样。(一个事务所做的执行,在提交前对其他事务不可见)
比如多个用户同时往一个账户转账,最后账户的结果应该和它们按先后次序转账的结果一样。
比如多个用户同时往一个账户转账,最后账户的结果应该和它们按先后次序转账的结果一样。
持久性
一旦提交,数据永久保存
操作(有头有尾不出错)
头
开启
begin;
or
start transaction;
begin;
or
start transaction;
修改数据的命令会自动触发事务,insert update delete
尾
提交
commit;
commit;
报错的语句不执行,其他都提交
回滚
rollback;
rollback;
报错回滚报错那条语句,这好像是废话
回滚取消之前一切操作
例
开两个终端
终端A
begin;
insert into classes(name) values('python_04期');
select * from classes;查询发现有新数据
insert into classes(name) values('python_04期');
select * from classes;查询发现有新数据
commit;
终端B
select * from classes;
查询不到A新插入的数据
A提交后,才能查询到新数据
NoSQL VS SQL
关系型数据库
数据与数据之间有关联
支持事务
适合数据结构复杂的数据
数据相对安全
数据存储持久化
存在磁盘里,不删会一直在
非关系型数据库
不支持sql语言,每种数据库有单独的语言
数据与数据之间无关联
不支持事务
适合结构复杂的数据
数据不太安全
如Redis,存在内存里,断电就没了
页面缓存,验证码
Redis(存在内存)
简介:
特性
优势
应用场景
特性
优势
应用场景
redis数据库
redis-cli,连接Redis服务器
ping,返回pong,说明连接正常
16个默认数据库(0-15)
select 10,选择10号数据库
默认存二进制
数据操作
数据类型
字符串string
set key value
setex key seconds value
过期时间为秒
mset key1 value1 key2 value2 .....
多个键值对
get key
获取key对应的value
mget key1 key2...
获取多个键的values
键命令
keys *
查看所有key
keys m*
查看m开头的所有key
exists key
查看这个key是否存在(0不存在/1存在)
type key
查看value的数据类型
默认str
del key1 key2...
以key删数据
哈希hash
hash用于存储对象,对象结构为属性、值,值类型为string
hset key field value
hmset key field1 value1 field2 value2
只能在同一个大key下设置多个键值对
hkeys key
查key对应的field
hget key field
查对应的value
hmget key field1 field2...
hvals key
查整个key的所有values
hdel key field1 field2...
del key
redis中的hash数据
列表list
redis中的列表按照插入顺序排序
先入后出
先入后出
lpush key value1 value2....
left插入
rpush key value1 value2....
right插入
lrange key start stop
只有左取值
-1是最后一位
lrem key count(要删除的个数) value
count>0 从左往右删
count<0 从右往左删
count=0 删除所有
count<0 从右往左删
count=0 删除所有
无序集合set
元素都是string,元素唯一不重复
sadd key member1 member2...
smembers key
所有元素
srem key member
删指定元素
有序集合zset
zadd key socre1 member1 socre2 member2....
zrange key start stop
zrem key member2 member2...
MongoDB(存在磁盘)
简介
需要自己创建数据库
没有表的概念,自己创建集合(相当于MySQL的表)
命令
数据库
终端进入
mongo
查看当前数据库
db
查看数据库s
show dbs
切换or新建数据库
use
如果数据库中无数据,且到别的数据库后该数据库会消失
删除当前数据库
db.dropDatabase()
v 4.0用小写d
看当前数据库版本
version()
集合
创建数据库
db.createCollection('数据库名')
创建限定大小的集合
一般不用
查看集合s
show collections
删除集合
db.集合名.drop()
drop v 4.0/Drop v 3.0
数据
数据类型
Object ID
文档ID
16进制数字,类似主键
String
字符串
布尔
true,false
integer
整数
double
浮点值
arrays
数据或列表,多个值存到一个键
Object
理解为对象
Null
Timestamp
时间戳
Date
日期
数据操作
增
插入数据
db.集合名.insert({'name': 'xiaowang', 'age': 18})
(里面写个JSON)
_id可以自己设id
key可以不用加引号
(里面写个JSON)
_id可以自己设id
key可以不用加引号
_id不可以重复
删
db.集合名.remove({字段},{justOne: true})
如果字段有重复,加justOne: true
如果字段有重复,加justOne: true
改
db.集合名.save(id重复也可以保存)
db.集合名.update({原字段}, {修改后的字段})
没update的字段会消失
没update的字段会消失
用update({原字段},{$set: {修改后的字段}})
查(主要操作都在这)
查集合里的数据
db.集合名.find({可添加搜索条件})
查询所有
查询所有
findOne
查询一个
查询一个
自动pretty()
find().pretty()对数据格式化输出
比较运算符
等于 :
小于 $lt
小于等于$lte
大于 $gt
db.stu_info.find({age:{$gt: 18}})
大于等于$gte
不等于 $ne
范围
18或者80(age: {$in: [18, 28]})
逻辑运算符
and
,
or
$or
{$or: [{条件一}, {条件2}]}
整合应用
45岁的
或者
hometown在
桃花岛或者华山的
或者
hometown在
桃花岛或者华山的
正则
以abc开头
{字段: /^abc/}
只能用/ /
{字段: {$regex: '^abc'}}
用$regex,后面可‘ ’可/ /
以789结尾
789$
翻页
find().limit(3)
查前3条数据
find().skip(3)
跳过前3条数据
find().skip(2).limit(2)
跳过前2条,查第3,4条数据
自定义查询
js语法
投影
相当于mysql的查询2个字段
select name, gender from stu_info;
select name, gender from stu_info;
find({条件,查*就空着},{需要显示的字段:1,_id:(【默认】显示:1,不显示:0)})
0只针对_id
0只针对_id
排序
find().sort({字段:正序1,倒序-1,条件2})
统计
find().count()
count({条件})
或者
find({条件}).count()
或者
find({条件}).count()
去重
distinct(‘字段’,{条件})
聚合操作
db.集合名称.aggregate({管道:{表达式}})
常用管道
$group
分组
统计男女各多少条
统计男女各多少条数据+男女各自的平均年龄
不加分组条件(统计所有)
$match
过滤(按条件筛选)
查询年龄大于20的
$project
修改输⼊⽂档的结构,如重命名、增加、删除字段、创建计算结果
投影
重命名
记得把_id去掉
联合使用
逻辑很简单,注意各种标点就行
$sort
排序
普通排序
查询结果排序
$limit
输出指定条数
和find的翻页用法差不多,加个$而已
$skip
跳过指定条数
$unwind
将数组类型的字段进行拆分
表达式
$sum
计算总和,$sum: 1 表示一倍计算
$avg
平均值
$min
最小值
$max
最大值
$push
在结果文档中插入
$first
获取第一条数据
$last
获取最后一条数据
索引优化(一般情况下MongoDB不设置索引)
python
MySQL(3306)
pymysql
全局变量
异常
connect函数
cursor()
游标对象,负责执行SQL语句
游标对象,负责执行SQL语句
类型
例
1.利用pymysql连接数据库
2.创建表
3.插入数据(更新表/删除表也一样,就是sql不一样)
插入数据,使用execute()的参数
Redis(6379)
from redis import Redis
1.创建对象
from redis import Redis
redis_obj = Redis(host='localhost', port=6379, db=0)
redis_obj = Redis(host='localhost', port=6379, db=0)
string
2.添加字符串
result = redis_obj.set('姓名', 'anma')
3.获取数据
get_result = redis_obj.get('name')
print(get_result.decode())
print(get_result.decode())
4.修改数据(重新set)
set_result = redis_obj.set('姓名', 'lucas')
5.删除key
del_result = redis_obj.delete('姓名')
6.获取key
hash
太长了,点击图片查看
MongoDB(27017)
from pymongo import MongoClient
创建连接
client['数据库']['集合'],不存在对应数据库或者集合,会自动创建
插入数据
单条
放字典
多条
推导式,注意for 放哪里
查询
find_one( )
find( )
find( )
更新
一条
update_one()
多条
update_many()
name为test0-test9,添加age字段,值为0-9
其实这条语句用update_one就行
其实这条语句用update_one就行
修改所有name为test1的数据
删除
一条
delete_one()
多条
delete_many()
注意值的数据类型
练习
前端
HTML
基础结构
<!DOCTYPE html>
<head>
<title></title>
<style></style>
<script> </script>
</head>
<body>
</body>
</html>
一切靠开头+Tab补全
<head>
<title></title>
<style></style>
<script> </script>
</head>
<body>
</body>
</html>
一切靠开头+Tab补全
标签
点开慢慢找
file:///E:/zoom/1palegechong/1%E5%9B%BE%E7%81%B5/6.%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/%E5%89%8D%E7%AB%AFvip%E8%AF%BE%E4%BB%B6/html%E6%A0%87%E7%AD%BE.md
属性
file:///E:/zoom/1palegechong/1%E5%9B%BE%E7%81%B5/6.%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/%E5%89%8D%E7%AB%AFvip%E8%AF%BE%E4%BB%B6/html%E5%B1%9E%E6%80%A7.md
资源路径
用相对路径,绝对路径换机器就找不到文件了
列表标签(双)
现在都用ul无序列表,少有用ol有序列表的
表格标签(双)
<table>标签:表示一个表格
<tr>标签:表示表格中的一行
<th>标签:表示表格中的表头
<td>标签:表示表格中的列
<tr>标签:表示表格中的一行
<th>标签:表示表格中的表头
<td>标签:表示表格中的列
表单标签(双)
**<form>**标签 表示表单标签,定义整体的表单区域
提交
action属性 设置表单数据提交地址
method属性 设置表单提交的方式,一般有“GET”方式和“POST”方式, 不区分大小写
属性
name属性 设置表单元素的名称,该名称是提交数据时的参数名
value属性 设置表单元素的值,该值是提交数据时参数名所对应的值
**<label>**标签 表示表单元素的文字标注标签,定义文字标注
**<input>**标签 表示表单元素的用户输入标签,定义不同类型的用户输入数据方式
type属性
type="text" 定义单行文本输入框
type="password" 定义密码输入框
type="radio" 定义单选框
type="checkbox" 定义复选框
type="file" 定义上传文件
type="submit" 定义提交按钮
type="reset" 定义重置按钮
type="button" 定义一个普通按钮
type="text" 定义单行文本输入框
type="password" 定义密码输入框
type="radio" 定义单选框
type="checkbox" 定义复选框
type="file" 定义上传文件
type="submit" 定义提交按钮
type="reset" 定义重置按钮
type="button" 定义一个普通按钮
**<textarea>**标签 表示表单元素的多行文本输入框标签 定义多行文本输入框
**<select>**标签 表示表单元素的下拉列表标签 定义下拉列表
**<option>标签与<select>**标签配合,定义下拉列表中的选项
CSS
引入方式
行内(优先级1)
<div style="width:100px; height:100px; background:red ">hello</div>
内嵌(优先级2)
<head>
<style type="text/css">
h3{
color:red;
}
</style>
</head>
<style type="text/css">
h3{
color:red;
}
</style>
</head>
外联(优先级3)
<link rel="stylesheet" type="text/css" href="css/main.css">
选择器
标签
以标签开头
<style type="text/css">
p{
color: red;
}
</style>
p{
color: red;
}
</style>
类
以 . 开头
<style type="text/css">
.blue{color:blue}
.big{font-size:20px}
.box{width:100px;height:100px;background:gold}
</style>
<div class="blue">这是一个div</div>
<h3 class="blue big box">这是一个标题</h3>
<p class="blue box">这是一个段落</p>
.blue{color:blue}
.big{font-size:20px}
.box{width:100px;height:100px;background:gold}
</style>
<div class="blue">这是一个div</div>
<h3 class="blue big box">这是一个标题</h3>
<p class="blue box">这是一个段落</p>
层级
E F{ }
E 的所有 F 后代
E>F{ }
E 的所有 子 F
G+F{ }
G和F同父,F紧接在G之后
G~F{ }
G和F同父,F在G后面
id
#id名{ }
组
,分开
.box1,.box2,.box3{width:100px;height:100px}
伪类
a:link{ }未访问过的链接
a:visited{ }访问过的链接
property:active{ }点击后的效果
property:hover{ }鼠标放置的效果
div p:nth-child(n) 第n个p标签
属性(属性名:属性值)
布局常用属性
文本常用属性
元素溢出
子元素(标签)的尺寸超过父元素(标签)的尺寸,设置overflow属性
overflow的设置项:
1. visible 默认值, 显示子标签溢出部分。
2. hidden 隐藏子标签溢出部分。
3. auto 如果子标签溢出,则可以滚动查看其余的内容。
1. visible 默认值, 显示子标签溢出部分。
2. hidden 隐藏子标签溢出部分。
3. auto 如果子标签溢出,则可以滚动查看其余的内容。
显示特性
display
none 元素隐藏且不占位置
inline 元素以行内元素显示
block 元素以块元素显示
inline 元素以行内元素显示
block 元素以块元素显示
盒子模型
JavaScript
使用方式
行内(优先级1)
<input type="button" name="" onclick="alert('ok!');">
内嵌(优先级2)
<script type="text/javascript">
alert('ok!');
</script>
alert('ok!');
</script>
外联(优先级3)
<script type="text/javascript" src="js/index.js"></script>
变量和数据类型
变量
var 变量名 = 变量值;
javaScript 是一种弱类型语言,也就是说不需要指定变量的类型,JavaScript的变量类型由它的值来决定
注释
// 单行注释
/*
多行注释
多行注释
*/
多行注释
多行注释
*/
数据类型
1、number 数字类型
2、string 字符串类型
3、boolean 布尔类型 true 或 false
4、undefined undefined类型,变量声明未初始化,它的值就是undefined
5、null
6、object 数组、函数和JavaScript对象都属于复合类型
变量命名规范
1、区分大小写
2、第一个字符必须是字母、下划线(_)或者美元符号($)
3、其他字符可以是字母、下划线、美元符或数字
2、第一个字符必须是字母、下划线(_)或者美元符号($)
3、其他字符可以是字母、下划线、美元符或数字
匈牙利命名风格
对象o Object 比如:oDiv
数组a Array 比如:aItems
字符串s String 比如:sUserName
整数i Integer 比如:iItemCount
布尔值b Boolean 比如:bIsComplete
浮点数f Float 比如:fPrice
函数fn Function 比如:fnHandler
数组a Array 比如:aItems
字符串s String 比如:sUserName
整数i Integer 比如:iItemCount
布尔值b Boolean 比如:bIsComplete
浮点数f Float 比如:fPrice
函数fn Function 比如:fnHandler
函数
function
<script type="text/javascript">
// 函数定义
function fnAlert(){
alert('hello!');
}
</script>
// 函数定义
function fnAlert(){
alert('hello!');
}
</script>
调用:函数名()
返回值
return
1、返回函数中的值 2、执行完return函数执行结束
变量作用域
局部
全局
条件语句
if...
if...else
if...else if...else
if...else
if...else if...else
switch case
比较运算符
例如 x=5
逻辑运算符
假如 x=6, y=3
获取标签元素
document.getElementById( )
设置标签元素
事件
事件=‘函数名()’
onMouseOver onMouseOut
onMouseDown按下press, onMouseUp松开release
onLoad in body
加载页面时执行对应函数
onLoad in element
<body>, <frame>, <frameset>, <iframe>, <img>, <link>, <script>, <style>
中才有onLoad
中才有onLoad
onFocus
获得焦点
常用于 <input>, <select>, 和<a>
onBlur
失去焦点
onChange
失去焦点和变值
onselect
选中
onSubmit
提交
数组
创建
var aList = new Array(1,2,3);
var aList2 = [1,2,3,'asd'];
var aList = [[1,2,3],['a','b','c']];
获取数组的长度
var aList = [1,2,3,4];
alert(aList.length); // 弹出4
alert(aList.length); // 弹出4
根据下标取值
var aList = [1,2,3,4];
alert(aList[0]); // 弹出1
alert(aList[0]); // 弹出1
从数组最后添加和删除数据
var aList = [1,2,3,4];
aList.push(5);
alert(aList); //弹出1,2,3,4,5
aList.pop();
alert(aList); // 弹出1,2,3,4
aList.push(5);
alert(aList); //弹出1,2,3,4,5
aList.pop();
alert(aList); // 弹出1,2,3,4
根据下标添加和删除元素
arr.splice(start,num,element1,.....,elementN)
start:必需,开始删除的索引。
num:可选,删除数组元素的个数。
elementN:可选,在start索引位置要插入的新元素。
num:可选,删除数组元素的个数。
elementN:可选,在start索引位置要插入的新元素。
循环
for
var array = [1, 4, 5];
for(var index = 0; index < array.length; index++){
result = array[index];
alert(result);
}
for(var index = 0; index < array.length; index++){
result = array[index];
alert(result);
}
while
var array = [1, 4, 5];
var index = 0;
while (index < array.length) {
result = array[index];
alert(result);
index++;
}
var index = 0;
while (index < array.length) {
result = array[index];
alert(result);
index++;
}
do-while
var array = [1, 4, 5];
var index = 0;
do {
result = array[index];
alert(result);
index++;
} while (index < array.length);
var index = 0;
do {
result = array[index];
alert(result);
index++;
} while (index < array.length);
条件不成立时,do也会执行一次
字符串拼接
+
定时器
创建
以指定的时间间隔(以毫秒计)调用一次函数的定时器
setTimeout(func[, delay, param1, param2, ...])
以指定的时间间隔(以毫秒计)重复调用一个函数的定时器
setInterval(func[, delay, param1, param2, ...])
清除
clearTimeout(计时器对象)
clearInterval(计时器对象)
window对象
JS组成
ECMAScript标准
即JS的基本语法,JavaScript的核心,描述了语言的基本语法和数据类型,ECMAScript是一套标准,定义了一种语言的标准与具体实现无关。
DOM
BOM
docment常见属性对象
window对象的navigator属性
navigator.webdriver
是否是模拟浏览器
是否是模拟浏览器
Window对象的Location属性
Window frames 属性
frames 属性返回窗口中所有命名的框架
window history属性
OM中的window对象通过window.history方法提供了对浏览器历史记录的读取,
让你可以在用户的访问记录中前进和后退。
使用back(),forward(),和go()方法可以在用户的历史记录中前进和后退
让你可以在用户的访问记录中前进和后退。
使用back(),forward(),和go()方法可以在用户的历史记录中前进和后退
Window Screen属性
window.screen 对象包含有关用户屏幕的信息。
window.screen对象在编写时可以不使用 window 这个前缀。
一些属性:
1. screen.availWidth - 可用的屏幕宽度
2. screen.availHeight - 可用的屏幕高度
window.screen对象在编写时可以不使用 window 这个前缀。
一些属性:
1. screen.availWidth - 可用的屏幕宽度
2. screen.availHeight - 可用的屏幕高度
JavaScript对象
创建
object类创建
<script>
var person = new Object();
// 添加属性:
person.name = 'tom';
person.age = '25';
// 添加方法:
person.sayName = function(){
alert(this.name);
}
// 调用属性和方法:
alert(person.age);
person.sayName();
</script>
var person = new Object();
// 添加属性:
person.name = 'tom';
person.age = '25';
// 添加方法:
person.sayName = function(){
alert(this.name);
}
// 调用属性和方法:
alert(person.age);
person.sayName();
</script>
字面量创建
<script>
var person2 = {
name:'Rose',
age: 18,
sayName:function(){
alert('My name is' + this.name);
}
}
// 调用属性和方法:
alert(person2.age);
person2.sayName();
</script>
var person2 = {
name:'Rose',
age: 18,
sayName:function(){
alert('My name is' + this.name);
}
}
// 调用属性和方法:
alert(person2.age);
person2.sayName();
</script>
jQuery
引入
如果下载了,可以把文件放在本地,用相对路径引用
<script src="js/jquery-1.12.4.min.js"></script>
或者去搜jQuery在线地址
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
一定要先开一个<script></script>把jQuery先引入
jQuery入口函数
// 完整写法
$(document).ready(function(){
...
});
$(document).ready(function(){
...
});
// 简化写法
$(function(){
...
});
$(function(){
...
});
jQuery选择器
标签选择器
类选择器
id选择器
层级选择器
属性选择器
类选择器
id选择器
层级选择器
属性选择器
$('#myId') //选择id为myId的标签
$('.myClass') // 选择class为myClass的标签
$('li') //选择所有的li标签
$('#ul1 li span') //选择id为ul1标签下的所有li标签下的span标签
$('input[name=first]') // 选择name属性等于first的input标签
<div id="div1" class='myClass'>这是一个div元素</div>
<input type="text" name="first" id="input1" value="20px">
<a href="#" id="myId" class="sty01">这是一个链接</a>
$('.myClass') // 选择class为myClass的标签
$('li') //选择所有的li标签
$('#ul1 li span') //选择id为ul1标签下的所有li标签下的span标签
$('input[name=first]') // 选择name属性等于first的input标签
<div id="div1" class='myClass'>这是一个div元素</div>
<input type="text" name="first" id="input1" value="20px">
<a href="#" id="myId" class="sty01">这是一个链接</a>
可以使用length属性来判断标签是否选择成功, 如果length大于0表示选择成功,否则选择失败
选择集过滤
has(选择器名称)
修改第一个div
eq(索引)
修改第2个div
选择集转移
以选择的标签为参照,获取转移后的标签
$('#box').prev(); 表示选择id是box元素的上一个的同级元素
$('#box').prevAll(); 表示选择id是box元素的上面所有的同级元素
$('#box').next(); 表示选择id是box元素的下一个的同级元素
$('#box').nextAll(); 表示选择id是box元素的下面所有的同级元素
$('#box').parent(); 表示选择id是box元素的父元素
$('#box').children(); 表示选择id是box元素的所有子元素
$('#box').siblings(); 表示选择id是box元素的其它同级元素
$('#box').find('.myClass'); 表示选择id是box元素的class等于myClass的元素
$('#box').prevAll(); 表示选择id是box元素的上面所有的同级元素
$('#box').next(); 表示选择id是box元素的下一个的同级元素
$('#box').nextAll(); 表示选择id是box元素的下面所有的同级元素
$('#box').parent(); 表示选择id是box元素的父元素
$('#box').children(); 表示选择id是box元素的所有子元素
$('#box').siblings(); 表示选择id是box元素的其它同级元素
$('#box').find('.myClass'); 表示选择id是box元素的class等于myClass的元素
<script>
$(function(){
var $div = $('#div01');
$div.prev().css({'color':'red'});
$div.prevAll().css({'text-indent':50});
$div.next().css({'color':'blue'});
$div.nextAll().css({'text-indent':80});
$div.siblings().css({'text-decoration':'underline'})
$div.parent().css({'background':'gray'});
$div.children().css({'color':'red'});
$div.find('.sp02').css({'font-size':30});
});
</script>
$(function(){
var $div = $('#div01');
$div.prev().css({'color':'red'});
$div.prevAll().css({'text-indent':50});
$div.next().css({'color':'blue'});
$div.nextAll().css({'text-indent':80});
$div.siblings().css({'text-decoration':'underline'})
$div.parent().css({'background':'gray'});
$div.children().css({'color':'red'});
$div.find('.sp02').css({'font-size':30});
});
</script>
获取和设置元素内容
html()
获取标签后.html()
html(修改的内容直接写在括号里)
添加.append()
获取和设置元素属性
prop()
$获取.prop(‘属性名’)
$获取.val(),获取value简写方式
jQuery事件
click() 鼠标单击
blur() 元素失去焦点
focus() 元素获得焦点
mouseover() 鼠标进入(进入子元素也触发)
mouseout() 鼠标离开(离开子元素也触发)
ready() DOM加载完成
blur() 元素失去焦点
focus() 元素获得焦点
mouseover() 鼠标进入(进入子元素也触发)
mouseout() 鼠标离开(离开子元素也触发)
ready() DOM加载完成
$获取 . 事件(事件函数);
事件代理
事件冒泡
事件会向它的父级一级一级传递
点击子元素,点击事件会向它父级元素传递,也会触发父元素的点击事件
事件代理
一般事件绑定
一次性绑定所有子元素
$(function(){
$ali = $('#list li');
$ali.click(function() {
$(this).css({background:'red'});
});
})
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
$ali = $('#list li');
$ali.click(function() {
$(this).css({background:'red'});
});
})
<ul id="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
事件代理
$父元素 . delegate(子元素,事件名,执行函数)
$list.delegate('li', 'click', function() {
// $(this)表示当前点击的子元素对象
$(this).css({background:'red'});
});
// $(this)表示当前点击的子元素对象
$(this).css({background:'red'});
});
事件绑定在父元素上,执行对应子元素才会临时绑定一次事件
JSON
JavaScript Object Notation(javascript对象表示法)
json本质上是一个字符串
json本质上是一个字符串
对象格式
{
"name":"tom",
"age":18
}
"name":"tom",
"age":18
}
数组格式
["tom",18,"programmer"]
JSON中key一定是“”
区别字典和json,就是引号的单双
区别字典和json,就是引号的单双
json转js对象
JSON.parse(JSON对象)
ajax
爬虫
基础知识
爬虫分类
通用爬虫
如搜索引擎
聚焦爬虫
针对特定的网站
爬虫流程
1.获取到资源地址
2.发送请求获取数据
3.提取信息
4.保存数据
http基本原理
1.URL
协议-域名-(端口)-资源-查询参数
列子:
https://www.baidu.com/item/10056474?fr=aladdin http://IP:port/资源路径/?wd=python#flg
协议 :这代表网页使用的请求协议
域名部分:该URL的域名部分为“www.baidu.com”。一个URL中,也可以使用IP地址作为域名使用:202.108.22.5
端口部分:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分。
资源部分:从域名后的最后一个“/”开始到“?”为止,是资源部分
查询参数:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分
https://www.baidu.com/item/10056474?fr=aladdin http://IP:port/资源路径/?wd=python#flg
协议 :这代表网页使用的请求协议
域名部分:该URL的域名部分为“www.baidu.com”。一个URL中,也可以使用IP地址作为域名使用:202.108.22.5
端口部分:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分。
资源部分:从域名后的最后一个“/”开始到“?”为止,是资源部分
查询参数:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分
http://IP:port/资源路径/?wd=python#flg
2.超文本
hypertext,html就是超文本,网页上看到的图片,结构,标签等等
我们在浏览器里看到的网页就是超文本解析而成的
我们在浏览器里看到的网页就是超文本解析而成的
3.http和https
超文本传输协议,默认端口80
http+ssl(安全套接字),默认端口443
http+ssl(安全套接字),默认端口443
4.http请求过程
图解
过程
1.浏览器向地址栏中的URL发起请求,并获取相应
2.在返回的响应内容(html)中,会带有css、js、图片等url地址,以及ajax代码,
浏览器按照响应内容中的顺序依次发送其他的请求,并获取相应的响应
浏览器按照响应内容中的顺序依次发送其他的请求,并获取相应的响应
3.浏览器每获取一个响应就对展示出的结果进行添加(加载),
js,css等内容会修改页面的内容,js也可以重新发送请求,获取响应
js,css等内容会修改页面的内容,js也可以重新发送请求,获取响应
4.从获取第一个响应并在浏览器中展示,直到最终获取全部响应,并在展示的结果中添加内容或修改
————这个过程叫做浏览器的渲染
————这个过程叫做浏览器的渲染
注:但是在爬虫中,爬虫只会请求url地址,
对应的拿到url地址对应的响应(该响应的内容可以是html,css,js,图片等)
浏览器渲染出来的页面和爬虫请求的页面很多时候并不一样
所以在爬虫中,需要以url地址对应的响应为准来进行数据的提取
对应的拿到url地址对应的响应(该响应的内容可以是html,css,js,图片等)
浏览器渲染出来的页面和爬虫请求的页面很多时候并不一样
所以在爬虫中,需要以url地址对应的响应为准来进行数据的提取
http请求
组成
请求方法(Request Method)
请求的网址 (Request URL)
请求头(Request Headers)
请求体(Request Body)。
请求的网址 (Request URL)
请求头(Request Headers)
请求体(Request Body)。
f12面板
1. 请求方法Request Method
一般常用的就get和post两个
2.请求网址Request URL
,即统一资源定位符 URL,它可以唯一确定我们想请求的资源。
3.请求头Request headers
4. 请求体(在Payload里查看)
POST 请求中的表单数据,而对于 GET 请求,请求体则为空
5. 响应
组成
响应状态码(Response Status Code)
响应头 (Response Headers)
响应体(Response Body)
响应头 (Response Headers)
响应体(Response Body)
1.响应状态码Status Code
常见状态码
2. 响应头Response Headers
常用响应头
Date:标识响应产生的时间。
Last-Modified:指定资源的最后修改时间。
Content-Encoding:指定响应内容的编码。
Server:包含服务器的信息,比如名称、版本号等。
Content-Type:文档类型,指定返回的数据类型是什么,如 text/html 代表返回 HTML 文档,
application/x-javascript:代表返回 JavaScript 文件,image/jpeg 则代表返回图片。
Set-Cookie:设置 Cookies。响应头中的 Set-Cookie 告诉浏览器需要将此内容放在 Cookies 中,下次请求携带 Cookies 请求。
Expires:指定响应的过期时间,可以使代理服务器或浏览器将加载的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。
Last-Modified:指定资源的最后修改时间。
Content-Encoding:指定响应内容的编码。
Server:包含服务器的信息,比如名称、版本号等。
Content-Type:文档类型,指定返回的数据类型是什么,如 text/html 代表返回 HTML 文档,
application/x-javascript:代表返回 JavaScript 文件,image/jpeg 则代表返回图片。
Set-Cookie:设置 Cookies。响应头中的 Set-Cookie 告诉浏览器需要将此内容放在 Cookies 中,下次请求携带 Cookies 请求。
Expires:指定响应的过期时间,可以使代理服务器或浏览器将加载的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。
3. 响应体Response、(图片在Preview里看)
获取到的数据
浏览器开发者工具 F12
这玩意多用久熟了,尽量看英文的
会话与cookies
1.会话Session
有始有终的一系列动作 / 消息
2.cookies
某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据
3.cookies列表
socket套接字
获取图片
面向对象写法
直接for循环写法
发送请求
request
发get请求
用content接收
content.decode()解码
用content接收
content.decode()解码
常用属性
.text
响应体str类型
解码类型:requests模块自动根据http头部响应的编码做推测
在接收请求时用encoding=‘ ’修改
在接收请求时用encoding=‘ ’修改
.content
响应体bytes类型
.json()
响应体json类型
.status_code
响应状态码
.request.headers
请求头
.headers
响应头
.cookies
响应的cookie
带header的请求
headers是字典
带参数
params=参数
也是字典
一般会有很多参数,删掉没用的,一个个试
一般会有很多参数,删掉没用的,一个个试
POST
post需要发送data或者json给服务器
在Payload里找,也是字典
在Payload里找,也是字典
form data的数据是data=,
request payload是json=,
query string params是params=
request payload是json=,
query string params是params=
代理
反向代理:浏览器不知道服务器真实地址,例如Nginx
正向代理:浏览器知道服务器真实地址,例如VPN
正向代理:浏览器知道服务器真实地址,例如VPN
透明代理(Transparent Proxy):透明代理虽然可以直接“隐藏”你的IP地址,但是还是可以查到你是谁。
匿名代理(Anonymous Proxy):使用匿名代理,别人只能知道你用了代理,无法知道你是谁。
高匿代理(Elite proxy或High Anonymity Proxy):高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。
匿名代理(Anonymous Proxy):使用匿名代理,别人只能知道你用了代理,无法知道你是谁。
高匿代理(Elite proxy或High Anonymity Proxy):高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。
从请求使用的协议可以分为:
http代理
https代理
socket代理等
http代理
https代理
socket代理等
代理池要更新,付费代理很多时候也有超过10%的用不了
检测代理ip是否可用
如果返回代理ip,则可用
cookies
注意:cookie有过期时间
可以用专门程序获取cookie,但有的网站获取时间较长,所以还是直接复制简单
可以用专门程序获取cookie,但有的网站获取时间较长,所以还是直接复制简单
1.在headers里面
在request headers里找到cookies,然后加在header里
cookie的内容都是键值对,去键和值就行
cookie的内容都是键值对,去键和值就行
2.传参
以键值对的方式,传参给cookies=
3.使用session处理
session实例在请求了一个网站后,对方服务器设置在本地的cookie会保存在session中,
下一次再使用session请求对方服务器的时候,会带上前一次的cookie
下一次再使用session请求对方服务器的时候,会带上前一次的cookie
4.requests中cookirJar的处理方法
response.cookies是CookieJar类型
使用requests.utils.dict_from_cookiejar,能够实现把cookiejar对象转化为字典
使用requests.utils.dict_from_cookiejar,能够实现把cookiejar对象转化为字典
在前面的requests的session类中,我们不需要处理cookie的任何细节,
如果有需要,我们可以使用这个
如果有需要,我们可以使用这个
处理证书错误
您的连接不是私密连接
ssl的证书不安全导致
import requests
url = "https://www.12306.cn/mormhweb/"
response = requests.get(url)
url = "https://www.12306.cn/mormhweb/"
response = requests.get(url)
ssl.CertificateError ...
这是加个参数,忽略证书
import requests
url = "https://www.12306.cn/mormhweb/"
response = requests.get(url,verify=False)
url = "https://www.12306.cn/mormhweb/"
response = requests.get(url,verify=False)
超时
参数timeout
比如检测代理ip,总不能一直等在那
比如检测代理ip,总不能一直等在那
response = requests.get(url,timeout=3)
retrying
一个装饰器
@retry(stop_max_attempt_number=3)
重试3次再报错
重试3次再报错
提取数据
爬虫中的数据分类
结构化数据:json,xml等
处理方式:xpath,转换python类型处理,bs4
非结构化数据:HTML,字符串
处理方式:正则表达式、xpath、bs4
JSON数据
查看语法-文件操作-json
抓取工具排名
正则(最快)
match方法
result = re.match(正则表达式,要匹配的字符串)
result.group()提取数据group(0是匹配的信息,1,2...是其中组里的内容)
result.group()提取数据group(0是匹配的信息,1,2...是其中组里的内容)
匹配不到会报错,所以用的少
匹配单个字符
常用方法
match(从头找一个)
search(找一个)
findall(找所有)
返回列表,没有返回空列表
sub(替换)
re.sub("\d","_","tu1ling2") >> ["tu_ling_"]
compile(编译)
p = re.compile("\d",re.S)
p.findall("tuling2")
p.findall("tuling2")
匹配多个
{m,n}中间不能加空格
开头结尾
^开头字符
结尾字符$
结尾字符$
[^字符字符字符]
不以【】里的的字符开头的
分组匹配
\num,引用分组(第num组),匹配到的结果
match_obj = re.match("<([a-zA-Z1-6]+)>.*</\\1>", "<html>hh</html>")
match_obj = re.match("<([a-zA-Z1-6]+)>.*</\\1>", "<html>hh</html>")
\\1:\转译 \1引用(1)匹配的结果,即html
match_obj = re.match("<(?P<name1>[a-zA-Z1-6]+)><(?P<name2>[a-zA-Z1-6]+)>.*</(?P=name2)></(?P=name1)>", "<html><h1>www.tuling.cn</h1></html>")
起名
引用
引用
python中原始字符串r的用法
不转译(比如Windows中的路径)
例如"\n"的原始字符串就是"\\n",或者r‘\n’
匹配中文
[\u4e00-\u9fa5]
大部分中文
贪婪非贪婪
非贪婪:在量词后面加?
贪婪:最大长度匹配
非贪婪:最小长度匹配
非贪婪:最小长度匹配
练习
爬取前十页列表中的小说名
基础版
异步版
线程池异步爬取猫眼电影图片
lxml
xpath
快速的定位特定元素以及获取节点信息
语法
nodename
选中该元素。
/
从根节点选取、或者是元素和元素间的过渡。
//
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
.
选取当前节点。
..
选取当前节点的父节点。
@
选取属性。
//li/span[@class='s2']
text()
选取文本。
例
top250
所有的电影的名称
//span[@class='title'][1],
href,
//div[@class='hd']/a/@href
评分,
//span[@class='rating_num']
评价人数
//div[@class='star']/span[4]
//span[@class='title'][1],
href,
//div[@class='hd']/a/@href
评分,
//span[@class='rating_num']
评价人数
//div[@class='star']/span[4]
在python中使用xpath
from lxml import etree
获取响应字符串转element再转字符串
lxml能够把确实的标签补充完成,但是请注意html是人写的,很多时候由于网页不够规范,或者是html的bug,即使参考url地址对应的响应去提取数据,任然获取不到,这个时候我们需要使用etree.tostring的方法,观察etree到底把html转化成了什么样子,即根据转化后的html字符串去进行数据的提取。
html = etree.HTML(text)
ret_list = html.xpath("xpath字符串")
ret_list = html.xpath("xpath字符串")
获取到的数据为列表
返回的是element对象
把每个小说的详情页面链接和小说的名字组成一个字典
取属性和文本
小说名xpath://div[@class='l']//span[@class='s2']/a/text()
链接xpath://div[@class='l']//span[@class='s2']/a/@href
链接xpath://div[@class='l']//span[@class='s2']/a/@href
取节点
利用三元运算,如果内容为空显示None
爬取链家网里的租房信息获取【标题,位置,房屋的格局(三室一厅),关注人数,单价,总价】
https://sz.lianjia.com/ershoufang/pudong/pg2/
BS4(最慢)
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxmll。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
创建beautiful soup对象
对输出格式化
对输出格式化
搜索文档树
find_all(name, attrs, recursive, text, **kwargs)
返回列表
返回列表
name参数
1.传字符
soup对象.find_all('标签名')
2.传正则
soup对象.find_all(re.compile("正则表达式"))
3.传列表
soup对象.find_all(['标签名1','标签名2'])
keyword参数
soup对象.find_all(attrs={'class':"blue_no_underline"})
find
返回第一个匹配结果
css选择器
select()
返回列表
返回列表
标签选择器
soup.select('span')
类选择器
soup.select('.类名')
类名中间带空格
soup.select('.adsbygoogle.allinone_good')
soup.select('.adsbygoogle.allinone_good')
id选择器
soup.select('#id名')
层级选择器
soup.select('div .类名')
div下的 类
属性选择器
soup.select('tr[align="center"]')
soup.select('a[class="navigationlink"]')
伪类
soup.select('tr td:nth-child(3)')
获取文本
get_text()
获取属性
get(‘属性的名字’)
http://ip.yqie.com/ipproxy.htm
获取每个ip的代理IP地址、端口、服务器地址、是否匿名、类型、存活时间
获取每个ip的代理IP地址、端口、服务器地址、是否匿名、类型、存活时间
数据储存
txt文本储存
with open(‘文件名’,‘操作类型’,encoding=‘utf-8’)as f:
JSON
操作
json.dumps()
python对象转成json对象,生成字符串
file.write(json.dumps(data_list, indent=2, ensure_ascii=False))
首行缩进和编码格式
首行缩进和编码格式
json.dump()
dict类型数据转成str,并写入json文件中
json.loads()
json字符串解码成python
json.load()
从json文件中读取数据
注意
JSON standard allows only one top-level value
JSON数据只能有一个顶级[ ]or{ },所以要循环写入多个数据,要注意数据格式
表格csv
写入
csv.writer(文件).writerow([写入的内容])
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
修改列与列之间的分隔符
writer = csv.writer(csvfile, delimiter=' ')
writer = csv.writer(csvfile, delimiter=' ')
写多行
writer.writerows([['10001', 'Mike', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]])
writer.writerows([['10001', 'Mike', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]])
字典写入
import csv
with open('data.csv', 'w') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'id': '10001', 'name': 'Mike', 'age': 20})
writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22})
writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21})
with open('data.csv', 'w') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'id': '10001', 'name': 'Mike', 'age': 20})
writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22})
writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21})
MySQL存储
创建数据库
创建数据表
插入数据
爬取百度招聘
MongoDB
连接数据库
import pymongo # 如果是云服务的数据库 用公网IP连接
client = pymongo.MongoClient(host='localhost', port=27017)
client = pymongo.MongoClient(host='localhost', port=27017)
指定数据库和表
collection = client['students']['stu']
插入单条
student = {'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male' }
result = collection.insert_one(student)
result = collection.insert_one(student)
插入多条
student1 = { 'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male' }
student2 = { 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male' }
collection.insert_many([student1, student2])
student2 = { 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male' }
collection.insert_many([student1, student2])
高性能爬虫
守护线程
在python3中,主线程主进程结束,子线程,子进程不会结束
为了能够让主线程回收子线程,可以把子线程设置为守护线程,即该线程不重要,主线程结束,子线程结束
为了能够让主线程回收子线程,可以把子线程设置为守护线程,即该线程不重要,主线程结束,子线程结束
t1.setDaemon(True)
t1.join()等待子线程结束再运行下面的代码
队列
from queue import Queue
创建队列设置最大容量为100
q = Queue(maxsize=100)
放入值
q.put(值)
放入数据,队列满的时候回等待
q.put_nowait(值)
不等待直接放,队列满的时候会报错
取出数据
q.get()
取出数据,队列为空的时候会等待
q.get_nowait()
不等待直接取,队列空的时候会报错
q.qsize()
获取队列中现存数据的个数
put一次size+1
get一次size-1
get一次size-1
q.join()
队列中维持了一个计数,
计数不为0时候让主线程阻塞等待,
队列计数为0的时候才会继续往后执行
计数不为0时候让主线程阻塞等待,
队列计数为0的时候才会继续往后执行
q.task_done()
put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1
多线程
思路
案例
爱奇艺视频
E:\1palegechong\1图灵\7.爬虫\4.高性能爬虫
搜狐视频
E:\1palegechong\1图灵\7.爬虫\4.高性能爬虫
了解一下
E:\1palegechong\1图灵\7.爬虫\4.高性能爬虫\day10\课件
多进程
占资源,但也不是用不到
代码基本一样,
但Windows电脑的数据库信息要放在类属性里
且用进程专用队列
但Windows电脑的数据库信息要放在类属性里
且用进程专用队列
线程池
用不到
异步爬虫(协程)
requests无法异步,
使用aiohttp, 基于asyncio
async 生成一个异步函数
await 声明程序挂起
使用aiohttp, 基于asyncio
async 生成一个异步函数
await 声明程序挂起
使用方式和requests基本保持一致
requests使用代理是proxies,aiohttp是proxy
aiohttp获取进制数据是read()
requests使用代理是proxies,aiohttp是proxy
aiohttp获取进制数据是read()
对比
aiomysql异步存储
使用 aiohttp 来代替 requests 来执行异步的网络请求操作,
使用 motor 来代替同步的 pymongo 库来操作mongo数据库
使用aiomysql来代替pymysql
使用 motor 来代替同步的 pymongo 库来操作mongo数据库
使用aiomysql来代替pymysql
selenium
通过selenium将动态网页转为静态网页,绕过反爬
https://selenium-python-zh.readthedocs.io/en/latest/
官方文档
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys # 模拟键盘操作
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys # 模拟键盘操作
基本使用
打开指定浏览器
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()
指定加载页面
browser.get("http://www.baidu.com/")
页面最大化
browser.maximize_window()
设置宽高
browser.set_window_size(480, 800)
打开新页面(通过js代码)
js_code = 'window.open("http://httpbin.org/ip");'
browser.execute_script(js_code)
browser.execute_script(js_code)
通过(标签属性)选择文本框元素,并设置内容
browser.find_element(By.NAME,'wd').send_keys("selenium")
通过属性找到按钮,并操作
browser.find_element(By.ID,"su").click()
提取页面
print(browser.page_source.encode('utf-8'))
提取cookie
print(browser.get_cookies())
获取当前截屏
print(browser.get_screenshot_as_file('123.png'))
提取当前页面地址
print(browser.current_url)
关闭
关闭浏览器
browser.quit()
关闭当前页面
browser.close()
初始化设置(这些命令用到查文档或者百度)
首先申明options对象
options = webdriver.ChromeOptions()
然后设置options
最后将options加入浏览器对象
browser = webdriver.Chrome(options=options)
options = webdriver.ChromeOptions()
然后设置options
最后将options加入浏览器对象
browser = webdriver.Chrome(options=options)
禁止加载图片
prefs = {"profile.managed_default_content_settings.images": 2}
options.add_experimental_option("prefs", prefs)
options.add_experimental_option("prefs", prefs)
关闭自动关闭
options.add_experimental_option('detach', True)
无头模式
不打开浏览器,在后台运行
不打开浏览器,在后台运行
options.add_argument("-headless")
关这个
options.add_experimental_option('useAutomationExtension', False) # 去掉开发者警告
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('excludeSwitches', ['enable-automation'])
设置user agent
user_ag='MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22;CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1'
options.add_argument('user-agent=%s'% user_ag)
options.add_argument('user-agent=%s'% user_ag)
添加插件
extension_path = r'E:\BaiduNetdiskDownload\Chrome插件\iguge_2011\igg_2.0.11.crx'
options.add_extension(extension_path)
options.add_extension(extension_path)
设置代理
options.add_argument("--proxy-server=http://58.20.184.187:9091")
查找节点
find_element()
通过标签name
s = browser.find_element(By.NAME,'wd')
s.send_keys('衣服')
s.send_keys(Keys.ENTER) # 回车 确定的意思
s.send_keys('衣服')
s.send_keys(Keys.ENTER) # 回车 确定的意思
ID选择器
input_text = browser.find_element(By.ID, "kw")
input_text.send_keys("selenium")
input_text.send_keys("selenium")
css选择器
s =browser.find_element(By.CSS_SELECTOR,'input.s_ipt') # 标签.class
s.send_keys('衣服')
s.send_keys('衣服')
xpath选择器
s = browser.find_element(By.XPATH,'//input[@id="kw"]')
s.send_keys('衣服')
s.send_keys('衣服')
find_elements(),获取的是列表
列表中的每个节点都是 WebElement 类型
节点交互
send_keys
输入文字
clear
清空文字
click
点击按钮
切换 IFrame(html里嵌套另一个html)
browser.get('https://www.douban.com/')
login_iframe = browser.find_element(By.XPATH,'//div[@class="login"]/iframe')
browser.switch_to.frame(login_iframe)
browser.find_element(By.CLASS_NAME,'account-tab-account').click()
browser.find_element(By.ID,'username').send_keys('123123123')
login_iframe = browser.find_element(By.XPATH,'//div[@class="login"]/iframe')
browser.switch_to.frame(login_iframe)
browser.find_element(By.CLASS_NAME,'account-tab-account').click()
browser.find_element(By.ID,'username').send_keys('123123123')
不切换iFrame获取不到iFrame里面的标签
动作链
按住-拖动-放
from selenium import webdriver
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
log = browser.find_element(By.XPATH, '//div[@id="iframewrapper"]/iframe')
browser.switch_to.frame(log)
source = browser.find_element(By.CSS_SELECTOR,'#draggable')
target = browser.find_element(By.CSS_SELECTOR,'#droppable')
actions = ActionChains(browser)
# 按住source,拖到target后放
actions.drag_and_drop(source, target)
# 执行
actions.perform()
from selenium.webdriver import ActionChains
browser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)
log = browser.find_element(By.XPATH, '//div[@id="iframewrapper"]/iframe')
browser.switch_to.frame(log)
source = browser.find_element(By.CSS_SELECTOR,'#draggable')
target = browser.find_element(By.CSS_SELECTOR,'#droppable')
actions = ActionChains(browser)
# 按住source,拖到target后放
actions.drag_and_drop(source, target)
# 执行
actions.perform()
页面滚动
js代码
# 浏览器滚动到底部 10000位置
document.documentElement.scrollTop=10000
# 滚动到顶部
document.documentElement.scrollTop=0
# 移动到页面最底部
window.scrollTo(0, document.body.scrollHeight)
# 移动到指定的坐标(相对当前的坐标移动)
window.scrollBy(0, 700)
# 结合上面的scrollBy语句,相当于移动到700+800=1600像素位置
window.scrollBy(0, 800)
# 移动到窗口绝对位置坐标,如下移动到纵坐标1600像素位置
window.scrollTo(0, 1600)
# 结合上面的scrollTo语句,仍然移动到纵坐标1200像素位置
window.scrollTo(0, 1200)
document.documentElement.scrollTop=10000
# 滚动到顶部
document.documentElement.scrollTop=0
# 移动到页面最底部
window.scrollTo(0, document.body.scrollHeight)
# 移动到指定的坐标(相对当前的坐标移动)
window.scrollBy(0, 700)
# 结合上面的scrollBy语句,相当于移动到700+800=1600像素位置
window.scrollBy(0, 800)
# 移动到窗口绝对位置坐标,如下移动到纵坐标1600像素位置
window.scrollTo(0, 1600)
# 结合上面的scrollTo语句,仍然移动到纵坐标1200像素位置
window.scrollTo(0, 1200)
获取标签中的文字
标签的WebElement对象.text
获取属性
get_attribute(‘属性名’)
from selenium import webdriver
from selenium.webdriver.common.by import By
url = 'https://pic.netbian.com/4kmeinv/index.html'
browser = webdriver.Chrome()
browser.get(url)
src = browser.find_elements(By.XPATH, '//ul[@class="clearfix"]/li/a/img')
for i in src:
url = i.get_attribute('src')
print(url)
from selenium.webdriver.common.by import By
url = 'https://pic.netbian.com/4kmeinv/index.html'
browser = webdriver.Chrome()
browser.get(url)
src = browser.find_elements(By.XPATH, '//ul[@class="clearfix"]/li/a/img')
for i in src:
url = i.get_attribute('src')
print(url)
延时等待
不按时间算
按加载项算
按加载项算
其他EC.的等待条件
https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions
选项卡
所有标签
print(browser.window_handles)
列表
切换
browser.switch_to.window(browser.window_handles[1])
下标1,第二个页面
异常
from selenium.common.exceptions import TimeoutException, NoSuchElementException
所有异常看文档
https://selenium-python.readthedocs.io/api.html#module-selenium.common.exceptions
绕过检测
https://bot.sannysoft.com/
options.add_argument('--disable-blink-features=AutomationControlled')
加选项,设置屏蔽
navigator.webdriver
false则绕过成功
true说明没成功
false则绕过成功
true说明没成功
f12 console,判断是否是模拟浏览器
案例
唯品会口红
义乌购
Charles抓包(不全)
主界面
File
Edit
View
IP池搭建(直接用付费代理)
加密 编码
MD5
32位
base64编码
SHA-1
40位
DES
框架
搭建虚拟环境
目的:不让其他的环境资源干扰到当前项目
打开anaconda prompt(将anaconda script地址添加到环境path里,就可以用cmd操作了)
conda env list:查看当前所有虚拟环境
conda create -n 环境名 python=x.x:创建虚拟环境
最好加个指定目录conda create python=3.9 --prefix=D:\anaconda3\envs\py_scrapy_1
最好用管理员打开cmd
-p/--prefix和-n/--name不能同时使用
activate 环境名:激活虚拟环境
deactivate:退出虚拟环境
conda remove -n 环境名 --all:删除对应环境
安装的环境在anaconda3\envs里,也可以直接删除文件夹
项目环境导入导出(用到哪些库)
查看
pip list
导出
pip freeze > ./requirements.txt
导入
pip install -r ./requirements.txt
下载了别人的项目,导入别人的requirements
scrapy
流程
模块于模块之间相互独立,只和引擎交互
1. 调度器(scheduler)把requests对象-->引擎(引擎判断这是requests对象)-->下载中间件--->下载器
2.下载器发送请求,获取响应---->下载中间件---->引擎--->爬虫中间件--->爬虫
3.爬虫提取url地址,组装成request对象---->爬虫中间件--->引擎(引擎判断这还是requests对象)--->调度器(scheduler)
4.爬虫提取数据--->引擎(引擎判断这是items)--->管道(pipeline)
5.管道(pipeline)进行数据的处理和保存
各个模块及作用
Scrapy Engine引擎
总指挥:负责数据和信号在不同模块间的传递
scrapy已实现
Scheduler调度器
一个队列,存放引擎发送过来的requests请求
scrapy已实现
Downloader下载器
下载引擎发送过来的requests请求,并返回给引擎
scrapy已实现
Spider爬虫
处理引擎发来的response,提取数据,提取URL,并交给引擎
需要手写
Item Pipeline管道
处理引擎传过来的数据,比如存储
需要手写
Downloader Middlewares下载中间件
可以自定义的下载扩展,比如设置代理,设置请求头
一般不用手写
Spider Middlewares爬虫中间件
可以自定义requests请求和进行response过滤
一般不用手写
项目实战
首先进入虚拟环境
activate 环境名:激活虚拟环境
在空环境里下载scrapy
pip install scrapy==2.5.0
下载后scrapy,查看信息
在项目目录下创建项目
scrapy startproject myspider
一键生成各种模块
创建爬虫文件
先cd到project目录下(cd myspider)
scrapy genspider douban movie.douban.com
*** *** 文件名 目标域名
右击项目文件夹,pycharm单独窗口打开,在setting里导入之前创建的虚拟环境中的python解释器(interpreter)
编写douban.py
启动
scrapy crawl douban(爬虫name)--nolog(无日志)
返回日志,目前是403
调试
查看豆瓣的robots.txt
在settings里把 ROBOTSTXT_OBEY = False
请求头
加入user-agent
请求成功
在pycharm中调试
# scrapy封装的命令执行
from scrapy import cmdline
***
***
***
if __name__ == '__main__':
cmdline.execute('scrapy crawl douban --nolog'.split())
from scrapy import cmdline
***
***
***
if __name__ == '__main__':
cmdline.execute('scrapy crawl douban --nolog'.split())
*注*
1. response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法
2. extract() 返回一个包含有字符串的列表
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. spider中的parse方法必须有
5. 需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
6. 启动爬虫的时候注意启动的位置,是在项目路径下启动
2. extract() 返回一个包含有字符串的列表
3. extract_first() 返回列表中的第一个字符串,列表为空没有返回None
4. spider中的parse方法必须有
5. 需要抓取的url地址必须属于allowed_domains,但是start_urls中的url地址没有这个限制
6. 启动爬虫的时候注意启动的位置,是在项目路径下启动
response对象属性
1. url HTTP相应的 URL地址,str类型的
2. status HTTP响应状态码,int类型的(在pycharm的控制台中你可以看到,例如200,404)
3. body HTTP响应正文,bytes类型
4. text 文本形式的HTTP响应正文,str类型,由response.body使用response.encoding解码得到
5. encoding HTTP响应正文的编码
6. request 产生该HTTP响应的Requset对象
7. selector (这个比较重要了)选择器对象用来提取response中的数据
8. xpath(query) 即xml路径语言,用来确定xml文档中某部分位置的语言(html属性xml)
9. css(query) 也是一种选择器,用来提取页面内中的数据,但是不如xpath强大。
10. urljoin(url) 用来构造绝对url
2. status HTTP响应状态码,int类型的(在pycharm的控制台中你可以看到,例如200,404)
3. body HTTP响应正文,bytes类型
4. text 文本形式的HTTP响应正文,str类型,由response.body使用response.encoding解码得到
5. encoding HTTP响应正文的编码
6. request 产生该HTTP响应的Requset对象
7. selector (这个比较重要了)选择器对象用来提取response中的数据
8. xpath(query) 即xml路径语言,用来确定xml文档中某部分位置的语言(html属性xml)
9. css(query) 也是一种选择器,用来提取页面内中的数据,但是不如xpath强大。
10. urljoin(url) 用来构造绝对url
保存
对douban爬虫进行修改完善
在爬虫文件douban.py中parse()函数中最后添加
yield item
**注意:yield能够传递的对象只能是:BaseItem,Request,dict,None**
yield item
**注意:yield能够传递的对象只能是:BaseItem,Request,dict,None**
yield item将item返回给引擎
修改pipelines.py文件
class MyspiderPipeline:
# 爬虫文件中提取数据的方法每yield一次item,就会运行一次
# 该方法为固定名称函数
def process_item(self, item, spider):
print(item)
# 爬虫文件中提取数据的方法每yield一次item,就会运行一次
# 该方法为固定名称函数
def process_item(self, item, spider):
print(item)
在settings.py设置开启pipeline
ITEM_PIPELINES
自动生成json文件,用utf-8保存
这里要在配置里关闭pipeline
开启管道,数据会被管道取出,item里就没数据了
cmdline.execute('scrapy crawl douban -o douban.json -s FEED_EXPORT_ENCODING="utf-8"'.split())
*日志
日志等级
- CRITICAL:严重错误
- ERROR:一般错误
- WARNING:警告
- INFO: 一般信息
- DEBUG:调试信息
**注意:** 默认的日志等级是DEBUG
- ERROR:一般错误
- WARNING:警告
- INFO: 一般信息
- DEBUG:调试信息
**注意:** 默认的日志等级是DEBUG
日志输出信息
Versions:所使用的工具版本信息
Overridden settings: 重写的配置
Telnet Password:Telnet 平台密码(Scrapy附带一个内置的telnet控制台,用于检查和控制Scrapy运行过程)
Enabled extensions :开启的拓展功能
Enabled downloader middlewares:开启的下载器中间件
Enabled spider middlewares:开启的爬虫中间件
Enabled item pipelines:开启的管道
Dumping Scrapy stats:所以的信息汇总
Overridden settings: 重写的配置
Telnet Password:Telnet 平台密码(Scrapy附带一个内置的telnet控制台,用于检查和控制Scrapy运行过程)
Enabled extensions :开启的拓展功能
Enabled downloader middlewares:开启的下载器中间件
Enabled spider middlewares:开启的爬虫中间件
Enabled item pipelines:开启的管道
Dumping Scrapy stats:所以的信息汇总
调整输出
修改配置文件
LOG_LEVEL = 'WARNING' # 设置日志显示的等级为WARNING
LOG_FILE = './log.txt' # 将日志信息全部记录到log.txt文件中
LOG_FILE = './log.txt' # 将日志信息全部记录到log.txt文件中
翻页
手动版
1. 找到下一页的url地址
2. 构造url地址的请求,传递给引擎
2. 构造url地址的请求,传递给引擎
框架自带
重写start_requests方法
(只要重写方法就行,不用考虑调用的问题)
(只要重写方法就行,不用考虑调用的问题)
start_urls = ['https://movie.douban.com/top250?start={}&filter=']
原方法
*scrapy.Request的更多参数
总览
scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])
1. 中括号中的参数为可选参数
2. callback:表示当前的url的响应交给哪个函数去处理
3. meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
4. dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
5. method:指定POST或GET请求
6. headers:接收一个字典,其中不包括cookies
7. cookies:接收一个字典,专门放置cookies
8. body:接收一个字典,为POST的数据
2. callback:表示当前的url的响应交给哪个函数去处理
3. meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
4. dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
5. method:指定POST或GET请求
6. headers:接收一个字典,其中不包括cookies
7. cookies:接收一个字典,专门放置cookies
8. body:接收一个字典,为POST的数据
meta参数的使用
meta的形式:字典
meta的作用:meta可以实现数据在不同的解析函数中的传递
meta的作用:meta可以实现数据在不同的解析函数中的传递
特别注意
1. meta参数是一个字典
2. meta字典中有一个固定的键`proxy`,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍
3. meta的download_timeout设置请求超时
1. meta参数是一个字典
2. meta字典中有一个固定的键`proxy`,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍
3. meta的download_timeout设置请求超时
例子
items.py
先定义item,确定爬取的目标
Item能够做什么
1. 定义item即提前规划好哪些字段需要抓取,scrapy.Field()仅仅是提前占坑,通过item.py能够让别人清楚自己的爬虫是在抓取什么,同时定义好哪些字段是需要抓取的,没有定义的字段不能使用,防止手误
2. 在python大多数框架中,大多数框架都会自定义自己的数据类型(在python自带的数据结构基础上进行封装),目的是增加功能,增加自定义异常
1. 定义item即提前规划好哪些字段需要抓取,scrapy.Field()仅仅是提前占坑,通过item.py能够让别人清楚自己的爬虫是在抓取什么,同时定义好哪些字段是需要抓取的,没有定义的字段不能使用,防止手误
2. 在python大多数框架中,大多数框架都会自定义自己的数据类型(在python自带的数据结构基础上进行封装),目的是增加功能,增加自定义异常
例子
在items.py文件中定义要提取的字段:
修改douban.py
导入 from myspider.items import MyspiderItem
将原来的item={}改成item = MyspiderItem()
实例化,创建一个管道对象
这时的item的type为<class 'myspider.items.MyspiderItem'>
这时的item的type为<class 'myspider.items.MyspiderItem'>
*scrapy shell
scrapy提供的一个终端工具,能够通过它查看scrapy中对象的属性和方法,以及测试xpath
进入虚拟环境
scrapy shell https://movie.douban.com/top250
scrapy shell https://movie.douban.com/top250
- response.xpath():直接测试xpath规则是否正确
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.body:响应体,也就是html代码,默认是byte类型
- response.requests.headers:当前响应的请求头
- response.url:当前响应的url地址
- response.request.url:当前响应对应的请求的url地址
- response.headers:响应头
- response.body:响应体,也就是html代码,默认是byte类型
- response.requests.headers:当前响应的请求头
settings.py
https://www.jianshu.com/p/df9c0d1e9087
pipeline管道
常用方法
process_item(self,item,spider):实现对item数据的处理
open_spider(self, spider): 在爬虫开启的时候仅执行一次
比如连接数据库
close_spider(self, spider): 在爬虫关闭的时候仅执行一次
比如关闭数据库
例
首先在settings里开启pipeline
然后修改pipeline.py
注释
1. 不同的pipeline可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
2. 不同的pipeline能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存
3. 同一个管道类也可以处理不同爬虫的数据,通过spider.name属性来区分
1. 使用之前需要在settings中开启
2. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
3. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
4. pipeline中process_item的方法必须有,否则item没有办法接受和处理
5. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
6. open_spider(spider) :能够在爬虫开启的时候执行一次
7. close_spider(spider) :能够在爬虫关闭的时候执行一次
8. 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
2. pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过
3. 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
4. pipeline中process_item的方法必须有,否则item没有办法接受和处理
5. process_item方法接受item和spider,其中spider表示当前传递item过来的spider
6. open_spider(spider) :能够在爬虫开启的时候执行一次
7. close_spider(spider) :能够在爬虫关闭的时候执行一次
8. 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
进阶
中间件
scrapy中间的作用
1. 主要功能是在爬虫运行过程中进行一些处理,如对非200响应的重试(重新构造Request对象yield给引擎)
2. 也可以对header以及cookie进行更换和处理
3. 其他根据业务需求实现响应的功能
2. 也可以对header以及cookie进行更换和处理
3. 其他根据业务需求实现响应的功能
下载中间件的使用方法
方法汇总
process_request(self, request, spider):
请求前拦截
请求前拦截
- 当每个request通过下载中间件时,该方法被调用。处理请求的方法
- 返回None值:继续请求
- 返回Response对象:不再请求,把response返回给引擎
- 返回Request对象:把request对象交给调度器进行后续的请求
- 返回None值:继续请求
- 返回Response对象:不再请求,把response返回给引擎
- 返回Request对象:把request对象交给调度器进行后续的请求
process_response(self, request, response, spider):
请求后拦截
请求后拦截
- 当下载器完成http请求,传递响应给引擎的时候调用。拦截响应
- 返回Resposne:交给process_response来处理
- 返回Request对象:交给调取器继续请求
- 返回Resposne:交给process_response来处理
- 返回Request对象:交给调取器继续请求
from_crawler(cls, crawler):
- 类似于init初始化方法,只不过这里使用的classmethod类方法
- 可以直接crawler.settings获得参数,也可以搭配信号使用
- 可以直接crawler.settings获得参数,也可以搭配信号使用
实操
新增UA
middlewares.py
settings.py
放入UA列表
打开下载中间件
代理IP
settings.py
一样的操作,在下载中间件中添加类并加上权重
middlewares.py
设置代理,设置延迟
判断是200否
判断是200否
权重
- 请求时数字越小权重越高,越先执行
- 响应时数字越大越先执行
- 响应时数字越大越先执行
网址去重
dont_filter实现了框架去重的功能(默认False,不去重则改为True)
默认网址去重,同一个网址只请求一次,防止同样的数据返回多次
默认网址去重,同一个网址只请求一次,防止同样的数据返回多次
案例
下载器中间件配置自动化(结合selenium爬去腾讯招聘)
Scrapy的执行流程
**运行启动命令** :
1. 进入到cmdline.execute命令中进行加载
2. 检测并加载settings.py文件
3. 加载spiders文件夹并声明当前文件夹为scrapy框架的爬虫模块
4. 加载spiders下面的爬虫文件
5. 检索爬虫文件中的爬虫名称,看是否为运行文件
6. 检测到正确的启动文件后,进入到crawler.py底层文件并执行加载对应的方法
7. 所执行的是CrawlerRunner类里面的crawl方法
1. 进入到cmdline.execute命令中进行加载
2. 检测并加载settings.py文件
3. 加载spiders文件夹并声明当前文件夹为scrapy框架的爬虫模块
4. 加载spiders下面的爬虫文件
5. 检索爬虫文件中的爬虫名称,看是否为运行文件
6. 检测到正确的启动文件后,进入到crawler.py底层文件并执行加载对应的方法
7. 所执行的是CrawlerRunner类里面的crawl方法
1.1 Scrapy中的Crawler对象体系
Crawler类是一个爬虫类,主要用来管理整个执行引擎ExecutionEngine类和爬虫类实例化
JS逆向
常见反爬
头部签名验证
请求参数签名验证
cookie验证
响应数据验证
JS基础
注释
单行注释以 // 开头
多行注释以 /* 开始,以 */ 结尾
多行注释以 /* 开始,以 */ 结尾
变量和数据类型
var
函数作用域
var可以定义全局变量和局部变量
var的作用域主要和函数的定义有关
var的作用域主要和函数的定义有关
var x;
此时x是undefined
此时x是undefined
可重复声明
声明的变量可修改
声明的变量可修改
var x = 2
x = 3
x = 3
变量提升
JS引擎在预编译代码时,会优先获取所有被var声明的变量和function声明的函数,将它们放在代码的头部然后从上到下执行。
且函数提升优于变量提升
且函数提升优于变量提升
let
块作用域
块作用域由 { } 包括
可修改
不可重复声明
没有变量提升
没有变量提升
有暂存性死区
只有等到声明变量的那一行代码出现,才可以获取和使用该变量
const
只能声明一个只读的常量
不可重复声明、修改
这也意味着const声明时就必须初始化,不能等到之后赋值。
当我们修饰的标识符不会被再次赋值时, 就可以使用const来保证数据的安全性。
总结
作用域:
var声明的是全局作用域或函数作用域;而let和 const 是块作用域。
声明初始化:
var和let在声明的时候可以不进行初始化;而 const 在声明的时候必须初始化。
修改与重复声明:
var在可以修改和重复声明;而let只能修改,不能在同一作用域下重复声明;const 声明常量不可修改也不可重复声明。
变量提升:
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined;let和 const 不存在变量提升,即它们所声明的变量一定要在声明后使用,否则会报错。
暂存性死区:
var不存在暂时性死区;let和const存在暂存性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
var声明的是全局作用域或函数作用域;而let和 const 是块作用域。
声明初始化:
var和let在声明的时候可以不进行初始化;而 const 在声明的时候必须初始化。
修改与重复声明:
var在可以修改和重复声明;而let只能修改,不能在同一作用域下重复声明;const 声明常量不可修改也不可重复声明。
变量提升:
var声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined;let和 const 不存在变量提升,即它们所声明的变量一定要在声明后使用,否则会报错。
暂存性死区:
var不存在暂时性死区;let和const存在暂存性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
函数
声明函数
function 函数名(){}
>>456
>>123
这里就涉及函数提升,函数声明会提到前面去
>>123
这里就涉及函数提升,函数声明会提到前面去
变量 = function(){}
自执行函数
(function(参数){代码块;})();
多个自执行函数,在前面加个修饰符,常用 !
内部函数外部调用
var _xl;
!(function () {
function xl(){
console.log('hello')
}
_xl = xl;
})();
_xl()
!(function () {
function xl(){
console.log('hello')
}
_xl = xl;
})();
_xl()
定义一个全局变量,
把函数体赋值给全局变量,
然后就能调用这个内部函数了
把函数体赋值给全局变量,
然后就能调用这个内部函数了
参数
默认参数arguments,可以用来查看传入的参数
>>[Arguments] { '0': 1, '1': 2, '2': 3, '3': 6, '4': 5 } object
>>undefined
>>undefined
类型是object
不论传入的参数数量与定义的参数数量是否一致,都可以看到
不论传入的参数数量与定义的参数数量是否一致,都可以看到
返回值return
也是再return处终止代码,下面的不会执行
js变量的生命周期
JavaScript 变量生命周期在它声明时初始化。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。
局部变量在函数执行完毕后销毁。
全局变量在页面关闭后销毁。
对象
一组数据和功能的集合
对象也是一个变量,但对象可以包含多个值(多个变量),每个值以 name:value 对呈现。
var car = {name:"xialuo", model:500, color:"white"};
创建对象
person = new Object();
每个Object类型的实例共有的属性和方法:
constructor: 保存着用于创建当前对象的函数。
hasOwnProperty:用于检测给定的属性在当前对象的实例中是否存在。
isPrototypeOf : 用于检查传入的对象是否是当前对象的原型
propertyIsEnumerble : 用于检查给定属性能否使用for-in来枚举
toLocaleString() : 返回对象的字符串表示。
toString() : 返回对象的字符串表示。
valueOf() : 返回对象的字符串,数值,或布尔表示。通常和toString() 返回的值相同。
constructor: 保存着用于创建当前对象的函数。
hasOwnProperty:用于检测给定的属性在当前对象的实例中是否存在。
isPrototypeOf : 用于检查传入的对象是否是当前对象的原型
propertyIsEnumerble : 用于检查给定属性能否使用for-in来枚举
toLocaleString() : 返回对象的字符串表示。
toString() : 返回对象的字符串表示。
valueOf() : 返回对象的字符串,数值,或布尔表示。通常和toString() 返回的值相同。
给对象赋值
person.firstname="John";
person.lastname="Doe";
person.age=50;
person.lastname="Doe";
person.age=50;
访问对象
属性的值
person.firstname;
person['firstname']
person['firstname']
对象方法
var person = {
firstName: "xl",
lastName : "lili",
id : 5566,
fullName : function()
{
return this.firstName + " " + this.lastName;
}
};
firstName: "xl",
lastName : "lili",
id : 5566,
fullName : function()
{
return this.firstName + " " + this.lastName;
}
};
this 指向调用它所在方法的对象。
事件
onclick那些
json转换
JSON.parse() // 用于将一个 JSON 字符串转换为 JavaScript 对象。
JSON.stringify() // 用于将 JavaScript 值转换为 JSON 字符串。
JSON.stringify() // 用于将 JavaScript 值转换为 JSON 字符串。
JS调试
浏览器面板
Elements
Network
保留日志
Preserve log
+ 勾选每次刷新不会清除之前的请求
Preserve log
+ 勾选每次刷新不会清除之前的请求
停用缓存
Disable cache
+ 勾选后不会从缓存里面拉数据,每一次都是最新的数据,方便后续JS动态调试
Disable cache
+ 勾选后不会从缓存里面拉数据,每一次都是最新的数据,方便后续JS动态调试
Sources
page : 所有资源文件
filesystem: 关联本地文件
overrides: 可以做文件替换,比如替换JS
可以使用替换技术把解混淆的JS放进去调试
注:只可以针对原文件
JS 他是一个文件地址 原理就是关系映射
用处:1、删除JS里面的debugger 2、替换混淆JS 3、替换动态变化JS文件
注:只可以针对原文件
JS 他是一个文件地址 原理就是关系映射
用处:1、删除JS里面的debugger 2、替换混淆JS 3、替换动态变化JS文件
在“来源”面板中打开
编辑js文件
Ctrl+S保存,文件会变成紫色
放入替换文件夹
勾选 启用本地替换
之后每次打开F12,会加载本地的js
content script内容插件
snippets代码段
这里有全部的浏览器环境、扣JS的时候可以使用他调试 先跑通JS再去pycharm里面补环境即可
blackpoint断点调试
作用:对数据进行监听,跟值进行分析
业务逻辑被执行才可以被断住
业务逻辑被执行才可以被断住
js标签是从上往下顺序单线程加载的
做逆向的的时候 重点是关注发包
请求服务器 -> 加载HTML文件 -> 加载JS和CSS文件 -> 用户触发JS -> 加载了某一段JS -> 对参数加密 -> 发包 -> 解密数据 -> 接收并刷新网页数据
dom断点(在加密前断点)
从点击事件开始 发包 密码从明文变成了密文
xhr断点(在加密后)
执行比较靠后 距离加密函数相对较近 可以根据栈快速定位
**注意**:非`XHR`发送的就断不住
**注意**:非`XHR`发送的就断不住
方法栈
栈,先进后出
调用栈是解析器的一种机制,可以在脚本调用多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体又调用了哪个函数。
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
debug原理
https://gaokao.chsi.com.cn/zyk/zybk/
例如次网站,打开F12就会进入debugger,无法加载网站信息
例如次网站,打开F12就会进入debugger,无法加载网站信息
浏览器方法
1.
再debugger处右击never pause here
2.
添加条件断点 false
方法置空:将包含debugger的方法充值为空方法
function 目标方法(){ }
将debugger注释掉或者删除,用本地js文件替换
在控制台注入代码
var _constructor = constructor;
Function.prototype.constructor = function(s) {
if ( s== "debugger") {
console.log(s);
return null;
}
return _constructor(s);
}
Function.prototype.constructor = function(s) {
if ( s== "debugger") {
console.log(s);
return null;
}
return _constructor(s);
}
根据不同网站,注入的代码不同
这个是在debugger还是那传参
这个是在debugger还是那传参
0 条评论
下一页