目录
一、Session保持登录
在上节我们讲到了人人网的案例,发送携带表单数据的post请求可以成功访问到登录后的页面,如果我们此时想通过此页面去访问别人的主页我们应该怎么做?
import requests
if __name__ == '__main__':
# 1.确认目标的url > 登录的url
url_ = 'http://www.renren.com/PLogin.do'
# 用户代理
headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
# post请求携带数据,form表单..
data_ = {
'email': 'xxxxxxxx', # 账号
'password': 'xxxxxxxx' # 密码
}
# 2.发送请求
response_ = requests.post(url_, headers=headers_, data=data_)
str_data = response_.text
# 4.保存
with open('renren02.html', 'w', encoding='utf-8') as f:
f.write(str_data)
# 找到详情页url_ > 需要登录之后才能够访问的
url_ = 'http://www.renren.com/880151247/profile'
response_ = requests.get(url_, headers=headers_)
str_data = response_.text
# 保存在本地
with open('renren_dapeng.html', 'w', encoding='utf-8') as f:
f.write(str_data)
通过上述代码我们可以成功访问到人人网登录后的个人主页,但是当访问别人主页的时候,又跳转到了登录页面。
出现这种情况的原因很简单,我们使用的python代码进行访问网站并不是浏览器,所以当我们去访问别人主页时并没有携带登陆后带有登录信息的cookie,这才导致我们访问失败。
搞清楚了原因后解决就变得轻松多了,我们只要在第二次发送请求时带上登陆后的cookie就可以成功了。但是这样的作法也存在着一些问题:
当我们要去访问100个人的主页时,我们要做100次的模拟登陆,然后再去携带100个不同的cookie,就很麻烦,此时我们引出一种更简单的做法。
利用requests的session方法,创建一个session类,利用session发送的请求就会自动携带登录过后保存有登录信息的cookie
import requests
if __name__ == '__main__':
# 1.确认目标的url > 登录的url
url_ = 'http://www.renren.com/PLogin.do'
# 用户代理
headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
# post请求携带数据,form表单..
data_ = {
'email': 'xxxxxxx', # 账号
'password': 'xxxxxxx' # 密码
}
# 2.如果想要发送post请求,模拟了登录之后,自动携带登陆过后的cookie,就要使用session方法
session_ = requests.session() # 返回一个session对象
session_.post(url_, headers=headers_, data=data_) # 之后session对象就保持了登录信息
# 4.保存
# with open('renren02.html','w',encoding='utf-8') as f:
# f.write(str_data)
# 找到详情页url_ > 需要登录之后才能够访问的
url_ = 'http://www.renren.com/880151247/profile' # dapeng
# 此时的session是保持了登录状态的,自动携带了带有登录信息的cookie
response_ = session_.get(url_, headers=headers_)
str_data = response_.text
# 保存在本地
with open('renren_dapeng02.html', 'w', encoding='utf-8') as f:
f.write(str_data)
二、代理IP的使用
构造方法与请求头的构造方法一致,也是一个键值对
proxy_ = {
# 固定写法
'http': 'http://1.1.1.1:8888',
'https': 'https://1.1.1.1:8888'
}
键名为http或者https,值为 http://IP地址:端口号 或 https://IP地址:端口号
proxy_ = {
# 固定写法
'http': '1.1.1.1:8888',
'https': '1.1.1.1:8888'
}
第二种较为简单并且常用,键名与第一种方法一样,值为 IP地址:端口号
import requests
if __name__ == '__main__':
url_ = 'https://www.baidu.com/'
headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
# 代理IP的构建,以键值对的方式
# proxy_ = { # 固定的语法 ip地址:端口号
# 'http':'1.1.1.1:8888'
# }
proxy_ = { # 固定的语法 ip地址:端口号
'http': 'http://1.1.1.1:8888' # 是一个无效的代理IP
}
response_ = requests.get(url_, headers=headers_, proxies=proxy_)
bytes_data = response_.content
# 保存一下
with open('baidu01.html', 'wb') as f:
f.write(bytes_data)
在访问网站时的用法如上
上述代码中的代理IP是我们瞎编的,为什么还有访问成功呢?因为requests最近更新后,当代理IP使用不成功时,会自动使用本机IP
测试代理IP的有效性:
测试网站:http://2021.ip138.com/
我们既然有了测试IP地址的网站,那么使用代理IP访问该网站就可以测试代理IP是否有效
import requests
from lxml import etree # 提取html格式数据的
if __name__ == '__main__':
# 特殊的测试IP的url
url_ = 'http://2021.ip138.com/'
headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
# 代理IP的构建,以键值对的方式
# proxy_ = { # 固定的语法 ip地址:端口号
# 'http':'1.1.1.1:8888'
# }
proxy_ = { # 固定的语法 ip地址:端口号
'http': 'http://1.1.1.1:8888' # 是一个无效的代理IP
}
response_ = requests.get(url_, headers=headers_, proxies=proxy_)
# response_ = requests.get(url_,headers=headers_)
# print(response_)
str_data = response_.text # 得到字符串类型的响应文本数据
# 提取数据(后面会讲)
html_obj = etree.HTML(str_data)
res_ = html_obj.xpath('//title/text()')[0]
print(res_)
如果程序报错,就证明代理IP无效
当我们使用代理IP池时,我们可以通过循环测试他们的有效性,将有效的代理IP加入到一个空列表中,当循环结束时,这个列表中的代理IP就全是有效的了。
三、timeout参数
当我们访问网站的时候,会出现响应时间过长的情况。
例如:我们在测试代理IP有效性的时候,即使有些代理IP有效,但存在着访问网站获取响应速度过慢的情况,所以我们需要把这样的IP也剔除掉
response_ = requests.get(url_, headers=headers_, proxies=proxy_, timeout=3)
我们在发送请求时加上timeout参数就可以了,例子中 timeout=3 ,就是在3秒内没有获得响应的话就立即报错
四、retrying模块的使用
见名知意,retrying重新尝试,即访问失败时重新发送请求
当我们要发送很多请求时,可能由于网络波动或者其他原因导致极少数的url请求不成功,那么我们是直接放弃这些url的请求吗?答案当然是否定的,我们可以使用retrying模块来实现多给他几次发送请求的机会
retrying模块是个第三方模块,需要自己下载。
我们使用的是retrying模块中的retry,通过查看retry,可以发现他是一个三层嵌套的闭包,用来做带有参数的装饰器,因为装饰器只能对函数和方法进行装饰,所以此处我们需要采用面向对象的编程方法
import requests
from retrying import retry # 怎么利用retry去创造多几次请求的机会
class Demo:
def __init__(self):
self.url_ = 'xxxx.com'
# 设置一个记数的变量
self.num_ = 0
# 发送请求的发送
@retry(stop_max_attempt_number=3)
def send_request(self):
self.num_ += 1
print(self.num_)
requests.get(self.url_) # 报错,导致下面的代码执行不到
def run(self):
try:
self.send_request()
except Exception as e:
print(e)
if __name__ == '__main__':
demo = Demo()
demo.run()
因为我们要请求的网站是瞎编的,所以请求不会成功,以此我们来测试一下retry功能,retry中有一个参数:stop_max_attempt_number ,最多尝试次数,即我们请求失败时,有多少次重新请求的机会。
五、异步加载动态数据
异步:多任务可以同时进行,而且互不干扰
加载:页面的渲染
动态:页面没有发生跳转的情况下,里面的数据发生了变化
例子:https://movie.douban.com/chart页面右边电影的分类随便选择一类
当我们向下滑动页面时,页面并没有发生变化,但会有新的电影信息加载出来,这就是典型的异步加载的动态数据
分析:
异步加载:既然有新的数据产生,所以必定发送了相应的请求,在例子中就是向下滑动页面时触发了js代码中的ajax,然后发送了请求,获取了响应
动态数据:这些响应中就有数据,而这些数据我们就称之为动态数据
异步加载中说到发送了相应的请求,那必然是对不同的url进行了访问,html格式的一般都是静态数据,因为他是页面架构;动态数据是之后去填充页面的数据,基本上都是json格式。我们想要电影的信息,他是随着我们不断的向下滑动鼠标才能加载出来,即这些数据为动态数据。我们可以在鼠标右键检查的network中的xhr找到对应的动态数据包(动态数据包一般都可以在xhr中找到),我们在豆瓣电影的例子中发现一个动态数据包中有20条数据(20部电影的信息......),当鼠标向下滑动触发了ajax,就有请求发送,新的数据包就随之产生,然后新的数据包对页面进行填充。当向下滑动后,产生新的数据包,就意味着进行了翻页操作,1个url对应20条数据,当我们需要100条数据时,就得找到5个这样的url,而数据包的产生是类似的,即翻页是类似的,那么他们的url是否会存在一定的规律呢?
我们以豆瓣电影的爱情类电影为例:
'''
第一页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20
第二页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=20&limit=20
第三页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=40&limit=20
第四页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=60&limit=20
第五页:https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=80&limit=20
'''
我们通过对比分析,发现只有start的值不同,而且每一页之间的差为20,这就是翻页的规律
import requests
from fake_useragent import FakeUserAgent
import time
if __name__ == '__main__':
# 确认要抓取数据的页面个数
pages_ = int(input('请输入要抓取数据的页面个数:'))
for page_ in range(pages_):
# 1.确认目标的url
url_ = f'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start={20 * page_}&limit=20'
# 构造请求头
headers_ = {
'User-Agent': FakeUserAgent().random
}
# 2.发送请求
response_ = requests.get(url_, headers=headers_)
content_ = response_.content
# 3.解析数据(略)
# 4.保存数据
with open(f'豆瓣{page_ + 1}.json', 'wb') as f:
f.write(content_)
# 降低请求频率,防止被反爬
time.sleep(1)
我们现在需要五个页面的数据,即需要对页面发送请求,而且他们的url存在规律,每个url中start的值相差20,所以我们可以用一个循环。一到五页的start值为:0,20,40,60,80,range(pages_)的值分别为0,1,2,3,4,所以代码中的start值应该为page_ * 20
六、爬虫中数据的分类以及解析
Python中一般都是操作字符串类型的数据
json格式数据的提取,解析:需要一个第三方库:jsonpath,re作为辅助
html格式数据的提取,解析:xpath,re作为辅助
七、json格式数据
爬虫部分:我们从前端拿到(爬到)的json格式的数据,应该怎么转换成为python能够操作的数据呢?
后端部分:我们python提供的数据,怎么转换成为json格式的数据供前端使用呢?
python 转 json 用json中的dumps方法
import json
if __name__ == '__main__':
# python中的字典
python_data = {
'name': '小明',
'age': '18',
}
print(type(python_data), python_data)
# <class 'dict'> {'name': '小明', 'age': '18'}
# python转json 用dumps函数,如果需要显示中文,需要添加参数
json_data = json.dumps(python_data)
print(type(json_data), json_data)
# <class 'str'> {"name": "\u5c0f\u660e", "age": "18"}
json_data = json.dumps(python_data, ensure_ascii=False)
print(type(json_data), json_data)
# <class 'str'> {"name": "小明", "age": "18"}
# 如果需要格式更加美观,需要添加一个indent参数
json_data = json.dumps(python_data, ensure_ascii=False, indent=3)
print(json_data)
'''
{
"name": "小明",
"age": "18"
}
'''
需要注意的是:
1.我们定义python中字典是用的都是单引号,在转换成json格式后,所有单引号变成了双引号,这是因为json中的引号必须是双引号
2.我们定义python中字典时在最后面加了个逗号,在转换成json格式后,末尾的逗号消失了,这是因为json格式中末尾不能有逗号
3.python转json格式时如果不加参数:ensure_ascii=False,那么python格式中的中文在转化成json格式后会出现乱码
json 转 python 用json中的loads方法
import json
if __name__ == '__main__':
# python中的字典
python_data = {
'name': '小明',
'age': '18'
}
print(type(python_data), python_data)
# <class 'dict'> {'name': '小明', 'age': '18'}
json_data = json.dumps(python_data, ensure_ascii=False, indent=3)
print(type(json_data), json_data)
'''
<class 'str'>
{
"name": "小明",
"age": "18"
}
'''
# json 转 python 用loads函数
py_data = json.loads(json_data)
print(type(py_data), py_data)
# <class 'dict'> {'name': '小明', 'age': '18'}
如何将python字典写入json文件?
import json
if __name__ == '__main__':
file = open('json01.json', 'w')
# 创建一个python字典
python_dict = {
'name': '小明',
'age': 18
}
# 将python字典写入json文件用dump方法
# 正常显示中文需要添加参数ensure_ascii=False,格式好看需要加入一个参数indent
json.dump(python_dict, file, ensure_ascii=False, indent=3)
如何将json文件读入python转化为字典格式?
import json
if __name__ == '__main__':
file = open('json01.json', 'r')
# 将json文件读入python转化为字典格式用load
py_data = json.load(file)
print(py_data) # {'name': '小明', 'age': 18}
总结:
loads:json字符串 > python
load:json文件 > python
dumps:python > json字符串
dump:python > json文件
八、面向对象改写案例
非常繁琐,不是很推荐
import requests
import time
class DouBan:
def __init__(self):
"""定义静态的属性,url,User-Agent"""
self.url_ = 'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&limit=20'
self.headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
def send_request(self, params_): # url传参
"""发送请求,获取响应"""
response_ = requests.get(self.url_, headers=self.headers_, params=params_)
str_data = response_.text
return str_data
def save_file(self, page, str_data):
"""保存文件"""
with open(f'{page + 1}.json', 'w', encoding='utf-8') as f:
f.write(str_data)
def run(self):
"""调度方法"""
pages = int(input('请输入想要抓取的页数:'))
for page in range(pages):
# 定义url的start参数
params_ = {
'start': page * 20
}
# 调用发送请求的方法
data_ = self.send_request(params_)
# 调用保存的方法
self.save_file(page, data_)
# 降低请求频率,避免被反爬
time.sleep(1)
if __name__ == '__main__':
douban_ = DouBan()
douban_.run()
九、json数据的解析 > jsonpath的使用
网站:json.cn 把json格式的数据复制进去,可以使结构更美观,清晰
使用方法举例:
import jsonpath
data_ = { "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
# 使用方法
res_ = jsonpath.jsonpath(data_,'$.store.book[*].author')
JSON路径 | 结果 |
---|---|
$.store.book[*].author | 商店中所有书籍的作者 |
$..author | 所有的作者 |
$.store.* | 商店下所有的元素 |
$.store..price | 商店中所有内容的价格 |
$..book[2] | 第三本书 |
$..book[(@.length-1)] | $..book[-1:] | 一本书 |
$..book[0,1] | $..book[:2] | 前两本书 |
$..book[?(@.isbn)] | 获取有isbn的所有数 |
$..book[?(@.price<10)] | 获取价格大于10的所有的书 |
$..* | 获取所有数据 |
使用方法:jsonpath.jsonpath(对象,jsonpath路径)
使用jsonpath提取到的数据是一个列表
jsonpath的语法并不固定,要取同一个内容,jsonpath路径可能不同
注意:
简单方法中是使用了response的json方法,不是导入的json模块
# 将得到的json格式的数据转换为python能够操作的格式
data_ = response_.text
py_data = json.loads(data_)
# 简单方法
py_data = response_.json()
实际使用(豆瓣电影):
import jsonpath
import requests
import json
if __name__ == '__main__':
# 1.确认目标url
url_ = 'https://movie.douban.com/j/chart/top_list?type=13&interval_id=100%3A90&action=&start=0&limit=20'
# 构造用户代理
headers_ = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
# 2.发送请求,获取响应
response_ = requests.get(url_, headers=headers_)
# 转换为python格式
py_data = response_.json()
# 3.解析 title score
title_list = jsonpath.jsonpath(py_data, '$..title') # 列表
score_list = jsonpath.jsonpath(py_data, '$..score')
print(title_list)
print(score_list)
# 4.保存
for i in range(len(title_list)): # 一条一条数据写入,,让格式看起来更舒服
dict_ = {}
dict_[title_list[i]] = score_list[i]
# 转成json字符串写入
json_data = json.dumps(dict_,ensure_ascii=False) + ',\n' # 显示中文 换行
with open('duoban_03.json', 'a', encoding='utf-8') as f: # 进行追加不覆盖
f.write(json_data)