爬虫学习+实战

news2024/11/18 14:37:50

爬虫

概念:

网络爬虫:就是模拟客户端发送请求,获取响应数据,一种按照一定的规则,自动地抓取万维网上的信息的程序或者脚本

爬虫分类:

  1. 通用爬虫:抓取系统中重要的组成部分。抓取的是一整张页面数据
  2. 聚焦爬虫:建立在通用爬虫的基础上,抓取的是页面中特定的局部内容。
  3. 增量式爬虫:检测网站中数据更新的情况,只会抓取网站中最新更新的数据

爬虫的矛与盾

  1. 反爬机制:门户网站,通过制定相关的策略或技术手段,防止爬虫程序对网站数据的爬取
  2. 反反爬:破解门户网站具备的反爬机制,获取数据

爬虫和浏览器的区别:

​ 浏览器:

​ 浏览器向百度服务器发送请求 --> http 😕/www.baidu.com

​ 百度服务器发送数据–> 获取响应数据,进行渲染 -->给用户看

​ 爬虫:

​ 向百度服务器发送请求–> http 😕/www.baidu.com

​ 百度服务器发送数据–> 获取响应数据,并保存数据

URI和URL:

URI全称:Uniform Resource Identifier (统一资源标识符)

URL全程:Universe Resource Locator (统一资源定位符) , URL是URI的子集

举例说明: https://github .com/favicon.ico 这是GitHub网站的图标链接

访问协议https · 访问路径github.com · 资源名称favicon.ico

超文本:

hypertext–超文本 网页就是超文本解析而成的,网页的源代码就是HTML代码

HTTP和HTTPS协议:

HTTP:Hyper Text Transfer Protocol (超文本传输协议),用于从网络传输超文本数据到本地浏览器的传送协议,能保证高效而准确的传输超文本文档

HTTPS:Hyper Text Transfer Protocol over Secure Socket Layer (以安全为目标的HTTP通道),就是HTTP的安全版

就是服务器与客户端进行数据交互的一种形式。

常用请求头信息:

  1. User-Agent:请求载体的身份标识,可以使服务器识别操作系统的版本、浏览器等信息
  2. Conection:请求完之后,是断开连接还是保持连接
  3. Cookie: 为了辨别用户进行会话跟踪而储存在用户本地的数据,功能是 维持当前访问会话。就是用户登陆后,服务器会保持登录状态

常用响应头信息:

​ Content-Type:服务器响应回客户端的媒体类型信息,例如html、json、gif

加密方式:

  1. 对称密钥加密:客户端对数据进行加密,锁和密钥一起发给服务器,服务器再用密钥打开锁,发送过程中被拦截密钥会被拿走
  2. 非对称密钥加密:服务器制定好加密方式发送给客户端,客户端用该加密方式对数据进行加密,再发送给服务器用私钥进行解密。但是加密方式传输时可能被拦截篡改。
  3. 证书密钥加密:先把公开密钥给证书认证机构,给公钥进行签名,公钥封装到证书里,发送给客户端。客户端相信认证机构,客户端通过公钥对报文进行加密发送,服务器接收后用私钥进行解密

响应:

由服务端返回给客户端,分为三部分。响应状态码(Response Status Code),响应头(Response Headers),响应体(Response Body)

1.状态码:

200--成功
400--错误请求
403--禁止访问
404--找不到请求的网页
502--错误网关
503--服务器目前无法使用
504--网关超时

2.响应头:响应头,Server、Cookies等信息

3.响应体:请求网页的时候,返回的使HTML代码,请求图片的时候响应的使二进制数据

网页的结构:

title标签使网页的标题 · body是正文内容

div是网页的区块标签,id=“ ” id的内容在网页中是唯一的 class=“ ” 属性标记

HTML文档是树结构,节点树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gKtR4Fg8-1673159360178)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632450058735.png)]

为什么有时候解析不到数据?

1.beautifulsoup的标签不规范,无法识别定位

2.xpath的标签嵌套找不到,定位不到

3.js的渲染

使用HTTP请求库得到的源代码和浏览器中看到的数据不一样 ,是因为js会改变HTML的结点,向其添加内容,最后得到浏览器的页面

4.不要在Elements的选项卡中直接查看源码,那里的代码给渲染过了,要在network中查看

什么是会话(Session)和Cookies

HTTP的特点之一:无状态

指HTTP协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么

  • 意味着 如果后续需要处理前面的信息,必须重传

Session

在服务端----网站的服务器

  • 用来保存用户的Session信息

Cookies

在客户端也就可以理解为浏览器端

  • 辨别用户的身份、进行Session跟踪而储存再用户本地终端上的数据

浏览器在下次访问网页时会自动附带上cookies发送给服务器,服务器通过识别Cookies并鉴定出时哪个用户,然后再判断用户是否是登陆状态进而返回对应的响应

1.会话cookies:

​ cookies放在浏览器内存里,浏览器关闭之后该cookies失效

2.持久cookies:

​ 浏览器关闭,cookies不会消失,cookies会保存会话ID信息到硬盘上,再次打开浏览器,仍然能够找到原来的会话ID,依旧可以处于保持登录状态。

恰恰是这样,这需要服务器为会话机制设置一个失效时间,距离客户端上一次书院会话的时间超过失效时间,服务器就会删除会话以节省空间

代理:

代理–>代理服务器,proxy server

功能:代理网络用户去取得网络信息

理解:代理服务器就是在本机和服务器之间的一个桥,本机向代理发请求,然后代理再向服务器发请求,回来的响应也是经历这样的过程,web服务器无法识别我们的真实IP,从而实现IP伪装

urllib:

有4个模块

request: HTTP请求模块

error: 异常处理模块,如果出现错误,我们可以捕获异常,然后进行重试或者其他操作,保证程序不会意外终止

parse: 工具模块,提供URL的处理方法

robotparse: 识别网站的robot.txt

requests

requests是一个优雅而简单的Python Http请求库,作用是发送请求获取响应数据

使用3步骤:

  1. 导入模块

  2. 发送get请求,获取响应

  3. 从响应中获取数据

    import requests
    
    get(url= ,param= ,headers=)
    url:请求的路由
    param:要访问抓取的参数,用抓包工具用网页上抓取,有些固定的参数自己可以更改,注意数	  据类型是str
    headers:User-Agent,抓包工具抓一次就好了
    timeout:超时时间,过了这个时间服务器没响应,就返回失败
     
    post(url= ,headers= ,data= )	注意和get区分
    
    response = requests.get('http://www.baidu.com')
    response.status_code //返回请求状态
    response.encoding = 'utf-8'
    # print(response.text)
    #content :获取响应的二进制数据,图片、图标
    response.text = response.content.decode()  
    # 默认utf-8 ,如果是gbk ,decode(encoding = ‘gbk’)
    
    text(字符串),content(二进制) ,json(对象)
    
    
#session使用
headers={
    ...
}
proxies={
    ...
}
s = request.Session()
s.header.update(headers)
r = s.get(url,params,headers,timeout,proxies)
r = s.post(url,data)

常用UA:

User_Agent = [
    "Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_2 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5",
    "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5",
    "MQQBrowser/25 (Linux; U; 2.3.3; zh-cn; HTC Desire S Build/GRI40;480*800)",
    "Mozilla/5.0 (Linux; U; Android 2.3.3; zh-cn; HTC_DesireS_S510e Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
    "Mozilla/5.0 (SymbianOS/9.3; U; Series60/3.2 NokiaE75-1 /110.48.125 Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413",
    "Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Mobile/8J2",
    "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
    "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A5313e Safari/7534.48.3",
    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; OMNIA7)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; XBLWP7; ZuneWP7)",
    "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30",
    "Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0",
    "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
    "Mozilla/4.0 (compatible; MSIE 60; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.9.168 Version/11.50",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)",
    "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)",
    "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16",
    "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14",
    "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.14",
    "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.02",
    "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
    "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00",
    "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00",
    "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00",
    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1",
    "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0",
    "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0",
    "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0",
    "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0",
    "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101  Firefox/28.0",
    "Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3",
    "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0",
    "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0"
]

UA:

UA= User-Agent(请求载体的身份标识)

UA伪装:门户网站的服务器会检测对应请求的载体身份标识。如果是某一款浏览器就是一个正常的请求,如果不是就是爬虫,是不正常的请求,服务端可能会拒绝请求,是拿不到数据的。

为了避免被拒绝,爬虫需要进行UA伪装:让爬虫对应的身份标识伪装成某一个浏览器

将 User-Agent封装到一个字典中:

headers={

​ ’ User-Agent‘ = ‘ 网站上的 User-Agent对应的字符串’

}

response.json()  //拿json数据
filename= input+'.json'
fp= open('./douban.json','w', encording='utf-8')
dump.(filename, fp= fp, ensure_ascii=False)

代理:

破解封IP这种反扒机制,突破自身IP访问的限制,隐藏自身IP不会被封。 什么是代理服务器:作为一个中转服务器,我们的IP发给代理,代理再发IP给服务器,我们的IP地址不会别封。

代理相关网站:快代理、西祠代理、www.goubanjia.com

代理ip类型:
http:应用到http协议对应的url中
https:应用到https协议对应的url中


代理ip的透明度:
-透明:服务器知道该次请求使用了代理,也知道真实的ip
-匿名:知道使用了代理,不知道真实的ip
-高匿:不知道使用了代理也不知道真实的ip

例如:
proxy = '121.230.211.41'
如果要用户名: proxy = usernamme:password@'121.230.211.41'
proxies={'HTTP/HTTPS':'ip地址',
			  'HTTP':'http'+proxy}
page_text= requests.get(url=url,headers=headers,proxies=proxies).text

数据解析:

数据解析:Beautifulsoup,正则表达式,xpath

概念:解析的局部标签对应的属性中存储的数据值进行提取

  1. 进行标签的定位
  2. 标签或者标签对应的属性中储存的数据值进行提取

解析图片:

url= '...'
img_data= requests.get(url=url).content

with open('./giutu.jpg','wb') as fp:
	fp.write(img_data)

bs4数据解析:

  1. 实例化一个BeautifulSoup对象,并将页面源码数据加载到该对象中
  2. 通过BeautifulSoup对象的属性或者方法进行标签定位和数据提取

Beautiful Soup

BeautifulSoup 对象:代表要解析整个文档树,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法

它的作用就是从 HTML 和 BeautifulSoup 对象的创建

fp=open('.html','r',encording='utf-8')
soup= BeautifulSoup(fp,'lxml')

soup.tagName():返回的是html中第一次出现的tagName,tagName就是标签名
    

find()方法:

find的作用就是找标签

步骤:

  1. 导入模块
  2. 准备文档字符串
  3. 创建BeautifulSoup标签
  4. 查找文档中的标签

find(self ,name=, attrs={}, recursive=True, text= ,**kwargs )

find_all(name , attrs , recursive , text,**kargs)

参数:

  1. name: 标签名
  2. attrs:属性字典
  3. recursive: 是否递归查找
  4. text: 根据文本内容查找

返回:查找到的第一个元素对象

soup = BeautifulSoup('<html>data</html>', 'lxml')  
# BeautifulSoup 会自动修正代码print(soup)

#要准备html的文档
html='''
		...		'''
soup= BeautifulSoup(html , 'lxml')
print(soup)

#查找title和a标签
title= soup.find('title')
a= soup,find('a')  	
#属性定位
soup.find('div',class_=' song')  两个属性定位,标签+属性(可以是id或者attr)

#查找所有的a
soup= find_all('a')
print(a+title)

soup.select('.属性名')  . 代表class_   id/
soup.select('.tang >ul>li>a')[0] 	标签层级选择器,大于号表示一个层级,[]表示第几个
soup.select('.tang >ul a')[0]		空格一次性表示多个层级

soup.select('.tang >ul a')[0].text  直接获取文本

soup.a['标签名']  直接提取文本
#根据属性进行查找
	#查找id 为link1的标签
    a= soup.find(id='link1')
    a= soup.find(attrs={'id':'link1'})		#两种方法
    print(a)
#根据文本内容进行查找
text= soup.find(text=' 要查找的内容')

#Tag对象
print('标签名',a.name)
print('标签所有的属性',a.attrs)
print('标签文本内容',a.text)
Tag可以获取标签的属性和文本

Xpath:

原理:实例化一个etree的对象,且需要将被解析的页面的源码数据加载到该对象中,调用xpath对象的xpath方法获取标签的定位和内容捕获

如何实例化etree对象:

  1. 将本地的html文档的源码数据加载到etree中:etree.parse(filepath)
  2. 可以将互联网上获取的源码数据加载到该对象中:etree.HTML(‘page_text’)
  3. xpath(‘xpath的表达式’)
tree = etree.parse('test.html')

r= tree.xpath('./html/body/div')
r=tree.xpath('./html/html/div')

#属性定位 tag[@attrname='attrValue']:
r=tree.xpath('//div[@class='song']')

#索引定位 /p[]索引从1开始:(//[@class=标签名]小标签名[第几个])
r=tree.xpath('//div[@class='song']/p[3]')	

#精确文本定位,[0]小技巧去掉列表符
#   /text():拿直系标签的文本
#	//text():拿所有的文本内容
r= tree.xpath('//div[@class='tang']//li[5]/a/text()')[0]	
r= tree.xpath('//div//li[7]//text')[0]

#取属性值  /@attrname
r=tree.xpath('//div[@class='song']/img/@scr')

#属性是多值的匹配 contains(@属性名,标签名)
<li class "li li-first">first item>
result = html.xpath('//li[contain(@class,"li")]/text()')


PyQuery

  • 使用的是CSS选择器
from pyquery import PyQuery as pq
doc = pq(url="...")


doc('#container .list li')

#调用items方法后,会返回一个生成器
for item in doc('#container .list li').items():
    print(item.text())
  • find():item.find('css选择器')查找该节点的所有子孙节点
  • children(): 只拿子节点
  • parent() / parents():获取节点的(单个/多个)父节点
  • siblings(): 获取兄弟节点

pyquery选择返回的结果都是pyquery类型的,可以进行类型转换

获取属性值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVJYpUAU-1673159360180)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1635860179367.png)]

或者:a.attr.href

获取文本:

a.text() 返回的是该节点的所有的文本,字符串类型

a.html() 返回当前节点文本

动态更改节点属性

1.addClass 和removeClass

  • 动态更改class属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6qQT0dQ-1673159360181)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1635860890528.png)]

2.attr方法对属性进行操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poL86Tnx-1673159360181)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1635861027622.png)]

3.text()和html()

  • 可以更改节点内部的内容

4.append、empty、prepend方法

Unicode乱码尝试方法:

UnicodeError是在处理字符串时出现的错误,分为两个子异常类,UnicodeEncode和UnicodeDecodeError

处理这个问题需要使用 encode() 和 decode()方法

encode和decode均可接收encoding与errors两个参数,用来指定编码、解码的错误和出现错误的时候的反应
#注意!解码和解码前后的字符串的长度可能不同,因为二者的长度意义不同。对于Unicode长度为其中字符的长度,str的长度取决于bytes的数目
encode :将unicode字符串翻译成bytes,也就是str对象
decode :相反,将bytes翻译为原有的Unicode字符串

unicode字符串就是str->  'hello world'
bytes字符串->	b'hello world'

Encoding Error: UnicodeDecodeError

报错原因:由于指定的编码格式不足以编码指定unicode字符串中的某些字符,说明使用的时错误的编码格式

>>>unicode_seq.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 13: ordinal not in range(128)
#ascii无法编码u'\xf8'字符

Decoding Error:UnicodeDecodeError

报错原因:你指定的编码格式无法解码该字符串

>>> wrong_seq.decode('utf-8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/encodings/utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x89 in position 1: invalid start byte
#utf-8无法解码byte流

解决报错的思路:

思路一:Unicode编码解码的顺序
程序的内部是Unicode字符串,程序外部是bytes字符串,所以在程序的入口解码读取到的数据,在程序的出口编码数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ucquxuNt-1673159360182)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CPictures%5CSaved%20Pictures%5C70)]

思路二:了解被处理的数据是那种类型
两个方法:1.type()方法查看这个数据的类型  2.repr()方法查看它的Unicode字符串到底是什么
#乱码尝试方法1
 中文转Unicode编码:
 	text = '中国'
 	result = text.encode('unicode_escape')
 Unicode转中文:
 	result = u_str.decode('unicode_escape')

# 乱码尝试方法2
 1.图片:img_name = img_name.encode('iso-8859-1').decode('gbk')
 2.响应数据:response.encoding = 'utf-8'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQN8S858-1673159360183)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632404127737.png)]

Excel:

读取excel,保存为了excel:

 df = pd.DataFrame()

    for i in data:
        df = df.append([[
        i['rank'],
        i['score'],
        i['types'],
        i['regions'],
        i["release_date"],
        i['title'],
        i['actors']]]
        )
    df.columns = ['排名','豆瓣评分','电影类型','国家','上映日期','电影名称','演员']
    df = df.reset_index(drop=True)
    # print(df)

#写入pandas的同时直接保存到excel
#列表符号那些,可以用excel中自带功能进行删除替换处理,快捷键Ctrl+H
    df.to_excel('D:/360Downloads/豆瓣喜剧TOP100.xlsx',sheet_name='Top100',index=False)

img

wb = xlwt.Workbook(encording='utf-8',style_compression=0)
sh=wb.add_sheet('moviesTop250',cell_overwrite_ok=True)
col = ('表格的表头名')
for i in range(0,8):
	sh.write(0,i,col[i])
#通过for循环将列表中的数据逐一填写到Excel单元格内
for i in range(0,len(dataList)):
    print('%d'%(i+1))
    data= dataList[i]
    for j in range(0,8):
		sh.write(i+1,j,data[j])
sFile = 'D:/360DownLoad:\doubanmovies.xls'
wb.save(sFile)

文件处理:

from openpyxl import Workbook

wb= Workbook()	#创建表
ws= wb.active	#获取当前sheet表
sheet.title = '表名'	#改sheet名

wb2 = load_workbook('文件名字'.xlsx)


#写数据
#method1:直接分配到单元格
sheet["C5"] = 'text'
#method2:附加行
sheet.append([1,2,3])
#method3:自动类型转换
sheet['A3']=''

正则表达式

常用匹配操作:

1.^符和&符的使用,不要单独使用,结合文本 例如:^He(\d+)Demo$

2.省略的部分书写(非贪婪模式):(.*?)

3.遇到不规则的字符:加一个反斜杠 例如:\(百度)www… 避免开始匹配不到

修饰符

re.I:使匹配对大小写不敏感
re.L:做本地化识别
re.M:多行匹配,影响^和&
re.S:使 . 符匹配包括换行符在内的所有字符
re.U:根据Unicode字符集解析字符,这个标志影响\w,\W,\b和\B
re.X:给予你更灵活的格式以便于你将正则表达式写得更容易理解

模式

match():从字符串起始的位置匹配正则表达式,如果匹配就返回,没有匹配就返回None
	用.group(索引)来获取匹配到的内容
search():match方法在开头不匹配可能会终止,search可以避免这个问题,sreach可以搜索整个			字符串
sub():不多说,替换
compile():规定一个同一个的正则表达式,可以重复的使用

爬虫中匹配

匹配标签中的内容:
	re.compile(<li .*?active.*?singer="(.*?)"> (.*?)</a>,html.re.S)
处理字符串:
	s = re.sub('<a.*?>|</a>','',re.S)  去除标签
	s.strip()
	

字符匹配:

  1. \d:代表任意数字 \D:代表不是数字的(大写的字母一般是和小写的唱反调)

  2. \w:代表字母,数字,下划线,也就是a-z,A-Z,0-9,—。 \W:不是字母数字下划线的

  3. \n:代表一个换行

  4. \r:代表一个回车 \f:代表一个换页 \t:代表一个tab

  5. \s:代表所有的空白字符(包括换行回车换页tab) \S:不是空白的字符

  6. \A:代表字符串的开始 \Z:代表字符串的结束

  7. ^:匹配字符串的开始位置 &:匹配字符串的结束位置

  8. \b:匹配一个单词的边界 \B:匹配非单词边界

  9. |:匹配两边的表达式 +() 要想匹配的不多不少刚刚好,就必须用^和&来规定开始结束

  10. 星号:匹配前面的字符串零次或多次 例如:zo* 能匹配z、zo、zoo

  11. +:匹配前面的字符串一次或多次 例如:zo+能匹配zo、zoo但不能匹配z

  12. ?:匹配前面的字符串零次或一次 例如:do(es)?可以匹配do或者does

  13. [n]:n是一个非负整数,匹配确定的n次 例如o[2]必须匹配到2个o,bob就不能匹配到

  14. [n,]:就是至少匹配n次,可以多不能少 [n,m]:最少匹配n次,最多匹配m次

  15. . : 匹配 \r \n 之外的任何的单个字符

  16. […]:表示一个范围内的字符 例如[a-z]就是a-z间任意一个字符 [^]唱反调

  17. {n}:匹配在{n}前面的东西 n次

  18. ()括号可以作为一个分组 ,括号括起来的内容可以作为中间变量记录下来,要想记录下来并使用把后面的那一部分也括起来然后写 (\1)数字就是第几个括号

    括号一多容易混,所以用信息加以定义 (?p=< 定义名字>) 例子:(?p=< key1>)

    首先导入模块re

    校验数字:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmrJ6avx-1673159360184)(C:\Users\银晗\AppData\Roaming\Typora\typora-user-images\1611386525311.png)]

    校验字符的表达式:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z1rOmeqR-1673159360184)(C:\Users\银晗\AppData\Roaming\Typora\typora-user-images\1611386578655.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYEO8mCp-1673159360185)(C:\Users\银晗\AppData\Roaming\Typora\typora-user-images\1611386615056.png)]

    re模块的方法:

    1. 循环所有匹配:re.findall()

      re.findall(pattern, string , flags=0)

      pattern=正则表达式 string=匹配的字符串

      res=re.findall(r‘ r[ua]n’, ‘run ran ren’ ) print(res) 输出run 和ran

      注意如果要找单独的单词,请在要匹配的单词首尾各空一个空格

    2. re.match(正则表达式,要匹配的字符串) result=re.match() 从左到右匹配字符串,只返回匹配到的

      如果match匹配到数据用result.group() 返回数据

    3. 替换匹配内容:re.sub()

      re.sub( 待替换的字符串,要替换上的字符串,原来整个字符串)

      过滤掉网页中那些不需要的符号:re.sub( r‘<.+?>’, ,s)或者(r’</? \w+ >‘ )

      实例操作:re.sub(r “ http://.+?/)”,lambda x: x.group(1),s) 用匿名函数输出来替换

    4. re.search(r’‘ 搜索内容’, ‘匹配内容 ’) :与match不同终端无法继续

    5. 分裂内容:re.split()

      res=re.split(r ‘ ,;.\ \(分裂标志)’ , ‘a,b;c.d\e’) 就是把,;. \之间的值全部分裂开输出a,b,c,d,e

      返回的是列表

    6. 包装正则表达式:re.compile()

      compile_re=re.complie(r’ r[ua]n ’) 就相当于等效了一下

      res=compile_re.findall(‘ run ran ren’)

    7. 贪婪模式:只有不设置限制,系统默认一直往后找

      用括号括起要关闭贪婪模式的表达式 加上一个?

JSon

json模块是Python自带的模块,用于json与python数据之间的相互转换

json.load()方法:

  1. 把json字符串,转换为python数据

    json_str=‘’‘ … str …’‘’

    rs = json.loads(json_str)

  2. 读取文件:

    with open(‘data.json’) as fp:

    python_list = json.load(fp)

python 数据转换为json数据

json.dumps(obj) 转换为json字符串

json_str = json.dumps(rs, ensure_ascii=Flase) #有中文就要指定ensure_ascii=Flase

python 数据转换为json数据写入文件

with open('data.json', 'w') as fp:
    json.dump(rs, fp, ensure_ascii=Flase)
    

文件存储:

1.json文件存储

with open(file = 'result.txt',mode='a',encording='utf-8') as f:
	f.write(json.dumps(content,ensure_ascii=False)+'\n',indent=2)
    #indent表示缩进2个空格

2.csv文件存储

import csv
with open('data.csv','w') as csvfile:
	writer.writerow(['','',''])  //写入每行的数据
	

不过我还是建议用pandas中转保存!

3.连接MySql数据库:

import pymysql
db = pymqsql.connect(host='localhost',user='root',password='20020520zyh,port=3306')
cursor = db.cursor() //获得mysql的操作游标
// 用execute执行sql语句
cursor.execute('SELECT VERSION()')//获取版本
data = cursor.fetchone()
print('DATABASE version:',data)
cursor.execute("Create database spiders default character set utf-8")
db.close()

高性能异步爬虫:

目的:在爬虫中使用异步实现高性能的数据的爬取

#同步爬虫,单线程,get()很慢,是一个阻塞的方法
response=requests.get(url=url,headers=headers)
if response.status_code == 200:				#判断是否有响应
	return response.content
	

异步爬虫方式:

  1. 多线程,多进程:可以为相关阻塞的操作单独开启线程或者进程,不会等。但是不能无限制的开启多线程

  2. 线程池、进程池:可以降低系统对进程或者线程创建和销毁的一个频率,提升效率。但是池中线程数量有上限

    from multiprocessing.dummy import Pool
    
    name_list=['aa','bb','cc','dd']
    def get_page(str):
    {
        print(str)
    }
    #实例化线程对象
    pool=Pool(4)	#四个对象
    pool.map(get_page,name_list)	#map(阻塞的方法,对象列表)
    
  3. 单线程+异步协程:

    1. event_loop:事件循环,相当于无限循环,把函数注册到事件循环上,当满足某些条件时,函数就会被循环执行
    2. coroutine:协程对象,把协程的对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即执行,而是返回一个协程对象。
    3. task:任务,它是对协程对象的封装,包含了任务的各个状态。
    4. future:代表将来执行或者还没执行的任务,和task没有本质区别
    5. async:定义一个协程
    6. await:用来挂起阻塞的方法
import asyncio


async def request(url):
    # 用async修饰的函数,调用后返回是一个协程对象
    print('请求的对象是', url)


c = request('www.baidu.com')

# #创建事件循环对象
# loop = asyncio.get_event_loop()
# #将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)

# task的使用
loop = asyncio.get_event_loop()
# 基于loop创建一个task对象
task= loop.create_task(c)
print(task)
loop.run_until_complete(task)
print(task)
#future的使用
loop = asyncio.get_event_loop()
task= asyncio.ensure_future(c)
loop.run_until_complete(task)

# 多任务异步协程实现
urls = {
    'www.baidu.com',
    'www.sougou.com',
    'www.goubanjia.com',
}
stasks = []  # 存放任务列表
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    stasks.append(task)
    # 需要将任务列表封装到wait中
    loop.run_until_complete(asyncio.wait(stasks))
    # 但是在异步协程中出现同步模块的代码,那么无法实现异步

async def requests(url):
    print('正在下载',url)
    # 改一下time.sleep(2)
    await asyncio.sleep(2)

数据分析:

jupyter notebook:

基本操作快捷键:

  1. 添加cell:a或者b
  2. 删除cell:x
  3. 修改成markdown模式:m
  4. 修改成code模式:y
  5. 执行cell:shift+enter
  6. 自动补全:tab
  7. 打开帮助文档:shift+tab

Selenium

之前动态加载数据,需要用抓包工具,抓XHR中的参数,用param参数列表进行爬取,selenium更方便

selenium是基于自动化的一个模块

基本使用:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_condition as EC
from selenium.webdriver.support.wait import WebDriverWait

browser = webdriver.Chrome()
try:
    browser.get('https://www.baidu.com') #访问页面
    input = broswer.find_element_by_id('kw') #找到输入框
    input.send_keys('Python') #输入
    input.send_keys(Keys.ENTER) #按回车
    wait = WebdriverWait(broswer,10)  #等待10s
    wait.until(EC.presence_of_element_located((By.ID),'content_left'))
    
    print(broswer.current_url,
         broswer.get_cookies(),
         broswe.page_source())
expect Expection as e:
    print(e)
Fianlly:
    broswer.close()

自动化代码:

  1. 发起请求:get()

  2. broswer= webdriver.Edge()

  3. 标签定位:find_element_by_选择器()

  4. 标签交互:send_keys()

  5. 执行js程序:excute_script(‘jsCode’)

  6. 前进、后退:back()、forward()

  7. 关闭:quit()

  8. 点击:click()

  9. 清空:clear()

  10. 获取cookies:

  11. #选项卡管理
    bro.get(...)
    bro.execute_script('window.open()')
    bro.switch_to_window(bro.window_handles[1])
    bro.get(...)
    bro.switch_to_window(bro.window_handles[0])
    
    window.open()是JS语句,开启一个选项卡
    bro.window_handles :获取当前开启的所有选项卡
    switch_to_window(选项卡代号):切换选项卡
    
  12. 异常处理

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icAPXOl9-1673159360185)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632557905053.png)]

标签选择器:

单个标签:find_element_by_选择器()

选择器:id name xpath link_text tag_name class_name css_selector

多个标签:find_elements_by_选择器()

结点交互:

输入:send_keys(‘内容’)

前进、后退:back()、forward()

关闭:quit()

点击:click()

清空:clear()

获取结点信息:

通过page_source可以获取网页的源代码,接着可以用解析库解析了,但是selenium提供了选择节点的方法

例:

bro = webdriver.Chrome()
url = 'https://zhihu.com'
bro.get(url)
input = bro.find_element_by_class_name('zu-top-add-question')
print(input.text)

获取结点的信息主要通过属性:

input.id
input.location
input.tag_name
input.size

切换frame:

网页中有一种节点叫做iframe , 也就是子Frame,相当于页面的子页面,它的结构和外部页面一致,但是selenium打开页面之后默认在父级Frame里面操作,不能获取子Frame里面的节点,这时候需要swith_to.frame()来切换Frame

browser.get(url) 
browser.switch_to.frame('iframresult ’) 
browser.switch_to.parent_frame()

等待:

隐式等待:Selenium没有找到节点,就等着直到超出设定时间,则抛出异常

browser=webdriver.Edge()
browser=implicitly_wait(10) 等待10s

显式等待:指定最长等待时间,如果在规定时间内加载出来就返回,超时则抛出异常

wait = WebDriverWait(browser,10)
input = wait.until(EC.presence_of_element_located(By.ID,'q'))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZJn26uG-1673159360186)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632556633791.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDgiFWOM-1673159360187)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632556869868.png)]

cookies:

.get_cookies()   

.add_cookies({‘name’:  ,‘domain’:  ,‘values’:  }) 

 delete_all_cookies()

选项卡管理:

bro.get(...)
bro.execute_script('window.open()')
bro.switch_to_window(bro.window_handles[1])
bro.get(...)
bro.switch_to_window(bro.window_handles[0])

window.open()是JS语句,开启一个选项卡
bro.window_handles :获取当前开启的所有选项卡(列表)
switch_to_window(选项卡代号):切换选项卡

无头浏览器设置:

rom selenium import webdriver
from selenium.webdriver.edge.options import Options
from time import sleep

#创建无头浏览器对象
edge_options = Options()
path = "MicrosoftWebDriver.exe"
EDGE = {
    "browserName": "MicrosoftEdge",
    "version": "",
    "platform": "WINDOWS",
    "ms:edgeOptions": {
        'extensions': [],
        'args': [
            '--headless',
            '--disable-gpu'
        ]}
}
bro = webdriver.Edge(executable_path=path,capabilities=EDGE)

iframe:

#切换浏览器的标签作用域:
bro.switch_to.frame(‘iframeResult’)//切换到子页面
bro.switch_to.parent_frame()

div=bro.find_element_by_id('id')

#动作链
action= ActionChains(bro)
#点击长按指定标签
action.click_and_hold(div)

for i in range(5):
    action.move_by_offset(17).perform()	#perform执行动作链,移动操纵
    sleep(0.3)
action.release()	#释放动作链
    
   
隐式等待:
browser=webdriver.Edge()
browser=implicitly_wait(10) 等待10s

显式等待:指定最长等待时间,如果在规定时间内加载出来就返回,超时则抛出异常
wait = WebDriverWait(browser,10)
input = wait.until(EC.presence_of_element_located(By.ID,'q'))

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lJ8SCOOf-1673159360187)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632556633791.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wbvz4nGt-1673159360188)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1632556869868.png)]

scrapy:

创建工程:scrapy startproject 文件名

​ cd 文件名

在spiders子目录中创建一个爬虫文件:scrapy genspider first www.xxx.com

执行工程:scrapy crawl 文件名

进入setting:

进行UA伪装,USER_AGENT='...'
把ROBOTSTXT_OBEY = True 改成Flase 
添加LOG_LEVEL='ERROR'

import scrapy


class FirstSpider(scrapy.Spider):
    name = 'first'  # 爬虫文件的名称
    # allowed_domains = ['www.xxx.com']   允许的域名,start_urls中哪些url可以进行请求,不过一般不用这种机制
    start_urls = ['https://www.baidu.com/', 'https://www.sogou.com/']  # 起始的url列表,可以有多个url

    # 用于数据解析,response参数表示就是start_urls列表的url请求成功后对应响应的对象
    def parse(self, response):
        pass

持久化存储:

#基于终端指令:只可以将parse方法的返回值存储到本地的文本文件中
scrapy crawl 爬虫名 -o ./文件名.csv
#基于管道:在items.py中添加文本对象
文本对象名= scrapy.Field()
#再在pipelines.py中process_item接收持久化储存对象
class QiubaiproPipeline:
    fp= None
    def open_spider(self,spider):
        self.fp= open('./qiubai.txt','w',encoding='utf-8')
		
    def process_item(self, item, spider):
        author= item['author']				#定义属性
        page_text= item['page_text']		#定义属性	
        self.fp.write(author+':'+page_text+'\n')

        return item	#传递下一个管道类,养成习惯
    
    def close_spider(self,spider):
        self.fp.close()
#一个管道类存储一份数据

        
        
        
        
        
        
#再去setting第65-68行开启管道

#爬虫主程序

from qiubaiPro.items import QiubaiproItem
#把工程中items.py文件中 类名导入
item= QiubaiproItem()			
item['author'] = author			#item回倒主程序
item['page_text'] = page_text	#item回倒主程序
yield item



#直接写到mysql数据库:

#重写一个管道类

import pymysql

class mysqlPileLine(object):
    conn= None
    cursor= None
    def open_spider(self,spider):
        #链接对象
        self.conn= pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='20020520zyh',db=qubai,charset='utf8')

    def process_item(self,item,spider):
        #创建游标对象
        self.cursor = self.conn.cursor()

        try:
            self.cursor.execute('insert into qubai values ("%s","%s")'%(item["author"],item["page_text"]))
            self.conn.commit()  #提交
        except Exception as e:
            print('e')
            self.conn.rollback()
		return item
    def close_spider(self,spider):
        self.cursor.close()
        self.conn.close()

#去setting里添加管道
TEM_PIPELINES = {
   'qiubaiPro.pipelines.QiubaiproPipeline': 300,
   'qiubaiPro.pipelines.mysqlPileLine': 301,
}


循环爬取网站多页数据:

class HanSpider(scrapy.Spider):
    name = 'han'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.xxx.com/']
    # 通用url模板
    url = '...%d...'
    page_num = 2

    def parse(self, response):
        li_list = response.xpath(' ')
        for li in li_list:
            page_text = li.xpath('... | ... ').extract_first()  # 出现数据为空,直接去找空的情况对应的xpath路径,用或连接
            print(page_text)
            if self.page_num <=11:
                new_url = format(self.url % self.page_num)
                self.page_num+=1
                yield scrapy.Request(url=new_url, callback=self.parse())    #手动发送请求,callback回调函数专门用于数据解析

深度爬取:

#第二层网页的爬取
    def parse_detail(self, response):
        # 接收参数
        item = response.meta['item']

        job_detail = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract_first()
        job_detail = ''.join(job_detail)
        print(job_detail)
        item['job_detail'] = job_detail
        yield item  # 提交管道

def parse(self, response):
        li_list = response.xpath(' ')
        for li in li_list:
            page_text = li.xpath('... | ... ').extract_first() 
            # 出现数据为空,直接去找空的情况对应的xpath路径,用或连接
            item['job'] = job
            item['job_area'] = job_area
            
            # 请求传参
            yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item}, dont_filter=True)

图片爬取:

字符串数据:只需要基于xpath进行解析,且提交管道进行持久化存储
图片:xpath解析出图片的src的属性值,单独的对图片地址发起请求获取图片二进制类型的数据

ImagesPipeline:只需要将img的src的属性值解析进行解析,提交到管道,管道就会对src进行请求发送获取图片的二进制数据
    
    
1.xpath解析图片地址
2.将存储图片地址的item存储到制定管道类
 src = li.xpath('./div/a/img/@src2').extract_first()
            src = 'https:'+src
            # print(src)
            item = ImgsproItem()
            item['src'] = src
            yield item
3.重写管道类

from scrapy.pipelines.images import ImagesPipeline
import scrapy
class imagesPileLine(ImagesPipeline):

    #对图片进行请求操作
    def get_media_requests(self, item, info):


        yield scrapy.Request(item['src'])

    #指定图片的存储路径
    def file_path(self, request, response=None, info=None):
        imgName = request.url.split('/')[-1]
        return imgName

    def item_completed(self, results, item, info):
        return item  #返回给下一个即将被执行的管道类,养成习惯
    
4.进入setting更改配置:

开启管道类,并修改类名,改为自定义的管道类名

末尾增加一行存储路径
IMAGES_STORE = './imgs_han'

中间件:

middlewares.py文件中
# import random

	网上找一些ip复制进来
    # user_agent_list=[
    #     '...'
    # ]
    # PROXY_http = [
    #     '...'
    # ]
    #
    # PROXY_https = [
    #     '...'
    # ]
    def process_request(self, request, spider):
        # request.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None

    def process_response(self, request, response, spider):

        return response

    def process_exception(self, request, exception, spider):

        # if request.url.split(':')[0] == 'http':
        #     request.meta['proxy'] = 'http://'+random.choice(self.PROXY_http)
        #
        # else:
        # #代理
        # request.meta['proxy'] = 'https://'+random.choice(self.PROXY_http)
        #
        # return request
       

    
    setting文件中:第55行开启中间件
#DOWNLOADER_MIDDLEWARES = {
#    'imgsPro.middlewares.ImgsproDownloaderMiddleware': 543,
#}

CrawlSpider:

全站数据爬取的方式:基于spider:手动请求;基于CrawlSpider

使用:创建爬虫文件,scrapy genspider -t crawl 文件名 www.xxx.com

 #链接提取器
    link = LinkExtractor(allow=r'type=4&page=\d+')#匹配到了当前页面的链接
#去起始url中,根据指定规则,提取链接  allow="正则表达式"
    rules = (
        #规则解析器对象
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )
    

全站数据爬取过程:

  1. 可以使用链接提取器提取所有的页码链接
  2. 让链接提取器提取所有的新闻详情页链接

道类,养成习惯

4.进入setting更改配置:

开启管道类,并修改类名,改为自定义的管道类名

末尾增加一行存储路径
IMAGES_STORE = ‘./imgs_han’




#### 中间件:

```python
middlewares.py文件中
# import random

	网上找一些ip复制进来
    # user_agent_list=[
    #     '...'
    # ]
    # PROXY_http = [
    #     '...'
    # ]
    #
    # PROXY_https = [
    #     '...'
    # ]
    def process_request(self, request, spider):
        # request.headers['User-Agent'] = random.choice(self.user_agent_list)
        return None

    def process_response(self, request, response, spider):

        return response

    def process_exception(self, request, exception, spider):

        # if request.url.split(':')[0] == 'http':
        #     request.meta['proxy'] = 'http://'+random.choice(self.PROXY_http)
        #
        # else:
        # #代理
        # request.meta['proxy'] = 'https://'+random.choice(self.PROXY_http)
        #
        # return request
       

    
    setting文件中:第55行开启中间件
#DOWNLOADER_MIDDLEWARES = {
#    'imgsPro.middlewares.ImgsproDownloaderMiddleware': 543,
#}

CrawlSpider:

全站数据爬取的方式:基于spider:手动请求;基于CrawlSpider

使用:创建爬虫文件,scrapy genspider -t crawl 文件名 www.xxx.com

 #链接提取器
    link = LinkExtractor(allow=r'type=4&page=\d+')#匹配到了当前页面的链接
#去起始url中,根据指定规则,提取链接  allow="正则表达式"
    rules = (
        #规则解析器对象
        Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )
    

全站数据爬取过程:

  1. 可以使用链接提取器提取所有的页码链接
  2. 让链接提取器提取所有的新闻详情页链接

异步爬虫:

目的:在爬虫中使用异步实现高性能的数据的爬取

#同步爬虫,单线程,get()很慢,是一个阻塞的方法
response=requests.get(url=url,headers=headers)
if response.status_code == 200:				#判断是否有响应
	return response.content
	

异步爬虫方式:

  1. 多线程,多进程:可以为相关阻塞的操作单独开启线程或者进程,不会等。但是不能无限制的开启多线程

  2. 线程池、进程池:可以降低系统对进程或者线程创建和销毁的一个频率,提升效率。但是池中线程数量有上限

    from multiprocessing.dummy import Pool
    
    name_list=['aa','bb','cc','dd']
    def get_page(str):
    {
        print(str)
    }
    #实例化线程对象
    pool=Pool(4)	#四个对象
    pool.map(get_page,name_list)	#map(阻塞的方法,对象列表)
    
  3. 单线程+异步协程:

    1. event_loop:事件循环,相当于无限循环,把函数注册到事件循环上,当满足某些条件时,函数就会被循环执行
    2. coroutine:协程对象,把协程的对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即执行,而是返回一个协程对象。
    3. task:任务,它是对协程对象的封装,包含了任务的各个状态。
    4. future:代表将来执行或者还没执行的任务,和task没有本质区别
    5. async:定义一个协程
    6. await:用来挂起阻塞的方法
import asyncio


async def request(url):
    # 用async修饰的函数,调用后返回是一个协程对象
    print('请求的对象是', url)


c = request('www.baidu.com')

# #创建事件循环对象
# loop = asyncio.get_event_loop()
# #将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)

# task的使用
loop = asyncio.get_event_loop()
# 基于loop创建一个task对象
task= loop.create_task(c)
print(task)
loop.run_until_complete(task)
print(task)
#future的使用
loop = asyncio.get_event_loop()
task= asyncio.ensure_future(c)
loop.run_until_complete(task)

# 多任务异步协程实现
urls = {
    'www.baidu.com',
    'www.sougou.com',
    'www.goubanjia.com',
}
stasks = []  # 存放任务列表
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    stasks.append(task)
    # 需要将任务列表封装到wait中
    loop.run_until_complete(asyncio.wait(stasks))
    # 但是在异步协程中出现同步模块的代码,那么无法实现异步

async def requests(url):
    print('正在下载',url)
    # 改一下time.sleep(2)
    await asyncio.sleep(2)

模拟登录

验证码的识别:

  1. 对网页发起request请求,拿到验证码的图片链接
  2. 拿到链接之后,再对这个图片的链接发起request请求,拿到content二进制数据
  3. 然后打开一个文件,将二进制数据write到这个文件里面
  4. 第三方平台进行解码(传入图片文件路径,验证码类型)
  5. 登录之后,找到login的数据包,拿到param参数列表,更改icode ,改为验证码识别的内容
  6. 再次发送post请求,拿到page_text
  7. 利用状态码,看是否是200,是200就成功了

登录成功之后,重新发起详情页的请求后,页面会重新回到登录页面,因为cookies的原因

自动处理cookies:

1.cookies值的来源来自哪里?来源上一次对网页发起请求后生成的

2.使用session对象:

​ 1.可以进行请求的发送

​ 2.如果请求过程中产生了cookie,则该cookie会被自动储存,即携带在session对象中

创建session对象

#自动携带cookie,不用手动写cookies了
session = requests.Session()
response = session.get(url,headers,data)

失败常见原因

1.url错误

2.登陆后的参数列表错误,参数列表除了icode其余都要写成键值对的形式,因为icode每次都会变化

如何保存图片和HTML文件:

#图片数据
with open('./code.jpg','wb') as fp:
	fp.write(img_data)
#HTML文件    
with open('./return.html','w',encoding = 'utf-8') as fp:
	fp.write(login_page_text)    

代理使用:

#构建代理池
proxie = { 
        'http' : 'http://xx.xxx.xxx.xxx:xxxx',
        'http' : 'http://xxx.xx.xx.xxx:xxx',
        ....
    }  

response = request.get(url,headers,proxies={'http/https':'代理号'})

异步

协程

协程不是计算机提供,程序员是认为创造。

也叫微线程,是一种用户态内的上下文切换技术,简而言之,其实就是通过,一个线程实现代码块相互切换


协程的意义

在一个协程中如果遇到IO等待时间,线程不会等待,利用空闲的时候再去干的别的事。

实现协程的方法:

  • greenlet :早期模块
  • yield :生成器
  • asyncio装饰器
  • async,await关键字

greenlet

def func1():
	print(1)     #第1步:输出1
	gr2.switch() #第2步:切换到func2
	print(2)   #第5步 输出2
	gr2.switch() #第6步 切换func2
	
def func2():
	print(3)  #第3步 输出3
	gr1.switch() #第4步 切换func1
	print(4) #第7步 输出4
    
gr1 = greenlet(func1)
gr2 = greenlet(func2)

gr1.switch(func1) 

yield

def func1():
    yield 1
    yield from func2() #跳到func2
    yield 2
    
def func2():
    yield 3
    yield from func1()
    yield 4
    
f1 = func1()
from item in f1:
    print(item)

asyncio

遇到IO阻塞就自动切换代码块执行,遇到耗时的操作要等待的时候,就立即切换下一个代码块,加快速度


协程函数:asyncio def 函数名

协程对象: 执行协程函数产生的协程对象

asyncio def func():
    print(...)
    
result = func()
asyncio.run(result)
asyncio描述
get_event_loop()开启事件循环
run_until_complete(task列表)执行任务
await()等待事件结束,返回结果
run()运行
ensure_future()创建future对象

理解性代码:

import asyncio

async def func1(): #必须带上关键字async
    print(3)
    await asyncio.sleep()  #模拟耗时操作,自动切换
    print(2)
    
    
async def func2(): 
    print(3)
    await asyncio.sleep()
    print(4)
    
 tasks = [
     asyncio.ensure_future(func1())
     asyncio.ensure_future(func2())
 ]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

事件循环:

理解为一个死循环

1.去检测任务列表,

2.如果任务可执行,就去执行,

3.如果不可执行,就忽略跳过,

4.如果有任务执行完成,就从任务列表中移除

代码操作:

import asyncio

#生成一个事件循环
loop = asyncio.get_event_loop()

#将任务循环放到任务列表
loop.run_until_complete(任务)

await

await + 可等待对象(协程对象、Future对象、Task对象)

#单个协程对象
async def func():
	print('..')
	result = await asyncio.sleep(2)  #等待这个io结束,并返回结果
	print(result)
	
asyncio.run(func())
#多个协程对象
async def others():
    print('start')
    await asyncio.sleep(2)
    print('end')
	return 2
    
    
async def func1():
	print('..')
	result1 = await others() #接收返回的2
	print(result1)
    
async def func2():
	print('..')
    result1 = await others() #遇到IO阻塞,会切换到其他的协程但是不会执行result2
	print(result1)
	result2 = await others() #接收返回的2
	print(result2)	

asyncio.run(func())

Task对象

在事件循环中添加多个任务

Task用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。除此之外,太可以用低级一点的loop.create_task ()或者 ensure_future() 函数,本质上是一样的。

示例代码1:

async def others():
    print('start')
    await asyncio.sleep(2)
    print('end')
	return 2

async def main():
    print('main开始')
    
    task1 = asyncio.create_task(func())
    
    #这里特别解释一下,task1中在执行others函数中 await的sleep的操作时遇到阻塞会自动切换到task2去执行,所以输出第一个start然后接着输出第二个start
    
    task2 = asyncio.create_task(func())
    
    ret1 = await task1
    ret2 = await task2
    
    
asyncio.run(main())

实例代码2:

async def others():
    print('start')
    await asyncio.sleep(2)
    print('end')
	return 2

async def main():
    print('main开始')
    
    task_list =[
        asyncio.create_task(func(),name = '设置task的名字'),
        asyncio.create_task(func())
    ]
    print('main结束')
    
    done,pending = await asyncio.wait(task_list,timeout=2)  
    
    #等待任务列表中的任务全部结束
    #done是已完成的任务的返回值的列表
    #pending是超过timeout时间问完成的任务列表
    
asyncio.run(main())

#另一种写法:
task_list =[
        func(),
        func()]
#这里会内部自动创建任务,所以列表中就不用创建任务
asyncio.run(asyncio.wait(task_list))

asyncio.Future对象

基于协程,Task继承Future,Task对象内部await结果处理基于Future对象

async def main():
    #创建当前时间循环
	loop = asyncio.get_running_loop()
    
    #创建future对象的任务
    fut = loop.create_future()
    
    #等待任务循环(Future对象),没有结果则会一直等下去
    await fut
    
    
asyncio.run(main())

实例代码:

async def func(fut):
    await asyncio.sleep(2)
    fut.func('666')

async def main():
    #创建当前时间循环
	loop = asyncio.get_running_loop()
	fut = loop.create_future()
	
    #创建任务,绑定func任务
	await loop.create_task( func(fut) )
    
    data = awiat fut
    print(data)

concurrent.futures.Future对象:

使用线程池、进程池实现异步操作时用到的对象

from concurrent.futures import Future
from concurrent.futures.thread import ThreadPoolExcutor
from concurrent.futures.process import ProcessExecutor

def func(values):
    time.sleep(1)
    print(values)
    
#创建线程池
pool = ThreadPoolExecutor(max_workers = 5)
#创建线程池
pool = ProcessPoolExecutor(max_workers = 5)

for i in range(10):
    fut = pool.submit(func , i )
    #创建10个线程,但线程池容量为5
    print(fut)
import time
import asyncio
import concurrent.futures

def func1():
    time.sleep(2)
    return "bb"

async def main():
    loop = asyncio.get_running_loop()
    
    #首先,内部调用THreadPoolExecute方法去线程池中执行func函数,并返回一个concurrent.futures.Future对象
    #然后,调用asyncio.wrap_future对象将concurrent.futures.Future对象包装成asyncio.Future对象,因为concurrent.futures.Future对象不支持await方法,所以需要包装
    fut = loop.run_in_executor(None, func1)
    result = await fut
    print('default thread pool' ,result)
    
    #使用线程池
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_execute(pool,func1)
    #使用进程池   
    with concurrent.futures.ProcessPoolExecute() as pool:
        result = await loop.run_in_execute(pool,func1)
        
 asyncio.run(main())

实战案例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hx7C5Yol-1673159330044)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1636385289648.png)]

爬虫内部有三个线程,下载一个图片的同时会执行其他的线程,即下载另一张图片

异步上下文管理器

自动打开和关闭协程函数

定义_ aenyer ()和 _ aexit _ _()方法对async with 语句中的环境进行控制

class AsyncManager:
    def __init__(self):
        self.conn = conn
        
    #第2步:执行函数
    async def do_something(self):
        return 666
    
    #第1步:先执行这里
    async def __aenter__(self):
        #异步连接数据库
        self.conn = await asyncio.sleep(1)
        return self
    
    async def __aexit__(self,exc_type,exc,tb):
        await asyncio.sleep(1)

 	async def func():        
        asynci with AsyncManager() as f:
            result = await f.do_something()
            print(result)
 asyncio.run(func())

异步连接Mysql

pip install aiomysql
import asyncio
import aiomysql

async def execute():
    #连接数据库,先连接'47.93.41.197',遇到阻塞就连接'23.23.67.346'
    conn = await 		    aiomysql.connect(host=host,port=3306,user='root',password=' ',db="mysql")
    
    #网络IO操作,遇到阻塞会自动切换任务
    cur = await conn.cursor()
    
    #网络IO操作,遇到阻塞会自动切换任务
    await cur.execute("SELECT HOST ,USER FROM user")
    await cur.execute('select* from exp ...')
    #网络IO操作,遇到阻塞会自动切换任务
    result = await cur.fetchall()
    print(result)
    
    #网络IO操作,遇到阻塞会自动切换任务
    await cur.close()
    conn.close()
    print("结束")
task_list = [
    execute('47.93.41.197',"root12345"),
    execute('23.23.67.346',"root12345")
]    
  

异步爬虫

pip install aiohttp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytlugbKF-1673159330045)(C:%5CUsers%5C%E9%93%B6%E6%99%97%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5C1636416125977.png)]

")

#网络IO操作,遇到阻塞会自动切换任务
cur = await conn.cursor()

#网络IO操作,遇到阻塞会自动切换任务
await cur.execute("SELECT HOST ,USER FROM user")
await cur.execute('select* from exp ...')
#网络IO操作,遇到阻塞会自动切换任务
result = await cur.fetchall()
print(result)

#网络IO操作,遇到阻塞会自动切换任务
await cur.close()
conn.close()
print("结束")

task_list = [
execute(‘47.93.41.197’,“root12345”),
execute(‘23.23.67.346’,“root12345”)
]


#### 异步爬虫

```python
pip install aiohttp

[外链图片转存中…(img-ytlugbKF-1673159330045)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/149351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

I2C总线驱动

一. I2C背景知识 SOC芯片平台的外设分为&#xff1a; 一级外设&#xff1a;外设控制器集成在SOC芯片内部二级外设&#xff1a;外设控制器由另一块芯片负责&#xff0c;通过一些通讯总线与SOC芯片相连 Inter-Integrated Circuit&#xff1a; 字面意思是用于“集成电路之间”的…

SELECT COUNT(*) 会造成全表扫描?回去等通知吧

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/T…

CPU_并行(多线程)不同高性能旋转图片

并行(多线程)不同高性能旋转图片 代码 ImageStuff.h struct ImgProp {int Hpixels;int Vpixels;unsigned char HeaderInfo[54];unsigned long int Hbytes; };struct Pixel {unsigned char R;unsigned char G;unsigned char B; };unsigned char** CreateBlankBMP(); unsigned…

Java中>>,>>=,<<,<<=运算符

今天在刷LeetCode的时候遇到了一个运算符<<&#xff0c;对这个运算符的意思有点模糊&#xff0c;然后便开始面向百度学习&#xff0c;但是发现&#xff0c;很多篇帖子表达的意思太文章化&#xff0c;不够通俗易懂&#xff0c;于是打算写下这篇帖子&#xff0c;让大家能够…

工作笔记——微信支付开发相关知识整理

在最近的工作中&#xff0c;引入了微信小程序支付&#xff0c;在开发过程中积累和整理了一些技术知识&#xff0c;现将其整理如下 目录 一、概念认识 &#xff08;一&#xff09;术语介绍 &#xff08;二&#xff09;名词解释 &#xff08;四&#xff09;对接微信支付接口规…

Win10安卓子系统安装教程

Win10安卓子系统安装教程必要安装文件下载和安装子系统安装方法方法一&#xff1a;安装 WSA PacMan方法二&#xff1a;安装 APK安装程序必要安装文件下载和安装 win10安卓子系统和win11子系统的安装一样&#xff0c;都必须要安装适用于 Android ™的 Windows 子系统设置的软件…

Java设计模式中行为型模式是什么/模板方式又是什么,编程怎么运用

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 6.行为型模式 6.1 概述 6.1.1 特点 用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎么相互协作共同完成单个对象都无法单独完成任务涉…

分布式基础篇3——前端开发基础知识(谷粒商城)

前端技术对比一、ES61、简介2、什么是 JavaScript3、ES6新特性3.1 let3.2 const3.3 解构表达式3.4 字符串扩展3.5 函数优化3.6 对象优化3.7 map 和 reduce3.8 Promise3.9 模块化二、Vue1、MVVM 思想2、Vue 简介3、Vue 入门案例4、Vue 指令插值表达式v-text、v-htmlv-bindv-mode…

【CANN训练营第三季】基于昇腾PyTorch框架的模型训练调优

文章目录性能分析工具PyTorch Profiling性能分析工具CANN Profiling结业考核1、使用Pytorch实现LeNet网络的minist手写数字识别。2、采用课程中学习到的手工或者自动迁移方式&#xff0c;将上述脚本迁移到昇腾npu上&#xff0c;单机单卡&#xff0c;提供迁移脚本&#xff0c;突…

YOLOv5视觉AI库安装

打开YOLOv5开源仓库: https://github.com/ultralytics/yolov5/blob/master/README.zh-CN.md下载源码:安装 : pip install -r requirements.txt完成安装目标检测推理可通过PyTorch Hub加载YOLOv5检测模型检测图像并返回数据帧使用YOLOv5要先安装opencv-python和pandas库安装open…

C#,图像二值化(18)——全局阈值的模糊集理论算法(Huang Thresholding)与源程序

1 模糊集理论模糊集理论,也称为模糊集合论,或简单地称为模糊集,1965年美国学者扎德在数学上创立了一种描述模糊现象的方法—模糊集合论。这种方法把待考察的对象及反映它的模糊概念作为一定的模糊集合&#xff0c;建立适当的隶属函数&#xff0c;通过模糊集合的有关运算和变换&…

arduino - pinMode参数1的确定 - 以arduino nano every核心板为例

文章目录arduino - pinMode参数1的确定 - 以arduino nano every核心板为例概述笔记pins_arduino.hABX00028-datasheet.pdf简单的辨认管脚号就照ABX00028-datasheet.pdf来ENDarduino - pinMode参数1的确定 - 以arduino nano every核心板为例 概述 arduino nano every的核心板使…

我的交易抽象思路分享

这几天我老是抛出一些问题给老师们&#xff0c;都是故意而为之&#xff0c;因为我靠这种方式自己引导自己很多年&#xff1b; 比如&#xff1a;龙头真的存在么&#xff1f;为何前几天它还不是龙头&#xff0c;怎么今天就是了&#xff1f; 再如&#xff1a;交易模式和交易系统…

微信小程序解密encryptedData报错pad block corrupted

前要&#xff1a; 今天调试一下微信授权登录的时候老是第一次报错解密失败pad block corrupted&#xff0c;第二次授权的时候正常&#xff0c;因为第一次已经获取到手机号码&#xff01; 后端代码&#xff1a; public static JSONObject getUserInfo(String encryptedData, S…

微信自动回复软件

软件介绍 软件名称&#xff1a; 微信超级管家 适用平台&#xff1a; windows 是否免费&#xff1a; 完全免费 病毒检测&#xff1a; 火绒安全检测通过 流氓检测&#xff1a; 无广告、无弹窗、无其他流氓行为 软件大小&#xff1a; 183M 这个软件依赖的是本地微信客户端&#x…

C++ 初始化列表详解

目录 1.什么是初始化列表 2.什么时候需要使用初始化列表&#xff1f; 3.初始化列表的效率 4.初始化列表的初始化顺序 1.什么是初始化列表 class A { public:A(int value):m_dada(value){}private:int m_dada; }; 如上图&#xff0c;红色圈起来的部分&#xff0c;就是构造函…

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《5》

在上一篇文章的介绍中&#xff0c;我们知道语义分割可以对图像中的每个像素进行类别预测。这节主要讲关于全卷积网络(Fully Convolutional Network,FCN)&#xff0c;实现从图像像素到像素类别的变换。 那这里的卷积神经网络跟以往的有什么不一样的地方吗? 这里的网络是通过转置…

Java中享元模式是什么/享元模式有什么用,编程如何实现,哪里用到了享元模式

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 5.8 享元模式 5.8.1 概述 运用共享技术来有效地支持大量细粒度对象的复用&#xff0c;通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象…

图文并茂strapi 4.5.5自定义搭建指南以及数据库字段名接口返回mapping分析

strapi是什么&#xff1f; 基于Nodejs的开源免费CMS框架 为什么选择它&#xff1f; 基于nodejs,100&#xff05;JavaScript&#xff0c;上手迅速可轻松创建功能强大且可自定义的API可以使用任何喜欢的数据库 先决条件 首先你的电脑需要具备以下环境&#xff0c;再执行命令…

技术破局:程序员2023为何跳出舒适圈?

1前言今天的冬日暖阳高照&#xff0c;给我羽绒服下的肉身火一般的燥热&#xff0c;给了我一个错觉&#xff0c;以为到了阳春三月。刚刚送完老妈还有老婆孩子回老家&#xff0c;我坐到电脑机器前&#xff0c;准备捋一下思绪&#xff0c;回首2022的生活和工作。 2 2022 回顾今年用…