概念
爬虫避免进局子的风险
- 时常的优化自己的程序,避免干扰被访问网站的正常运行
- 在使用,传播获取到的数据时,审查抓取到的内容,如果涉及到用户隐私,商业机密等敏感内容需要及时停止或传播
爬虫的分类:
- 通用爬虫:抓取系统重要组成部分,抓取的是一整张页面数据
- 聚焦爬虫:是建立在通用爬虫基础上的,爬取的是需求页面中特定的局部内容
- 增量式爬虫:检测网站中的数据更新情况,只会爬取网站中最新更新出来的数据;
爬虫的矛盾
反爬机制
- 门户网站,可以通过相应的策略或者技术手段,防止爬虫程序进行网页数据的爬取
反反爬策略 - 爬虫程序可以通过制定相关的策略或技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站的数据;
robots.txt协议: - 君子协议.规定了网站中哪些数据可以被爬虫程序抓取哪些不可以;
http协议
- 概念: 就是服务器和客户端的进行数据交互的一种形式,超文本传输协议
常用的请求头:
- User-Agent:请求载体的身份标识符
- Connection:请求完毕后,是断开连接还是保持连接
常见的响应头
- Content-Type:服务器响应回复客户端的数据类型
https协议
- 安全的超文本传输协议
加密方式
- 对称秘钥加密
- 非对称秘钥加密
- 证书加密
requests模块
- urllib 模块
- requests 模块
requests模块:python中原生的一款基于网络请求的模块,功能非常强大,简单便捷,效率极高.
作用:模拟浏览器发送请求
如何使用:
- 指定url
- 发起请求
- 获取响应数据
- 持久化存储
环境安装: pip install requests
实战编码:
import requests
heads = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}
def get_url():
kw = input('请输入检索关键字')
return "https://cn.bing.com/search?q=" + kw
def get_data(url):
res = requests.get(url, headers=heads)
save_file("检索结果数据", res.text)
def save_file(name, data):
with open(name + ".html", "w", encoding="utf-8") as wf:
wf.write(data)
if __name__ == "__main__":
url = get_url()
get_data(url)
数据解析分类
- 正则
- bs4
- xpath (重点)
正则解析
ex=‘<div class="thumb">.*?<img src="(.*?)" alt.*?</div>’
数据解析概况:
解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
- 进行指定标签定位
- 标签或者标签对应的属性中存储的数据进行提取(解析)
bs4进行数据解析
- 数据解析的原理
- 标签定位
- 提取标签、标签属性中存储的数据值
- bs4数据解析原理
1. 通过实例化一个BeautifulSoup对象,并且将页面数据加载到该对象中
2. 通过调用BeautifulSoup对象中的属性或方法进行标签定位和数据提取
环境安装
- pip install bs4
- pip install lxml
如何实例化BeautifulSoup对象:
from bs4 import BeautifulSoup
对象的实例化
- 将本地的html文档中的数据加载到该对象中
fp=open('./test.html','r',encoding='utf-8')
soup=BeautifulSoup(fp,'lxml') - 将互联网上获取的页面源码加载到该对象中
page_text = response.text
soup=BeautifulSoup(page_text,'lxml')
提供的用于数据解析的方法和属性
soup.tagName:返回的是文档中第一次出现的tagName对应的标签,如soup.title
soup.find():find('tagname')等同于soup.div-属性定位soup.find('div',class_/id/attr='song)
soup.find_all('tagName',)返回符合要求的所有标签(列表)
select:
select('某种选择器(id,class,标签...选择器'))返回的是一个列表.
层级选择器:
--soup.select('.tang > ul > li > a')>标识是一个层级
--soup.select('.tang > ul a')>空格标识多个层级
获取标签之间的数据
soup.a.text/string.get_text()text/get_text()可以过去摸一个标签中所有的文本内容
string 值可以获取该标签下的文本内容
获取标签中的属性值
soup.a['href']
Xpath解析
xpath解析:最常用最便捷的高效的一种解析方式,通用性.
xpath 解析原理:
- 实例化一个etree的对象,且需要将其解析源码数据加载到该对象中
- 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获.
环境的安装:
- pip install lxml
使用:from lxml import etree
如何实例化一个etree对象,且需要将解析的页面源码数据加载到该对象中. - 将本地的html文档的数据源码加载到etree对象中:etree.parse(filePath, etree.HTMLParser())
- 可以将互联网上获取的源码数据加载到etree对象中:etree.HTML(str)
使用:result = html.xpath('//li')
print(result)
xpath表达式:
- /表示的是从根节点开始,表示的是一个层级
- //表示的是多个层级,可以表示从任意位置开始
- 属性定位://div[@class="song"] tag[@attrName="attrValue"]
- 索引定位://div[@class="song"]/p[3] 索引是从1开始的
- 取文本:
- /text()获取标签中直系的文本内容如://p[1]/span/a/text()
- //text()获取标签中非直系的文本内容(该标签所有文本内容)
- 取属性:
- /@attrName //img[@id="imgCode"]/@src'
验证码识别
验证码和爬虫之间的爱恨情仇?
反爬虫机制:验证码。识别验证码图片中的数据,用于模拟登录操作
识别验证码的操作:
- 人工肉眼识别
- 第三方 自动识别验证码识别技术开发文档-云码 (jfbym.com)
ddddocr的库使用
云打码使用
- 进入打码平台注册账号登陆后使用接口调用即可
import requests
import base64
def get_ocr_img(filePath):
url='https://www.jfbym.com/api/YmServer/verifyapi'
base=encode_base64(filePath)
data={
"image":base,
"token":"lvfJhEHuS+2Kk5HWWFGz2pYZOOiaDvLeIauN35puIc=",
"type":"10101"
}
headers={
"Content-Type":"application/x-www-form-urlencoded"
}
res=requests.post(url,data=data,headers=headers)
print(res.text)
def encode_base64(file):
with open(file, 'rb') as f:
img_data = f.read()
base64_data = base64.b64encode(img_data)
print(type(base64_data))
# print(base64_data)
# 如果想要在浏览器上访问base64格式图片,需要在前面加上:data:image/jpeg;base64,
base64_str = str(base64_data, 'utf-8')
print(base64_str)
return base64_data
def decode_base64(base64_data):
with open('./images/base64.jpg', 'wb') as file:
img = base64.b64decode(base64_data)
file.write(img)
if __name__ == '__main__':
get_ocr_img('./yzm.jpg')
http/https协议特征:无状态。
没有请求到对应页面数据的原因
- 发起的第二次基于个人主页面请求的时候。服务器端并不知道该此请求是基于登录状态下的请求。
cookie:用来让服务器端记录客户端的相应状态:
- 手动处理:通过抓包工具获取cookie值。将该值封装到headers中(不建议)
- 自动处理:
- 创建一个session对象:session=requests.Session()
- 使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
- session对象对个人主页对应的get请求发送(携带了cookie)
代理:破解封IP这种反爬机制。
什么是代理?-代理服务器
代理的作用:突破自身IP访问的限制,隐藏自身的真实IP
代理相关网站:快代理,西祠代理,www.goubanjia.com
代理IP的类型
- http:应用到http协议对应的url中
- https:应用到https协议对应的url中
使用方法:res=requests.get(url,headers=headers,proxies={'https':'103.103.3.6:8080'}).text
代理ip的匿名度
- 透明:服务器知道该次请求使用了代理,也知道对应真是的IP地址
- 匿名:知道了使用代理,不是到真实IP
- 高匿:不知道使用了代理,不知道真是IP
import requests
url ='https://ip.hao86.com/'
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.53 Safari/537.36 Edg/103.0.1264.37'}
res=requests.get(url,headers=headers,proxies={'https':'10.103.3.6:8080'}).text
with open('./dd.html','w',encoding='utf-8') as wf:
wf.write(res)
高性能异步爬虫
目的:在爬虫中使用异步实现高性能的数据爬取操作
异步爬虫的方式:
- 多线程,多进程:(不建议)
好处:可以为相关阻塞的操作单独开启线程或进程,阻塞操作就可以异步执行.
弊端:无法限制的开启多线程或多进程. - 线程池,进程池(适当使用)
好处:我们可以降低系统对进程或线程创建和销毁的一个频率,从而很好的降低系统的开销
弊端:池中线程或进程的数量是有上限. - 单线程+异步协程(推荐):
event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行
coroutine 协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用.我们可以使用async关键字来顶一个方法,这个方法在调用时不会被执行,而是返回一个协程对象.
task 任务,他是对协程对象的进一步封装,包含了任务的各个状态
future:代表将来执行或者还没有执行的任务,实际上和task没有本质区别
async定义一个协程
await 用来挂起阻塞方法的执行.
不使用线程池方式
import time
def get_page(str):
print('start-',str)
time.sleep(2)
print('end-',str)
name_list=['xiaozi','aaa','bbb','ccc']
start_time=time.time()
for i in range(len(name_list)):
get_page(name_list[i])
end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 8 sdde
使用线程池方式
import time
from multiprocessing.dummy import Pool
def get_page(str):
print('start-',str)
time.sleep(2)
print('end-',str)
name_list=['xiaozi','aaa','bbb','ccc']
start_time=time.time()
# for i in range(len(name_list)):
# get_page(name_list[i])
# 实例化线程池对象
pool=Pool(4)# 开启4个线程池
# 将列表中每一个列表元素传递给get_page进行处理,返回的结果就是get_page的return
pool.map(get_page,name_list)
end_time=time.time()
print('%d sdde'%(end_time-start_time)) # 2 sdde
使用单线程+异步协程
import asyncio
async def request_url(url):
print('正在请求的url时',url)
print('请求成功',url)
return url
c=request_url('www.baidu.com')
# 创建一个事件循环对象
# loop=asyncio.get_event_loop()
# # 将协程对象注册到loop中然后启动loop
# loop.run_until_complete(c)
# asyncio.run(c) # 上述的封装方法
# task的使用
# loop=asyncio.get_event_loop()
# task=loop.create_task(c,name='task1')
# print(task)
# loop.run_until_complete(task)
# # future
# loop=asyncio.get_event_loop()
# task=asyncio.ensure_future(c)
# print(task)
# loop.run_until_complete(task)
# 绑定回调
def callback_func(task):
print(task.result())
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中,task执行完成后就会调用该函数
task.add_done_callback(callback_func)
loop.run_until_complete(task)
import asyncio
import time
async def request_url(url):
print('正在下载',url)
await asyncio.sleep(2)
print('下载完成',url)
start=time.time()
urls=[
'baidu',
'google',
'sougou',
'bing'
]
# stasks=[]
# for ul in urls:
# c=request_url(ul)
# task=asyncio.ensure_future(c)
# stasks.append(task)
tasks=[request_url(url) for url in urls]
loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
ends=time.time()
print(ends-start)
案例
import asyncio
import time
import requests
import aiohttp
urls=[
'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
print('正在下载',url)
# requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送,或者给他弄成线程再转换为协程
# aiohttp基于异步网络请求的模块
async with aiohttp.ClientSession() as session:
# get(),post()
# headers,params/data,proxy='http://ip:port'
async with session.get(url) as res:
pageText= await res.text()
print('下载完毕',pageText)
tasks=[get_page(url) for url in urls]
start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('总耗时',ends-start) # 总耗时 2.0147018432617188
错误演示//request没有协程
import asyncio
import time
import requests
urls=[
'http://127.0.0.1:5000/b1',
'http://127.0.0.1:5000/b2',
'http://127.0.0.1:5000/b3',
]
async def get_page(url):
print('正在下载',url)
res=requests.get(url)
print('下载完毕',res.text)
tasks=[get_page(url) for url in urls]
start=time.time()
asyncio.run(asyncio.wait(tasks))
ends=time.time()
print('总耗时',ends-start)
后端代码 类似下方方式
from flask import Flask
import time
app=Flask(__name__)
@app.route('/b1')
def index_b1():
time.sleep(2)
return 'Hello b1'
@app.route('/b2')
def index_b2():
time.sleep(2)
return 'Hello b2'
@app.route('/b3')
def index_b3():
time.sleep(2)
return 'Hello b3'
if __name__=='__main__':
app.run(threaded=True)
扩展Python自带的网络请求库
示例
发送get请求
import urllib.request
url='https://funletu.com/dong-tai/page/2'
req=urllib.request.Request(url)
with urllib.request.urlopen(req) as response:
data=response.read()
ss=data.decode()
print(ss)
发送post请求
import urllib.request
import urllib.parse
url='https://funletu.com/dong-tai/page/2'
params_dict={'id':123,'query':'all'}
params_str=urllib.parse.urlencode(params_dict)
params_bytes=params_str.encode()
req=urllib.request.Request(url,data=params_bytes)
with urllib.request.urlopen(req) as response:
data=response.read()
ss=data.decode()
print(ss)