http协议与https协议
服务器和客户端的交互方式
常用的请求头(request)的关键信息:
User-Agent : 请求载体的身份标识
Connection : 请求完毕后,是断开连接还是保持连接
常用的响应头(response)的关键信息:
Content-Type : 服务器响应客户端的数据类型
https:安全的超文本传输协议(数据加密)
加密方式:
- 对称密钥加密
- 非对称密钥加密
- 证书密钥加密(https采用)
requests
基础语法
response = requset.get(url)
实例1:爬取搜狗搜索
需求:需要爬取搜索内容写入之后搜狗响应的内容
import requests
#指定需求url
url = 'https://www.sogou.com/web' #因为要传递参数所以在源url要把参数去掉
#请求准备
kw = input('输入你想搜索到的结果')
parmam = { 'query' : kw }
UA = {'User-Agent':'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
#对指定url请求并给出传递的参数
response = requests.get(url,parmam,headers = UA)
#处理数据
page_text = response.text
fileName = kw + '.html'
#数据持久化
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件'+fileName,'w',encoding = 'utf-8') as file:
file.write(page_text)
print('保存成功')
User-Agent伪装
User-Agent
请求载体身份标识
每次访问网站都会进行UA检测,如果请求载体的身份标识是浏览器,说明该请求是正常请求;如果不是一款浏览器而是表示其他的为不正常请求(爬虫),服务器就会拒绝
- UA检测属于反爬虫机制
实例2:破解百度翻译
需求:需要爬取输入其他语言时百度翻译翻译出来的解析(而不只是翻译内容)
分析网页
-
百度翻译的Ajax是使用
POST
请求并 携带参数-
注:有一些POST请求的参数是放在data(表单数据)里面的
所以要分辨好到底那个数据包才是正确的数据包
response里面返回的可以是unicode编码(\uxxx)
-
-
Response返回的是
json
格式 (json学习)
import requests
Ajax请求
Ajax,全称是异步的JavaScript和xml,不是一门专门的编程语言,而是利用JavaScript在 保证页面不被整体刷新的情况下与服务器交换数据并且更新部分网页显示的技术 。
获取Ajax请求方法:
- 右键网页 -> 检查
- 上方信息栏 -> Network(网络)
- 把获取的数据包类型选中
Fetch/XHR
- 分析给出的数据包找到相应的数据包里面的url进行爬取
- 注意相应的数据包请求后给出的Response(响应)
- 注意相应的数据包请求后给出的
Content-Type
实例3:豆瓣排行榜
需求:爬取豆瓣排行榜中的喜剧榜
分析网页
- GET请求方法
- 特别注意URL正不正确
import requests
import json
# 指定url
url = 'https://movie.douban.com/j/chart/top_list'
# 参数处理
param = {
'type': '24',
'interval_id': '100:90',
'action': '',
'start': '60', # 据观察发现这个参数是从库中的其实位置取'limit'个数并显示
'limit': '20' # 取出个数
}
# UA伪装
UA = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
# 对指定url请求并给出传递的参数
response = requests.get(url, params=param, headers=UA)
print(response.text)
# 数据处理
# Content-Type: application/json
Movie_data = response.json()
# 数据持久化
fp = open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\03豆瓣排行榜.json',
'w', encoding='utf-8')
json.dump(Movie_data, fp=fp, ensure_ascii=False)
print('over!!')
数据解析
– 1. 进行指定标签的定位
– 2.标签或者标签对应属性中存储的数据值进行提取
实例1值得买图片爬取
-
如何爬取图片:使用连接访问图片就能爬取
注意:图片是二进制格式
import requests
import re
# 指定url
url = 'https://post.smzdm.com/'
# 参数处理
# UA伪装
UA = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
# 爬取网页数据
response = requests.get(url=url, headers=UA)
webdata = response.text
# 数据处理
# 创建正则匹配对象
pattern = re.compile('<img src="(.*?)"', re.S)
result = pattern.findall(webdata) # 返回列表
result = list(result)
# 处理后再爬取
num = 0
for i in result:
if i[:2] == '//':
i = 'http:' + i
rep = requests.get(url=i, headers=UA)
#数据持久化
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\04值得买图片爬取\\' + str(num) + '.jpg', 'wb') as f:
f.write(rep.content)
num += 1
print('over!!!')
正则表达式
-
[字符集]字符
匹配中括号中可能出现的所有字符[Pp]ython >>> python Python
—
表示区间
#我们匹配手机号码时可以这样匹配 [1234567890] >>> 15520087981 #可以用区间匹配法 [0-9] >>> 15520087981 [a-z] >>> fuck #大小写同时匹配(写在一起 [a-zA-Z] >>> I love You
-
\—
转义字符 - 进行匹配(其他特殊字符同理)#数字、小写字母,大写字母,横杠 [0-9a-zA-Z\-] >>> I-1ove-U
-
[^....]
取反 匹配不包含中间字符的其他字符#匹配不包含0-9的所有字符 [^0-9] >>> abc ABC
-
快捷方式
\d :匹配所有数字 \w :匹配所有字符(数字,大写字母,小写字母)
-
\s
快捷方式可以匹配空白字符,比如空格,tab、换行等。\s >>> code jiaonang
-
\b
匹配的是单词的边界,主要用于截取单词给出字符串:code jiaonang \bcode\b >>> code
-
快捷方式取反
#把小写改成大写就是取反 \D \W \S \B
-
-
开始与结尾
-
^...
表示开头#给出字符:python-a-py ^python py$ >>> python-a-py
-
-
.
匹配任意字符(单个位)#给出字符:axasfwf asfww ax..... >>> axasfwf
-
字符?
表示字符可以出现可以不出现#给出字符:honur honr honu?r >>> honur honr
-
在一个字符组后加上
{N}
就可以表示在它之前的字符组出现N
次给出字符:0256-595624152 \d{4}-\d{9} >>> 0256-595624152
* 开闭区间 `{数字,}` 表示匹配几位后接着匹配
* `+`等价于`{1,}`,`*`等价于`{0,}`。
```python
#给出字符:025 102256
\d{1,}
>>> 025 102256
-
注意{num1,num2}默认为贪婪匹配{匹配到num2个字符}
-
使用
?
来取消贪婪匹配 (匹配到num1个字符) -
如果给出的两个数不相邻并且取消了贪婪匹配,就会匹配num2个字符,用num1个为每组进行匹配
#观日出字符123456789
{2,9}?
>>> 12 34 56 78
RE模块
re.compile( ) 编译函数
re.compile(pattern, flags=0)
pattern 即第一章中提到的正则表达式,flags是可选的,默认为 0。flags 的可选值如下:
.re.I (re.IGNORECASE): 忽略大小写
.re.M (MULTILINE): 多行模式,改变’^‘和’$‘的行为
.re.S (DOTALL): 点任意匹配模式,改变’.'的行为 ,忽略换行符的干扰
.re.L (LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
.re.U (UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
.re.X (VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入
上述 flag 可以单独用,也可以一起使用
re.match() 和 re.search() 匹配和搜索函数
re.match(pattern, string, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
string 即被匹配的字符串。
re.search(pattern, string, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
string 即被匹配的字符串。
search 只找到第一个符合条件字符串的就返回
group
match 也好,search 也好,都是返回的一个对象,这个对象有方法 group(),groups(),如果没有找到的时候,返回的是 None 对象。
**group() **
方法或者返回所有匹配对象,或者是根据特定要求返回某个特定子组。
groups()
方法很简单,它返回一个包含唯一或者所有子组的元组,如果正则表达式中没有子组的话,groups() 将返回一个空元组,而 group() 则仍然返回全部匹配对象。
可以使用 () 来划分子组
re.findall() 和 re.finditer() 查找所有符合条件的字符串
re.findall(pattern, string, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
string 即被匹配的字符串。
re.findall 可以查找出所有的符合要求的字符串,并且返回一个列表
re.finditer(pattern, string, flags=0) flags 和 re.compile() 中的 flags 用法一致。
string 即被匹配的字符串。
re.finditer() 和 re.findall() 非常相似,只不过返回的是一个***迭代器***,而不是列表,对于每个匹配返回一个***匹配对象(re.match 匹配对象)***。所以读取方式有了变化,这样内存会更加友好
re.split() 分割函数
re.split(pattern, string, maxsplit=0, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
string 即被匹配的字符串。
maxsplit 是最多能被分割的次数,从左而右计算。
**re.split() **返回一个列表
a="das:fgfd:dfdgf:dadf"
res=re.split(":",a,2)
res
>>> ['das','fgfd','dfdgf:dadf']
re.sub() 和 re.subn() 替换函数
re.sub(pattern, repl, string, count=0, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
repl 即用来替换的字符串
string 即被替换的字符串。
count 即被替换的次数,为 0 时候意味着全部替换。
re.sub()返回被替换后的字符串
re.subn(pattern, repl, string, count=0, flags=0)
flags 和 re.compile() 中的 flags 用法一致。
repl 即用来替换的字符串
string 即被替换的字符串
count 即被替换的次数,为 0 时候意味着全部替换。
Beautiful Soup
本内容统一使用数据:
html_str = '''
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
'''
获取标签之间的文本数据:
– soup.a.text / string / get_text()
text/get_text() 可以获取某个标签中所有文本内容
string:只可以获取该标签下面直系的文本内容
获取标签中属性值:
– soup.a[‘href’]
BeautifulSoup(markup,features)
markup:
被解释的HTML字符串或者文件内容
from bs4 import Beautiful Soup
fp = open('...')#获取文件对象
soup = BeautifulSoup(fp)
features:
解析器类型,如果不指定就会使用默认的解析器
节点选择器
###元素提取
定位标签: soup.tag(标签)
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str,'lxml')
print(soup.title)
它返回的是Tag类的对象
当html中存在多个相同的节点的时候,只会提取第一个节点
信息提取
定位标签并提取信息: soup.tag.name
name
获取名称 返回:字符串
attrs
获取属性 返回:**字典 ** 注意:class是返回的列表
string
获取内容 返回:字符串
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str,'lxml')
#获取标签名称
print(soup.a.name)
print(type(soup.a.name))
#获取标签属性
print(soup.a.attrs)
print(type(soup.a.attrs))
#获取标签里面的内容
print(soup.a.string)
print(type(soup.a.string))
嵌套选择
如果提取到的节点中还有其他子节点,就可以用嵌套选择
格式: soup.tag.tag
返回: 节点元素
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str,'lxml')
print(soup.head.title)
print(soup.head.title.string)
关联选择
子节点
格式: soup.tag.contents
返回值:列表
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str,'lxml')
# 获取p节点的子节点
print(soup.p.contents)
# 输出结果
[<b>The Dormouse's story</b>]
格式:soup.tag.children
返回值:生成器
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str,'lxml')
# 获取p节点的子节点
print(soup.p.children)
for i child in enumerate(soup.p.children):
print(i,child)
# 输出结果
# <list_iterator object at 0x0000020B5472DBA0>
# 0 <b>The Dormouse's story</b>
父节点
格式:soup.tag.parent
返回值:节点元素
祖先节点
格式:soup.tag.parents
返回值:生成器
兄弟节点
-
获取后面一个节点
格式:
soup.tag.next_sibling
返回值:节点元素
获得的是同级节点
-
获取后面所有节点
格式:
soup.tag,next_siblings
返回值:生成器
-
获取前面一个节点
格式:
soup.tag.previous_sibling
返回值:节点元素
-
获取前面所有节点
格式:
soup.tag.previous_siblings
返回值:生成器
CSS选择器
调用select()
方法,并结合CSS选择器语法就能定位到元素位置
格式:soup.select()
> soup是Beautiful Soup4对象
> select()
中属性CSS选择器语法
id选择器
因为在css中id是 #
开头,所以只需要 #id内容
就可以了
类选择器
类(class) 在css中是 .
开头,所以只需要 .class内容
标签选择器
直接使用标签名就可以选择 如 h4
混合使用
顺序: class -> id -> tag
soup.select('.tang > ul > li > a') #大于号表示一个层级
soup.select('.tang ul a') #空格表示多个层级
select()可以被下标索引
方法选择器
find_all()
作用:用于搜索当前节点的所有符合条件的节点列表
范围:当前节点下的所有节点,如果没有指定当前节点,则进行全文搜索
find_all(name,attrs,recursive,text,**kwargs)
-
name:查找所有名字为
name
的节点(标签名)(tag对象) 可以使用:字符串,正则表达式,列表,True
- 字符串:当
name
为字符串时,会查找完全匹配的内容 - 正则表达式:当
name
为正则表达式时,会使用match()
函数来匹配内容 - 列表:当
name
为列表时,会把列表中的每一元素匹配后返回,返回一个列表 - True:当
name
为True时,会匹配任何值
- 字符串:当
-
attrs:查询含有接受的属性值的标签
参数形式:字典类型
soup.find_all(attrs={id:'link1'})
-
kwargs参数:接收常用属性参数,如
id
或class
参数形式:变量赋值的形式
soup.find_all(id='link1')
-
text:查询含有接收文本的标签
参数形式:字符串
通过搜索文档中的字符串内容来确定文件标签
soup.find_all(text='Elsie')
一般text与其他参数连用来过滤 其他的tag
soup.find_all('a',text='Elsie')
-
limit:限制返回结果的数量
参数类型:整数
soup.find_all('a',limit=2)
-
recursive:决定是否获取子孙节点
参数类型:布尔型,默认为True
find()
与 find_all
区别:
只返回第一个查询到的节点
limit
参数不能使用
OS
import OS
函数:
os.getcwd()
获取当前工作目录 [返回:str]os.chdir(path)
修改当前工作目录 [path:路径]os.listdir(path)
获取指定文件夹中的所有内容的信息组成的列表 [path:路径]mkdir(path)
创建文件夹 [path:路径]- 只会在给出的路径下创建文件夹
makedirs(path)
递归创建文件夹- 可以多层创建文件夹
rmdir(path)
删除空文件夹removedirs(path)
递归删除空文件夹rename(oldpath,newpath)
修改文件或者文件夹的名称stat()
获取文件或文件夹的状态信息system(order)
执行操作系统命令
环境变量相关的方法
-
getenv()
获取系统的环境变量 -
putenv()
设置python的环境变量 -
environ[成员]
获取环境变量 -
设置环境变量
-
os.environ['path'] = 'D:/a/b/c'
这样会直接覆盖 -
需要这样添加:
os.environ['path'] = os.environ['path'] +';'+D:/a/b/c'
-
属性:
curdir
获取当前的路径.
表示当前文件夹 [相对路径]
pardir
获取上层文件夹..
表示上一层文件夹 [相对路径]
- path 是os库的子模块
name
获取系统的标识符号- nt 表示windows操作系统
- posix 表示 linux和unix系统
sep
获取当前系统路径的分隔符号- \ windows系统的路径分隔符
- / linux和unix的路径分隔符
extsep
获取文件名称与文件后缀名之间的分隔符linesep
获取系统的换行符号- windows \r\n
- linux \n
- macos 10.9->\r 10.10->\n
PATH
主要用于路径相关的操作
import os
os.path.function()
-
abspath()
将相对路径转化为绝对路径 -
basename(path)
获取路径的主题部分(这个文件或文件夹) -
dirname(path)
获取路径中路径部分(完整路径的前部分) -
join(path1,path2)
将两个路径合并到一起(两个不能是绝对路径) -
split(path)
直接将路径拆分成路径部分和主体部分的元组 -
splitext(path)
将路径拆分为文件后缀和其他部分 -
getsize(path)
获取文件大小(字节数)
返回布尔型
isdir(path)
检测是否是一个文件夹isfile(path)
检测是否是一个文件islink(path)
检测是否是一个连接
返回的是时间戳
-
getctime()
获取文件的创建时间 -
getmtime()
获取文件的修改时间 -
getatime()
获取文件的访问时间 -
exists()
检测指定路径是否真实存在 -
isabs()
检测路径是否为绝对路径 -
samefile()
检测两个路径是否指向同一个文件或者文件夹
Xpath
from lxml import etree
把网页解析为 Xpath
结构
res = etree.HTML(res.text)#网页数据
res = etree.parse(path)#本地的html
提取数据
res.xpath('xpath语法')
在网页检查中可以右键源码,copy xpath 来直接获取xpath语法
- 提取文本内容:…/text()
- 返回的是列表,如果需要输出字符串就索引它
[]
//text()
获取不是直系标签,前面的标签下面所有内容
- 返回的是列表,如果需要输出字符串就索引它
绝对路径:
从<html>
开始一层一层递进到需要的标签
格式:'/html/head/title/text()'
相对路径:
可以不从<html>
开始,前面的标签用 /
代替
格式: //title/text()
'//'
可以提取任意节点
指定属性提取文字
- 格式: // 标签 [@属性] /…
res.xpath('//a[@class='menu']/text()')
如果有多个属性:and
res.xpath('//a[@class='menu' and @href]/text()')
提取属性
*
通配符,后面可以直接[条件]
[]
可以把标签索引,里面可以加上想提取第几个
提取标签的属性:/@标签属性名
res.xpath('//a/@href')
注:在二次提取时,使用相对路径的前面要加上 .
来表示从之前的路径开始提取,如果不加就会从<html>
标签开始遍历寻找,就可能会找到重复的标签
因为会解析为xpath的类型,要不然遍历,要不然索引
实例2:爬取彼岸图网美女图片
import requests
from lxml import etree
import os
# 指定url
url = 'https://pic.netbian.com/4kmeinv/'
# 参数处理
# UA伪装
UA = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
# 爬取数据
rep = requests.get(url=url, headers=UA)
rep.encoding = 'gbk'
# 数据处理
rep_xpath = etree.HTML(rep.text)
link = rep_xpath.xpath('//ul[@class="clearfix"]/li/a/img/@src')
os.mkdir('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\06彼岸图网\\美女')
# 处理后再爬取
for i, k in enumerate(link):
ture_link = 'https://pic.netbian.com' + k
photo = requests.get(ture_link, headers=UA)
# 数据持久化
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\06彼岸图网\\美女\\' + str(i) + '.jpg', 'wb') as file:
file.write(photo.content)
print('over!!!')
作业
https://sc.chinaz.com/jianli/free.html
验证码识别
验证码其实就是一种反爬机制
识别验证码:
- 第三方自动识别
第三方识别
安装库
pip install tesseract
pip install pytesseract
pip install pillow
from PIL import Image
import pytesseract
path = '验证码01.jpg'
captcha = Image.open(path)
result = pytesseract.image_to_string(captcha)
import requests
import pytesseract
from PIL import Image
from lxml import etree
# 指定url
url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
# 参数处理
# UA伪装
UA = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
# 爬取数据
rep = requests.get(url=url, headers=UA)
# 数据处理
deal = etree.HTML(rep.text)
data = deal.xpath(
'//div[@class="mainreg2"]/div[@class="mainreg2"]/img/@src')[0]
# 再次爬取
img_url = 'https://so.gushiwen.cn' + data
img_data = requests.get(img_url, headers=UA)
# 数据持久化
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\08验证码识别\\验证码.jpg', 'wb') as f:
f.write(img_data.content)
# 验证码识别
captcha = Image.open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\08验证码识别\\验证码.jpg')
result = pytesseract.image_to_string(captcha)
print(result)
模拟登录
-
在网页检查里面右键检查 -> 网络 -> 保留日志(preserve log) 这样才能抓到登录数据包
Cookie:让服务器记录客户端的相关状态
手动:如果要指定
Cookie
就把他封装到headers
里面 -
引入概:实现自动化登录
Session会话对象:
作用:
1:可以进行请求发送
2:如果请求过程中产生了cookie,则该cookie会被自动存储/携带在session对象中
使用方法:
session = requests.Session() #模拟一个session对象
模拟登录:
response = session.post(url)
Cookie,Session,Token
原文章学习
代理
用来破解封IP这种反爬机制
– 突破自身IP的访问限制
– 隐藏自己的真实IP
代理网站:
https://www.kuaidaili.com/ 快代理
requests.get(url,proxies={'https':'...'})
import requests
import re
from lxml import etree
# 指定url
url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip'
# 参数处理
# UA伪装
UA = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Mobile Safari/537.36'}
# 爬取源码
rep = requests.get(url=url, headers=UA)
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\09百度ip代理\\baidu.html', 'w', encoding='utf-8') as f:
f.write(rep.text)
# 数据处理
deal = etree.HTML(rep.text)
deal_over = deal.xpath('//div[@class="c-line-clamp1"]/span/text()')
deal_over = deal_over[2]
deal_over = deal_over[1:16]
print('无代理的ip:',deal_over)
#代理
rep = requests.get(url=url, headers=UA,proxies={'https':'https://222.110.147.50:3128'})
with open('C:\\Users\\Yuan\\Desktop\\爬虫\\爬取文件\\09百度ip代理\\baiduproxy.html', 'w', encoding='utf-8') as f:
f.write(rep.text)
# 数据处理
deal = etree.HTML(rep.text)
deal_over = deal.xpath('//div[@class="c-line-clamp1"]/span/text()')
deal_over = deal_over[2]
deal_over = deal_over[1:16]
print('有代理的ip:',deal_over)
代理ip透明度:
- 透明:服务器知道该次请求使用了代理,也知道请求对应的真实ip
- 匿名:知道使用代理,不知道真实ip
- 高匿:不知道代理,也不知道ip
异步爬虫
- 多线程,多进程(不推荐)
- 好处:可以为香断阻塞操作单独开启线程或者进程,阻塞操作可以异步执行
- 弊端:无法无限制开启多线程或者多进程
- 线程池,进程池(适当使用)
- 好处:可以降低系统对进程或者线程创建销毁的频率(不需要频繁创建),从而很好的降低系统的开销
- 弊端:池中线程或进程的数量是有上限
导入线程模块
from multiprocessing.dummy import Pool
实例化线程池对象
pool = Pool(n) #n:线程数量
线程使用
pool.map(func,iterable)
func:发生阻塞的函数(爬取模块)
iterable:可迭代对象(要爬取的url对象)
工作原理:把iterable
里面的每一个传入func
里面进行处理
pool.map的返回值就是func
的返回值
使用完成需要关闭
pool.close()
#使其不在接受新的(主进程)任务
让主线程让子线程结束之后再关闭
pool.join()
#主进程阻塞后,让子进程继续运行完成,子进程运行完后,再把主进程全部关掉。
注意: 有些网页可能是动态的,所以如果要获取标签的数据这些还是要以返回的**response
**为准(在response中寻找需要的数据)
-
单线程 + 异步协程 (推荐)
基于
python
3.6event_loop
: 事件循环,相当于一个无限循环,我们考研把一些函数注册到这个事件循环上面,当满足某些条件的时候,函数就会被循环执行
coroutine
: 协程对象,我们可以将协程对象注册到事件循环(
event_loop
)中,他会被事件循环调用。我们可以使用async
关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象task
: 任务,它时对协程对象的进一步封装,包含了任务的各个状态future
: 代表将来执行或还没有执行的任务,实际上和task
没有本质区别anync
: 定义一个协程await
:用来挂起阻塞方法的执行
协程
不是计算机提供,是程序员人为创造
协程(Conutine),也可以称之为为线程,是一种用户态内的上下文切换技术,总而言之,就是通过一个线程实现代码块相互切换执行
#示例
def fun1():
pass
....
def fun2():
pass
....
fun1()
fun2()
'''
协程操作就是指两个函数的代码块之间可以来回切换执行
而不是先执行fun1,再执行fun2
'''
实现协程的模块
- greenlet 早期模块
- yield关键字
- asyncio装饰器 (py 3.4)
- async,await关键字 (py3.5) [推荐]
协程的意义
在一个线程中如果遇到IO等待时间,线程不会傻傻等,利用空闲的时间再去干点其他事
greenlet实现协程
pip install greenlet
案例:
from greenlet import greenlet
def fun1():
print(1)
gr2.switch()#2. 输出1后跳转到协程对象2运行
print(2)
gr2.switch()#4. 输出2后再转到协程对象2运行
def fun2():
print(3)
gr1.switch()#3. 输出3之后再转到协程对象1运行
print(4)
# 把两个函数转为协程对象
gr1 = greenlet(fun1)
gr2 = greenlet(fun2)
gr1.switch() #1. 先运行协程对象gr1
yield关键字
实现协程
def fun1():
yield 1
yield from fun2()# 调用fun2函数并生成数据
yield 2
def fun2():
yield 3
yield 4
f1 = fun1() # 这里返回的是生成器,所以可以被遍历,遍历一次就是运行一条
for item in f1:
print(item)
asyncio
在python3,4之后
import asyncio
'''
asyncio的牛逼之处就在于
可以在IO等待的过程中自动切换运行
'''
@asyncio.coroutine #装饰器装饰成协程函数
def fun1():
print(1) # 1.执行第一步
yield from asyncio.sleep(2) # 2.在等待的时间转到fun2接着运行
'''
这里只要遇到IO耗时的操作(requests),就自动切换tasks里面的其他任务运行
'''
print(2) # 3.运行print(这里与fun2是同步的)
@asyncio.coroutine
def fun2():
print(3) # 2.运行print(这里与fun1是同步的)
yield from asyncio.sleep(2) # 3.在等待的时间转到fun1接着运行
print(4) # 4.上面所有全部运行完成,最后才是print(4)
#把两个函数转为协程任务
tasks = [
asyncio.ensure_future(fun1()),
asyncio.ensure_future(fun2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
async & await 关键字
import asyncio
#async & await只是把装饰器和生成器改成关键字,推荐使用
async def fun1():
print(1) # 1.执行第一步
await asyncio.sleep(2) # 2.在等待的时间转到fun2接着运行
print(2)# 3.运行print(这里与fun2是同步的)
async def fun2():
print(3) # 2.运行print(这里与fun1是同步的)
await asyncio.sleep(2) # 3.在等待的时间转到fun1接着运行
print(4) # 4.上面所有全部运行完成,最后才是print(4)
# 把两个函数转为协程任务
tasks = [
asyncio.ensure_future(fun1()),
asyncio.ensure_future(fun2())
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
异步编程
事件循环
理解成为一个死循环,去检车并执行某些代码
任务列表 = [任务1 , 任务2 , 任务3 ,...]
while True:
可执行的任务列表 , 已完成的任务列表 = 去任务列表中查找所有的任务,将 '可执行' 和 '已完成' 的任务返回
for 就绪任务 in 可执行的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表:
在任务列表中删除 已完成的任务
如果 任务列表 中的任务都已经完成,则终止循环
运用到代码
import asyncio
# 去生成或在获取一个事件循环
loop = asyncio.get_event_loop()
# 将任务放到 任务列表
loop.run_until_complete(任务)
生成事件循环后判断任务的状态并执行
基础异步编程
协程函数:定义函数的时候 async def 函数名
协程对象:执行 协程函数() 得到的协程对象
async def func():
pass
result = func()
注意:执行协程函数创建协程对象,函数内部代码不会执行
异步编程:需要将协程对象搭配协程函数与事件循环联合起来使用
import asyncio
# 创建协程函数
async def func():
print('快来执行我吧')
# 建立协程对象
result = func()
# 建立事件循环
loop = asyncio.get_event_loop()
loop.run_until_complete( result )
如果想要运行协程函数内部代码,必须要把协程对象交给事件循环来处理
但是前面的是python3.7之前的写法,3.7之后就会有集成写法
# python 3.7 之后
import asyncio
# 创建协程函数
async def func2():
print('快来执行我吧')
# 建立协程对象
result = func2()
# 建立事件循环
asyncio.run(result)
'''
loop = asyncio.get_event_loop()
loop.run_until_complete( result )
'''
await
await + 可等待对象 (协程对象,Futrue,Task对象 -> IO等待)
# 示例1
print('示例1:')
import asyncio
async def func():
print('来玩啊')
response = await asyncio.sleep(2)
print('结束', response)
asyncio.run(func())
print()
print()
# 示例2
print('示例2:')
import asyncio
async def others():
print('start')
await asyncio.sleep(2) # 因为这里运行任务只有func()一个,所以在await等待的时候不会异步运行
print('end')
return '返回值'
async def func():
print('执行协程函数内部代码')
'''
这里注意是'await 协程对象'后在这个等待时间是等待的others运行完成
'''
response = await others() # others是协程函数,所以others()是协程对象(因为相当于others被调用)
print('IO请求结束,结果为:', response)
asyncio.run(func())
# 因为这里运行任务只有func()一个,所以在await等待的时候不会异步运行
print()
print()
# 示例3
print('示例3:')
import asyncio
async def others():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
async def func():
print('执行协程函数内部代码')
'''
这里注意是'await 协程对象'后在这个等待时间是等待的others运行完成
'''
response1 = await others() # 这个运行完了下面才会运行
print('IO请求结束,结果为:', response1)
response2 = await others() # 上面运行了相同的这里也运行
print('IO请求结束,结果为:', response2)
asyncio.run(func())
Task
在事件循环中添加多个任务的
Tasks 用于并发调度谢谢成,通过asyncio.create_task(协程对象)
的方式来创建Task对象,这样可以让鞋厂加入事件循环中等待被调度执行。哈可以使用更改低层级的loop.create_task()
或者ensure_future()
,不建议手动实列化Tasks对象
注意:asyncio.create_task()
只能在python3.7后使用,3.7之前可以改用低层级的loop.create_task()
或者ensure_future()
# 示例1
print('示例1:')
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
async def main():
print('main开始')
# 创建Task对象,将当前执行func函数任务添加到事件循环中
task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func())
# 创建了Task对象并添加到事件循环之后,遇到IO阻塞时就能切换Task中其他的地方运行
print('main结束')
ret1 = await task1
ret2 = await task2
print(ret1, ret2)
asyncio.run(main()) # 运行main()
print()
print()
# 示例2
print('示例2:')
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
async def main():
print('main开始')
# 直接使用列表把任务装起来
tasks = [
# 也可以通过指定属性更改返回集合的对象
asyncio.create_task(func(),name='n1'),
asyncio.create_task(func(),name='n2')
]
print('main结束')
done, pending = await asyncio.wait(tasks, timeout=None)
'''
done:是函数完成后的返回值集合
pending:是函数没完成的集合,常与timeout搭配
timeout:这个参数是给予task的完成规定时间(秒),限制函数只能在多少秒完成
因为await + 可等待对象 (协程对象,Futrue,Task对象)
所以要使用asyncio.wait()函数来先让tasks里面的创建任务先运行
'''
print(done, pending)
asyncio.run(main()) # 运行main()
print()
print()
#示例3
print('示例3:')
#完全可以不用这么复杂
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
# 直接使用列表把任务装起来
tasks = [
func(),
func()
]
done,pending = asyncio.run(asyncio.wait(tasks))
# asyncio.run()函数可以自动创建Task对象,但是需要asyncio.wait()来给予创建时间
Futrue对象
Task继承Futrue,Task对象内部await结果的处理基于Futrue对象来的
asyncio.create_task()只能在python3.7后使用,3.7之前可以改用低层级的**
loop.create_task()或者
ensure_future()`
# 示例1
print('示例1:')
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
async def main():
print('main开始')
# 创建Task对象,将当前执行func函数任务添加到事件循环中
task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func())
# 创建了Task对象并添加到事件循环之后,遇到IO阻塞时就能切换Task中其他的地方运行
print('main结束')
ret1 = await task1
ret2 = await task2
print(ret1, ret2)
asyncio.run(main()) # 运行main()
print()
print()
# 示例2
print('示例2:')
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
async def main():
print('main开始')
# 直接使用列表把任务装起来
tasks = [
# 也可以通过指定属性更改返回集合的对象
asyncio.create_task(func(),name='n1'),
asyncio.create_task(func(),name='n2')
]
print('main结束')
done, pending = await asyncio.wait(tasks, timeout=None)
'''
done:是函数完成后的返回值集合
pending:是函数没完成的集合,常与timeout搭配
timeout:这个参数是给予task的完成规定时间(秒),限制函数只能在多少秒完成
因为await + 可等待对象 (协程对象,Futrue,Task对象)
所以要使用asyncio.wait()函数来先让tasks里面的创建任务先运行
'''
print(done, pending)
asyncio.run(main()) # 运行main()
print()
print()
#示例3
print('示例3:')
#完全可以不用这么复杂
import asyncio
async def func():
print('start')
await asyncio.sleep(2)
print('end')
return '返回值'
# 直接使用列表把任务装起来
tasks = [
func(),
func()
]
done,pending = asyncio.run(asyncio.wait(tasks))
# asyncio.run()函数可以自动创建Task对象,但是需要asyncio.wait()来给予创建时间
Futrue对象
Task继承Futrue,Task对象内部await结果的处理基于Futrue对象来的