1. 什么是爬虫
通过编写程序,模拟浏览器上网,去互联网上抓取数据的过程。
B站爬虫视频链接:https://www.bilibili.com/video/BV1Yh411o7Sz/share_source=copy_web&vd_source=446ad00dba84b9259d0acb03cdc7f892&t=1
2. 使用分类
- 通用爬虫:抓取系统重要组成部分。抓取的是一整张页面数据。
- 聚焦爬虫:是建立在通用爬虫的基础之上。抓取的是页面中特定的局部内容。
- 增量式爬虫:检测网站中数据更新的情况。只会抓取网站中最新更新的数据。
3. http和https
- http协议:服务器黑人客户端进行数据交互的一种方式
- https协议:安全的超文本传输协议
3.1 加密方式
-
对称密钥加密
-
非对称密钥加密:
使用的时候有两把锁:“私有密钥”、“公开密钥”。
服务器首先告诉客户端按照自己给定的公开密钥进行加密处理,客户端按照公开密钥加密以后,服务器接受到信息再通过自己的私有秘钥进行解密。
好处:
- 私有秘钥根本不会传输,避免了被劫持的风险
- 公开密钥就算被窃听者那到,也很难进行解密,解密过程是对离散对数求值,不是轻而易举就能做到的事
缺点:
- 服务器端发送公开密钥时,如何保证客户端收到的是没有被篡改过。只要发送密钥,就有可能被劫持
- 效率比较低,处理起来更为复杂。通信过程中使用就有一定的效率问题而影响通信速度的风险
-
证书密钥加密方式:
4. requests模块
python中原生的一款基于网络请求的模块。功能强大,简单效率高。
作用:模拟浏览器发请求。
使用过程:
- 指定url
- UA伪装
- 请求参数的处理
- 发起请求
- 获取响应数据
- 持久化存储
环境安装:pip install requests
简单案例:
4.1 UA伪装
UA:User-Agent(请求载体的身份标识)
-
反爬机制:门户网站通过制定相应的策略或者技术手段,防止爬虫程序进行网站数据的爬取
-
UA检测:门户网站的服务器会检测对应请求的载体身份标识。
如果检测到请求的载体身份标识为某一款浏览器,说明该请求是一个正常的请求。
如果检测到请求的载体身份标识不是基于某一款浏览器的,则表示该请求为不正常的请求(爬虫),则服务器端就很有可能拒绝该次请求。
-
-
反反爬策略:爬虫程序破解门户网站中具备的反爬机制,从而获取门户网站中相关数据
-
UA伪装:让爬虫对应的请求载体身份标识伪装成某一款浏览器。
将对应的User-Agent封装到一个字典中。
-
4.2 get和post请求
一般都需要url,headers。
- GET请求时,使用params,参数会直接追加至请求**字符串(url)**后
- POST请求时,使用data,参数是添加到**请求体(body)**的
注意:返回的response是一个对象,
-
encoding是从http中的header中的charset字段中提取的编码方式
若header中没有charset字段则默认为ISO-8859-1编码模式,则无法解析中文,这也是很多时候出现乱码的原因 -
apparent_encoding会从网页的内容中分析网页编码的方式,所以apparent_encoding比encoding更加准确。
当网页出现乱码时可以把apparent_encoding的编码格式赋值给encoding。
这也是为什么爬虫里大多数会写 :
response.encoding = ressponse.apparent_encoding
4.3 response.json()
http://t.csdn.cn/4VC4O
5. 数据解析
5.1 概述
在requests模块实现数据爬取后,通常在持久化存储之前需要进行指定数据解析
大多数情况下的需求,都会使用聚焦爬虫(爬取页面中指定的页面内容),而不是整个页面的数据。
数据解析分类:
- 正则
- bs4
- xpath(重点)
数据解析原理:解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储。
- 进行指定标签的定位
- 标签或者标签对应的属性中存储的数据值进行提取(解析)
所以整个爬虫流程变成:
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 持久化存储
5.2 正则表达式
-
正则表达式学习:https://cloud.tencent.com/developer/article/1859919?shareByChannel=link#3.2
-
贪婪模式和非贪婪模式:http://t.csdn.cn/3s4yk
-
re模块:http://t.csdn.cn/ahlzy
-
re.S和re.M:http://t.csdn.cn/m9wWx。re.S将多行当做一个整体进行匹配
-
re.findall函数
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。注意**:** match 和 search 是匹配第一个符合要求的, findall 匹配所有
-
5.3 bs4(python独有)
5.3.1 概述
环境安装:
- pip install bs4
- pip install lxml
bs4数据解析的原理:
- 实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
- 通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
5.3.2 实例化BeautifulSoup
导包:from bs4 import BeautifulSoup
实例化对象有两种:
-
将本地的html文档中的数据加载到该对象中
with open('./test.html','r',encoding='utf-8') as fp: soup = BeautifulSoup(fp,'lxml')
-
将互联网上获取的页面源码加载到该对象中
page_text = response.text soup = BeatifulSoup(page_text,'lxml')
5.3.3 BeautifulSoup中方法和属性
-
soup.tagName:返回的是文档中第一次出现的tagName对应的标签
soup.div,soup.span ……
-
soup.find():返回第一个
-
find(‘tagName’):等同于soup.tagName
-
属性定位:
soup.find(‘div’,class_/id/attr=‘属性名’)
class是python的关键字,所以这里是class_
-
-
soup.find_all(‘tagName’):返回符合要求的所有标签(列表)
-
select:返回的是一个列表
-
select(‘某种选择器(id,class,标签…选择器)’)
层级选择器:
- soup.select(‘.tang > ul > li > a’):>表示的是一个一个人层级的查找
- oup.select(‘.tang a’):空格表示的多个层级
# 获取id为list-2下的li节点,list-2和li之间还可以有其他标签 print(soup.select('#list-2 li')) # list-2和li之间没有其他标签,li是list-2的直系子标签 print(soup.select('#list-2 > li'))
-
-
获取标签之间的文本数据:soup.a.text,soup.a.string,soup.a.get_text()
- text/get_text():可以获取某一个标签中所有的文本内容
- string:只可以获取该标签下面直系的文本内容
-
获取标签中属性值:
soup.a[‘href’],soup.img[‘src’]
detail_page_text.enconding = 'utf-8’即可解决乱码
5.4 xpath(最常用)
5.4.1 概述
环境安装:pip install lxml
xpath解析原理:
- 实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
- 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
- 如果是js代码的话,就用正则表达式进行匹配
5.4.2 实例化etree对象
导包:from lxml import etree
和bs4一样,有两种:
-
将本地的html文档中的源码数据加载到etree对象中:
etree.parse(filePath)
-
可以将从互联网上获取的源码数据加载到该对象中
etree.HTML('page_text')
5.4.3 xpath表达式
etree.xpath(‘xpath表达式’):返回一个Element对象组成的列表。Element中存储相关标签内容。
-
/:表示的是从根节点开始定位。也可以表示的是一个层级。
-
//:表示的是多个层级。可以表示从任意位置开始定位。
-
属性定位:通用写法tag[@attrName=“attrValue”]
//div[@class='song'] # class为song的div标签
-
索引定位: 索引是从1开始的。
//div[@class="song"]/p[3] # class为song的div中第3个p标签
-
取文本:
-
/text():获取的是标签中直系的文本内容
tree.xpath('//div[@class="tang"]//li[5]/a/text()') # class为tang的div下第5个li中的a标签中的文本 # 返回的是一个列表,可以用下标取出内容
-
//text():标签中非直系的文本内容(所有的文本内容)
和bs4中text一样
-
-
取属性:/@attrName
img/@src # img标签中src的属性值
如果出现乱码:response.encoding = ressponse.apparent_encoding
xpath中可以写多个表达式,用逻辑符分隔:或|
6. 验证码
反爬机制:验证码。识别验证码图片中的数据,用于模拟登陆操作。
识别验证码的操作:下载验证码图片到本地,再进行识别
-
人工肉眼识别。(不推荐)
-
第三方自动识别(推荐)
-
云码 :https://www.jfbym.com/price/
-
超级鹰:http://www.chaojiying.com/
-
云打码(已跑路):http://www.yundama.com/demo.html
-
-
python库:
-
ddddocr
import ddddocr ocr = ddddocr.DdddOcr() with open('./code.jpg', 'rb') as fp: img_bytes = fp.read() res = ocr.classification(img_bytes) print('识别出的验证码为:' + res)
-
7. cookie
7.1 什么是cookie
我们第二次发起基于个人主页页面请求的时候,服务器端并不知道此次请求是基于登录状态。这就可以用cookie解决。
也就是再次请求时不需要再次进行登录,用来让服务器端记录客户端的相关状态
获取cookie的两种方式:
-
手动获取:通过抓包工具获取cookie值,将该值封装到headers中。(不建议)
-
自动处理:
-
cookie值的来源是哪里?
- 模拟登录post请求后,由服务器端创建。
可以看到请求登录后响应头中set-cookie.
- 模拟登录post请求后,由服务器端创建。
-
session会话对象:
- 作用:
- 可以进行请求的发送。
- 如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中。
- 作用:
所以登录流程变成:
-
创建session对象
requests.Session()
-
使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
session.post
-
session对象对个人主页对应的get请求进行发送(携带了cookie)
session.get
-
7.2项目案例
模拟古诗文网登录:http://t.csdn.cn/8uzHa
8. 代理
-
代理是一种反反爬机制,破解封IP这种反爬机制。
-
什么是代理:代理服务器。
本机不直接向服务器发送请求,而是发送给代理服务器,再通过代理服务器发送给服务器。
-
代理的作用:
- 突破自身IP访问的限制。
- 隐藏自身真实IP
-
代理相关的网站:
-
快代理:
-
豌豆代理:https://www.wandouip.com
-
smartproxy:https://www.smartproxy.cn
-
-
代理ip的类型:
-
http:应用到http协议对应的url中
-
https:应用到https协议对应的url中
-
-
代理ip的匿名度:
- 透明:服务器知道该次请求使用了代理,也知道请求对应的真实ip
- 匿名:知道使用了代理,不知道真实ip
- 高匿:不知道使用了代理,更不知道真实的ip
# proxies = {'协议': '协议://IP:端口号'}
proxies = {
'http': 'http://{}'.format('8.129.28.247:8888'),
'https': 'https://{}'.format('8.129.28.247:8888'),
}
html=requests.get(url=url,headers=headers,proxies=proxies).text
相关讲解:http://t.csdn.cn/l4bct
9. 高性能异步爬虫
9.1 概述
-
目的:在爬虫中使用异步实现高性能的数据爬取操作。
-
异步爬虫的方式:
-
多线程,多进程(不建议)
-
好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行。
-
弊端:无法无限制的开启多线程或者多进程。耗费资源
-
-
线程池、进程池(适当的使用)
-
单线程+异步协程(推荐):
-
9.2 线程池、进程池
python 进程池pool简单使用 - shaomine - 博客园 (cnblogs.com)
- 好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销。
- 弊端:池中线程或进程的数量是有上限。
import time
# 导入进程池模块对应的类
from multiprocessing.dummy import Pool
start_time = time.time()
# 每次等待2秒,单线程的话4次需要8秒,用进程池的话只需要2秒左右
def get_page(str):
print("正在下载 :", str)
time.sleep(2) # 等待2秒
print('下载成功:', str)
name_list = ['dd', 'aa', 'bb', 'cc']
# 实例化一个进程池对象
pool = Pool(4)
# 将列表中每一个列表元素传递给get_page进行处理。
pool.map(get_page, name_list)
# 关闭进程池
pool.close()
# 主进程阻塞等待子进程的退出
pool.join()
end_time = time.time()
print(end_time - start_time)
梨视频爬取案例:http://t.csdn.cn/kMGCa。B站视频讲解的方法已失效,新代码可参考这位博主分享的,很全面。
该案例融合了很多知识点,熟练掌握。
9.2.1 Referer
梨视频的案例中在请求头中除了之前说过的Cookie
和User-Agent
,还有Referer
。
作用:http://t.csdn.cn/evEds。
告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。
- 防盗链
- 防止恶意请求
9.2.2 Pandas写入Excel函数
Pandas写入Excel函数——to_excel 技术总结
Pandas (to_excel) 编写 Excel 文件 (xlsx, xls)
简单样例:
import pandas as pd
if __name__ == "__main__":
names = ['1.mp4', '2.mp4', '3.mp4']
urls = ['one', 'two', 'three']
data = pd.DataFrame({"视频简介": names, "视频地址": urls})
data.to_excel("./视频信息.xlsx", sheet_name='Sheet1', index=False)
# 新建文件夹
if not os.path.exists('./新建文件夹'):
os.mkdir('./新建文件夹')
9.3 单线程+异步协程
-
event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,
当满足某些条件的时候,函数就会被循环执行。 -
coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。
我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回
一个协程对象。 -
async:定义一个协程。
python3.7后直接用
asyncio.run
执行一个协程对象,而不用get_event_loop
、run_until_complete
import asyncio # async定义一个协程函数 async def request(url): print('正在请求的url是', url) print('请求成功,', url) return url # async修饰的函数,调用之后返回的一个协程对象 c = request('www.baidu.com')
# 创建一个事件循环对象 loop = asyncio.get_event_loop() # 将协程对象注册到loop中,然后启动loop loop.run_until_complete(c) # 上面等价于下面 # python3.7及之后也可以直接用run asyncio.run(c)
-
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
通过
asyncio.create_task(协程对象)
的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。python3.7之后。还可以使用低层级的
loop.create_task()
或asyncio.ensure_future()
函数。loop = asyncio.get_event_loop() # 基于loop创建了一个task对象 task = loop.create_task(c)
import asyncio # 3.7之后 task_list = [ request(), request(), ] asyncio.run( asyncio.wait(task_list) ) # 事件循环自动创建Task对象 # 通常情况套在一个函数里面 async def main(): task_list = [ asyncio.create_task(request()), asyncio.create_task(request()), ] done, pending = await asyncio.wait(task_list, timeout = None) asyncio.run( main() )
-
future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
按照官方文档的描述,Task是Futrue的子类。
-
await:用来挂起阻塞方法的执行。
当在asyncio中遇到阻塞操作必须进行手动挂起
实例:
import asyncio
import time
async def request(url):
print('正在下载', url)
# 当在asyncio中遇到阻塞操作必须进行手动挂起
await asyncio.sleep(2)
print('下载完毕', url)
start = time.time()
urls = [
'www.baidu.com',
'www.sogou.com',
'www.goubanjia.com'
]
# 任务列表:存放多个任务对象
# stask = [request(url) for url in urls]
stasks = []
for url in urls:
c = request(url)
stasks.append(c)
asyncio.run(asyncio.wait(stasks))
print(time.time() - start)
9.4 aiohttp
requests模块是基于同步的,必须使用基于异步的网络请求模块进行指定url的请求发送
# 不存在这种写法,不是加个await就是异步
await response = requests.get(url=url)
而aiohttp就是基于异步网络请求的模块。
9.4.1 概述
安装:pip install aiohttp
使用该模块中的ClientSession
9.4.2 实例
async def get_page(url):
async with aiohttp.ClientSession() as session:
#get()、post():
#headers,params/data,proxy='http://ip:port'
async with await session.get(url) as response:
#text()返回字符串形式的响应数据
#read()返回的二进制形式的响应数据
#json()返回的就是json对象
page_text = await response.text() # 获取响应数据操作之前一定要使用await进行手动挂起
print(page_text)