requests高级
视频数据爬取
-
url:https://www.51miz.com/shipin/
- 爬取当前url页面中营销日期下的几个视频数据。
-
找寻每个视频的播放地址:
- 通过观察视频详情页的页面数据,并没有发现视频的播放地址,只有一张播放图片。
- 打开抓包工具,点击页面的播放按钮,找到了视频的播放数据包,可以提取出视频的播放地址,地址格式为:
https://video-js.51miz.com/preview/video/00/00/11/63/V-116374-7B5E698E.mp4
- 观察其他视频的播放地址,找寻规律:
相同之处:https://video-js.51miz.com/preview/video/ 不同之处:00/00/11/63/V-116374-7B5E698E.mp4
- 在首页url页面数据中,找到video标签(对应每个视频),标签的poster属性值中出现了视频播放地址中的(不同之处)相似部分,但是经过测试,不可用。
- 在video标签下面有一个source标签,其内部的src属性值正好就是视频的播放地址 (补充上https:即可) 。
import requests from lxml import etree headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' } url = 'https://www.51miz.com/shipin/' response = requests.get(url=url,headers=headers) page_text = response.text #数据解析 tree = etree.HTML(page_text) div_list = tree.xpath('/html/body/div[2]/div[2]/div[1]/div[2]/div[2]/div') for div in div_list: src_list = div.xpath('./a/div/div/div/video/source/@src') #要给视频地址进行补全 for src in src_list: #src就是一个完整的视频地址 src = 'https:'+src video_data = requests.get(url=src,headers=headers).content video_title = src.split('/')[-1] with open(video_title,'wb') as fp: fp.write(video_data) print(video_title,'爬取保存成功!') break
还可以这样写代码
import requests
from lxml import etree
import os
"""
通过分析,视频的url在首页,不在详情页,详情页的url是动态加载的,爬虫在详情页是获取不到的。
"""
url = 'https://www.51miz.com/shipin/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
response = requests.get(url=url, headers=headers).text # ValueError: can only parse strings报错要加.text,获取首页源码数据。
# 解析源码数据,获取类url
tree = etree.HTML(response) # 创建etree解析对象
div_list = tree.xpath('/html/body/div[2]/div[2]/div[1]/div[2]/div[2]/div') # 调用etree工具的xpath函数定位各个类节点,返回一个列表。
for div in div_list: # 遍历列表,取出单个分类节点div。
# 调用xpath定位单个分类节点div,获取分类title和分类的url
src_list = div.xpath('./a/div/div/div') # 定位在包括视频标题和url的节点。
for src in src_list:
species_url = src.xpath('./video/source/@src')[0] # 拿到source的属性值src,是字符串类型数据。加[0]解析出视频url
url = 'https:' + species_url # 补全视频url
video = requests.get(url=url, headers=headers).content # 发请求,获取源码数据,加.content转为二进制数据
title = src.xpath('./div/text()')[0] + '——' + species_url.split('-')[-1] # 视频标题。src.xpath('./div/text()')[0]获取目标节点下的文本数据并下标取值。species_url.split('-')[-1]把species_url中的值按-分割并下标取值最后一段数据。
directory = '../pydirectory/觅知网/'
# 检查目录是否存在,如果不存在则创建它,如果目录已存在,则直接存储文件。
if not os.path.exists(directory):
os.makedirs(directory) # 使用 os.makedirs() 函数来创建目录
file_path = os.path.join(directory, title) # 使用 os.path.join() 函数来拼接文件路径。这样可以确保目录存在,并且文件可以正确地保存在指定的路径中。
with open(file_path, 'wb') as fp:
fp.write(video)
print(file_path, '爬取成功')
"""
在当前脚本所在位置创建文件夹可以写下面代码:
directory = '../pydirectory/'
检查目录是否存在,如果不存在则创建它,如果目录已存在,则不会进行任何操作。
if not os.path.exists(directory):
os.makedirs(directory) # 使用 os.makedirs() 函数来创建目录
file_path = os.path.join(directory, menu_title + '.html') # 使用 os.path.join() 函数来拼接文件路径。这样可以确保目录存在,并且文件可以正确地保存在指定的路径中。
with open(file_path, 'w', encoding='utf-8') as fp:
fp.write(page_text)
"""
# break
Cookie
-
什么是cookie?
- cookie的本质就是一组数据(键值对的形式存在)
- 是由服务器创建,返回给客户端,最终会保存在客户端浏览器中。
- 如果客户端保存了cookie,则下次再次访问该服务器,就会携带cookie进行网络访问。
-
典型的案例:网站的免密登录
-
爬取雪球网中的咨询数据
-
经过分析发现帖子的内容是通过ajax动态加载出来的,因此通过抓包工具,定位到ajax请求的数据包,从数据包中提取:
- url:https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=311519&size=15
- 请求方式:get
- 请求参数:拼接在了url后面
-
import requests import os headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36', } url = 'https://xueqiu.com/statuses/hot/listV2.json' param = { "since_id": "-1", "max_id": "311519", "size": "15", } response = requests.get(url=url,headers=headers,params=param) data = response.json() print(data) #发现没有拿到我们想要的数据
-
分析why?
- 切记:只要爬虫拿不到你想要的数据,唯一的原因是爬虫程序模拟浏览器的力度不够!一般来讲,模拟的力度重点放置在请求头中!
- 上述案例,只需要在请求头headers中添加cookie即可!
-
爬虫中cookie的处理方式(两种方式):
-
手动处理:将抓包工具中的cookie复制到headers中即可
-
缺点:
- cookie通常都会存在有效时长,需要重复操作复制cookie
-
-
自动处理
基于session(绘画)对象实现自动处理cookie。
- 1.创建一个空白的session对象。
- 2.需要使用session对象发起请求,请求的目的是为了捕获cookie
- 注意:如果session对象在发请求的过程中,服务器端产生了cookie,则cookie会自动存储在session对象中。
- 3.使用携带cookie的session对象,对目的网址发起请求,就可以实现携带cookie的请求发送,从而获取想要的数据。
注意:session对象至少需要发起两次请求
-
第一次请求的目的是为了捕获存储cookie到session对象
-
后次的请求,就是携带cookie发起的请求了
-
缺点:
- 编写麻烦(涉及逆向)
# # 案例二(session的设置和运用) # 爬取雪球网中的咨询数据 # 打开抓包工具->在页面向下滚动滚轮,加载热帖->在抓包界面出现listV2.json格式的数据-点击listV2.json格式数据进入预览界面->itmes->original_status->description(文本数据)->标头->常规->请求网址url->复制headers(请求标头)数据(Cookie,User-Agent)->载荷->复制params(参数)数据。 # 请求网址url:https://xueqiu.com/statuses/hot/listV2.json?since_id=-1&max_id=553502&size=15,这个url是带了请求参数的,问号后面的是请求参数,url里去掉,封装在字典param里设置为动态请求参数。 ''' url = 'https://xueqiu.com/statuses/hot/listV2.json' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36', 'Cookie': } # 经过测试,请求参数的值变与不变都不影响数据的获取,故没有设置为动态参数 param = { # 如果遇到动态变化的请求参数,必须经过测试才能知道需不需要处理。 'since_id': '-1', 'max_id': '553502', # 动态变化的请求参数 'size': '15' # size表示一次请求15条数据。 } """ 如何测试请求参数是不是动态: 进入抓包工具(Network网络)->刷新网页->找到格式为listV2.json格式的数据条,点击进去->载荷->复制param并对比,看里面的参数有无变化,有变化是动态参数,无变化是静态参数。 """ response = requests.get(url=url, headers=headers, params=param).json() print(response) # 出现这样的报错:{'error_description': '遇到错误,请刷新页面或者重新登录帐号后再试', 'error_uri': '/statuses/hot/listV2.json', 'error_data': None, 'error_code': '400016'},需要处理请求标头,尤其要注意加上User-Agent,Referer,Cookie等参数。 ''' # 上面的代码可以优化为: url = 'https://xueqiu.com/statuses/hot/listV2.json' # 目标url headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' } # 经过测试,请求参数的值变与不变都不影响数据的获取,故没有设置为动态参数 param = { # 如果遇到动态变化的请求参数,必须经过测试才能知道需不需要处理。 'since_id': '-1', 'max_id': '553502', # 动态变化的请求参数 'size': '15' # size表示一次请求15条数据。 } # cookie是从什么时候创建的?是客户端第一次向服务器端发请求的时候,服务器端就向客户端创建并发送(响应)了cookie,客户端第二次发请求时就带上了cookie。 # session对象会实时保存跟踪服务器端给客户端创建的cookie # 创建一个session对象 session = requests.Session() # 这是空白的session对象 first_url = 'https://xueqiu.com/' # 首页url # 使用session对象对首页url进行请求发送,目的获取服务器发送的cookie。如果该次请求时,服务器端给客户端创建cookie的话,则该cookie就会被保存在seesion对象中。 session.get(url=first_url, headers=headers) # 这是session第一次发送请求(向首页发送,也只能向首页发送,否则获取不到cookie),目的获取cookie,所获cookie保存在当前的session对象中。后续就可以用保存了cookie的session对目标网页发请求获取所需数据。 # 使用保存了cookie的session对象进行后续请求发送 ret = session.get(url=url, headers=headers, params=param).json() # 这是携带了动态cookie的session第二此发送请求(向目标页),目的获取有效数据。 """ 如何判断请求后获取的数据用.json()还是用.text处理?最直接的方法就是看数据从哪获取到的,从元素中获取,就用.text,并进一步用xpath解析获取;从网络->响应处获取,就直接.json解析即可。还可以分别测试,如果用.json报错说response是str(字符串格式),就用.text。再就是看响应标头->Content-Type(响应数据类型):application/json;charset=UTF-8->application/json表示返回的是json格式的字符串,就用.json。json格式的字符串本质上还是字符串,但在某些数据处理方法上还是和纯字符串的处理方法不一样。json数据一般都是用Ajax加载出来的,找json数据也可以用Ajax抓取。 """ print(ret) """ 有些网页比较变态,就是不同层级网页的请求需要的cookie是不同的,需要将前面请求所获的cookie和后续所获cookie进行累加结合。cookie的本质是键值对。本质上还是通过给session添加新的键值对,就涉及到用逆向的知识解决问题了。处理涉及到cookie的反爬一定会用到session。可以手动往session里添加或删除键值对。 """
-
模拟登录
- 登录的目的有两个:
- 一是验证用户名和密码是否正确
- 二是让服务器端记住登录的状态,就用到cookie。cookie可以实现让服务器端接收或记住客户端相关的状态。
- 获取https://passport.17k.com/中的书架页面里的图书信息
# 案例三
# 模拟登录(获取https://passport.17k.com/中的书架页面里的图书信息)
# 清理cookie:打开网页->F12->Application(应用)->Storage(存储)->Cookie->https系列数据->清除所有cookie。
# 如何抓包cookie:打开登录页面->F12->清理cookie->网络(确保勾选了(Preserve log)保留日志)->登录(只有经过登录才能获取cookie,用requests.Session()获取)->Name(名称)->login(注册,登录)->Headers(标头)->常规(General)->Request URL(请求网址,爬虫第一次对它发请求,要复制此url)->Request Method(请求方法)->响应标头(Response Headers)->Set-cookie(创建/设置cookie)->Payload(载荷)->loginName和password(账号和密码):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
}
login_url = 'https://passport.17k.com/ck/user/login' # 第一次发请求的url。
# post请求用data设置参数。第一次发请求携带的参数,在载荷里复制。
data = {
'loginName': '150********',
'password': '************'
}
session = requests.Session() # 创建一个空的session,用来存储获取到的cookie。
# 这次请求的目的是为了获取cookie将其保存到session对象中,并不需要获取的源码数据,所以在请求成功后不需要加.text或.json。
session.post(url=login_url, headers=headers, data=data) # 第一次发请求。不需要加.text或.json。post请求用data参数,携带登录账号和密码。
# 携带cookie向数据请求发送,获取数据信息
# 书架信息是动态加载出来的,不能直接从元素获取,要进行全局搜索定位数据包。所以第二次发请求时,书架url的获取需要在书架页面进行全局搜索定位数据包(F12->网络->点击搜索按钮(小放大镜)->搜索框里输入书架页面中的任一作品名->点击刷新),在获取的数据包标头里的常规复制url。
book_url = 'https://user.17k.com/ck/author2/shelf?page=1&appKey=*********'
page_text = session.get(url=book_url, headers=headers).json() # 爬虫第二次发请求,获取书架源码数据,要加.json()。
# 解析书架信息
print(page_text)
代理
-
什么是代理
- 代理服务器
-
代理服务器的作用
-
就是用来转发请求和响应
-
在爬虫中为何需要使用代理?
- 有些时候,需要对网站服务器发起高频的请求,网站的服务器会检测到这样的异常现象,则会讲请求对应机器的ip地址加入黑名单,则该ip再次发起的请求,网站服务器就不再受理,则我们就无法再次爬取该网站的数据。
- 使用代理后,网站服务器接收到的请求,最终是由代理服务器发起,网站服务器通过请求获取的ip就是代理服务器的ip,并不是我们客户端本身的ip。
-
代理的匿名度
- 透明:网站的服务器知道你使用了代理,也知道你的真实ip
- 匿名:网站服务器知道你使用了代理,但是无法获知你真实的ip
- 高匿:网站服务器不知道你使用了代理,也不知道你的真实ip(推荐)
-
代理的类型(重要)
- http:该类型的代理服务器只可以转发http协议的请求
- https:可以转发https协议的请求
-
如何获取代理?
-
如何使用代理?
-
测试:访问如下网址,返回自己本机ip
-
import requests from lxml import etree headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36', } url = 'http://www.cip.cc/' page_text = requests.get(url,headers=headers).text tree = etree.HTML(page_text) text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0] print(text.split('\n')[0])
-
使用代理发起请求,查看是否可以返回代理服务器的ip
-
import requests from lxml import etree headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36', } url = 'http://www.cip.cc/' page_text = requests.get(url,headers=headers,proxies={'http':'代理IP'}).text # '代理IP'里填入代理IP tree = etree.HTML(page_text) text = tree.xpath('/html/body/div/div/div[3]/pre/text()')[0] print(text.split('\n')[0])
-
深度测试:
深度测试:
# 案例:https://www.kuaidaili.com/free/inha import requests from lxml import etree import random import time import random # # headers = { # 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36', # } # # # 先测试不用代理会不会返回数据。 # for page in range(1, 51): # print('正在爬取第%d页的ip数据......' % page) # #生成不同页码对应的url # url = 'https://www.kuaidaili.com/free/inha/%d/' % page # page_text = requests.get(url=url, headers=headers).text # 每一页的详情代码 # time.sleep(0.5) # 每两次请求隔0.5秒,请求频率太高会被封IP的,不会返回数据。间隔时间长不会被拒绝(1秒以上)。 # tree = etree.HTML(page_text) # ip = tree.xpath('//*[@id="list"]/div[1]/table/tbody/tr[1]/td[1]/text()')[0] # td[1]/text()每一页的第一条IP数据。 # print(ip) ```
-
对快代理进行n次请求,直到本机无法访问快代理为止(证明本机ip被快代理封掉了)
-
构建一个代理池(封装了很多代理ip和端口的容器),用于数据的批量爬取
-
import requests from lxml import etree import random import time import random # 构建一个代理池(封装了很多代理ip和端口的容器),用于数据的批量爬取 headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36', } #构建一个代理池(有很多不同的代理服务器) proxy_url = 'http://webapi.http.zhimacangku.com/getip?num=31&type=1&pro=&city=0&yys=0&port=1&time=1&ts=0&ys=0&cs=0&lb=1&sb=0&pb=4&mr=1®ions=' # 芝麻HTTP代理的代理服务器中'生成链接'并复制'复制链接'的url粘贴在proxy_url里。 # 提取数量设置多一点,十几个到几十个,看请求的频率和数量的多少而定。 page_text = requests.get(url=proxy_url,headers=headers).text # 拿到芝麻HTTP代理页面源码数据,如***.49.***.54:4224 # 把***.49.***.54:4224变成{'HTTP':'***.49.***.54:4224'}格式 proxy_list = [] #代理池 for ips in page_text.split('\n')[0:-1]: # 用回车'\n'切分字符串。[0:-1]去掉最后一个数据(最后一个是空数据)。 dic = {} # 创建一个空字典。 dic['https'] = ips.strip() # strip去除首尾空格\r。dic['https'] = ips字典的键为https,值为ips。把***.49.***.54:4224变成{'HTTP':'***.49.***.54:4224'} proxy_list.append(dic) # 把dic['https'] = ips.strip()生成的字典全部放进proxy_list = []里,生成代理池。代理池中的每一个字典都可以充当proxies的参数(代理)。 # 以上步骤就构建好了代理池。后续代码可以基于代理池发送请求。用隧道代理可以直接用,但隧道代理更贵一些。 for page in range(1, 51): print('正在爬取第%d页的ip数据......' % page) #生成不同页码对应的url url = 'https://www.kuaidaili.com/free/inha/%d/' % page page_text = requests.get(url=url, headers=headers,proxies=random.choice(proxy_list)).text # random.choice随机选取的模块,proxy_list是代理池,是一个列表。random.choice(proxy_list)的意思是随机从proxy_list列表里取出一个元素(一个字典格式数据。代理,是由代理服务器网站生成的代理。)proxies是字典格式。 time.sleep(0.5) tree = etree.HTML(page_text) ip = tree.xpath('//*[@id="list"]/div[1]/table/tbody/tr[1]/td[1]/text()')[0] print(ip)
-
验证码
-
图鉴平台:http://www.ttshitu.com/ (推荐)
-
使用流程:
-
注册登录图鉴平台
-
登录后,点击开发文档,提取识别的源代码
-
模块(tujian.py)的封装:
-
import base64 import json import requests # 一、图片文字类型(默认 3 数英混合): # 1 : 纯数字 # 1001:纯数字2 # 2 : 纯英文 # 1002:纯英文2 # 3 : 数英混合 # 1003:数英混合2 # 4 : 闪动GIF # 7 : 无感学习(独家) # 11 : 计算题 # 1005: 快速计算题 # 16 : 汉字 # 32 : 通用文字识别(证件、单据) # 66: 问答题 # 49 :recaptcha图片识别 # 二、图片旋转角度类型: # 29 : 旋转类型 # # 三、图片坐标点选类型: # 19 : 1个坐标 # 20 : 3个坐标 # 21 : 3 ~ 5个坐标 # 22 : 5 ~ 8个坐标 # 27 : 1 ~ 4个坐标 # 48 : 轨迹类型 # # 四、缺口识别 # 18 : 缺口识别(需要2张图 一张目标图一张缺口图) # 33 : 单缺口识别(返回X轴坐标 只需要1张图) # 五、拼图识别 # 53:拼图识别 #函数实现忽略 def base64_api(uname, pwd, img, typeid): with open(img, 'rb') as f: base64_data = base64.b64encode(f.read()) b64 = base64_data.decode() data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64} result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text) if result['success']: return result["data"]["result"] else: return result["message"] return "" def getImgCodeText(imgPath,imgType):#直接返回验证码内容 #imgPath:验证码图片地址 #imgType:验证码图片类型 result = base64_api(uname='**********', pwd='**********', img=imgPath, typeid=imgType) return result
-
验证码图片识别操作
- 识别本地的滑动验证和坐标点击验证
-